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