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