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 ABSL_FLAG(bool, help, false,
41 "show help on important flags for this binary [tip: all flags can "
42 "have two dashes]");
43 ABSL_FLAG(bool, helpfull, false, "show help on all flags");
44 ABSL_FLAG(bool, helpshort, false,
45 "show help on only the main module for this program");
46 ABSL_FLAG(bool, helppackage, false,
47 "show help on all modules in the main package");
48 ABSL_FLAG(bool, version, false, "show version and build info and exit");
49 ABSL_FLAG(bool, only_check_args, false, "exit after checking all flags");
50 ABSL_FLAG(std::string, helpon, "",
51 "show help on the modules named by this flag value");
52 ABSL_FLAG(std::string, helpmatch, "",
53 "show help on modules whose name contains the specified substr");
54
55 namespace absl {
56 ABSL_NAMESPACE_BEGIN
57 namespace flags_internal {
58 namespace {
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(int max_line_len,std::ostream & out)112 FlagHelpPrettyPrinter(int max_line_len, std::ostream& out)
113 : out_(out),
114 max_line_len_(max_line_len),
115 line_len_(0),
116 first_line_(true) {}
117
Write(absl::string_view str,bool wrap_line=false)118 void Write(absl::string_view str, bool wrap_line = false) {
119 // Empty string - do nothing.
120 if (str.empty()) return;
121
122 std::vector<absl::string_view> tokens;
123 if (wrap_line) {
124 for (auto line : absl::StrSplit(str, absl::ByAnyChar("\n\r"))) {
125 if (!tokens.empty()) {
126 // Keep line separators in the input string.
127 tokens.push_back("\n");
128 }
129 for (auto token :
130 absl::StrSplit(line, absl::ByAnyChar(" \t"), absl::SkipEmpty())) {
131 tokens.push_back(token);
132 }
133 }
134 } else {
135 tokens.push_back(str);
136 }
137
138 for (auto token : tokens) {
139 bool new_line = (line_len_ == 0);
140
141 // Respect line separators in the input string.
142 if (token == "\n") {
143 EndLine();
144 continue;
145 }
146
147 // Write the token, ending the string first if necessary/possible.
148 if (!new_line && (line_len_ + token.size() >= max_line_len_)) {
149 EndLine();
150 new_line = true;
151 }
152
153 if (new_line) {
154 StartLine();
155 } else {
156 out_ << ' ';
157 ++line_len_;
158 }
159
160 out_ << token;
161 line_len_ += token.size();
162 }
163 }
164
StartLine()165 void StartLine() {
166 if (first_line_) {
167 out_ << " ";
168 line_len_ = 4;
169 first_line_ = false;
170 } else {
171 out_ << " ";
172 line_len_ = 6;
173 }
174 }
EndLine()175 void EndLine() {
176 out_ << '\n';
177 line_len_ = 0;
178 }
179
180 private:
181 std::ostream& out_;
182 const int max_line_len_;
183 int line_len_;
184 bool first_line_;
185 };
186
FlagHelpHumanReadable(const CommandLineFlag & flag,std::ostream & out)187 void FlagHelpHumanReadable(const CommandLineFlag& flag, std::ostream& out) {
188 FlagHelpPrettyPrinter printer(80, out); // Max line length is 80.
189
190 // Flag name.
191 printer.Write(absl::StrCat("--", flag.Name()));
192
193 // Flag help.
194 printer.Write(absl::StrCat("(", flag.Help(), ");"), /*wrap_line=*/true);
195
196 // The listed default value will be the actual default from the flag
197 // definition in the originating source file, unless the value has
198 // subsequently been modified using SetCommandLineOption() with mode
199 // SET_FLAGS_DEFAULT.
200 std::string dflt_val = flag.DefaultValue();
201 std::string curr_val = flag.CurrentValue();
202 bool is_modified = curr_val != dflt_val;
203
204 if (flag.IsOfType<std::string>()) {
205 dflt_val = absl::StrCat("\"", dflt_val, "\"");
206 }
207 printer.Write(absl::StrCat("default: ", dflt_val, ";"));
208
209 if (is_modified) {
210 if (flag.IsOfType<std::string>()) {
211 curr_val = absl::StrCat("\"", curr_val, "\"");
212 }
213 printer.Write(absl::StrCat("currently: ", curr_val, ";"));
214 }
215
216 printer.EndLine();
217 }
218
219 // Shows help for every filename which matches any of the filters
220 // If filters are empty, shows help for every file.
221 // If a flag's help message has been stripped (e.g. by adding '#define
222 // STRIP_FLAG_HELP 1' then this flag will not be displayed by '--help'
223 // and its variants.
FlagsHelpImpl(std::ostream & out,flags_internal::FlagKindFilter filter_cb,HelpFormat format,absl::string_view program_usage_message)224 void FlagsHelpImpl(std::ostream& out, flags_internal::FlagKindFilter filter_cb,
225 HelpFormat format, absl::string_view program_usage_message) {
226 if (format == HelpFormat::kHumanReadable) {
227 out << flags_internal::ShortProgramInvocationName() << ": "
228 << program_usage_message << "\n\n";
229 } else {
230 // XML schema is not a part of our public API for now.
231 out << "<?xml version=\"1.0\"?>\n"
232 << "<!-- This output should be used with care. We do not report type "
233 "names for flags with user defined types -->\n"
234 << "<!-- Prefer flag only_check_args for validating flag inputs -->\n"
235 // The document.
236 << "<AllFlags>\n"
237 // The program name and usage.
238 << XMLElement("program", flags_internal::ShortProgramInvocationName())
239 << '\n'
240 << XMLElement("usage", program_usage_message) << '\n';
241 }
242
243 // Map of package name to
244 // map of file name to
245 // vector of flags in the file.
246 // This map is used to output matching flags grouped by package and file
247 // name.
248 std::map<std::string,
249 std::map<std::string, std::vector<const absl::CommandLineFlag*>>>
250 matching_flags;
251
252 flags_internal::ForEachFlag([&](absl::CommandLineFlag& flag) {
253 // Ignore retired flags.
254 if (flag.IsRetired()) return;
255
256 // If the flag has been stripped, pretend that it doesn't exist.
257 if (flag.Help() == flags_internal::kStrippedFlagHelp) return;
258
259 std::string flag_filename = flag.Filename();
260
261 // Make sure flag satisfies the filter
262 if (!filter_cb || !filter_cb(flag_filename)) return;
263
264 matching_flags[std::string(flags_internal::Package(flag_filename))]
265 [flag_filename]
266 .push_back(&flag);
267 });
268
269 absl::string_view package_separator; // controls blank lines between packages
270 absl::string_view file_separator; // controls blank lines between files
271 for (const auto& package : matching_flags) {
272 if (format == HelpFormat::kHumanReadable) {
273 out << package_separator;
274 package_separator = "\n\n";
275 }
276
277 file_separator = "";
278 for (const auto& flags_in_file : package.second) {
279 if (format == HelpFormat::kHumanReadable) {
280 out << file_separator << " Flags from " << flags_in_file.first
281 << ":\n";
282 file_separator = "\n";
283 }
284
285 for (const auto* flag : flags_in_file.second) {
286 flags_internal::FlagHelp(out, *flag, format);
287 }
288 }
289 }
290
291 if (format == HelpFormat::kHumanReadable) {
292 if (filter_cb && matching_flags.empty()) {
293 out << " No modules matched: use -helpfull\n";
294 }
295 } else {
296 // The end of the document.
297 out << "</AllFlags>\n";
298 }
299 }
300
301 } // namespace
302
303 // --------------------------------------------------------------------
304 // Produces the help message describing specific flag.
FlagHelp(std::ostream & out,const CommandLineFlag & flag,HelpFormat format)305 void FlagHelp(std::ostream& out, const CommandLineFlag& flag,
306 HelpFormat format) {
307 if (format == HelpFormat::kHumanReadable)
308 flags_internal::FlagHelpHumanReadable(flag, out);
309 }
310
311 // --------------------------------------------------------------------
312 // Produces the help messages for all flags matching the filter.
313 // 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)314 void FlagsHelp(std::ostream& out, absl::string_view filter, HelpFormat format,
315 absl::string_view program_usage_message) {
316 flags_internal::FlagKindFilter filter_cb = [&](absl::string_view filename) {
317 return filter.empty() || filename.find(filter) != absl::string_view::npos;
318 };
319 flags_internal::FlagsHelpImpl(out, filter_cb, format, program_usage_message);
320 }
321
322 // --------------------------------------------------------------------
323 // Checks all the 'usage' command line flags to see if any have been set.
324 // If so, handles them appropriately.
HandleUsageFlags(std::ostream & out,absl::string_view program_usage_message)325 int HandleUsageFlags(std::ostream& out,
326 absl::string_view program_usage_message) {
327 if (absl::GetFlag(FLAGS_helpshort)) {
328 flags_internal::FlagsHelpImpl(
329 out, flags_internal::GetUsageConfig().contains_helpshort_flags,
330 HelpFormat::kHumanReadable, program_usage_message);
331 return 1;
332 }
333
334 if (absl::GetFlag(FLAGS_helpfull)) {
335 // show all options
336 flags_internal::FlagsHelp(out, "", HelpFormat::kHumanReadable,
337 program_usage_message);
338 return 1;
339 }
340
341 if (!absl::GetFlag(FLAGS_helpon).empty()) {
342 flags_internal::FlagsHelp(
343 out, absl::StrCat("/", absl::GetFlag(FLAGS_helpon), "."),
344 HelpFormat::kHumanReadable, program_usage_message);
345 return 1;
346 }
347
348 if (!absl::GetFlag(FLAGS_helpmatch).empty()) {
349 flags_internal::FlagsHelp(out, absl::GetFlag(FLAGS_helpmatch),
350 HelpFormat::kHumanReadable,
351 program_usage_message);
352 return 1;
353 }
354
355 if (absl::GetFlag(FLAGS_help)) {
356 flags_internal::FlagsHelpImpl(
357 out, flags_internal::GetUsageConfig().contains_help_flags,
358 HelpFormat::kHumanReadable, program_usage_message);
359
360 out << "\nTry --helpfull to get a list of all flags.\n";
361
362 return 1;
363 }
364
365 if (absl::GetFlag(FLAGS_helppackage)) {
366 flags_internal::FlagsHelpImpl(
367 out, flags_internal::GetUsageConfig().contains_helppackage_flags,
368 HelpFormat::kHumanReadable, program_usage_message);
369
370 out << "\nTry --helpfull to get a list of all flags.\n";
371
372 return 1;
373 }
374
375 if (absl::GetFlag(FLAGS_version)) {
376 if (flags_internal::GetUsageConfig().version_string)
377 out << flags_internal::GetUsageConfig().version_string();
378 // Unlike help, we may be asking for version in a script, so return 0
379 return 0;
380 }
381
382 if (absl::GetFlag(FLAGS_only_check_args)) {
383 return 0;
384 }
385
386 return -1;
387 }
388
389 } // namespace flags_internal
390 ABSL_NAMESPACE_END
391 } // namespace absl
392