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