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