• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 &amp; 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 << "&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  
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