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