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 <map>
19#include <string>
20
21#include "absl/flags/flag.h"
22#include "absl/flags/internal/path_util.h"
23#include "absl/flags/internal/program_name.h"
24#include "absl/flags/usage_config.h"
25#include "absl/strings/ascii.h"
26#include "absl/strings/str_cat.h"
27#include "absl/strings/str_split.h"
28#include "absl/synchronization/mutex.h"
29
30ABSL_FLAG(bool, help, false,
31 "show help on important flags for this binary [tip: all flags can "
32 "have two dashes]");
33ABSL_FLAG(bool, helpfull, false, "show help on all flags");
34ABSL_FLAG(bool, helpshort, false,
35 "show help on only the main module for this program");
36ABSL_FLAG(bool, helppackage, false,
37 "show help on all modules in the main package");
38ABSL_FLAG(bool, version, false, "show version and build info and exit");
39ABSL_FLAG(bool, only_check_args, false, "exit after checking all flags");
40ABSL_FLAG(std::string, helpon, "",
41 "show help on the modules named by this flag value");
42ABSL_FLAG(std::string, helpmatch, "",
43 "show help on modules whose name contains the specified substr");
44
45namespace absl {
46namespace flags_internal {
47namespace {
48
49// This class is used to emit an XML element with `tag` and `text`.
50// It adds opening and closing tags and escapes special characters in the text.
51// For example:
52// std::cout << XMLElement("title", "Milk & Cookies");
53// prints "<title>Milk &amp; Cookies</title>"
54class XMLElement {
55 public:
56 XMLElement(absl::string_view tag, absl::string_view txt)
57 : tag_(tag), txt_(txt) {}
58
59 friend std::ostream& operator<<(std::ostream& out,
60 const XMLElement& xml_elem) {
61 out << "<" << xml_elem.tag_ << ">";
62
63 for (auto c : xml_elem.txt_) {
64 switch (c) {
65 case '"':
66 out << "&quot;";
67 break;
68 case '\'':
69 out << "&apos;";
70 break;
71 case '&':
72 out << "&amp;";
73 break;
74 case '<':
75 out << "&lt;";
76 break;
77 case '>':
78 out << "&gt;";
79 break;
80 default:
81 out << c;
82 break;
83 }
84 }
85
86 return out << "</" << xml_elem.tag_ << ">";
87 }
88
89 private:
90 absl::string_view tag_;
91 absl::string_view txt_;
92};
93
94// --------------------------------------------------------------------
95// Helper class to pretty-print info about a flag.
96
97class FlagHelpPrettyPrinter {
98 public:
99 // Pretty printer holds on to the std::ostream& reference to direct an output
100 // to that stream.
101 FlagHelpPrettyPrinter(int max_line_len, std::ostream* out)
102 : out_(*out),
103 max_line_len_(max_line_len),
104 line_len_(0),
105 first_line_(true) {}
106
107 void Write(absl::string_view str, bool wrap_line = false) {
108 // Empty std::string - do nothing.
109 if (str.empty()) return;
110
111 std::vector<absl::string_view> tokens;
112 if (wrap_line) {
113 tokens = absl::StrSplit(str, absl::ByAnyChar(" \f\n\r\t\v"),
114 absl::SkipEmpty());
115 } else {
116 tokens.push_back(str);
117 }
118
119 for (auto token : tokens) {
120 bool new_line = (line_len_ == 0);
121
122 // Write the token, ending the std::string first if necessary/possible.
123 if (!new_line && (line_len_ + token.size() >= max_line_len_)) {
124 EndLine();
125 new_line = true;
126 }
127
128 if (new_line) {
129 StartLine();
130 } else {
131 out_ << ' ';
132 ++line_len_;
133 }
134
135 out_ << token;
136 line_len_ += token.size();
137 }
138 }
139
140 void StartLine() {
141 if (first_line_) {
142 out_ << " ";
143 line_len_ = 4;
144 first_line_ = false;
145 } else {
146 out_ << " ";
147 line_len_ = 6;
148 }
149 }
150 void EndLine() {
151 out_ << '\n';
152 line_len_ = 0;
153 }
154
155 private:
156 std::ostream& out_;
157 const int max_line_len_;
158 int line_len_;
159 bool first_line_;
160};
161
162void FlagHelpHumanReadable(const flags_internal::CommandLineFlag& flag,
163 std::ostream* out) {
164 FlagHelpPrettyPrinter printer(80, out); // Max line length is 80.
165
166 // Flag name.
167 printer.Write(absl::StrCat("-", flag.Name()));
168
169 // Flag help.
170 printer.Write(absl::StrCat("(", flag.Help(), ");"), /*wrap_line=*/true);
171
172 // Flag data type (for V1 flags only).
173 if (!flag.IsAbseilFlag() && !flag.IsRetired()) {
174 printer.Write(absl::StrCat("type: ", flag.Typename(), ";"));
175 }
176
177 // The listed default value will be the actual default from the flag
178 // definition in the originating source file, unless the value has
179 // subsequently been modified using SetCommandLineOption() with mode
180 // SET_FLAGS_DEFAULT.
181 std::string dflt_val = flag.DefaultValue();
182 if (flag.IsOfType<std::string>()) {
183 dflt_val = absl::StrCat("\"", dflt_val, "\"");
184 }
185 printer.Write(absl::StrCat("default: ", dflt_val, ";"));
186
187 if (flag.modified) {
188 std::string curr_val = flag.CurrentValue();
189 if (flag.IsOfType<std::string>()) {
190 curr_val = absl::StrCat("\"", curr_val, "\"");
191 }
192 printer.Write(absl::StrCat("currently: ", curr_val, ";"));
193 }
194
195 printer.EndLine();
196}
197
198// Shows help for every filename which matches any of the filters
199// If filters are empty, shows help for every file.
200// If a flag's help message has been stripped (e.g. by adding '#define
201// STRIP_FLAG_HELP 1' then this flag will not be displayed by '--help'
202// and its variants.
203void FlagsHelpImpl(std::ostream& out, flags_internal::FlagKindFilter filter_cb,
204 HelpFormat format = HelpFormat::kHumanReadable) {
205 if (format == HelpFormat::kHumanReadable) {
206 out << flags_internal::ShortProgramInvocationName() << ": "
207 << flags_internal::ProgramUsageMessage() << "\n\n";
208 } else {
209 // XML schema is not a part of our public API for now.
210 out << "<?xml version=\"1.0\"?>\n"
211 // The document.
212 << "<AllFlags>\n"
213 // The program name and usage.
214 << XMLElement("program", flags_internal::ShortProgramInvocationName())
215 << '\n'
216 << XMLElement("usage", flags_internal::ProgramUsageMessage()) << '\n';
217 }
218
219 // Map of package name to
220 // map of file name to
221 // vector of flags in the file.
222 // This map is used to output matching flags grouped by package and file
223 // name.
224 std::map<std::string,
225 std::map<std::string,
226 std::vector<const flags_internal::CommandLineFlag*>>>
227 matching_flags;
228
229 flags_internal::ForEachFlag([&](flags_internal::CommandLineFlag* flag) {
230 absl::MutexLock l(InitFlagIfNecessary(flag));
231
232 std::string flag_filename = flag->Filename();
233
234 // Ignore retired flags.
235 if (flag->IsRetired()) return;
236
237 // If the flag has been stripped, pretend that it doesn't exist.
238 if (flag->Help() == flags_internal::kStrippedFlagHelp) return;
239
240 // Make sure flag satisfies the filter
241 if (!filter_cb || !filter_cb(flag_filename)) return;
242
243 matching_flags[std::string(flags_internal::Package(flag_filename))]
244 [flag_filename]
245 .push_back(flag);
246 });
247
248 absl::string_view
249 package_separator; // controls blank lines between packages.
250 absl::string_view file_separator; // controls blank lines between files.
251 for (const auto& package : matching_flags) {
252 if (format == HelpFormat::kHumanReadable) {
253 out << package_separator;
254 package_separator = "\n\n";
255 }
256
257 file_separator = "";
258 for (const auto& flags_in_file : package.second) {
259 if (format == HelpFormat::kHumanReadable) {
260 out << file_separator << " Flags from " << flags_in_file.first
261 << ":\n";
262 file_separator = "\n";
263 }
264
265 for (const auto* flag : flags_in_file.second) {
266 flags_internal::FlagHelp(out, *flag, format);
267 }
268 }
269 }
270
271 if (format == HelpFormat::kHumanReadable) {
272 if (filter_cb && matching_flags.empty()) {
273 out << " No modules matched: use -helpfull\n";
274 }
275 } else {
276 // The end of the document.
277 out << "</AllFlags>\n";
278 }
279}
280
281ABSL_CONST_INIT absl::Mutex usage_message_guard(absl::kConstInit);
282ABSL_CONST_INIT std::string* program_usage_message
283 GUARDED_BY(usage_message_guard) = nullptr;
284
285} // namespace
286
287// --------------------------------------------------------------------
288// Sets the "usage" message to be used by help reporting routines.
289
290void SetProgramUsageMessage(absl::string_view new_usage_message) {
291 absl::MutexLock l(&usage_message_guard);
292
293 if (flags_internal::program_usage_message != nullptr) {
294 ABSL_INTERNAL_LOG(FATAL, "SetProgramUsageMessage() called twice.");
295 std::exit(1);
296 }
297
298 program_usage_message = new std::string(new_usage_message);
299}
300
301// --------------------------------------------------------------------
302// Returns the usage message set by SetProgramUsageMessage().
303// Note: We able to return string_view here only because calling
304// SetProgramUsageMessage twice is prohibited.
305absl::string_view ProgramUsageMessage() {
306 absl::MutexLock l(&usage_message_guard);
307
308 return program_usage_message != nullptr
309 ? absl::string_view(*program_usage_message)
310 : "Warning: SetProgramUsageMessage() never called";
311}
312
313// --------------------------------------------------------------------
314// Produces the help message describing specific flag.
315void FlagHelp(std::ostream& out, const flags_internal::CommandLineFlag& flag,
316 HelpFormat format) {
317 if (format == HelpFormat::kHumanReadable)
318 flags_internal::FlagHelpHumanReadable(flag, &out);
319}
320
321// --------------------------------------------------------------------
322// Produces the help messages for all flags matching the filter.
323// If filter is empty produces help messages for all flags.
324void FlagsHelp(std::ostream& out, absl::string_view filter, HelpFormat format) {
325 flags_internal::FlagKindFilter filter_cb = [&](absl::string_view filename) {
326 return filter.empty() || filename.find(filter) != absl::string_view::npos;
327 };
328 flags_internal::FlagsHelpImpl(out, filter_cb, format);
329}
330
331// --------------------------------------------------------------------
332// Checks all the 'usage' command line flags to see if any have been set.
333// If so, handles them appropriately.
334int HandleUsageFlags(std::ostream& out) {
335 if (absl::GetFlag(FLAGS_helpshort)) {
336 flags_internal::FlagsHelpImpl(
337 out, flags_internal::GetUsageConfig().contains_helpshort_flags,
338 HelpFormat::kHumanReadable);
339 return 1;
340 }
341
342 if (absl::GetFlag(FLAGS_helpfull)) {
343 // show all options
344 flags_internal::FlagsHelp(out);
345 return 1;
346 }
347
348 if (!absl::GetFlag(FLAGS_helpon).empty()) {
349 flags_internal::FlagsHelp(
350 out, absl::StrCat("/", absl::GetFlag(FLAGS_helpon), "."));
351 return 1;
352 }
353
354 if (!absl::GetFlag(FLAGS_helpmatch).empty()) {
355 flags_internal::FlagsHelp(out, absl::GetFlag(FLAGS_helpmatch));
356 return 1;
357 }
358
359 if (absl::GetFlag(FLAGS_help)) {
360 flags_internal::FlagsHelpImpl(
361 out, flags_internal::GetUsageConfig().contains_help_flags);
362
363 out << "\nTry --helpfull to get a list of all flags.\n";
364
365 return 1;
366 }
367
368 if (absl::GetFlag(FLAGS_helppackage)) {
369 flags_internal::FlagsHelpImpl(
370 out, flags_internal::GetUsageConfig().contains_helppackage_flags);
371
372 out << "\nTry --helpfull to get a list of all flags.\n";
373
374 return 1;
375 }
376
377 if (absl::GetFlag(FLAGS_version)) {
378 if (flags_internal::GetUsageConfig().version_string)
379 out << flags_internal::GetUsageConfig().version_string();
380 // Unlike help, we may be asking for version in a script, so return 0
381 return 0;
382 }
383
384 if (absl::GetFlag(FLAGS_only_check_args)) {
385 return 0;
386 }
387
388 return -1;
389}
390
391} // namespace flags_internal
392} // namespace absl
393