usage.cc (16992B)
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 <algorithm> 21 #include <cstdlib> 22 #include <functional> 23 #include <iterator> 24 #include <map> 25 #include <ostream> 26 #include <string> 27 #include <utility> 28 #include <vector> 29 30 #include "absl/base/attributes.h" 31 #include "absl/base/config.h" 32 #include "absl/base/no_destructor.h" 33 #include "absl/base/thread_annotations.h" 34 #include "absl/flags/commandlineflag.h" 35 #include "absl/flags/flag.h" 36 #include "absl/flags/internal/flag.h" 37 #include "absl/flags/internal/path_util.h" 38 #include "absl/flags/internal/private_handle_accessor.h" 39 #include "absl/flags/internal/program_name.h" 40 #include "absl/flags/internal/registry.h" 41 #include "absl/flags/usage_config.h" 42 #include "absl/strings/match.h" 43 #include "absl/strings/str_cat.h" 44 #include "absl/strings/str_split.h" 45 #include "absl/strings/string_view.h" 46 #include "absl/strings/strip.h" 47 #include "absl/synchronization/mutex.h" 48 49 // Dummy global variables to prevent anyone else defining these. 50 bool FLAGS_help = false; 51 bool FLAGS_helpfull = false; 52 bool FLAGS_helpshort = false; 53 bool FLAGS_helppackage = false; 54 bool FLAGS_version = false; 55 bool FLAGS_only_check_args = false; 56 bool FLAGS_helpon = false; 57 bool FLAGS_helpmatch = false; 58 59 namespace absl { 60 ABSL_NAMESPACE_BEGIN 61 namespace flags_internal { 62 namespace { 63 64 using PerFlagFilter = std::function<bool(const absl::CommandLineFlag&)>; 65 66 // Maximum length size in a human readable format. 67 constexpr size_t kHrfMaxLineLength = 80; 68 69 // This class is used to emit an XML element with `tag` and `text`. 70 // It adds opening and closing tags and escapes special characters in the text. 71 // For example: 72 // std::cout << XMLElement("title", "Milk & Cookies"); 73 // prints "<title>Milk & Cookies</title>" 74 class XMLElement { 75 public: 76 XMLElement(absl::string_view tag, absl::string_view txt) 77 : tag_(tag), txt_(txt) {} 78 79 friend std::ostream& operator<<(std::ostream& out, 80 const XMLElement& xml_elem) { 81 out << "<" << xml_elem.tag_ << ">"; 82 83 for (auto c : xml_elem.txt_) { 84 switch (c) { 85 case '"': 86 out << """; 87 break; 88 case '\'': 89 out << "'"; 90 break; 91 case '&': 92 out << "&"; 93 break; 94 case '<': 95 out << "<"; 96 break; 97 case '>': 98 out << ">"; 99 break; 100 case '\n': 101 case '\v': 102 case '\f': 103 case '\t': 104 out << " "; 105 break; 106 default: 107 if (IsValidXmlCharacter(static_cast<unsigned char>(c))) { 108 out << c; 109 } 110 break; 111 } 112 } 113 114 return out << "</" << xml_elem.tag_ << ">"; 115 } 116 117 private: 118 static bool IsValidXmlCharacter(unsigned char c) { return c >= 0x20; } 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. 130 FlagHelpPrettyPrinter(size_t max_line_len, size_t min_line_len, 131 size_t wrapped_line_indent, std::ostream& out) 132 : out_(out), 133 max_line_len_(max_line_len), 134 min_line_len_(min_line_len), 135 wrapped_line_indent_(wrapped_line_indent), 136 line_len_(0), 137 first_line_(true) {} 138 139 void Write(absl::string_view str, bool wrap_line = false) { 140 // Empty string - do nothing. 141 if (str.empty()) return; 142 143 std::vector<absl::string_view> tokens; 144 if (wrap_line) { 145 for (auto line : absl::StrSplit(str, absl::ByAnyChar("\n\r"))) { 146 if (!tokens.empty()) { 147 // Keep line separators in the input string. 148 tokens.emplace_back("\n"); 149 } 150 for (auto token : 151 absl::StrSplit(line, absl::ByAnyChar(" \t"), absl::SkipEmpty())) { 152 tokens.push_back(token); 153 } 154 } 155 } else { 156 tokens.push_back(str); 157 } 158 159 for (auto token : tokens) { 160 bool new_line = (line_len_ == 0); 161 162 // Respect line separators in the input string. 163 if (token == "\n") { 164 EndLine(); 165 continue; 166 } 167 168 // Write the token, ending the string first if necessary/possible. 169 if (!new_line && (line_len_ + token.size() >= max_line_len_)) { 170 EndLine(); 171 new_line = true; 172 } 173 174 if (new_line) { 175 StartLine(); 176 } else { 177 out_ << ' '; 178 ++line_len_; 179 } 180 181 out_ << token; 182 line_len_ += token.size(); 183 } 184 } 185 186 void StartLine() { 187 if (first_line_) { 188 line_len_ = min_line_len_; 189 first_line_ = false; 190 } else { 191 line_len_ = min_line_len_ + wrapped_line_indent_; 192 } 193 out_ << std::string(line_len_, ' '); 194 } 195 void EndLine() { 196 out_ << '\n'; 197 line_len_ = 0; 198 } 199 200 private: 201 std::ostream& out_; 202 const size_t max_line_len_; 203 const size_t min_line_len_; 204 const size_t wrapped_line_indent_; 205 size_t line_len_; 206 bool first_line_; 207 }; 208 209 void FlagHelpHumanReadable(const CommandLineFlag& flag, std::ostream& out) { 210 FlagHelpPrettyPrinter printer(kHrfMaxLineLength, 4, 2, out); 211 212 // Flag name. 213 printer.Write(absl::StrCat("--", flag.Name())); 214 215 // Flag help. 216 printer.Write(absl::StrCat("(", flag.Help(), ");"), /*wrap_line=*/true); 217 218 // The listed default value will be the actual default from the flag 219 // definition in the originating source file, unless the value has 220 // subsequently been modified using SetCommandLineOption() with mode 221 // SET_FLAGS_DEFAULT. 222 std::string dflt_val = flag.DefaultValue(); 223 std::string curr_val = flag.CurrentValue(); 224 bool is_modified = curr_val != dflt_val; 225 226 if (flag.IsOfType<std::string>()) { 227 dflt_val = absl::StrCat("\"", dflt_val, "\""); 228 } 229 printer.Write(absl::StrCat("default: ", dflt_val, ";")); 230 231 if (is_modified) { 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. 246 void FlagsHelpImpl(std::ostream& out, PerFlagFilter 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 // Ordered 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, std::vector<const absl::CommandLineFlag*>>> 272 matching_flags; 273 274 flags_internal::ForEachFlag([&](absl::CommandLineFlag& flag) { 275 // Ignore retired flags. 276 if (flag.IsRetired()) return; 277 278 // If the flag has been stripped, pretend that it doesn't exist. 279 if (flag.Help() == flags_internal::kStrippedFlagHelp) return; 280 281 // Make sure flag satisfies the filter 282 if (!filter_cb(flag)) return; 283 284 std::string flag_filename = flag.Filename(); 285 286 matching_flags[std::string(flags_internal::Package(flag_filename))] 287 [flag_filename] 288 .push_back(&flag); 289 }); 290 291 absl::string_view package_separator; // controls blank lines between packages 292 absl::string_view file_separator; // controls blank lines between files 293 for (auto& package : matching_flags) { 294 if (format == HelpFormat::kHumanReadable) { 295 out << package_separator; 296 package_separator = "\n\n"; 297 } 298 299 file_separator = ""; 300 for (auto& flags_in_file : package.second) { 301 if (format == HelpFormat::kHumanReadable) { 302 out << file_separator << " Flags from " << flags_in_file.first 303 << ":\n"; 304 file_separator = "\n"; 305 } 306 307 std::sort(std::begin(flags_in_file.second), 308 std::end(flags_in_file.second), 309 [](const CommandLineFlag* lhs, const CommandLineFlag* rhs) { 310 return lhs->Name() < rhs->Name(); 311 }); 312 313 for (const auto* flag : flags_in_file.second) { 314 flags_internal::FlagHelp(out, *flag, format); 315 } 316 } 317 } 318 319 if (format == HelpFormat::kHumanReadable) { 320 FlagHelpPrettyPrinter printer(kHrfMaxLineLength, 0, 0, out); 321 322 if (filter_cb && matching_flags.empty()) { 323 printer.Write("No flags matched.\n", true); 324 } 325 printer.EndLine(); 326 printer.Write( 327 "Try --helpfull to get a list of all flags or --help=substring " 328 "shows help for flags which include specified substring in either " 329 "in the name, or description or path.\n", 330 true); 331 } else { 332 // The end of the document. 333 out << "</AllFlags>\n"; 334 } 335 } 336 337 void FlagsHelpImpl(std::ostream& out, 338 flags_internal::FlagKindFilter filename_filter_cb, 339 HelpFormat format, absl::string_view program_usage_message) { 340 FlagsHelpImpl( 341 out, 342 [&](const absl::CommandLineFlag& flag) { 343 return filename_filter_cb && filename_filter_cb(flag.Filename()); 344 }, 345 format, program_usage_message); 346 } 347 348 } // namespace 349 350 // -------------------------------------------------------------------- 351 // Produces the help message describing specific flag. 352 void FlagHelp(std::ostream& out, const CommandLineFlag& flag, 353 HelpFormat format) { 354 if (format == HelpFormat::kHumanReadable) 355 flags_internal::FlagHelpHumanReadable(flag, out); 356 } 357 358 // -------------------------------------------------------------------- 359 // Produces the help messages for all flags matching the filename filter. 360 // If filter is empty produces help messages for all flags. 361 void FlagsHelp(std::ostream& out, absl::string_view filter, HelpFormat format, 362 absl::string_view program_usage_message) { 363 flags_internal::FlagKindFilter filter_cb = [&](absl::string_view filename) { 364 return filter.empty() || absl::StrContains(filename, filter); 365 }; 366 flags_internal::FlagsHelpImpl(out, filter_cb, format, program_usage_message); 367 } 368 369 // -------------------------------------------------------------------- 370 // Checks all the 'usage' command line flags to see if any have been set. 371 // If so, handles them appropriately. 372 HelpMode HandleUsageFlags(std::ostream& out, 373 absl::string_view program_usage_message) { 374 switch (GetFlagsHelpMode()) { 375 case HelpMode::kNone: 376 break; 377 case HelpMode::kImportant: 378 flags_internal::FlagsHelpImpl( 379 out, flags_internal::GetUsageConfig().contains_help_flags, 380 GetFlagsHelpFormat(), program_usage_message); 381 break; 382 383 case HelpMode::kShort: 384 flags_internal::FlagsHelpImpl( 385 out, flags_internal::GetUsageConfig().contains_helpshort_flags, 386 GetFlagsHelpFormat(), program_usage_message); 387 break; 388 389 case HelpMode::kFull: 390 flags_internal::FlagsHelp(out, "", GetFlagsHelpFormat(), 391 program_usage_message); 392 break; 393 394 case HelpMode::kPackage: 395 flags_internal::FlagsHelpImpl( 396 out, flags_internal::GetUsageConfig().contains_helppackage_flags, 397 GetFlagsHelpFormat(), program_usage_message); 398 break; 399 400 case HelpMode::kMatch: { 401 std::string substr = GetFlagsHelpMatchSubstr(); 402 if (substr.empty()) { 403 // show all options 404 flags_internal::FlagsHelp(out, substr, GetFlagsHelpFormat(), 405 program_usage_message); 406 } else { 407 auto filter_cb = [&substr](const absl::CommandLineFlag& flag) { 408 if (absl::StrContains(flag.Name(), substr)) return true; 409 if (absl::StrContains(flag.Filename(), substr)) return true; 410 if (absl::StrContains(flag.Help(), substr)) return true; 411 412 return false; 413 }; 414 flags_internal::FlagsHelpImpl( 415 out, filter_cb, HelpFormat::kHumanReadable, program_usage_message); 416 } 417 break; 418 } 419 case HelpMode::kVersion: 420 if (flags_internal::GetUsageConfig().version_string) 421 out << flags_internal::GetUsageConfig().version_string(); 422 // Unlike help, we may be asking for version in a script, so return 0 423 break; 424 425 case HelpMode::kOnlyCheckArgs: 426 break; 427 } 428 429 return GetFlagsHelpMode(); 430 } 431 432 // -------------------------------------------------------------------- 433 // Globals representing usage reporting flags 434 435 namespace { 436 437 absl::Mutex* HelpAttributesMutex() { 438 static absl::NoDestructor<absl::Mutex> mutex; 439 return mutex.get(); 440 } 441 ABSL_CONST_INIT std::string* match_substr ABSL_GUARDED_BY(HelpAttributesMutex()) 442 ABSL_PT_GUARDED_BY(HelpAttributesMutex()) = nullptr; 443 ABSL_CONST_INIT HelpMode help_mode ABSL_GUARDED_BY(HelpAttributesMutex()) = 444 HelpMode::kNone; 445 ABSL_CONST_INIT HelpFormat help_format ABSL_GUARDED_BY(HelpAttributesMutex()) = 446 HelpFormat::kHumanReadable; 447 448 } // namespace 449 450 std::string GetFlagsHelpMatchSubstr() { 451 absl::MutexLock l(HelpAttributesMutex()); 452 if (match_substr == nullptr) return ""; 453 return *match_substr; 454 } 455 456 void SetFlagsHelpMatchSubstr(absl::string_view substr) { 457 absl::MutexLock l(HelpAttributesMutex()); 458 if (match_substr == nullptr) match_substr = new std::string; 459 match_substr->assign(substr.data(), substr.size()); 460 } 461 462 HelpMode GetFlagsHelpMode() { 463 absl::MutexLock l(HelpAttributesMutex()); 464 return help_mode; 465 } 466 467 void SetFlagsHelpMode(HelpMode mode) { 468 absl::MutexLock l(HelpAttributesMutex()); 469 help_mode = mode; 470 } 471 472 HelpFormat GetFlagsHelpFormat() { 473 absl::MutexLock l(HelpAttributesMutex()); 474 return help_format; 475 } 476 477 void SetFlagsHelpFormat(HelpFormat format) { 478 absl::MutexLock l(HelpAttributesMutex()); 479 help_format = format; 480 } 481 482 // Deduces usage flags from the input argument in a form --name=value or 483 // --name. argument is already split into name and value before we call this 484 // function. 485 bool DeduceUsageFlags(absl::string_view name, absl::string_view value) { 486 if (absl::ConsumePrefix(&name, "help")) { 487 if (name.empty()) { 488 if (value.empty()) { 489 SetFlagsHelpMode(HelpMode::kImportant); 490 } else { 491 SetFlagsHelpMode(HelpMode::kMatch); 492 SetFlagsHelpMatchSubstr(value); 493 } 494 return true; 495 } 496 497 if (name == "match") { 498 SetFlagsHelpMode(HelpMode::kMatch); 499 SetFlagsHelpMatchSubstr(value); 500 return true; 501 } 502 503 if (name == "on") { 504 SetFlagsHelpMode(HelpMode::kMatch); 505 SetFlagsHelpMatchSubstr(absl::StrCat("/", value, ".")); 506 return true; 507 } 508 509 if (name == "full") { 510 SetFlagsHelpMode(HelpMode::kFull); 511 return true; 512 } 513 514 if (name == "short") { 515 SetFlagsHelpMode(HelpMode::kShort); 516 return true; 517 } 518 519 if (name == "package") { 520 SetFlagsHelpMode(HelpMode::kPackage); 521 return true; 522 } 523 524 return false; 525 } 526 527 if (name == "version") { 528 SetFlagsHelpMode(HelpMode::kVersion); 529 return true; 530 } 531 532 if (name == "only_check_args") { 533 SetFlagsHelpMode(HelpMode::kOnlyCheckArgs); 534 return true; 535 } 536 537 return false; 538 } 539 540 // -------------------------------------------------------------------- 541 542 void MaybeExit(HelpMode mode) { 543 switch (mode) { 544 case flags_internal::HelpMode::kNone: 545 return; 546 case flags_internal::HelpMode::kOnlyCheckArgs: 547 case flags_internal::HelpMode::kVersion: 548 std::exit(0); 549 default: // For all the other modes we exit with 1 550 std::exit(1); 551 } 552 } 553 554 // -------------------------------------------------------------------- 555 556 } // namespace flags_internal 557 ABSL_NAMESPACE_END 558 } // namespace absl