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