diff options
Diffstat (limited to 'abseil-cpp/absl/flags/parse.cc')
-rw-r--r-- | abseil-cpp/absl/flags/parse.cc | 344 |
1 files changed, 237 insertions, 107 deletions
diff --git a/abseil-cpp/absl/flags/parse.cc b/abseil-cpp/absl/flags/parse.cc index 4f4bb3d..4cdd9d0 100644 --- a/abseil-cpp/absl/flags/parse.cc +++ b/abseil-cpp/absl/flags/parse.cc @@ -18,9 +18,11 @@ #include <stdlib.h> #include <algorithm> +#include <cstdint> +#include <cstdlib> #include <fstream> #include <iostream> -#include <iterator> +#include <ostream> #include <string> #include <tuple> #include <utility> @@ -30,6 +32,7 @@ #include <windows.h> #endif +#include "absl/algorithm/container.h" #include "absl/base/attributes.h" #include "absl/base/config.h" #include "absl/base/const_init.h" @@ -47,7 +50,9 @@ #include "absl/flags/usage.h" #include "absl/flags/usage_config.h" #include "absl/strings/ascii.h" +#include "absl/strings/internal/damerau_levenshtein_distance.h" #include "absl/strings/str_cat.h" +#include "absl/strings/str_join.h" #include "absl/strings/string_view.h" #include "absl/strings/strip.h" #include "absl/synchronization/mutex.h" @@ -72,6 +77,11 @@ ABSL_CONST_INIT absl::Mutex specified_flags_guard(absl::kConstInit); ABSL_CONST_INIT std::vector<const CommandLineFlag*>* specified_flags ABSL_GUARDED_BY(specified_flags_guard) = nullptr; +// Suggesting at most kMaxHints flags in case of misspellings. +ABSL_CONST_INIT const size_t kMaxHints = 100; +// Suggesting only flags which have a smaller distance than kMaxDistance. +ABSL_CONST_INIT const size_t kMaxDistance = 3; + struct SpecifiedFlagsCompare { bool operator()(const CommandLineFlag* a, const CommandLineFlag* b) const { return a->Name() < b->Name(); @@ -89,6 +99,8 @@ struct SpecifiedFlagsCompare { ABSL_NAMESPACE_END } // namespace absl +// These flags influence how command line flags are parsed and are only intended +// to be set on the command line. Avoid reading or setting them from C++ code. ABSL_FLAG(std::vector<std::string>, flagfile, {}, "comma-separated list of files to load flags from") .OnUpdate([]() { @@ -138,6 +150,8 @@ ABSL_FLAG(std::vector<std::string>, tryfromenv, {}, absl::flags_internal::tryfromenv_needs_processing = true; }); +// Rather than reading or setting --undefok from C++ code, please consider using +// ABSL_RETIRED_FLAG instead. ABSL_FLAG(std::vector<std::string>, undefok, {}, "comma-separated list of flag names that it is okay to specify " "on the command line even if the program does not define a flag " @@ -159,14 +173,14 @@ class ArgsList { // Returns success status: true if parsing successful, false otherwise. bool ReadFromFlagfile(const std::string& flag_file_name); - int Size() const { return args_.size() - next_arg_; } - int FrontIndex() const { return next_arg_; } + size_t Size() const { return args_.size() - next_arg_; } + size_t FrontIndex() const { return next_arg_; } absl::string_view Front() const { return args_[next_arg_]; } void PopFront() { next_arg_++; } private: std::vector<std::string> args_; - int next_arg_; + size_t next_arg_; }; bool ArgsList::ReadFromFlagfile(const std::string& flag_file_name) { @@ -181,7 +195,7 @@ bool ArgsList::ReadFromFlagfile(const std::string& flag_file_name) { // This argument represents fake argv[0], which should be present in all arg // lists. - args_.push_back(""); + args_.emplace_back(""); std::string line; bool success = true; @@ -203,7 +217,7 @@ bool ArgsList::ReadFromFlagfile(const std::string& flag_file_name) { break; } - args_.push_back(std::string(stripped)); + args_.emplace_back(stripped); continue; } @@ -269,7 +283,7 @@ std::tuple<absl::string_view, absl::string_view, bool> SplitNameAndValue( return std::make_tuple("", "", false); } - auto equal_sign_pos = arg.find("="); + auto equal_sign_pos = arg.find('='); absl::string_view flag_name = arg.substr(0, equal_sign_pos); @@ -358,7 +372,7 @@ bool ReadFlagsFromEnv(const std::vector<std::string>& flag_names, // This argument represents fake argv[0], which should be present in all arg // lists. - args.push_back(""); + args.emplace_back(""); for (const auto& flag_name : flag_names) { // Avoid infinite recursion. @@ -407,7 +421,7 @@ bool HandleGeneratorFlags(std::vector<ArgsList>& input_args, // programmatically before invoking ParseCommandLine. Note that we do not // actually process arguments specified in the flagfile, but instead // create a secondary arguments list to be processed along with the rest - // of the comamnd line arguments. Since we always the process most recently + // of the command line arguments. Since we always the process most recently // created list of arguments first, this will result in flagfile argument // being processed before any other argument in the command line. If // FLAGS_flagfile contains more than one file name we create multiple new @@ -590,6 +604,34 @@ bool CanIgnoreUndefinedFlag(absl::string_view flag_name) { return false; } +// -------------------------------------------------------------------- + +void ReportUnrecognizedFlags( + const std::vector<UnrecognizedFlag>& unrecognized_flags, + bool report_as_fatal_error) { + for (const auto& unrecognized : unrecognized_flags) { + // Verify if flag_name has the "no" already removed + std::vector<std::string> misspelling_hints; + if (unrecognized.source == UnrecognizedFlag::kFromArgv) { + misspelling_hints = + flags_internal::GetMisspellingHints(unrecognized.flag_name); + } + + if (misspelling_hints.empty()) { + flags_internal::ReportUsageError( + absl::StrCat("Unknown command line flag '", unrecognized.flag_name, + "'"), + report_as_fatal_error); + } else { + flags_internal::ReportUsageError( + absl::StrCat("Unknown command line flag '", unrecognized.flag_name, + "'. Did you mean: ", + absl::StrJoin(misspelling_hints, ", "), " ?"), + report_as_fatal_error); + } + } +} + } // namespace // -------------------------------------------------------------------- @@ -605,55 +647,144 @@ bool WasPresentOnCommandLine(absl::string_view flag_name) { // -------------------------------------------------------------------- +struct BestHints { + explicit BestHints(uint8_t _max) : best_distance(_max + 1) {} + bool AddHint(absl::string_view hint, uint8_t distance) { + if (hints.size() >= kMaxHints) return false; + if (distance == best_distance) { + hints.emplace_back(hint); + } + if (distance < best_distance) { + best_distance = distance; + hints = std::vector<std::string>{std::string(hint)}; + } + return true; + } + + uint8_t best_distance; + std::vector<std::string> hints; +}; + +// Return the list of flags with the smallest Damerau-Levenshtein distance to +// the given flag. +std::vector<std::string> GetMisspellingHints(const absl::string_view flag) { + const size_t maxCutoff = std::min(flag.size() / 2 + 1, kMaxDistance); + auto undefok = absl::GetFlag(FLAGS_undefok); + BestHints best_hints(static_cast<uint8_t>(maxCutoff)); + flags_internal::ForEachFlag([&](const CommandLineFlag& f) { + if (best_hints.hints.size() >= kMaxHints) return; + uint8_t distance = strings_internal::CappedDamerauLevenshteinDistance( + flag, f.Name(), best_hints.best_distance); + best_hints.AddHint(f.Name(), distance); + // For boolean flags, also calculate distance to the negated form. + if (f.IsOfType<bool>()) { + const std::string negated_flag = absl::StrCat("no", f.Name()); + distance = strings_internal::CappedDamerauLevenshteinDistance( + flag, negated_flag, best_hints.best_distance); + best_hints.AddHint(negated_flag, distance); + } + }); + // Finally calculate distance to flags in "undefok". + absl::c_for_each(undefok, [&](const absl::string_view f) { + if (best_hints.hints.size() >= kMaxHints) return; + uint8_t distance = strings_internal::CappedDamerauLevenshteinDistance( + flag, f, best_hints.best_distance); + best_hints.AddHint(absl::StrCat(f, " (undefok)"), distance); + }); + return best_hints.hints; +} + +// -------------------------------------------------------------------- + std::vector<char*> ParseCommandLineImpl(int argc, char* argv[], - ArgvListAction arg_list_act, - UsageFlagsAction usage_flag_act, - OnUndefinedFlag on_undef_flag) { + UsageFlagsAction usage_flag_action, + OnUndefinedFlag undef_flag_action, + std::ostream& error_help_output) { + std::vector<char*> positional_args; + std::vector<UnrecognizedFlag> unrecognized_flags; + + auto help_mode = flags_internal::ParseAbseilFlagsOnlyImpl( + argc, argv, positional_args, unrecognized_flags, usage_flag_action); + + if (undef_flag_action != OnUndefinedFlag::kIgnoreUndefined) { + flags_internal::ReportUnrecognizedFlags( + unrecognized_flags, + (undef_flag_action == OnUndefinedFlag::kAbortIfUndefined)); + + if (undef_flag_action == OnUndefinedFlag::kAbortIfUndefined) { + if (!unrecognized_flags.empty()) { + flags_internal::HandleUsageFlags(error_help_output, + ProgramUsageMessage()); std::exit(1); + } + } + } + + flags_internal::MaybeExit(help_mode); + + return positional_args; +} + +// -------------------------------------------------------------------- + +// This function handles all Abseil Flags and built-in usage flags and, if any +// help mode was handled, it returns that help mode. The caller of this function +// can decide to exit based on the returned help mode. +// The caller may decide to handle unrecognized positional arguments and +// unrecognized flags first before exiting. +// +// Returns: +// * HelpMode::kFull if parsing errors were detected in recognized arguments +// * The HelpMode that was handled in case when `usage_flag_action` is +// UsageFlagsAction::kHandleUsage and a usage flag was specified on the +// commandline +// * Otherwise it returns HelpMode::kNone +HelpMode ParseAbseilFlagsOnlyImpl( + int argc, char* argv[], std::vector<char*>& positional_args, + std::vector<UnrecognizedFlag>& unrecognized_flags, + UsageFlagsAction usage_flag_action) { ABSL_INTERNAL_CHECK(argc > 0, "Missing argv[0]"); - // This routine does not return anything since we abort on failure. - CheckDefaultValuesParsingRoundtrip(); + using flags_internal::ArgsList; + using flags_internal::specified_flags; std::vector<std::string> flagfile_value; - std::vector<ArgsList> input_args; - input_args.push_back(ArgsList(argc, argv)); - std::vector<char*> output_args; - std::vector<char*> positional_args; - output_args.reserve(argc); + // Once parsing has started we will not allow more flag registrations. + flags_internal::FinalizeRegistry(); + + // This routine does not return anything since we abort on failure. + flags_internal::CheckDefaultValuesParsingRoundtrip(); - // This is the list of undefined flags. The element of the list is the pair - // consisting of boolean indicating if flag came from command line (vs from - // some flag file we've read) and flag name. - // TODO(rogeeff): Eliminate the first element in the pair after cleanup. - std::vector<std::pair<bool, std::string>> undefined_flag_names; + input_args.push_back(ArgsList(argc, argv)); // Set program invocation name if it is not set before. - if (ProgramInvocationName() == "UNKNOWN") { + if (flags_internal::ProgramInvocationName() == "UNKNOWN") { flags_internal::SetProgramInvocationName(argv[0]); } - output_args.push_back(argv[0]); + positional_args.push_back(argv[0]); - absl::MutexLock l(&specified_flags_guard); + absl::MutexLock l(&flags_internal::specified_flags_guard); if (specified_flags == nullptr) { specified_flags = new std::vector<const CommandLineFlag*>; } else { specified_flags->clear(); } - // Iterate through the list of the input arguments. First level are arguments - // originated from argc/argv. Following levels are arguments originated from - // recursive parsing of flagfile(s). + // Iterate through the list of the input arguments. First level are + // arguments originated from argc/argv. Following levels are arguments + // originated from recursive parsing of flagfile(s). bool success = true; while (!input_args.empty()) { - // 10. First we process the built-in generator flags. - success &= HandleGeneratorFlags(input_args, flagfile_value); + // First we process the built-in generator flags. + success &= flags_internal::HandleGeneratorFlags(input_args, flagfile_value); - // 30. Select top-most (most recent) arguments list. If it is empty drop it + // Select top-most (most recent) arguments list. If it is empty drop it // and re-try. ArgsList& curr_list = input_args.back(); + // Every ArgsList starts with real or fake program name, so we can always + // start by skipping it. curr_list.PopFront(); if (curr_list.Size() == 0) { @@ -661,13 +792,13 @@ std::vector<char*> ParseCommandLineImpl(int argc, char* argv[], continue; } - // 40. Pick up the front remaining argument in the current list. If current - // stack of argument lists contains only one element - we are processing an - // argument from the original argv. + // Handle the next argument in the current list. If the stack of argument + // lists contains only one element - we are processing an argument from + // the original argv. absl::string_view arg(curr_list.Front()); bool arg_from_argv = input_args.size() == 1; - // 50. If argument does not start with - or is just "-" - this is + // If argument does not start with '-' or is just "-" - this is // positional argument. if (!absl::ConsumePrefix(&arg, "-") || arg.empty()) { ABSL_INTERNAL_CHECK(arg_from_argv, @@ -677,12 +808,8 @@ std::vector<char*> ParseCommandLineImpl(int argc, char* argv[], continue; } - if (arg_from_argv && (arg_list_act == ArgvListAction::kKeepParsedArgs)) { - output_args.push_back(argv[curr_list.FrontIndex()]); - } - - // 60. Split the current argument on '=' to figure out the argument - // name and value. If flag name is empty it means we've got "--". value + // Split the current argument on '=' to deduce the argument flag name and + // value. If flag name is empty it means we've got an "--" argument. Value // can be empty either if there were no '=' in argument string at all or // an argument looked like "--foo=". In a latter case is_empty_value is // true. @@ -690,10 +817,11 @@ std::vector<char*> ParseCommandLineImpl(int argc, char* argv[], absl::string_view value; bool is_empty_value = false; - std::tie(flag_name, value, is_empty_value) = SplitNameAndValue(arg); + std::tie(flag_name, value, is_empty_value) = + flags_internal::SplitNameAndValue(arg); - // 70. "--" alone means what it does for GNU: stop flags parsing. We do - // not support positional arguments in flagfiles, so we just drop them. + // Standalone "--" argument indicates that the rest of the arguments are + // positional. We do not support positional arguments in flagfiles. if (flag_name.empty()) { ABSL_INTERNAL_CHECK(arg_from_argv, "Flagfile cannot contain positional argument"); @@ -702,38 +830,36 @@ std::vector<char*> ParseCommandLineImpl(int argc, char* argv[], break; } - // 80. Locate the flag based on flag name. Handle both --foo and --nofoo + // Locate the flag based on flag name. Handle both --foo and --nofoo. CommandLineFlag* flag = nullptr; bool is_negative = false; - std::tie(flag, is_negative) = LocateFlag(flag_name); + std::tie(flag, is_negative) = flags_internal::LocateFlag(flag_name); if (flag == nullptr) { - if (on_undef_flag != OnUndefinedFlag::kIgnoreUndefined) { - undefined_flag_names.emplace_back(arg_from_argv, - std::string(flag_name)); + // Usage flags are not modeled as Abseil flags. Locate them separately. + if (flags_internal::DeduceUsageFlags(flag_name, value)) { + continue; } + unrecognized_flags.emplace_back(arg_from_argv + ? UnrecognizedFlag::kFromArgv + : UnrecognizedFlag::kFromFlagfile, + flag_name); continue; } - // 90. Deduce flag's value (from this or next argument) - auto curr_index = curr_list.FrontIndex(); + // Deduce flag's value (from this or next argument). bool value_success = true; - std::tie(value_success, value) = - DeduceFlagValue(*flag, value, is_negative, is_empty_value, &curr_list); + std::tie(value_success, value) = flags_internal::DeduceFlagValue( + *flag, value, is_negative, is_empty_value, &curr_list); success &= value_success; - // If above call consumed an argument, it was a standalone value - if (arg_from_argv && (arg_list_act == ArgvListAction::kKeepParsedArgs) && - (curr_index != curr_list.FrontIndex())) { - output_args.push_back(argv[curr_list.FrontIndex()]); - } - - // 100. Set the located flag to a new new value, unless it is retired. - // Setting retired flag fails, but we ignoring it here while also reporting - // access to retired flag. + // Set the located flag to a new value, unless it is retired. Setting + // retired flag fails, but we ignoring it here while also reporting access + // to retired flag. std::string error; if (!flags_internal::PrivateHandleAccessor::ParseFrom( - *flag, value, SET_FLAGS_VALUE, kCommandLine, error)) { + *flag, value, flags_internal::SET_FLAGS_VALUE, + flags_internal::kCommandLine, error)) { if (flag->IsRetired()) continue; flags_internal::ReportUsageError(error, true); @@ -743,69 +869,73 @@ std::vector<char*> ParseCommandLineImpl(int argc, char* argv[], } } - for (const auto& flag_name : undefined_flag_names) { - if (CanIgnoreUndefinedFlag(flag_name.second)) continue; + flags_internal::ResetGeneratorFlags(flagfile_value); - flags_internal::ReportUsageError( - absl::StrCat("Unknown command line flag '", flag_name.second, "'"), - true); + // All the remaining arguments are positional. + if (!input_args.empty()) { + for (size_t arg_index = input_args.back().FrontIndex(); + arg_index < static_cast<size_t>(argc); ++arg_index) { + positional_args.push_back(argv[arg_index]); + } + } - success = false; + // Trim and sort the vector. + specified_flags->shrink_to_fit(); + std::sort(specified_flags->begin(), specified_flags->end(), + flags_internal::SpecifiedFlagsCompare{}); + + // Filter out unrecognized flags, which are ok to ignore. + std::vector<UnrecognizedFlag> filtered; + filtered.reserve(unrecognized_flags.size()); + for (const auto& unrecognized : unrecognized_flags) { + if (flags_internal::CanIgnoreUndefinedFlag(unrecognized.flag_name)) + continue; + filtered.push_back(unrecognized); } -#if ABSL_FLAGS_STRIP_NAMES + std::swap(unrecognized_flags, filtered); + if (!success) { +#if ABSL_FLAGS_STRIP_NAMES flags_internal::ReportUsageError( "NOTE: command line flags are disabled in this build", true); - } +#else + flags_internal::HandleUsageFlags(std::cerr, ProgramUsageMessage()); #endif - - if (!success) { - flags_internal::HandleUsageFlags(std::cout, - ProgramUsageMessage()); - std::exit(1); + return HelpMode::kFull; // We just need to make sure the exit with + // code 1. } - if (usage_flag_act == UsageFlagsAction::kHandleUsage) { - int exit_code = flags_internal::HandleUsageFlags( - std::cout, ProgramUsageMessage()); + return usage_flag_action == UsageFlagsAction::kHandleUsage + ? flags_internal::HandleUsageFlags(std::cout, + ProgramUsageMessage()) + : HelpMode::kNone; +} - if (exit_code != -1) { - std::exit(exit_code); - } - } +} // namespace flags_internal - ResetGeneratorFlags(flagfile_value); +void ParseAbseilFlagsOnly(int argc, char* argv[], + std::vector<char*>& positional_args, + std::vector<UnrecognizedFlag>& unrecognized_flags) { + auto help_mode = flags_internal::ParseAbseilFlagsOnlyImpl( + argc, argv, positional_args, unrecognized_flags, + flags_internal::UsageFlagsAction::kHandleUsage); - // Reinstate positional args which were intermixed with flags in the arguments - // list. - for (auto arg : positional_args) { - output_args.push_back(arg); - } + flags_internal::MaybeExit(help_mode); +} - // All the remaining arguments are positional. - if (!input_args.empty()) { - for (int arg_index = input_args.back().FrontIndex(); arg_index < argc; - ++arg_index) { - output_args.push_back(argv[arg_index]); - } - } +// -------------------------------------------------------------------- - // Trim and sort the vector. - specified_flags->shrink_to_fit(); - std::sort(specified_flags->begin(), specified_flags->end(), - SpecifiedFlagsCompare{}); - return output_args; +void ReportUnrecognizedFlags( + const std::vector<UnrecognizedFlag>& unrecognized_flags) { + flags_internal::ReportUnrecognizedFlags(unrecognized_flags, true); } -} // namespace flags_internal - // -------------------------------------------------------------------- std::vector<char*> ParseCommandLine(int argc, char* argv[]) { return flags_internal::ParseCommandLineImpl( - argc, argv, flags_internal::ArgvListAction::kRemoveParsedArgs, - flags_internal::UsageFlagsAction::kHandleUsage, + argc, argv, flags_internal::UsageFlagsAction::kHandleUsage, flags_internal::OnUndefinedFlag::kAbortIfUndefined); } |