xref: /aosp_15_r20/external/cronet/third_party/abseil-cpp/absl/flags/internal/usage.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1 //
2 // Copyright 2019 The Abseil Authors.
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 //      https://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15 
16 #include "absl/flags/internal/usage.h"
17 
18 #include <stdint.h>
19 
20 #include <algorithm>
21 #include <cstdlib>
22 #include <functional>
23 #include <iterator>
24 #include <map>
25 #include <ostream>
26 #include <string>
27 #include <utility>
28 #include <vector>
29 
30 #include "absl/base/attributes.h"
31 #include "absl/base/config.h"
32 #include "absl/base/const_init.h"
33 #include "absl/base/thread_annotations.h"
34 #include "absl/flags/commandlineflag.h"
35 #include "absl/flags/flag.h"
36 #include "absl/flags/internal/flag.h"
37 #include "absl/flags/internal/path_util.h"
38 #include "absl/flags/internal/private_handle_accessor.h"
39 #include "absl/flags/internal/program_name.h"
40 #include "absl/flags/internal/registry.h"
41 #include "absl/flags/usage_config.h"
42 #include "absl/strings/match.h"
43 #include "absl/strings/str_cat.h"
44 #include "absl/strings/str_split.h"
45 #include "absl/strings/string_view.h"
46 #include "absl/strings/strip.h"
47 #include "absl/synchronization/mutex.h"
48 
49 // Dummy global variables to prevent anyone else defining these.
50 bool FLAGS_help = false;
51 bool FLAGS_helpfull = false;
52 bool FLAGS_helpshort = false;
53 bool FLAGS_helppackage = false;
54 bool FLAGS_version = false;
55 bool FLAGS_only_check_args = false;
56 bool FLAGS_helpon = false;
57 bool FLAGS_helpmatch = false;
58 
59 namespace absl {
60 ABSL_NAMESPACE_BEGIN
61 namespace flags_internal {
62 namespace {
63 
64 using PerFlagFilter = std::function<bool(const absl::CommandLineFlag&)>;
65 
66 // Maximum length size in a human readable format.
67 constexpr size_t kHrfMaxLineLength = 80;
68 
69 // This class is used to emit an XML element with `tag` and `text`.
70 // It adds opening and closing tags and escapes special characters in the text.
71 // For example:
72 // std::cout << XMLElement("title", "Milk & Cookies");
73 // prints "<title>Milk &amp; Cookies</title>"
74 class XMLElement {
75  public:
XMLElement(absl::string_view tag,absl::string_view txt)76   XMLElement(absl::string_view tag, absl::string_view txt)
77       : tag_(tag), txt_(txt) {}
78 
operator <<(std::ostream & out,const XMLElement & xml_elem)79   friend std::ostream& operator<<(std::ostream& out,
80                                   const XMLElement& xml_elem) {
81     out << "<" << xml_elem.tag_ << ">";
82 
83     for (auto c : xml_elem.txt_) {
84       switch (c) {
85         case '"':
86           out << "&quot;";
87           break;
88         case '\'':
89           out << "&apos;";
90           break;
91         case '&':
92           out << "&amp;";
93           break;
94         case '<':
95           out << "&lt;";
96           break;
97         case '>':
98           out << "&gt;";
99           break;
100         case '\n':
101         case '\v':
102         case '\f':
103         case '\t':
104           out << " ";
105           break;
106         default:
107           if (IsValidXmlCharacter(static_cast<unsigned char>(c))) {
108             out << c;
109           }
110           break;
111       }
112     }
113 
114     return out << "</" << xml_elem.tag_ << ">";
115   }
116 
117  private:
IsValidXmlCharacter(unsigned char c)118   static bool IsValidXmlCharacter(unsigned char c) { return c >= 0x20; }
119   absl::string_view tag_;
120   absl::string_view txt_;
121 };
122 
123 // --------------------------------------------------------------------
124 // Helper class to pretty-print info about a flag.
125 
126 class FlagHelpPrettyPrinter {
127  public:
128   // Pretty printer holds on to the std::ostream& reference to direct an output
129   // to that stream.
FlagHelpPrettyPrinter(size_t max_line_len,size_t min_line_len,size_t wrapped_line_indent,std::ostream & out)130   FlagHelpPrettyPrinter(size_t max_line_len, size_t min_line_len,
131                         size_t wrapped_line_indent, std::ostream& out)
132       : out_(out),
133         max_line_len_(max_line_len),
134         min_line_len_(min_line_len),
135         wrapped_line_indent_(wrapped_line_indent),
136         line_len_(0),
137         first_line_(true) {}
138 
Write(absl::string_view str,bool wrap_line=false)139   void Write(absl::string_view str, bool wrap_line = false) {
140     // Empty string - do nothing.
141     if (str.empty()) return;
142 
143     std::vector<absl::string_view> tokens;
144     if (wrap_line) {
145       for (auto line : absl::StrSplit(str, absl::ByAnyChar("\n\r"))) {
146         if (!tokens.empty()) {
147           // Keep line separators in the input string.
148           tokens.emplace_back("\n");
149         }
150         for (auto token :
151              absl::StrSplit(line, absl::ByAnyChar(" \t"), absl::SkipEmpty())) {
152           tokens.push_back(token);
153         }
154       }
155     } else {
156       tokens.push_back(str);
157     }
158 
159     for (auto token : tokens) {
160       bool new_line = (line_len_ == 0);
161 
162       // Respect line separators in the input string.
163       if (token == "\n") {
164         EndLine();
165         continue;
166       }
167 
168       // Write the token, ending the string first if necessary/possible.
169       if (!new_line && (line_len_ + token.size() >= max_line_len_)) {
170         EndLine();
171         new_line = true;
172       }
173 
174       if (new_line) {
175         StartLine();
176       } else {
177         out_ << ' ';
178         ++line_len_;
179       }
180 
181       out_ << token;
182       line_len_ += token.size();
183     }
184   }
185 
StartLine()186   void StartLine() {
187     if (first_line_) {
188       line_len_ = min_line_len_;
189       first_line_ = false;
190     } else {
191       line_len_ = min_line_len_ + wrapped_line_indent_;
192     }
193     out_ << std::string(line_len_, ' ');
194   }
EndLine()195   void EndLine() {
196     out_ << '\n';
197     line_len_ = 0;
198   }
199 
200  private:
201   std::ostream& out_;
202   const size_t max_line_len_;
203   const size_t min_line_len_;
204   const size_t wrapped_line_indent_;
205   size_t line_len_;
206   bool first_line_;
207 };
208 
FlagHelpHumanReadable(const CommandLineFlag & flag,std::ostream & out)209 void FlagHelpHumanReadable(const CommandLineFlag& flag, std::ostream& out) {
210   FlagHelpPrettyPrinter printer(kHrfMaxLineLength, 4, 2, out);
211 
212   // Flag name.
213   printer.Write(absl::StrCat("--", flag.Name()));
214 
215   // Flag help.
216   printer.Write(absl::StrCat("(", flag.Help(), ");"), /*wrap_line=*/true);
217 
218   // The listed default value will be the actual default from the flag
219   // definition in the originating source file, unless the value has
220   // subsequently been modified using SetCommandLineOption() with mode
221   // SET_FLAGS_DEFAULT.
222   std::string dflt_val = flag.DefaultValue();
223   std::string curr_val = flag.CurrentValue();
224   bool is_modified = curr_val != dflt_val;
225 
226   if (flag.IsOfType<std::string>()) {
227     dflt_val = absl::StrCat("\"", dflt_val, "\"");
228   }
229   printer.Write(absl::StrCat("default: ", dflt_val, ";"));
230 
231   if (is_modified) {
232     if (flag.IsOfType<std::string>()) {
233       curr_val = absl::StrCat("\"", curr_val, "\"");
234     }
235     printer.Write(absl::StrCat("currently: ", curr_val, ";"));
236   }
237 
238   printer.EndLine();
239 }
240 
241 // Shows help for every filename which matches any of the filters
242 // If filters are empty, shows help for every file.
243 // If a flag's help message has been stripped (e.g. by adding '#define
244 // STRIP_FLAG_HELP 1' then this flag will not be displayed by '--help'
245 // and its variants.
FlagsHelpImpl(std::ostream & out,PerFlagFilter filter_cb,HelpFormat format,absl::string_view program_usage_message)246 void FlagsHelpImpl(std::ostream& out, PerFlagFilter filter_cb,
247                    HelpFormat format, absl::string_view program_usage_message) {
248   if (format == HelpFormat::kHumanReadable) {
249     out << flags_internal::ShortProgramInvocationName() << ": "
250         << program_usage_message << "\n\n";
251   } else {
252     // XML schema is not a part of our public API for now.
253     out << "<?xml version=\"1.0\"?>\n"
254         << "<!-- This output should be used with care. We do not report type "
255            "names for flags with user defined types -->\n"
256         << "<!-- Prefer flag only_check_args for validating flag inputs -->\n"
257         // The document.
258         << "<AllFlags>\n"
259         // The program name and usage.
260         << XMLElement("program", flags_internal::ShortProgramInvocationName())
261         << '\n'
262         << XMLElement("usage", program_usage_message) << '\n';
263   }
264 
265   // Ordered map of package name to
266   //   map of file name to
267   //     vector of flags in the file.
268   // This map is used to output matching flags grouped by package and file
269   // name.
270   std::map<std::string,
271            std::map<std::string, std::vector<const absl::CommandLineFlag*>>>
272       matching_flags;
273 
274   flags_internal::ForEachFlag([&](absl::CommandLineFlag& flag) {
275     // Ignore retired flags.
276     if (flag.IsRetired()) return;
277 
278     // If the flag has been stripped, pretend that it doesn't exist.
279     if (flag.Help() == flags_internal::kStrippedFlagHelp) return;
280 
281     // Make sure flag satisfies the filter
282     if (!filter_cb(flag)) return;
283 
284     std::string flag_filename = flag.Filename();
285 
286     matching_flags[std::string(flags_internal::Package(flag_filename))]
287                   [flag_filename]
288                       .push_back(&flag);
289   });
290 
291   absl::string_view package_separator;  // controls blank lines between packages
292   absl::string_view file_separator;     // controls blank lines between files
293   for (auto& package : matching_flags) {
294     if (format == HelpFormat::kHumanReadable) {
295       out << package_separator;
296       package_separator = "\n\n";
297     }
298 
299     file_separator = "";
300     for (auto& flags_in_file : package.second) {
301       if (format == HelpFormat::kHumanReadable) {
302         out << file_separator << "  Flags from " << flags_in_file.first
303             << ":\n";
304         file_separator = "\n";
305       }
306 
307       std::sort(std::begin(flags_in_file.second),
308                 std::end(flags_in_file.second),
309                 [](const CommandLineFlag* lhs, const CommandLineFlag* rhs) {
310                   return lhs->Name() < rhs->Name();
311                 });
312 
313       for (const auto* flag : flags_in_file.second) {
314         flags_internal::FlagHelp(out, *flag, format);
315       }
316     }
317   }
318 
319   if (format == HelpFormat::kHumanReadable) {
320     FlagHelpPrettyPrinter printer(kHrfMaxLineLength, 0, 0, out);
321 
322     if (filter_cb && matching_flags.empty()) {
323       printer.Write("No flags matched.\n", true);
324     }
325     printer.EndLine();
326     printer.Write(
327         "Try --helpfull to get a list of all flags or --help=substring "
328         "shows help for flags which include specified substring in either "
329         "in the name, or description or path.\n",
330         true);
331   } else {
332     // The end of the document.
333     out << "</AllFlags>\n";
334   }
335 }
336 
FlagsHelpImpl(std::ostream & out,flags_internal::FlagKindFilter filename_filter_cb,HelpFormat format,absl::string_view program_usage_message)337 void FlagsHelpImpl(std::ostream& out,
338                    flags_internal::FlagKindFilter filename_filter_cb,
339                    HelpFormat format, absl::string_view program_usage_message) {
340   FlagsHelpImpl(
341       out,
342       [&](const absl::CommandLineFlag& flag) {
343         return filename_filter_cb && filename_filter_cb(flag.Filename());
344       },
345       format, program_usage_message);
346 }
347 
348 }  // namespace
349 
350 // --------------------------------------------------------------------
351 // Produces the help message describing specific flag.
FlagHelp(std::ostream & out,const CommandLineFlag & flag,HelpFormat format)352 void FlagHelp(std::ostream& out, const CommandLineFlag& flag,
353               HelpFormat format) {
354   if (format == HelpFormat::kHumanReadable)
355     flags_internal::FlagHelpHumanReadable(flag, out);
356 }
357 
358 // --------------------------------------------------------------------
359 // Produces the help messages for all flags matching the filename filter.
360 // If filter is empty produces help messages for all flags.
FlagsHelp(std::ostream & out,absl::string_view filter,HelpFormat format,absl::string_view program_usage_message)361 void FlagsHelp(std::ostream& out, absl::string_view filter, HelpFormat format,
362                absl::string_view program_usage_message) {
363   flags_internal::FlagKindFilter filter_cb = [&](absl::string_view filename) {
364     return filter.empty() || absl::StrContains(filename, filter);
365   };
366   flags_internal::FlagsHelpImpl(out, filter_cb, format, program_usage_message);
367 }
368 
369 // --------------------------------------------------------------------
370 // Checks all the 'usage' command line flags to see if any have been set.
371 // If so, handles them appropriately.
HandleUsageFlags(std::ostream & out,absl::string_view program_usage_message)372 HelpMode HandleUsageFlags(std::ostream& out,
373                           absl::string_view program_usage_message) {
374   switch (GetFlagsHelpMode()) {
375     case HelpMode::kNone:
376       break;
377     case HelpMode::kImportant:
378       flags_internal::FlagsHelpImpl(
379           out, flags_internal::GetUsageConfig().contains_help_flags,
380           GetFlagsHelpFormat(), program_usage_message);
381       break;
382 
383     case HelpMode::kShort:
384       flags_internal::FlagsHelpImpl(
385           out, flags_internal::GetUsageConfig().contains_helpshort_flags,
386           GetFlagsHelpFormat(), program_usage_message);
387       break;
388 
389     case HelpMode::kFull:
390       flags_internal::FlagsHelp(out, "", GetFlagsHelpFormat(),
391                                 program_usage_message);
392       break;
393 
394     case HelpMode::kPackage:
395       flags_internal::FlagsHelpImpl(
396           out, flags_internal::GetUsageConfig().contains_helppackage_flags,
397           GetFlagsHelpFormat(), program_usage_message);
398       break;
399 
400     case HelpMode::kMatch: {
401       std::string substr = GetFlagsHelpMatchSubstr();
402       if (substr.empty()) {
403         // show all options
404         flags_internal::FlagsHelp(out, substr, GetFlagsHelpFormat(),
405                                   program_usage_message);
406       } else {
407         auto filter_cb = [&substr](const absl::CommandLineFlag& flag) {
408           if (absl::StrContains(flag.Name(), substr)) return true;
409           if (absl::StrContains(flag.Filename(), substr)) return true;
410           if (absl::StrContains(flag.Help(), substr)) return true;
411 
412           return false;
413         };
414         flags_internal::FlagsHelpImpl(
415             out, filter_cb, HelpFormat::kHumanReadable, program_usage_message);
416       }
417       break;
418     }
419     case HelpMode::kVersion:
420       if (flags_internal::GetUsageConfig().version_string)
421         out << flags_internal::GetUsageConfig().version_string();
422       // Unlike help, we may be asking for version in a script, so return 0
423       break;
424 
425     case HelpMode::kOnlyCheckArgs:
426       break;
427   }
428 
429   return GetFlagsHelpMode();
430 }
431 
432 // --------------------------------------------------------------------
433 // Globals representing usage reporting flags
434 
435 namespace {
436 
437 ABSL_CONST_INIT absl::Mutex help_attributes_guard(absl::kConstInit);
438 ABSL_CONST_INIT std::string* match_substr
439     ABSL_GUARDED_BY(help_attributes_guard) = nullptr;
440 ABSL_CONST_INIT HelpMode help_mode ABSL_GUARDED_BY(help_attributes_guard) =
441     HelpMode::kNone;
442 ABSL_CONST_INIT HelpFormat help_format ABSL_GUARDED_BY(help_attributes_guard) =
443     HelpFormat::kHumanReadable;
444 
445 }  // namespace
446 
GetFlagsHelpMatchSubstr()447 std::string GetFlagsHelpMatchSubstr() {
448   absl::MutexLock l(&help_attributes_guard);
449   if (match_substr == nullptr) return "";
450   return *match_substr;
451 }
452 
SetFlagsHelpMatchSubstr(absl::string_view substr)453 void SetFlagsHelpMatchSubstr(absl::string_view substr) {
454   absl::MutexLock l(&help_attributes_guard);
455   if (match_substr == nullptr) match_substr = new std::string;
456   match_substr->assign(substr.data(), substr.size());
457 }
458 
GetFlagsHelpMode()459 HelpMode GetFlagsHelpMode() {
460   absl::MutexLock l(&help_attributes_guard);
461   return help_mode;
462 }
463 
SetFlagsHelpMode(HelpMode mode)464 void SetFlagsHelpMode(HelpMode mode) {
465   absl::MutexLock l(&help_attributes_guard);
466   help_mode = mode;
467 }
468 
GetFlagsHelpFormat()469 HelpFormat GetFlagsHelpFormat() {
470   absl::MutexLock l(&help_attributes_guard);
471   return help_format;
472 }
473 
SetFlagsHelpFormat(HelpFormat format)474 void SetFlagsHelpFormat(HelpFormat format) {
475   absl::MutexLock l(&help_attributes_guard);
476   help_format = format;
477 }
478 
479 // Deduces usage flags from the input argument in a form --name=value or
480 // --name. argument is already split into name and value before we call this
481 // function.
DeduceUsageFlags(absl::string_view name,absl::string_view value)482 bool DeduceUsageFlags(absl::string_view name, absl::string_view value) {
483   if (absl::ConsumePrefix(&name, "help")) {
484     if (name.empty()) {
485       if (value.empty()) {
486         SetFlagsHelpMode(HelpMode::kImportant);
487       } else {
488         SetFlagsHelpMode(HelpMode::kMatch);
489         SetFlagsHelpMatchSubstr(value);
490       }
491       return true;
492     }
493 
494     if (name == "match") {
495       SetFlagsHelpMode(HelpMode::kMatch);
496       SetFlagsHelpMatchSubstr(value);
497       return true;
498     }
499 
500     if (name == "on") {
501       SetFlagsHelpMode(HelpMode::kMatch);
502       SetFlagsHelpMatchSubstr(absl::StrCat("/", value, "."));
503       return true;
504     }
505 
506     if (name == "full") {
507       SetFlagsHelpMode(HelpMode::kFull);
508       return true;
509     }
510 
511     if (name == "short") {
512       SetFlagsHelpMode(HelpMode::kShort);
513       return true;
514     }
515 
516     if (name == "package") {
517       SetFlagsHelpMode(HelpMode::kPackage);
518       return true;
519     }
520 
521     return false;
522   }
523 
524   if (name == "version") {
525     SetFlagsHelpMode(HelpMode::kVersion);
526     return true;
527   }
528 
529   if (name == "only_check_args") {
530     SetFlagsHelpMode(HelpMode::kOnlyCheckArgs);
531     return true;
532   }
533 
534   return false;
535 }
536 
537 // --------------------------------------------------------------------
538 
MaybeExit(HelpMode mode)539 void MaybeExit(HelpMode mode) {
540   switch (mode) {
541     case flags_internal::HelpMode::kNone:
542       return;
543     case flags_internal::HelpMode::kOnlyCheckArgs:
544     case flags_internal::HelpMode::kVersion:
545       std::exit(0);
546     default:  // For all the other modes we exit with 1
547       std::exit(1);
548   }
549 }
550 
551 // --------------------------------------------------------------------
552 
553 }  // namespace flags_internal
554 ABSL_NAMESPACE_END
555 }  // namespace absl
556