blob: a588c7f73acaf7e33b9e739f66112e03b4d6923e [file] [log] [blame]
Austin Schuh36244a12019-09-21 17:52:38 -07001//
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
Austin Schuhb4691e92020-12-31 12:37:18 -080018#include <stdint.h>
Austin Schuh36244a12019-09-21 17:52:38 -070019
Austin Schuhb4691e92020-12-31 12:37:18 -080020#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"
Austin Schuh36244a12019-09-21 17:52:38 -070029#include "absl/flags/flag.h"
Austin Schuhb4691e92020-12-31 12:37:18 -080030#include "absl/flags/internal/flag.h"
Austin Schuh36244a12019-09-21 17:52:38 -070031#include "absl/flags/internal/path_util.h"
Austin Schuhb4691e92020-12-31 12:37:18 -080032#include "absl/flags/internal/private_handle_accessor.h"
Austin Schuh36244a12019-09-21 17:52:38 -070033#include "absl/flags/internal/program_name.h"
Austin Schuhb4691e92020-12-31 12:37:18 -080034#include "absl/flags/internal/registry.h"
Austin Schuh36244a12019-09-21 17:52:38 -070035#include "absl/flags/usage_config.h"
Austin Schuh36244a12019-09-21 17:52:38 -070036#include "absl/strings/str_cat.h"
37#include "absl/strings/str_split.h"
38#include "absl/strings/string_view.h"
Austin Schuh36244a12019-09-21 17:52:38 -070039
Austin Schuhb4691e92020-12-31 12:37:18 -080040// Dummy global variables to prevent anyone else defining these.
41bool FLAGS_help = false;
42bool FLAGS_helpfull = false;
43bool FLAGS_helpshort = false;
44bool FLAGS_helppackage = false;
45bool FLAGS_version = false;
46bool FLAGS_only_check_args = false;
47bool FLAGS_helpon = false;
48bool FLAGS_helpmatch = false;
Austin Schuh36244a12019-09-21 17:52:38 -070049
50namespace absl {
Austin Schuhb4691e92020-12-31 12:37:18 -080051ABSL_NAMESPACE_BEGIN
Austin Schuh36244a12019-09-21 17:52:38 -070052namespace flags_internal {
53namespace {
54
Austin Schuhb4691e92020-12-31 12:37:18 -080055using PerFlagFilter = std::function<bool(const absl::CommandLineFlag&)>;
56
57// Maximum length size in a human readable format.
58constexpr size_t kHrfMaxLineLength = 80;
59
Austin Schuh36244a12019-09-21 17:52:38 -070060// 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 &amp; Cookies</title>"
65class XMLElement {
66 public:
67 XMLElement(absl::string_view tag, absl::string_view txt)
68 : tag_(tag), txt_(txt) {}
69
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 << "&quot;";
78 break;
79 case '\'':
80 out << "&apos;";
81 break;
82 case '&':
83 out << "&amp;";
84 break;
85 case '<':
86 out << "&lt;";
87 break;
88 case '>':
89 out << "&gt;";
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
108class FlagHelpPrettyPrinter {
109 public:
110 // Pretty printer holds on to the std::ostream& reference to direct an output
111 // to that stream.
Austin Schuhb4691e92020-12-31 12:37:18 -0800112 FlagHelpPrettyPrinter(size_t max_line_len, size_t min_line_len,
113 size_t wrapped_line_indent, std::ostream& out)
114 : out_(out),
Austin Schuh36244a12019-09-21 17:52:38 -0700115 max_line_len_(max_line_len),
Austin Schuhb4691e92020-12-31 12:37:18 -0800116 min_line_len_(min_line_len),
117 wrapped_line_indent_(wrapped_line_indent),
Austin Schuh36244a12019-09-21 17:52:38 -0700118 line_len_(0),
119 first_line_(true) {}
120
121 void Write(absl::string_view str, bool wrap_line = false) {
Austin Schuhb4691e92020-12-31 12:37:18 -0800122 // Empty string - do nothing.
Austin Schuh36244a12019-09-21 17:52:38 -0700123 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()) {
Austin Schuhb4691e92020-12-31 12:37:18 -0800129 // Keep line separators in the input string.
Austin Schuh36244a12019-09-21 17:52:38 -0700130 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
Austin Schuhb4691e92020-12-31 12:37:18 -0800144 // Respect line separators in the input string.
Austin Schuh36244a12019-09-21 17:52:38 -0700145 if (token == "\n") {
146 EndLine();
147 continue;
148 }
149
Austin Schuhb4691e92020-12-31 12:37:18 -0800150 // Write the token, ending the string first if necessary/possible.
151 if (!new_line &&
152 (line_len_ + static_cast<int>(token.size()) >= max_line_len_)) {
Austin Schuh36244a12019-09-21 17:52:38 -0700153 EndLine();
154 new_line = true;
155 }
156
157 if (new_line) {
158 StartLine();
159 } else {
160 out_ << ' ';
161 ++line_len_;
162 }
163
164 out_ << token;
165 line_len_ += token.size();
166 }
167 }
168
169 void StartLine() {
170 if (first_line_) {
Austin Schuhb4691e92020-12-31 12:37:18 -0800171 line_len_ = min_line_len_;
Austin Schuh36244a12019-09-21 17:52:38 -0700172 first_line_ = false;
173 } else {
Austin Schuhb4691e92020-12-31 12:37:18 -0800174 line_len_ = min_line_len_ + wrapped_line_indent_;
Austin Schuh36244a12019-09-21 17:52:38 -0700175 }
Austin Schuhb4691e92020-12-31 12:37:18 -0800176 out_ << std::string(line_len_, ' ');
Austin Schuh36244a12019-09-21 17:52:38 -0700177 }
178 void EndLine() {
179 out_ << '\n';
180 line_len_ = 0;
181 }
182
183 private:
184 std::ostream& out_;
Austin Schuhb4691e92020-12-31 12:37:18 -0800185 const size_t max_line_len_;
186 const size_t min_line_len_;
187 const size_t wrapped_line_indent_;
188 size_t line_len_;
Austin Schuh36244a12019-09-21 17:52:38 -0700189 bool first_line_;
190};
191
Austin Schuhb4691e92020-12-31 12:37:18 -0800192void FlagHelpHumanReadable(const CommandLineFlag& flag, std::ostream& out) {
193 FlagHelpPrettyPrinter printer(kHrfMaxLineLength, 4, 2, out);
Austin Schuh36244a12019-09-21 17:52:38 -0700194
195 // Flag name.
Austin Schuhb4691e92020-12-31 12:37:18 -0800196 printer.Write(absl::StrCat("--", flag.Name()));
Austin Schuh36244a12019-09-21 17:52:38 -0700197
198 // Flag help.
199 printer.Write(absl::StrCat("(", flag.Help(), ");"), /*wrap_line=*/true);
200
Austin Schuh36244a12019-09-21 17:52:38 -0700201 // The listed default value will be the actual default from the flag
202 // definition in the originating source file, unless the value has
203 // subsequently been modified using SetCommandLineOption() with mode
204 // SET_FLAGS_DEFAULT.
205 std::string dflt_val = flag.DefaultValue();
Austin Schuhb4691e92020-12-31 12:37:18 -0800206 std::string curr_val = flag.CurrentValue();
207 bool is_modified = curr_val != dflt_val;
208
Austin Schuh36244a12019-09-21 17:52:38 -0700209 if (flag.IsOfType<std::string>()) {
210 dflt_val = absl::StrCat("\"", dflt_val, "\"");
211 }
212 printer.Write(absl::StrCat("default: ", dflt_val, ";"));
213
Austin Schuhb4691e92020-12-31 12:37:18 -0800214 if (is_modified) {
Austin Schuh36244a12019-09-21 17:52:38 -0700215 if (flag.IsOfType<std::string>()) {
216 curr_val = absl::StrCat("\"", curr_val, "\"");
217 }
218 printer.Write(absl::StrCat("currently: ", curr_val, ";"));
219 }
220
221 printer.EndLine();
222}
223
224// Shows help for every filename which matches any of the filters
225// If filters are empty, shows help for every file.
226// If a flag's help message has been stripped (e.g. by adding '#define
227// STRIP_FLAG_HELP 1' then this flag will not be displayed by '--help'
228// and its variants.
Austin Schuhb4691e92020-12-31 12:37:18 -0800229void FlagsHelpImpl(std::ostream& out, PerFlagFilter filter_cb,
Austin Schuh36244a12019-09-21 17:52:38 -0700230 HelpFormat format, absl::string_view program_usage_message) {
231 if (format == HelpFormat::kHumanReadable) {
232 out << flags_internal::ShortProgramInvocationName() << ": "
233 << program_usage_message << "\n\n";
234 } else {
235 // XML schema is not a part of our public API for now.
236 out << "<?xml version=\"1.0\"?>\n"
Austin Schuhb4691e92020-12-31 12:37:18 -0800237 << "<!-- This output should be used with care. We do not report type "
238 "names for flags with user defined types -->\n"
239 << "<!-- Prefer flag only_check_args for validating flag inputs -->\n"
Austin Schuh36244a12019-09-21 17:52:38 -0700240 // The document.
241 << "<AllFlags>\n"
242 // The program name and usage.
243 << XMLElement("program", flags_internal::ShortProgramInvocationName())
244 << '\n'
245 << XMLElement("usage", program_usage_message) << '\n';
246 }
247
248 // Map of package name to
249 // map of file name to
250 // vector of flags in the file.
251 // This map is used to output matching flags grouped by package and file
252 // name.
253 std::map<std::string,
Austin Schuhb4691e92020-12-31 12:37:18 -0800254 std::map<std::string, std::vector<const absl::CommandLineFlag*>>>
Austin Schuh36244a12019-09-21 17:52:38 -0700255 matching_flags;
256
Austin Schuhb4691e92020-12-31 12:37:18 -0800257 flags_internal::ForEachFlag([&](absl::CommandLineFlag& flag) {
Austin Schuh36244a12019-09-21 17:52:38 -0700258 // Ignore retired flags.
Austin Schuhb4691e92020-12-31 12:37:18 -0800259 if (flag.IsRetired()) return;
Austin Schuh36244a12019-09-21 17:52:38 -0700260
261 // If the flag has been stripped, pretend that it doesn't exist.
Austin Schuhb4691e92020-12-31 12:37:18 -0800262 if (flag.Help() == flags_internal::kStrippedFlagHelp) return;
Austin Schuh36244a12019-09-21 17:52:38 -0700263
264 // Make sure flag satisfies the filter
Austin Schuhb4691e92020-12-31 12:37:18 -0800265 if (!filter_cb(flag)) return;
266
267 std::string flag_filename = flag.Filename();
Austin Schuh36244a12019-09-21 17:52:38 -0700268
269 matching_flags[std::string(flags_internal::Package(flag_filename))]
270 [flag_filename]
Austin Schuhb4691e92020-12-31 12:37:18 -0800271 .push_back(&flag);
Austin Schuh36244a12019-09-21 17:52:38 -0700272 });
273
Austin Schuhb4691e92020-12-31 12:37:18 -0800274 absl::string_view package_separator; // controls blank lines between packages
275 absl::string_view file_separator; // controls blank lines between files
Austin Schuh36244a12019-09-21 17:52:38 -0700276 for (const auto& package : matching_flags) {
277 if (format == HelpFormat::kHumanReadable) {
278 out << package_separator;
279 package_separator = "\n\n";
280 }
281
282 file_separator = "";
283 for (const auto& flags_in_file : package.second) {
284 if (format == HelpFormat::kHumanReadable) {
285 out << file_separator << " Flags from " << flags_in_file.first
286 << ":\n";
287 file_separator = "\n";
288 }
289
290 for (const auto* flag : flags_in_file.second) {
291 flags_internal::FlagHelp(out, *flag, format);
292 }
293 }
294 }
295
296 if (format == HelpFormat::kHumanReadable) {
Austin Schuhb4691e92020-12-31 12:37:18 -0800297 FlagHelpPrettyPrinter printer(kHrfMaxLineLength, 0, 0, out);
298
Austin Schuh36244a12019-09-21 17:52:38 -0700299 if (filter_cb && matching_flags.empty()) {
Austin Schuhb4691e92020-12-31 12:37:18 -0800300 printer.Write("No flags matched.\n", true);
Austin Schuh36244a12019-09-21 17:52:38 -0700301 }
Austin Schuhb4691e92020-12-31 12:37:18 -0800302 printer.EndLine();
303 printer.Write(
304 "Try --helpfull to get a list of all flags or --help=substring "
305 "shows help for flags which include specified substring in either "
306 "in the name, or description or path.\n",
307 true);
Austin Schuh36244a12019-09-21 17:52:38 -0700308 } else {
309 // The end of the document.
310 out << "</AllFlags>\n";
311 }
312}
313
Austin Schuhb4691e92020-12-31 12:37:18 -0800314void FlagsHelpImpl(std::ostream& out,
315 flags_internal::FlagKindFilter filename_filter_cb,
316 HelpFormat format, absl::string_view program_usage_message) {
317 FlagsHelpImpl(
318 out,
319 [&](const absl::CommandLineFlag& flag) {
320 return filename_filter_cb && filename_filter_cb(flag.Filename());
321 },
322 format, program_usage_message);
323}
324
Austin Schuh36244a12019-09-21 17:52:38 -0700325} // namespace
326
327// --------------------------------------------------------------------
328// Produces the help message describing specific flag.
Austin Schuhb4691e92020-12-31 12:37:18 -0800329void FlagHelp(std::ostream& out, const CommandLineFlag& flag,
Austin Schuh36244a12019-09-21 17:52:38 -0700330 HelpFormat format) {
331 if (format == HelpFormat::kHumanReadable)
Austin Schuhb4691e92020-12-31 12:37:18 -0800332 flags_internal::FlagHelpHumanReadable(flag, out);
Austin Schuh36244a12019-09-21 17:52:38 -0700333}
334
335// --------------------------------------------------------------------
Austin Schuhb4691e92020-12-31 12:37:18 -0800336// Produces the help messages for all flags matching the filename filter.
Austin Schuh36244a12019-09-21 17:52:38 -0700337// If filter is empty produces help messages for all flags.
338void FlagsHelp(std::ostream& out, absl::string_view filter, HelpFormat format,
339 absl::string_view program_usage_message) {
340 flags_internal::FlagKindFilter filter_cb = [&](absl::string_view filename) {
341 return filter.empty() || filename.find(filter) != absl::string_view::npos;
342 };
343 flags_internal::FlagsHelpImpl(out, filter_cb, format, program_usage_message);
344}
345
346// --------------------------------------------------------------------
347// Checks all the 'usage' command line flags to see if any have been set.
348// If so, handles them appropriately.
349int HandleUsageFlags(std::ostream& out,
350 absl::string_view program_usage_message) {
Austin Schuhb4691e92020-12-31 12:37:18 -0800351 switch (GetFlagsHelpMode()) {
352 case HelpMode::kNone:
353 break;
354 case HelpMode::kImportant:
355 flags_internal::FlagsHelpImpl(
356 out, flags_internal::GetUsageConfig().contains_help_flags,
357 GetFlagsHelpFormat(), program_usage_message);
358 return 1;
Austin Schuh36244a12019-09-21 17:52:38 -0700359
Austin Schuhb4691e92020-12-31 12:37:18 -0800360 case HelpMode::kShort:
361 flags_internal::FlagsHelpImpl(
362 out, flags_internal::GetUsageConfig().contains_helpshort_flags,
363 GetFlagsHelpFormat(), program_usage_message);
364 return 1;
Austin Schuh36244a12019-09-21 17:52:38 -0700365
Austin Schuhb4691e92020-12-31 12:37:18 -0800366 case HelpMode::kFull:
367 flags_internal::FlagsHelp(out, "", GetFlagsHelpFormat(),
368 program_usage_message);
369 return 1;
Austin Schuh36244a12019-09-21 17:52:38 -0700370
Austin Schuhb4691e92020-12-31 12:37:18 -0800371 case HelpMode::kPackage:
372 flags_internal::FlagsHelpImpl(
373 out, flags_internal::GetUsageConfig().contains_helppackage_flags,
374 GetFlagsHelpFormat(), program_usage_message);
Austin Schuh36244a12019-09-21 17:52:38 -0700375
Austin Schuhb4691e92020-12-31 12:37:18 -0800376 return 1;
Austin Schuh36244a12019-09-21 17:52:38 -0700377
Austin Schuhb4691e92020-12-31 12:37:18 -0800378 case HelpMode::kMatch: {
379 std::string substr = GetFlagsHelpMatchSubstr();
380 if (substr.empty()) {
381 // show all options
382 flags_internal::FlagsHelp(out, substr, GetFlagsHelpFormat(),
383 program_usage_message);
384 } else {
385 auto filter_cb = [&substr](const absl::CommandLineFlag& flag) {
386 if (absl::StrContains(flag.Name(), substr)) return true;
387 if (absl::StrContains(flag.Filename(), substr)) return true;
388 if (absl::StrContains(flag.Help(), substr)) return true;
Austin Schuh36244a12019-09-21 17:52:38 -0700389
Austin Schuhb4691e92020-12-31 12:37:18 -0800390 return false;
391 };
392 flags_internal::FlagsHelpImpl(
393 out, filter_cb, HelpFormat::kHumanReadable, program_usage_message);
394 }
Austin Schuh36244a12019-09-21 17:52:38 -0700395
Austin Schuhb4691e92020-12-31 12:37:18 -0800396 return 1;
397 }
398 case HelpMode::kVersion:
399 if (flags_internal::GetUsageConfig().version_string)
400 out << flags_internal::GetUsageConfig().version_string();
401 // Unlike help, we may be asking for version in a script, so return 0
402 return 0;
Austin Schuh36244a12019-09-21 17:52:38 -0700403
Austin Schuhb4691e92020-12-31 12:37:18 -0800404 case HelpMode::kOnlyCheckArgs:
405 return 0;
Austin Schuh36244a12019-09-21 17:52:38 -0700406 }
407
408 return -1;
409}
410
Austin Schuhb4691e92020-12-31 12:37:18 -0800411// --------------------------------------------------------------------
412// Globals representing usage reporting flags
413
414namespace {
415
416ABSL_CONST_INIT absl::Mutex help_attributes_guard(absl::kConstInit);
417ABSL_CONST_INIT std::string* match_substr
418 ABSL_GUARDED_BY(help_attributes_guard) = nullptr;
419ABSL_CONST_INIT HelpMode help_mode ABSL_GUARDED_BY(help_attributes_guard) =
420 HelpMode::kNone;
421ABSL_CONST_INIT HelpFormat help_format ABSL_GUARDED_BY(help_attributes_guard) =
422 HelpFormat::kHumanReadable;
423
424} // namespace
425
426std::string GetFlagsHelpMatchSubstr() {
427 absl::MutexLock l(&help_attributes_guard);
428 if (match_substr == nullptr) return "";
429 return *match_substr;
430}
431
432void SetFlagsHelpMatchSubstr(absl::string_view substr) {
433 absl::MutexLock l(&help_attributes_guard);
434 if (match_substr == nullptr) match_substr = new std::string;
435 match_substr->assign(substr.data(), substr.size());
436}
437
438HelpMode GetFlagsHelpMode() {
439 absl::MutexLock l(&help_attributes_guard);
440 return help_mode;
441}
442
443void SetFlagsHelpMode(HelpMode mode) {
444 absl::MutexLock l(&help_attributes_guard);
445 help_mode = mode;
446}
447
448HelpFormat GetFlagsHelpFormat() {
449 absl::MutexLock l(&help_attributes_guard);
450 return help_format;
451}
452
453void SetFlagsHelpFormat(HelpFormat format) {
454 absl::MutexLock l(&help_attributes_guard);
455 help_format = format;
456}
457
458// Deduces usage flags from the input argument in a form --name=value or
459// --name. argument is already split into name and value before we call this
460// function.
461bool DeduceUsageFlags(absl::string_view name, absl::string_view value) {
462 if (absl::ConsumePrefix(&name, "help")) {
463 if (name == "") {
464 if (value.empty()) {
465 SetFlagsHelpMode(HelpMode::kImportant);
466 } else {
467 SetFlagsHelpMode(HelpMode::kMatch);
468 SetFlagsHelpMatchSubstr(value);
469 }
470 return true;
471 }
472
473 if (name == "match") {
474 SetFlagsHelpMode(HelpMode::kMatch);
475 SetFlagsHelpMatchSubstr(value);
476 return true;
477 }
478
479 if (name == "on") {
480 SetFlagsHelpMode(HelpMode::kMatch);
481 SetFlagsHelpMatchSubstr(absl::StrCat("/", value, "."));
482 return true;
483 }
484
485 if (name == "full") {
486 SetFlagsHelpMode(HelpMode::kFull);
487 return true;
488 }
489
490 if (name == "short") {
491 SetFlagsHelpMode(HelpMode::kShort);
492 return true;
493 }
494
495 if (name == "package") {
496 SetFlagsHelpMode(HelpMode::kPackage);
497 return true;
498 }
499
500 return false;
501 }
502
503 if (name == "version") {
504 SetFlagsHelpMode(HelpMode::kVersion);
505 return true;
506 }
507
508 if (name == "only_check_args") {
509 SetFlagsHelpMode(HelpMode::kOnlyCheckArgs);
510 return true;
511 }
512
513 return false;
514}
515
Austin Schuh36244a12019-09-21 17:52:38 -0700516} // namespace flags_internal
Austin Schuhb4691e92020-12-31 12:37:18 -0800517ABSL_NAMESPACE_END
Austin Schuh36244a12019-09-21 17:52:38 -0700518} // namespace absl