diff options
Diffstat (limited to 'absl/flags')
-rw-r--r-- | absl/flags/BUILD.bazel | 5 | ||||
-rw-r--r-- | absl/flags/CMakeLists.txt | 7 | ||||
-rw-r--r-- | absl/flags/commandlineflag.h | 2 | ||||
-rw-r--r-- | absl/flags/flag_test.cc | 64 | ||||
-rw-r--r-- | absl/flags/internal/commandlineflag.cc | 2 | ||||
-rw-r--r-- | absl/flags/internal/flag.cc | 4 | ||||
-rw-r--r-- | absl/flags/internal/flag.h | 31 | ||||
-rw-r--r-- | absl/flags/internal/flag_msvc.inc | 2 | ||||
-rw-r--r-- | absl/flags/internal/parse.h | 17 | ||||
-rw-r--r-- | absl/flags/internal/usage.cc | 52 | ||||
-rw-r--r-- | absl/flags/internal/usage.h | 42 | ||||
-rw-r--r-- | absl/flags/internal/usage_test.cc | 94 | ||||
-rw-r--r-- | absl/flags/marshalling.cc | 45 | ||||
-rw-r--r-- | absl/flags/marshalling.h | 5 | ||||
-rw-r--r-- | absl/flags/marshalling_test.cc | 198 | ||||
-rw-r--r-- | absl/flags/parse.cc | 295 | ||||
-rw-r--r-- | absl/flags/parse.h | 108 | ||||
-rw-r--r-- | absl/flags/parse_test.cc | 279 | ||||
-rw-r--r-- | absl/flags/usage.cc | 1 |
19 files changed, 899 insertions, 354 deletions
diff --git a/absl/flags/BUILD.bazel b/absl/flags/BUILD.bazel index f5b3e033..50bf387c 100644 --- a/absl/flags/BUILD.bazel +++ b/absl/flags/BUILD.bazel @@ -99,6 +99,7 @@ cc_library( "//absl/base:config", "//absl/base:core_headers", "//absl/base:log_severity", + "//absl/numeric:int128", "//absl/strings", "//absl/strings:str_format", "//absl/types:optional", @@ -284,6 +285,7 @@ cc_library( ":usage_internal", "//absl/base:config", "//absl/base:core_headers", + "//absl/base:raw_logging_internal", "//absl/strings", "//absl/synchronization", ], @@ -386,6 +388,7 @@ cc_test( ":reflection", "//absl/base:core_headers", "//absl/base:malloc_internal", + "//absl/numeric:int128", "//absl/strings", "//absl/time", "@com_google_googletest//:gtest_main", @@ -451,8 +454,8 @@ cc_test( ":parse", ":reflection", ":usage_internal", - "//absl/base:raw_logging_internal", "//absl/base:scoped_set_env", + "//absl/log", "//absl/strings", "//absl/types:span", "@com_google_googletest//:gtest_main", diff --git a/absl/flags/CMakeLists.txt b/absl/flags/CMakeLists.txt index 2f234aa4..b20463f5 100644 --- a/absl/flags/CMakeLists.txt +++ b/absl/flags/CMakeLists.txt @@ -87,6 +87,7 @@ absl_cc_library( absl::config absl::core_headers absl::log_severity + absl::int128 absl::optional absl::strings absl::str_format @@ -262,6 +263,7 @@ absl_cc_library( absl::config absl::core_headers absl::flags_usage_internal + absl::raw_logging_internal absl::strings absl::synchronization ) @@ -296,7 +298,7 @@ absl_cc_library( ) ############################################################################ -# Unit tests in alpahabetical order. +# Unit tests in alphabetical order. absl_cc_test( NAME @@ -344,6 +346,7 @@ absl_cc_test( absl::flags_internal absl::flags_marshalling absl::flags_reflection + absl::int128 absl::strings absl::time GTest::gtest_main @@ -373,7 +376,7 @@ absl_cc_test( absl::flags_parse absl::flags_reflection absl::flags_usage_internal - absl::raw_logging_internal + absl::log absl::scoped_set_env absl::span absl::strings diff --git a/absl/flags/commandlineflag.h b/absl/flags/commandlineflag.h index f2fa0897..c30aa609 100644 --- a/absl/flags/commandlineflag.h +++ b/absl/flags/commandlineflag.h @@ -186,7 +186,7 @@ class CommandLineFlag { // command line. virtual bool IsSpecifiedOnCommandLine() const = 0; - // Validates supplied value usign validator or parseflag routine + // Validates supplied value using validator or parseflag routine virtual bool ValidateInputValue(absl::string_view value) const = 0; // Checks that flags default value can be converted to string and back to the diff --git a/absl/flags/flag_test.cc b/absl/flags/flag_test.cc index 845b4eba..f9cda02e 100644 --- a/absl/flags/flag_test.cc +++ b/absl/flags/flag_test.cc @@ -34,6 +34,7 @@ #include "absl/flags/marshalling.h" #include "absl/flags/reflection.h" #include "absl/flags/usage_config.h" +#include "absl/numeric/int128.h" #include "absl/strings/match.h" #include "absl/strings/numbers.h" #include "absl/strings/str_cat.h" @@ -127,6 +128,11 @@ TEST_F(FlagTest, Traits) { flags::FlagValueStorageKind::kAlignedBuffer); EXPECT_EQ(flags::StorageKind<std::vector<std::string>>(), flags::FlagValueStorageKind::kAlignedBuffer); + + EXPECT_EQ(flags::StorageKind<absl::int128>(), + flags::FlagValueStorageKind::kSequenceLocked); + EXPECT_EQ(flags::StorageKind<absl::uint128>(), + flags::FlagValueStorageKind::kSequenceLocked); } // -------------------------------------------------------------------- @@ -135,6 +141,8 @@ constexpr flags::FlagHelpArg help_arg{flags::FlagHelpMsg("literal help"), flags::FlagHelpKind::kLiteral}; using String = std::string; +using int128 = absl::int128; +using uint128 = absl::uint128; #if !defined(_MSC_VER) || defined(__clang__) #define DEFINE_CONSTRUCTED_FLAG(T, dflt, dflt_kind) \ @@ -171,6 +179,8 @@ DEFINE_CONSTRUCTED_FLAG(float, 7.8, kOneWord); DEFINE_CONSTRUCTED_FLAG(double, 9.10, kOneWord); DEFINE_CONSTRUCTED_FLAG(String, &TestMakeDflt<String>, kGenFunc); DEFINE_CONSTRUCTED_FLAG(UDT, &TestMakeDflt<UDT>, kGenFunc); +DEFINE_CONSTRUCTED_FLAG(int128, 13, kGenFunc); +DEFINE_CONSTRUCTED_FLAG(uint128, 14, kGenFunc); template <typename T> bool TestConstructionFor(const absl::Flag<T>& f1, absl::Flag<T>& f2) { @@ -202,6 +212,8 @@ TEST_F(FlagTest, TestConstruction) { TEST_CONSTRUCTED_FLAG(double); TEST_CONSTRUCTED_FLAG(String); TEST_CONSTRUCTED_FLAG(UDT); + TEST_CONSTRUCTED_FLAG(int128); + TEST_CONSTRUCTED_FLAG(uint128); } // -------------------------------------------------------------------- @@ -220,6 +232,8 @@ ABSL_DECLARE_FLAG(double, test_flag_09); ABSL_DECLARE_FLAG(float, test_flag_10); ABSL_DECLARE_FLAG(std::string, test_flag_11); ABSL_DECLARE_FLAG(absl::Duration, test_flag_12); +ABSL_DECLARE_FLAG(absl::int128, test_flag_13); +ABSL_DECLARE_FLAG(absl::uint128, test_flag_14); namespace { @@ -251,6 +265,10 @@ TEST_F(FlagTest, TestFlagDeclaration) { "test_flag_11"); EXPECT_EQ(absl::GetFlagReflectionHandle(FLAGS_test_flag_12).Name(), "test_flag_12"); + EXPECT_EQ(absl::GetFlagReflectionHandle(FLAGS_test_flag_13).Name(), + "test_flag_13"); + EXPECT_EQ(absl::GetFlagReflectionHandle(FLAGS_test_flag_14).Name(), + "test_flag_14"); } #endif // !ABSL_FLAGS_STRIP_NAMES @@ -270,6 +288,9 @@ ABSL_FLAG(double, test_flag_09, -9.876e-50, "test flag 09"); ABSL_FLAG(float, test_flag_10, 1.234e12f, "test flag 10"); ABSL_FLAG(std::string, test_flag_11, "", "test flag 11"); ABSL_FLAG(absl::Duration, test_flag_12, absl::Minutes(10), "test flag 12"); +ABSL_FLAG(absl::int128, test_flag_13, absl::MakeInt128(-1, 0), "test flag 13"); +ABSL_FLAG(absl::uint128, test_flag_14, absl::MakeUint128(0, 0xFFFAAABBBCCCDDD), + "test flag 14"); namespace { @@ -384,6 +405,24 @@ TEST_F(FlagTest, TestFlagDefinition) { absl::GetFlagReflectionHandle(FLAGS_test_flag_12).Filename(), expected_file_name)) << absl::GetFlagReflectionHandle(FLAGS_test_flag_12).Filename(); + + EXPECT_EQ(absl::GetFlagReflectionHandle(FLAGS_test_flag_13).Name(), + "test_flag_13"); + EXPECT_EQ(absl::GetFlagReflectionHandle(FLAGS_test_flag_13).Help(), + "test flag 13"); + EXPECT_TRUE(absl::EndsWith( + absl::GetFlagReflectionHandle(FLAGS_test_flag_13).Filename(), + expected_file_name)) + << absl::GetFlagReflectionHandle(FLAGS_test_flag_13).Filename(); + + EXPECT_EQ(absl::GetFlagReflectionHandle(FLAGS_test_flag_14).Name(), + "test_flag_14"); + EXPECT_EQ(absl::GetFlagReflectionHandle(FLAGS_test_flag_14).Help(), + "test flag 14"); + EXPECT_TRUE(absl::EndsWith( + absl::GetFlagReflectionHandle(FLAGS_test_flag_14).Filename(), + expected_file_name)) + << absl::GetFlagReflectionHandle(FLAGS_test_flag_14).Filename(); } #endif // !ABSL_FLAGS_STRIP_NAMES @@ -414,6 +453,10 @@ TEST_F(FlagTest, TestDefault) { ""); EXPECT_EQ(absl::GetFlagReflectionHandle(FLAGS_test_flag_12).DefaultValue(), "10m"); + EXPECT_EQ(absl::GetFlagReflectionHandle(FLAGS_test_flag_13).DefaultValue(), + "-18446744073709551616"); + EXPECT_EQ(absl::GetFlagReflectionHandle(FLAGS_test_flag_14).DefaultValue(), + "1152827684197027293"); EXPECT_EQ(absl::GetFlagReflectionHandle(FLAGS_test_flag_01).CurrentValue(), "true"); @@ -439,6 +482,10 @@ TEST_F(FlagTest, TestDefault) { ""); EXPECT_EQ(absl::GetFlagReflectionHandle(FLAGS_test_flag_12).CurrentValue(), "10m"); + EXPECT_EQ(absl::GetFlagReflectionHandle(FLAGS_test_flag_13).CurrentValue(), + "-18446744073709551616"); + EXPECT_EQ(absl::GetFlagReflectionHandle(FLAGS_test_flag_14).CurrentValue(), + "1152827684197027293"); EXPECT_EQ(absl::GetFlag(FLAGS_test_flag_01), true); EXPECT_EQ(absl::GetFlag(FLAGS_test_flag_02), 1234); @@ -452,6 +499,9 @@ TEST_F(FlagTest, TestDefault) { EXPECT_NEAR(absl::GetFlag(FLAGS_test_flag_10), 1.234e12f, 1e5f); EXPECT_EQ(absl::GetFlag(FLAGS_test_flag_11), ""); EXPECT_EQ(absl::GetFlag(FLAGS_test_flag_12), absl::Minutes(10)); + EXPECT_EQ(absl::GetFlag(FLAGS_test_flag_13), absl::MakeInt128(-1, 0)); + EXPECT_EQ(absl::GetFlag(FLAGS_test_flag_14), + absl::MakeUint128(0, 0xFFFAAABBBCCCDDD)); } // -------------------------------------------------------------------- @@ -553,6 +603,13 @@ TEST_F(FlagTest, TestGetSet) { absl::SetFlag(&FLAGS_test_flag_12, absl::Seconds(110)); EXPECT_EQ(absl::GetFlag(FLAGS_test_flag_12), absl::Seconds(110)); + + absl::SetFlag(&FLAGS_test_flag_13, absl::MakeInt128(-1, 0)); + EXPECT_EQ(absl::GetFlag(FLAGS_test_flag_13), absl::MakeInt128(-1, 0)); + + absl::SetFlag(&FLAGS_test_flag_14, absl::MakeUint128(0, 0xFFFAAABBBCCCDDD)); + EXPECT_EQ(absl::GetFlag(FLAGS_test_flag_14), + absl::MakeUint128(0, 0xFFFAAABBBCCCDDD)); } // -------------------------------------------------------------------- @@ -582,6 +639,11 @@ TEST_F(FlagTest, TestGetViaReflection) { EXPECT_EQ(*handle->TryGet<std::string>(), ""); handle = absl::FindCommandLineFlag("test_flag_12"); EXPECT_EQ(*handle->TryGet<absl::Duration>(), absl::Minutes(10)); + handle = absl::FindCommandLineFlag("test_flag_13"); + EXPECT_EQ(*handle->TryGet<absl::int128>(), absl::MakeInt128(-1, 0)); + handle = absl::FindCommandLineFlag("test_flag_14"); + EXPECT_EQ(*handle->TryGet<absl::uint128>(), + absl::MakeUint128(0, 0xFFFAAABBBCCCDDD)); } // -------------------------------------------------------------------- @@ -980,7 +1042,7 @@ TEST_F(FlagTest, TesTypeWrappingEnum) { // This is a compile test to ensure macros are expanded within ABSL_FLAG and // ABSL_DECLARE_FLAG. -#define FLAG_NAME_MACRO(name) prefix_ ## name +#define FLAG_NAME_MACRO(name) prefix_##name ABSL_DECLARE_FLAG(int, FLAG_NAME_MACRO(test_macro_named_flag)); ABSL_FLAG(int, FLAG_NAME_MACRO(test_macro_named_flag), 0, "Testing macro expansion within ABSL_FLAG"); diff --git a/absl/flags/internal/commandlineflag.cc b/absl/flags/internal/commandlineflag.cc index 4482955c..3c114d10 100644 --- a/absl/flags/internal/commandlineflag.cc +++ b/absl/flags/internal/commandlineflag.cc @@ -19,7 +19,7 @@ namespace absl { ABSL_NAMESPACE_BEGIN namespace flags_internal { -FlagStateInterface::~FlagStateInterface() {} +FlagStateInterface::~FlagStateInterface() = default; } // namespace flags_internal ABSL_NAMESPACE_END diff --git a/absl/flags/internal/flag.cc b/absl/flags/internal/flag.cc index cc656f9d..65d0e58f 100644 --- a/absl/flags/internal/flag.cc +++ b/absl/flags/internal/flag.cc @@ -197,7 +197,7 @@ void FlagImpl::AssertValidType(FlagFastTypeId rhs_type_id, FlagFastTypeId lhs_type_id = flags_internal::FastTypeId(op_); // `rhs_type_id` is the fast type id corresponding to the declaration - // visibile at the call site. `lhs_type_id` is the fast type id + // visible at the call site. `lhs_type_id` is the fast type id // corresponding to the type specified in flag definition. They must match // for this operation to be well-defined. if (ABSL_PREDICT_TRUE(lhs_type_id == rhs_type_id)) return; @@ -238,7 +238,7 @@ void FlagImpl::StoreValue(const void* src) { switch (ValueStorageKind()) { case FlagValueStorageKind::kValueAndInitBit: case FlagValueStorageKind::kOneWordAtomic: { - // Load the current value to avoid setting 'init' bit manualy. + // Load the current value to avoid setting 'init' bit manually. int64_t one_word_val = OneWordValue().load(std::memory_order_acquire); std::memcpy(&one_word_val, src, Sizeof(op_)); OneWordValue().store(one_word_val, std::memory_order_release); diff --git a/absl/flags/internal/flag.h b/absl/flags/internal/flag.h index 6154638c..b41f9a69 100644 --- a/absl/flags/internal/flag.h +++ b/absl/flags/internal/flag.h @@ -121,7 +121,7 @@ inline void* Clone(FlagOpFn op, const void* obj) { flags_internal::CopyConstruct(op, obj, res); return res; } -// Returns true if parsing of input text is successfull. +// Returns true if parsing of input text is successful. inline bool Parse(FlagOpFn op, absl::string_view text, void* dst, std::string* error) { return op(FlagOp::kParse, &text, dst, error) != nullptr; @@ -139,12 +139,12 @@ inline size_t Sizeof(FlagOpFn op) { return static_cast<size_t>(reinterpret_cast<intptr_t>( op(FlagOp::kSizeof, nullptr, nullptr, nullptr))); } -// Returns fast type id coresponding to the value type. +// Returns fast type id corresponding to the value type. inline FlagFastTypeId FastTypeId(FlagOpFn op) { return reinterpret_cast<FlagFastTypeId>( op(FlagOp::kFastTypeId, nullptr, nullptr, nullptr)); } -// Returns fast type id coresponding to the value type. +// Returns fast type id corresponding to the value type. inline const std::type_info* RuntimeTypeId(FlagOpFn op) { return reinterpret_cast<const std::type_info*>( op(FlagOp::kRuntimeTypeId, nullptr, nullptr, nullptr)); @@ -223,12 +223,12 @@ extern const char kStrippedFlagHelp[]; // first overload if possible. If help message is evaluatable on constexpr // context We'll be able to make FixedCharArray out of it and we'll choose first // overload. In this case the help message expression is immediately evaluated -// and is used to construct the absl::Flag. No additionl code is generated by +// and is used to construct the absl::Flag. No additional code is generated by // ABSL_FLAG Otherwise SFINAE kicks in and first overload is dropped from the // consideration, in which case the second overload will be used. The second // overload does not attempt to evaluate the help message expression -// immediately and instead delays the evaluation by returing the function -// pointer (&T::NonConst) genering the help message when necessary. This is +// immediately and instead delays the evaluation by returning the function +// pointer (&T::NonConst) generating the help message when necessary. This is // evaluatable in constexpr context, but the cost is an extra function being // generated in the ABSL_FLAG code. template <typename Gen, size_t N> @@ -308,19 +308,20 @@ constexpr int64_t UninitializedFlagValue() { } template <typename T> -using FlagUseValueAndInitBitStorage = std::integral_constant< - bool, absl::type_traits_internal::is_trivially_copyable<T>::value && - std::is_default_constructible<T>::value && (sizeof(T) < 8)>; +using FlagUseValueAndInitBitStorage = + std::integral_constant<bool, std::is_trivially_copyable<T>::value && + std::is_default_constructible<T>::value && + (sizeof(T) < 8)>; template <typename T> -using FlagUseOneWordStorage = std::integral_constant< - bool, absl::type_traits_internal::is_trivially_copyable<T>::value && - (sizeof(T) <= 8)>; +using FlagUseOneWordStorage = + std::integral_constant<bool, std::is_trivially_copyable<T>::value && + (sizeof(T) <= 8)>; template <class T> -using FlagUseSequenceLockStorage = std::integral_constant< - bool, absl::type_traits_internal::is_trivially_copyable<T>::value && - (sizeof(T) > 8)>; +using FlagUseSequenceLockStorage = + std::integral_constant<bool, std::is_trivially_copyable<T>::value && + (sizeof(T) > 8)>; enum class FlagValueStorageKind : uint8_t { kValueAndInitBit = 0, diff --git a/absl/flags/internal/flag_msvc.inc b/absl/flags/internal/flag_msvc.inc index c31bd27f..614d09fd 100644 --- a/absl/flags/internal/flag_msvc.inc +++ b/absl/flags/internal/flag_msvc.inc @@ -29,7 +29,7 @@ // second level of protection is a global Mutex, so if two threads attempt to // construct the flag concurrently only one wins. // -// This solution is based on a recomendation here: +// This solution is based on a recommendation here: // https://developercommunity.visualstudio.com/content/problem/336946/class-with-constexpr-constructor-not-using-static.html?childToView=648454#comment-648454 namespace flags_internal { diff --git a/absl/flags/internal/parse.h b/absl/flags/internal/parse.h index 0a7012fc..10c531b8 100644 --- a/absl/flags/internal/parse.h +++ b/absl/flags/internal/parse.h @@ -16,11 +16,14 @@ #ifndef ABSL_FLAGS_INTERNAL_PARSE_H_ #define ABSL_FLAGS_INTERNAL_PARSE_H_ +#include <iostream> +#include <ostream> #include <string> #include <vector> #include "absl/base/config.h" #include "absl/flags/declare.h" +#include "absl/flags/internal/usage.h" #include "absl/strings/string_view.h" ABSL_DECLARE_FLAG(std::vector<std::string>, flagfile); @@ -32,7 +35,6 @@ namespace absl { ABSL_NAMESPACE_BEGIN namespace flags_internal { -enum class ArgvListAction { kRemoveParsedArgs, kKeepParsedArgs }; enum class UsageFlagsAction { kHandleUsage, kIgnoreUsage }; enum class OnUndefinedFlag { kIgnoreUndefined, @@ -40,10 +42,15 @@ enum class OnUndefinedFlag { kAbortIfUndefined }; -std::vector<char*> ParseCommandLineImpl(int argc, char* argv[], - ArgvListAction arg_list_act, - UsageFlagsAction usage_flag_act, - OnUndefinedFlag on_undef_flag); +// This is not a public interface. This interface exists to expose the ability +// to change help output stream in case of parsing errors. This is used by +// internal unit tests to validate expected outputs. +// When this was written, `EXPECT_EXIT` only supported matchers on stderr, +// but not on stdout. +std::vector<char*> ParseCommandLineImpl( + int argc, char* argv[], UsageFlagsAction usage_flag_action, + OnUndefinedFlag undef_flag_action, + std::ostream& error_help_output = std::cout); // -------------------------------------------------------------------- // Inspect original command line diff --git a/absl/flags/internal/usage.cc b/absl/flags/internal/usage.cc index 5efc7b07..13852e14 100644 --- a/absl/flags/internal/usage.cc +++ b/absl/flags/internal/usage.cc @@ -18,6 +18,7 @@ #include <stdint.h> #include <algorithm> +#include <cstdlib> #include <functional> #include <iterator> #include <map> @@ -91,8 +92,16 @@ class XMLElement { case '>': out << ">"; break; + case '\n': + case '\v': + case '\f': + case '\t': + out << " "; + break; default: - out << c; + if (IsValidXmlCharacter(static_cast<unsigned char>(c))) { + out << c; + } break; } } @@ -101,6 +110,7 @@ class XMLElement { } private: + static bool IsValidXmlCharacter(unsigned char c) { return c >= 0x20; } absl::string_view tag_; absl::string_view txt_; }; @@ -130,7 +140,7 @@ class FlagHelpPrettyPrinter { for (auto line : absl::StrSplit(str, absl::ByAnyChar("\n\r"))) { if (!tokens.empty()) { // Keep line separators in the input string. - tokens.push_back("\n"); + tokens.emplace_back("\n"); } for (auto token : absl::StrSplit(line, absl::ByAnyChar(" \t"), absl::SkipEmpty())) { @@ -354,8 +364,8 @@ void FlagsHelp(std::ostream& out, absl::string_view filter, HelpFormat format, // -------------------------------------------------------------------- // Checks all the 'usage' command line flags to see if any have been set. // If so, handles them appropriately. -int HandleUsageFlags(std::ostream& out, - absl::string_view program_usage_message) { +HelpMode HandleUsageFlags(std::ostream& out, + absl::string_view program_usage_message) { switch (GetFlagsHelpMode()) { case HelpMode::kNone: break; @@ -363,25 +373,24 @@ int HandleUsageFlags(std::ostream& out, flags_internal::FlagsHelpImpl( out, flags_internal::GetUsageConfig().contains_help_flags, GetFlagsHelpFormat(), program_usage_message); - return 1; + break; case HelpMode::kShort: flags_internal::FlagsHelpImpl( out, flags_internal::GetUsageConfig().contains_helpshort_flags, GetFlagsHelpFormat(), program_usage_message); - return 1; + break; case HelpMode::kFull: flags_internal::FlagsHelp(out, "", GetFlagsHelpFormat(), program_usage_message); - return 1; + break; case HelpMode::kPackage: flags_internal::FlagsHelpImpl( out, flags_internal::GetUsageConfig().contains_helppackage_flags, GetFlagsHelpFormat(), program_usage_message); - - return 1; + break; case HelpMode::kMatch: { std::string substr = GetFlagsHelpMatchSubstr(); @@ -400,20 +409,19 @@ int HandleUsageFlags(std::ostream& out, flags_internal::FlagsHelpImpl( out, filter_cb, HelpFormat::kHumanReadable, program_usage_message); } - - return 1; + break; } case HelpMode::kVersion: if (flags_internal::GetUsageConfig().version_string) out << flags_internal::GetUsageConfig().version_string(); // Unlike help, we may be asking for version in a script, so return 0 - return 0; + break; case HelpMode::kOnlyCheckArgs: - return 0; + break; } - return -1; + return GetFlagsHelpMode(); } // -------------------------------------------------------------------- @@ -521,6 +529,22 @@ bool DeduceUsageFlags(absl::string_view name, absl::string_view value) { return false; } +// -------------------------------------------------------------------- + +void MaybeExit(HelpMode mode) { + switch (mode) { + case flags_internal::HelpMode::kNone: + return; + case flags_internal::HelpMode::kOnlyCheckArgs: + case flags_internal::HelpMode::kVersion: + std::exit(0); + default: // For all the other modes we exit with 1 + std::exit(1); + } +} + +// -------------------------------------------------------------------- + } // namespace flags_internal ABSL_NAMESPACE_END } // namespace absl diff --git a/absl/flags/internal/usage.h b/absl/flags/internal/usage.h index c0bcac57..a96cbf38 100644 --- a/absl/flags/internal/usage.h +++ b/absl/flags/internal/usage.h @@ -17,11 +17,11 @@ #define ABSL_FLAGS_INTERNAL_USAGE_H_ #include <iosfwd> +#include <ostream> #include <string> #include "absl/base/config.h" #include "absl/flags/commandlineflag.h" -#include "absl/flags/declare.h" #include "absl/strings/string_view.h" // -------------------------------------------------------------------- @@ -36,6 +36,18 @@ enum class HelpFormat { kHumanReadable, }; +// The kind of usage help requested. +enum class HelpMode { + kNone, + kImportant, + kShort, + kFull, + kPackage, + kMatch, + kVersion, + kOnlyCheckArgs +}; + // Streams the help message describing `flag` to `out`. // The default value for `flag` is included in the output. void FlagHelp(std::ostream& out, const CommandLineFlag& flag, @@ -57,28 +69,18 @@ void FlagsHelp(std::ostream& out, absl::string_view filter, // If any of the 'usage' related command line flags (listed on the bottom of // this file) has been set this routine produces corresponding help message in -// the specified output stream and returns: -// 0 - if "version" or "only_check_flags" flags were set and handled. -// 1 - if some other 'usage' related flag was set and handled. -// -1 - if no usage flags were set on a commmand line. -// Non negative return values are expected to be used as an exit code for a -// binary. -int HandleUsageFlags(std::ostream& out, - absl::string_view program_usage_message); +// the specified output stream and returns HelpMode that was handled. Otherwise +// it returns HelpMode::kNone. +HelpMode HandleUsageFlags(std::ostream& out, + absl::string_view program_usage_message); // -------------------------------------------------------------------- -// Globals representing usage reporting flags +// Encapsulates the logic of exiting the binary depending on handled help mode. -enum class HelpMode { - kNone, - kImportant, - kShort, - kFull, - kPackage, - kMatch, - kVersion, - kOnlyCheckArgs -}; +void MaybeExit(HelpMode mode); + +// -------------------------------------------------------------------- +// Globals representing usage reporting flags // Returns substring to filter help output (--help=substr argument) std::string GetFlagsHelpMatchSubstr(); diff --git a/absl/flags/internal/usage_test.cc b/absl/flags/internal/usage_test.cc index 209a7be9..6847386f 100644 --- a/absl/flags/internal/usage_test.cc +++ b/absl/flags/internal/usage_test.cc @@ -24,7 +24,6 @@ #include "gtest/gtest.h" #include "absl/flags/flag.h" #include "absl/flags/internal/parse.h" -#include "absl/flags/internal/path_util.h" #include "absl/flags/internal/program_name.h" #include "absl/flags/reflection.h" #include "absl/flags/usage.h" @@ -40,6 +39,8 @@ ABSL_FLAG(double, usage_reporting_test_flag_03, 1.03, "usage_reporting_test_flag_03 help message"); ABSL_FLAG(int64_t, usage_reporting_test_flag_04, 1000000000000004L, "usage_reporting_test_flag_04 help message"); +ABSL_FLAG(std::string, usage_reporting_test_flag_07, "\r\n\f\v\a\b\t ", + "usage_reporting_test_flag_07 help \r\n\f\v\a\b\t "); static const char kTestUsageMessage[] = "Custom usage message"; @@ -204,8 +205,12 @@ TEST_F(UsageReportingTest, TestFlagsHelpHRF) { Some more help. Even more long long long long long long long long long long long long help - message.); default: ""; + message.); default: "";)" + + "\n --usage_reporting_test_flag_07 (usage_reporting_test_flag_07 " + "help\n\n \f\v\a\b ); default: \"\r\n\f\v\a\b\t \";\n" + R"( Try --helpfull to get a list of all flags or --help=substring shows help for flags which include specified substring in either in the name, or description or path. @@ -256,7 +261,8 @@ path. TEST_F(UsageReportingTest, TestNoUsageFlags) { std::stringstream test_buf; - EXPECT_EQ(flags::HandleUsageFlags(test_buf, kTestUsageMessage), -1); + EXPECT_EQ(flags::HandleUsageFlags(test_buf, kTestUsageMessage), + flags::HelpMode::kNone); } // -------------------------------------------------------------------- @@ -265,9 +271,11 @@ TEST_F(UsageReportingTest, TestUsageFlag_helpshort) { flags::SetFlagsHelpMode(flags::HelpMode::kShort); std::stringstream test_buf; - EXPECT_EQ(flags::HandleUsageFlags(test_buf, kTestUsageMessage), 1); - EXPECT_EQ(test_buf.str(), - R"(usage_test: Custom usage message + EXPECT_EQ(flags::HandleUsageFlags(test_buf, kTestUsageMessage), + flags::HelpMode::kShort); + EXPECT_EQ( + test_buf.str(), + R"(usage_test: Custom usage message Flags from absl/flags/internal/usage_test.cc: --usage_reporting_test_flag_01 (usage_reporting_test_flag_01 help message); @@ -284,8 +292,12 @@ TEST_F(UsageReportingTest, TestUsageFlag_helpshort) { Some more help. Even more long long long long long long long long long long long long help - message.); default: ""; + message.); default: "";)" + + "\n --usage_reporting_test_flag_07 (usage_reporting_test_flag_07 " + "help\n\n \f\v\a\b ); default: \"\r\n\f\v\a\b\t \";\n" + R"( Try --helpfull to get a list of all flags or --help=substring shows help for flags which include specified substring in either in the name, or description or path. @@ -298,9 +310,11 @@ TEST_F(UsageReportingTest, TestUsageFlag_help_simple) { flags::SetFlagsHelpMode(flags::HelpMode::kImportant); std::stringstream test_buf; - EXPECT_EQ(flags::HandleUsageFlags(test_buf, kTestUsageMessage), 1); - EXPECT_EQ(test_buf.str(), - R"(usage_test: Custom usage message + EXPECT_EQ(flags::HandleUsageFlags(test_buf, kTestUsageMessage), + flags::HelpMode::kImportant); + EXPECT_EQ( + test_buf.str(), + R"(usage_test: Custom usage message Flags from absl/flags/internal/usage_test.cc: --usage_reporting_test_flag_01 (usage_reporting_test_flag_01 help message); @@ -317,8 +331,12 @@ TEST_F(UsageReportingTest, TestUsageFlag_help_simple) { Some more help. Even more long long long long long long long long long long long long help - message.); default: ""; + message.); default: "";)" + + "\n --usage_reporting_test_flag_07 (usage_reporting_test_flag_07 " + "help\n\n \f\v\a\b ); default: \"\r\n\f\v\a\b\t \";\n" + R"( Try --helpfull to get a list of all flags or --help=substring shows help for flags which include specified substring in either in the name, or description or path. @@ -332,7 +350,8 @@ TEST_F(UsageReportingTest, TestUsageFlag_help_one_flag) { flags::SetFlagsHelpMatchSubstr("usage_reporting_test_flag_06"); std::stringstream test_buf; - EXPECT_EQ(flags::HandleUsageFlags(test_buf, kTestUsageMessage), 1); + EXPECT_EQ(flags::HandleUsageFlags(test_buf, kTestUsageMessage), + flags::HelpMode::kMatch); EXPECT_EQ(test_buf.str(), R"(usage_test: Custom usage message @@ -356,9 +375,11 @@ TEST_F(UsageReportingTest, TestUsageFlag_help_multiple_flag) { flags::SetFlagsHelpMatchSubstr("test_flag"); std::stringstream test_buf; - EXPECT_EQ(flags::HandleUsageFlags(test_buf, kTestUsageMessage), 1); - EXPECT_EQ(test_buf.str(), - R"(usage_test: Custom usage message + EXPECT_EQ(flags::HandleUsageFlags(test_buf, kTestUsageMessage), + flags::HelpMode::kMatch); + EXPECT_EQ( + test_buf.str(), + R"(usage_test: Custom usage message Flags from absl/flags/internal/usage_test.cc: --usage_reporting_test_flag_01 (usage_reporting_test_flag_01 help message); @@ -375,8 +396,12 @@ TEST_F(UsageReportingTest, TestUsageFlag_help_multiple_flag) { Some more help. Even more long long long long long long long long long long long long help - message.); default: ""; + message.); default: "";)" + + "\n --usage_reporting_test_flag_07 (usage_reporting_test_flag_07 " + "help\n\n \f\v\a\b ); default: \"\r\n\f\v\a\b\t \";\n" + R"( Try --helpfull to get a list of all flags or --help=substring shows help for flags which include specified substring in either in the name, or description or path. @@ -389,9 +414,11 @@ TEST_F(UsageReportingTest, TestUsageFlag_helppackage) { flags::SetFlagsHelpMode(flags::HelpMode::kPackage); std::stringstream test_buf; - EXPECT_EQ(flags::HandleUsageFlags(test_buf, kTestUsageMessage), 1); - EXPECT_EQ(test_buf.str(), - R"(usage_test: Custom usage message + EXPECT_EQ(flags::HandleUsageFlags(test_buf, kTestUsageMessage), + flags::HelpMode::kPackage); + EXPECT_EQ( + test_buf.str(), + R"(usage_test: Custom usage message Flags from absl/flags/internal/usage_test.cc: --usage_reporting_test_flag_01 (usage_reporting_test_flag_01 help message); @@ -408,8 +435,12 @@ TEST_F(UsageReportingTest, TestUsageFlag_helppackage) { Some more help. Even more long long long long long long long long long long long long help - message.); default: ""; + message.); default: "";)" + "\n --usage_reporting_test_flag_07 (usage_reporting_test_flag_07 " + "help\n\n \f\v\a\b ); default: \"\r\n\f\v\a\b\t \";\n" + + R"( Try --helpfull to get a list of all flags or --help=substring shows help for flags which include specified substring in either in the name, or description or path. @@ -422,7 +453,8 @@ TEST_F(UsageReportingTest, TestUsageFlag_version) { flags::SetFlagsHelpMode(flags::HelpMode::kVersion); std::stringstream test_buf; - EXPECT_EQ(flags::HandleUsageFlags(test_buf, kTestUsageMessage), 0); + EXPECT_EQ(flags::HandleUsageFlags(test_buf, kTestUsageMessage), + flags::HelpMode::kVersion); #ifndef NDEBUG EXPECT_EQ(test_buf.str(), "usage_test\nDebug build (NDEBUG not #defined)\n"); #else @@ -436,7 +468,8 @@ TEST_F(UsageReportingTest, TestUsageFlag_only_check_args) { flags::SetFlagsHelpMode(flags::HelpMode::kOnlyCheckArgs); std::stringstream test_buf; - EXPECT_EQ(flags::HandleUsageFlags(test_buf, kTestUsageMessage), 0); + EXPECT_EQ(flags::HandleUsageFlags(test_buf, kTestUsageMessage), + flags::HelpMode::kOnlyCheckArgs); EXPECT_EQ(test_buf.str(), ""); } @@ -447,7 +480,8 @@ TEST_F(UsageReportingTest, TestUsageFlag_helpon) { flags::SetFlagsHelpMatchSubstr("/bla-bla."); std::stringstream test_buf_01; - EXPECT_EQ(flags::HandleUsageFlags(test_buf_01, kTestUsageMessage), 1); + EXPECT_EQ(flags::HandleUsageFlags(test_buf_01, kTestUsageMessage), + flags::HelpMode::kMatch); EXPECT_EQ(test_buf_01.str(), R"(usage_test: Custom usage message @@ -461,9 +495,11 @@ path. flags::SetFlagsHelpMatchSubstr("/usage_test."); std::stringstream test_buf_02; - EXPECT_EQ(flags::HandleUsageFlags(test_buf_02, kTestUsageMessage), 1); - EXPECT_EQ(test_buf_02.str(), - R"(usage_test: Custom usage message + EXPECT_EQ(flags::HandleUsageFlags(test_buf_02, kTestUsageMessage), + flags::HelpMode::kMatch); + EXPECT_EQ( + test_buf_02.str(), + R"(usage_test: Custom usage message Flags from absl/flags/internal/usage_test.cc: --usage_reporting_test_flag_01 (usage_reporting_test_flag_01 help message); @@ -480,8 +516,12 @@ path. Some more help. Even more long long long long long long long long long long long long help - message.); default: ""; + message.); default: "";)" + + "\n --usage_reporting_test_flag_07 (usage_reporting_test_flag_07 " + "help\n\n \f\v\a\b ); default: \"\r\n\f\v\a\b\t \";\n" + R"( Try --helpfull to get a list of all flags or --help=substring shows help for flags which include specified substring in either in the name, or description or path. diff --git a/absl/flags/marshalling.cc b/absl/flags/marshalling.cc index 81f9cebd..dc69754f 100644 --- a/absl/flags/marshalling.cc +++ b/absl/flags/marshalling.cc @@ -19,6 +19,7 @@ #include <cmath> #include <limits> +#include <sstream> #include <string> #include <type_traits> #include <vector> @@ -26,6 +27,7 @@ #include "absl/base/config.h" #include "absl/base/log_severity.h" #include "absl/base/macros.h" +#include "absl/numeric/int128.h" #include "absl/strings/ascii.h" #include "absl/strings/match.h" #include "absl/strings/numbers.h" @@ -68,8 +70,10 @@ bool AbslParseFlag(absl::string_view text, bool* dst, std::string*) { // puts us in base 16. But leading 0 does not put us in base 8. It // caused too many bugs when we had that behavior. static int NumericBase(absl::string_view text) { - const bool hex = (text.size() >= 2 && text[0] == '0' && - (text[1] == 'x' || text[1] == 'X')); + if (text.empty()) return 0; + size_t num_start = (text[0] == '-' || text[0] == '+') ? 1 : 0; + const bool hex = (text.size() >= num_start + 2 && text[num_start] == '0' && + (text[num_start + 1] == 'x' || text[num_start + 1] == 'X')); return hex ? 16 : 10; } @@ -125,6 +129,32 @@ bool AbslParseFlag(absl::string_view text, unsigned long long* dst, return ParseFlagImpl(text, *dst); } +bool AbslParseFlag(absl::string_view text, absl::int128* dst, std::string*) { + text = absl::StripAsciiWhitespace(text); + + // check hex + int base = NumericBase(text); + if (!absl::numbers_internal::safe_strto128_base(text, dst, base)) { + return false; + } + + return base == 16 ? absl::SimpleHexAtoi(text, dst) + : absl::SimpleAtoi(text, dst); +} + +bool AbslParseFlag(absl::string_view text, absl::uint128* dst, std::string*) { + text = absl::StripAsciiWhitespace(text); + + // check hex + int base = NumericBase(text); + if (!absl::numbers_internal::safe_strtou128_base(text, dst, base)) { + return false; + } + + return base == 16 ? absl::SimpleHexAtoi(text, dst) + : absl::SimpleAtoi(text, dst); +} + // -------------------------------------------------------------------- // AbslParseFlag for floating point types. @@ -171,6 +201,17 @@ std::string Unparse(long v) { return absl::StrCat(v); } std::string Unparse(unsigned long v) { return absl::StrCat(v); } std::string Unparse(long long v) { return absl::StrCat(v); } std::string Unparse(unsigned long long v) { return absl::StrCat(v); } +std::string Unparse(absl::int128 v) { + std::stringstream ss; + ss << v; + return ss.str(); +} +std::string Unparse(absl::uint128 v) { + std::stringstream ss; + ss << v; + return ss.str(); +} + template <typename T> std::string UnparseFloatingPointVal(T v) { // digits10 is guaranteed to roundtrip correctly in string -> value -> string diff --git a/absl/flags/marshalling.h b/absl/flags/marshalling.h index 325e75e5..301213a9 100644 --- a/absl/flags/marshalling.h +++ b/absl/flags/marshalling.h @@ -200,6 +200,7 @@ #define ABSL_FLAGS_MARSHALLING_H_ #include "absl/base/config.h" +#include "absl/numeric/int128.h" #if defined(ABSL_HAVE_STD_OPTIONAL) && !defined(ABSL_USES_STD_OPTIONAL) #include <optional> @@ -233,6 +234,8 @@ bool AbslParseFlag(absl::string_view, unsigned long*, std::string*); // NOLINT bool AbslParseFlag(absl::string_view, long long*, std::string*); // NOLINT bool AbslParseFlag(absl::string_view, unsigned long long*, // NOLINT std::string*); +bool AbslParseFlag(absl::string_view, absl::int128*, std::string*); // NOLINT +bool AbslParseFlag(absl::string_view, absl::uint128*, std::string*); // NOLINT bool AbslParseFlag(absl::string_view, float*, std::string*); bool AbslParseFlag(absl::string_view, double*, std::string*); bool AbslParseFlag(absl::string_view, std::string*, std::string*); @@ -310,6 +313,8 @@ std::string Unparse(long v); // NOLINT std::string Unparse(unsigned long v); // NOLINT std::string Unparse(long long v); // NOLINT std::string Unparse(unsigned long long v); // NOLINT +std::string Unparse(absl::int128 v); +std::string Unparse(absl::uint128 v); std::string Unparse(float v); std::string Unparse(double v); diff --git a/absl/flags/marshalling_test.cc b/absl/flags/marshalling_test.cc index 7b6d2ad5..b0e055f5 100644 --- a/absl/flags/marshalling_test.cc +++ b/absl/flags/marshalling_test.cc @@ -137,11 +137,10 @@ TEST(MarshallingTest, TestInt16Parsing) { EXPECT_EQ(value, 16); EXPECT_TRUE(absl::ParseFlag("0X234", &value, &err)); EXPECT_EQ(value, 564); - // TODO(rogeeff): fix below validations - EXPECT_FALSE(absl::ParseFlag("-0x7FFD", &value, &err)); - EXPECT_NE(value, -3); - EXPECT_FALSE(absl::ParseFlag("+0x31", &value, &err)); - EXPECT_NE(value, 49); + EXPECT_TRUE(absl::ParseFlag("-0x7FFD", &value, &err)); + EXPECT_EQ(value, -32765); + EXPECT_TRUE(absl::ParseFlag("+0x31", &value, &err)); + EXPECT_EQ(value, 49); // Whitespace handling EXPECT_TRUE(absl::ParseFlag("10 ", &value, &err)); @@ -194,9 +193,8 @@ TEST(MarshallingTest, TestUint16Parsing) { EXPECT_EQ(value, 16); EXPECT_TRUE(absl::ParseFlag("0X234", &value, &err)); EXPECT_EQ(value, 564); - // TODO(rogeeff): fix below validations - EXPECT_FALSE(absl::ParseFlag("+0x31", &value, &err)); - EXPECT_NE(value, 49); + EXPECT_TRUE(absl::ParseFlag("+0x31", &value, &err)); + EXPECT_EQ(value, 49); // Whitespace handling EXPECT_TRUE(absl::ParseFlag("10 ", &value, &err)); @@ -254,11 +252,11 @@ TEST(MarshallingTest, TestInt32Parsing) { EXPECT_EQ(value, 16); EXPECT_TRUE(absl::ParseFlag("0X234", &value, &err)); EXPECT_EQ(value, 564); - // TODO(rogeeff): fix below validations - EXPECT_FALSE(absl::ParseFlag("-0x7FFFFFFD", &value, &err)); - EXPECT_NE(value, -3); - EXPECT_FALSE(absl::ParseFlag("+0x31", &value, &err)); - EXPECT_NE(value, 49); + + EXPECT_TRUE(absl::ParseFlag("-0x7FFFFFFD", &value, &err)); + EXPECT_EQ(value, -2147483645); + EXPECT_TRUE(absl::ParseFlag("+0x31", &value, &err)); + EXPECT_EQ(value, 49); // Whitespace handling EXPECT_TRUE(absl::ParseFlag("10 ", &value, &err)); @@ -311,9 +309,8 @@ TEST(MarshallingTest, TestUint32Parsing) { EXPECT_EQ(value, 564); EXPECT_TRUE(absl::ParseFlag("0xFFFFFFFD", &value, &err)); EXPECT_EQ(value, 4294967293); - // TODO(rogeeff): fix below validations - EXPECT_FALSE(absl::ParseFlag("+0x31", &value, &err)); - EXPECT_NE(value, 49); + EXPECT_TRUE(absl::ParseFlag("+0x31", &value, &err)); + EXPECT_EQ(value, 49); // Whitespace handling EXPECT_TRUE(absl::ParseFlag("10 ", &value, &err)); @@ -371,11 +368,12 @@ TEST(MarshallingTest, TestInt64Parsing) { EXPECT_EQ(value, 16); EXPECT_TRUE(absl::ParseFlag("0XFFFAAABBBCCCDDD", &value, &err)); EXPECT_EQ(value, 1152827684197027293); - // TODO(rogeeff): fix below validation - EXPECT_FALSE(absl::ParseFlag("-0x7FFFFFFFFFFFFFFE", &value, &err)); - EXPECT_NE(value, -2); - EXPECT_FALSE(absl::ParseFlag("+0x31", &value, &err)); - EXPECT_NE(value, 49); + EXPECT_TRUE(absl::ParseFlag("-0x7FFFFFFFFFFFFFFE", &value, &err)); + EXPECT_EQ(value, -9223372036854775806); + EXPECT_TRUE(absl::ParseFlag("-0x02", &value, &err)); + EXPECT_EQ(value, -2); + EXPECT_TRUE(absl::ParseFlag("+0x31", &value, &err)); + EXPECT_EQ(value, 49); // Whitespace handling EXPECT_TRUE(absl::ParseFlag("10 ", &value, &err)); @@ -428,9 +426,8 @@ TEST(MarshallingTest, TestUInt64Parsing) { EXPECT_EQ(value, 16); EXPECT_TRUE(absl::ParseFlag("0XFFFF", &value, &err)); EXPECT_EQ(value, 65535); - // TODO(rogeeff): fix below validation - EXPECT_FALSE(absl::ParseFlag("+0x31", &value, &err)); - EXPECT_NE(value, 49); + EXPECT_TRUE(absl::ParseFlag("+0x31", &value, &err)); + EXPECT_EQ(value, 49); // Whitespace handling EXPECT_TRUE(absl::ParseFlag("10 ", &value, &err)); @@ -455,6 +452,125 @@ TEST(MarshallingTest, TestUInt64Parsing) { // -------------------------------------------------------------------- +TEST(MarshallingTest, TestInt128Parsing) { + std::string err; + absl::int128 value; + + // Decimal values. + EXPECT_TRUE(absl::ParseFlag("0", &value, &err)); + EXPECT_EQ(value, 0); + EXPECT_TRUE(absl::ParseFlag("1", &value, &err)); + EXPECT_EQ(value, 1); + EXPECT_TRUE(absl::ParseFlag("-1", &value, &err)); + EXPECT_EQ(value, -1); + EXPECT_TRUE(absl::ParseFlag("123", &value, &err)); + EXPECT_EQ(value, 123); + EXPECT_TRUE(absl::ParseFlag("-98765", &value, &err)); + EXPECT_EQ(value, -98765); + EXPECT_TRUE(absl::ParseFlag("+3", &value, &err)); + EXPECT_EQ(value, 3); + + // Leading zero values. + EXPECT_TRUE(absl::ParseFlag("01", &value, &err)); + EXPECT_EQ(value, 1); + EXPECT_TRUE(absl::ParseFlag("001", &value, &err)); + EXPECT_EQ(value, 1); + EXPECT_TRUE(absl::ParseFlag("0000100", &value, &err)); + EXPECT_EQ(value, 100); + + // Hex values. + EXPECT_TRUE(absl::ParseFlag("0x10", &value, &err)); + EXPECT_EQ(value, 16); + EXPECT_TRUE(absl::ParseFlag("0xFFFAAABBBCCCDDD", &value, &err)); + EXPECT_EQ(value, 1152827684197027293); + EXPECT_TRUE(absl::ParseFlag("0xFFF0FFFFFFFFFFFFFFF", &value, &err)); + EXPECT_EQ(value, absl::MakeInt128(0x000000000000fff, 0xFFFFFFFFFFFFFFF)); + + EXPECT_TRUE(absl::ParseFlag("-0x10000000000000000", &value, &err)); + EXPECT_EQ(value, absl::MakeInt128(-1, 0)); + EXPECT_TRUE(absl::ParseFlag("+0x31", &value, &err)); + EXPECT_EQ(value, 49); + + // Whitespace handling + EXPECT_TRUE(absl::ParseFlag("16 ", &value, &err)); + EXPECT_EQ(value, 16); + EXPECT_TRUE(absl::ParseFlag(" 16", &value, &err)); + EXPECT_EQ(value, 16); + EXPECT_TRUE(absl::ParseFlag(" 0100 ", &value, &err)); + EXPECT_EQ(value, 100); + EXPECT_TRUE(absl::ParseFlag(" 0x7B ", &value, &err)); + EXPECT_EQ(value, 123); + + // Invalid values. + EXPECT_FALSE(absl::ParseFlag("", &value, &err)); + EXPECT_FALSE(absl::ParseFlag(" ", &value, &err)); + EXPECT_FALSE(absl::ParseFlag(" ", &value, &err)); + EXPECT_FALSE(absl::ParseFlag("--1", &value, &err)); + EXPECT_FALSE(absl::ParseFlag("\n", &value, &err)); + EXPECT_FALSE(absl::ParseFlag("\t", &value, &err)); + EXPECT_FALSE(absl::ParseFlag("2U", &value, &err)); + EXPECT_FALSE(absl::ParseFlag("FFF", &value, &err)); +} + +// -------------------------------------------------------------------- + +TEST(MarshallingTest, TestUint128Parsing) { + std::string err; + absl::uint128 value; + + // Decimal values. + EXPECT_TRUE(absl::ParseFlag("0", &value, &err)); + EXPECT_EQ(value, 0); + EXPECT_TRUE(absl::ParseFlag("1", &value, &err)); + EXPECT_EQ(value, 1); + EXPECT_TRUE(absl::ParseFlag("123", &value, &err)); + EXPECT_EQ(value, 123); + EXPECT_TRUE(absl::ParseFlag("+3", &value, &err)); + EXPECT_EQ(value, 3); + + // Leading zero values. + EXPECT_TRUE(absl::ParseFlag("01", &value, &err)); + EXPECT_EQ(value, 1); + EXPECT_TRUE(absl::ParseFlag("001", &value, &err)); + EXPECT_EQ(value, 1); + EXPECT_TRUE(absl::ParseFlag("0000100", &value, &err)); + EXPECT_EQ(value, 100); + + // Hex values. + EXPECT_TRUE(absl::ParseFlag("0x10", &value, &err)); + EXPECT_EQ(value, 16); + EXPECT_TRUE(absl::ParseFlag("0xFFFAAABBBCCCDDD", &value, &err)); + EXPECT_EQ(value, 1152827684197027293); + EXPECT_TRUE(absl::ParseFlag("0xFFF0FFFFFFFFFFFFFFF", &value, &err)); + EXPECT_EQ(value, absl::MakeInt128(0x000000000000fff, 0xFFFFFFFFFFFFFFF)); + EXPECT_TRUE(absl::ParseFlag("+0x31", &value, &err)); + EXPECT_EQ(value, 49); + + // Whitespace handling + EXPECT_TRUE(absl::ParseFlag("16 ", &value, &err)); + EXPECT_EQ(value, 16); + EXPECT_TRUE(absl::ParseFlag(" 16", &value, &err)); + EXPECT_EQ(value, 16); + EXPECT_TRUE(absl::ParseFlag(" 0100 ", &value, &err)); + EXPECT_EQ(value, 100); + EXPECT_TRUE(absl::ParseFlag(" 0x7B ", &value, &err)); + EXPECT_EQ(value, 123); + + // Invalid values. + EXPECT_FALSE(absl::ParseFlag("", &value, &err)); + EXPECT_FALSE(absl::ParseFlag(" ", &value, &err)); + EXPECT_FALSE(absl::ParseFlag(" ", &value, &err)); + EXPECT_FALSE(absl::ParseFlag("-1", &value, &err)); + EXPECT_FALSE(absl::ParseFlag("--1", &value, &err)); + EXPECT_FALSE(absl::ParseFlag("\n", &value, &err)); + EXPECT_FALSE(absl::ParseFlag("\t", &value, &err)); + EXPECT_FALSE(absl::ParseFlag("2U", &value, &err)); + EXPECT_FALSE(absl::ParseFlag("FFF", &value, &err)); + EXPECT_FALSE(absl::ParseFlag("-0x10000000000000000", &value, &err)); +} + +// -------------------------------------------------------------------- + TEST(MarshallingTest, TestFloatParsing) { std::string err; float value; @@ -844,6 +960,40 @@ TEST(MarshallingTest, TestUint64Unparsing) { // -------------------------------------------------------------------- +TEST(MarshallingTest, TestInt128Unparsing) { + absl::int128 value; + + value = 1; + EXPECT_EQ(absl::UnparseFlag(value), "1"); + value = 0; + EXPECT_EQ(absl::UnparseFlag(value), "0"); + value = -1; + EXPECT_EQ(absl::UnparseFlag(value), "-1"); + value = 123456789L; + EXPECT_EQ(absl::UnparseFlag(value), "123456789"); + value = -987654321L; + EXPECT_EQ(absl::UnparseFlag(value), "-987654321"); + value = 0x7FFFFFFFFFFFFFFF; + EXPECT_EQ(absl::UnparseFlag(value), "9223372036854775807"); +} + +// -------------------------------------------------------------------- + +TEST(MarshallingTest, TestUint128Unparsing) { + absl::uint128 value; + + value = 1; + EXPECT_EQ(absl::UnparseFlag(value), "1"); + value = 0; + EXPECT_EQ(absl::UnparseFlag(value), "0"); + value = 123456789L; + EXPECT_EQ(absl::UnparseFlag(value), "123456789"); + value = absl::MakeUint128(0, 0xFFFFFFFFFFFFFFFF); + EXPECT_EQ(absl::UnparseFlag(value), "18446744073709551615"); +} + +// -------------------------------------------------------------------- + TEST(MarshallingTest, TestFloatUnparsing) { float value; diff --git a/absl/flags/parse.cc b/absl/flags/parse.cc index fa953f55..4cdd9d0a 100644 --- a/absl/flags/parse.cc +++ b/absl/flags/parse.cc @@ -19,9 +19,10 @@ #include <algorithm> #include <cstdint> +#include <cstdlib> #include <fstream> #include <iostream> -#include <iterator> +#include <ostream> #include <string> #include <tuple> #include <utility> @@ -98,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([]() { @@ -147,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 " @@ -190,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; @@ -212,7 +217,7 @@ bool ArgsList::ReadFromFlagfile(const std::string& flag_file_name) { break; } - args_.push_back(std::string(stripped)); + args_.emplace_back(stripped); continue; } @@ -278,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); @@ -367,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. @@ -416,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 @@ -599,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 // -------------------------------------------------------------------- @@ -638,7 +671,7 @@ 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)); - absl::flags_internal::ForEachFlag([&](const CommandLineFlag& f) { + 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); @@ -664,59 +697,94 @@ std::vector<std::string> GetMisspellingHints(const absl::string_view flag) { // -------------------------------------------------------------------- std::vector<char*> ParseCommandLineImpl(int argc, char* argv[], - ArgvListAction arg_list_act, - UsageFlagsAction usage_flag_act, - OnUndefinedFlag on_undef_flag) { - ABSL_INTERNAL_CHECK(argc > 0, "Missing argv[0]"); + UsageFlagsAction usage_flag_action, + OnUndefinedFlag undef_flag_action, + std::ostream& error_help_output) { + std::vector<char*> positional_args; + std::vector<UnrecognizedFlag> unrecognized_flags; - // Once parsing has started we will not have more flag registrations. - // If we did, they would be missing during parsing, which is a problem on - // itself. - flags_internal::FinalizeRegistry(); + auto help_mode = flags_internal::ParseAbseilFlagsOnlyImpl( + argc, argv, positional_args, unrecognized_flags, usage_flag_action); - // This routine does not return anything since we abort on failure. - CheckDefaultValuesParsingRoundtrip(); + if (undef_flag_action != OnUndefinedFlag::kIgnoreUndefined) { + flags_internal::ReportUnrecognizedFlags( + unrecognized_flags, + (undef_flag_action == OnUndefinedFlag::kAbortIfUndefined)); - std::vector<std::string> flagfile_value; + 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]"); + + 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(static_cast<size_t>(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) { @@ -724,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, @@ -740,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. @@ -753,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"); @@ -765,43 +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) { // Usage flags are not modeled as Abseil flags. Locate them separately. if (flags_internal::DeduceUsageFlags(flag_name, value)) { continue; } - - if (on_undef_flag != OnUndefinedFlag::kIgnoreUndefined) { - undefined_flag_names.emplace_back(arg_from_argv, - std::string(flag_name)); - } + 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); @@ -811,78 +869,73 @@ std::vector<char*> ParseCommandLineImpl(int argc, char* argv[], } } - for (const auto& flag_name : undefined_flag_names) { - if (CanIgnoreUndefinedFlag(flag_name.second)) continue; - // Verify if flag_name has the "no" already removed - std::vector<std::string> flags; - if (flag_name.first) flags = GetMisspellingHints(flag_name.second); - if (flags.empty()) { - flags_internal::ReportUsageError( - absl::StrCat("Unknown command line flag '", flag_name.second, "'"), - true); - } else { - flags_internal::ReportUsageError( - absl::StrCat("Unknown command line flag '", flag_name.second, - "'. Did you mean: ", absl::StrJoin(flags, ", "), " ?"), - true); + flags_internal::ResetGeneratorFlags(flagfile_value); + + // 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 (size_t arg_index = input_args.back().FrontIndex(); - arg_index < static_cast<size_t>(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); } diff --git a/absl/flags/parse.h b/absl/flags/parse.h index 929de2cb..f2a5cb10 100644 --- a/absl/flags/parse.h +++ b/absl/flags/parse.h @@ -23,6 +23,7 @@ #ifndef ABSL_FLAGS_PARSE_H_ #define ABSL_FLAGS_PARSE_H_ +#include <string> #include <vector> #include "absl/base/config.h" @@ -31,27 +32,96 @@ namespace absl { ABSL_NAMESPACE_BEGIN +// This type represent information about an unrecognized flag in the command +// line. +struct UnrecognizedFlag { + enum Source { kFromArgv, kFromFlagfile }; + + explicit UnrecognizedFlag(Source s, absl::string_view f) + : source(s), flag_name(f) {} + // This field indicates where we found this flag: on the original command line + // or read in some flag file. + Source source; + // Name of the flag we did not recognize in --flag_name=value or --flag_name. + std::string flag_name; +}; + +inline bool operator==(const UnrecognizedFlag& lhs, + const UnrecognizedFlag& rhs) { + return lhs.source == rhs.source && lhs.flag_name == rhs.flag_name; +} + +namespace flags_internal { + +HelpMode ParseAbseilFlagsOnlyImpl( + int argc, char* argv[], std::vector<char*>& positional_args, + std::vector<UnrecognizedFlag>& unrecognized_flags, + UsageFlagsAction usage_flag_action); + +} // namespace flags_internal + +// ParseAbseilFlagsOnly() +// +// Parses a list of command-line arguments, passed in the `argc` and `argv[]` +// parameters, into a set of Abseil Flag values, returning any unparsed +// arguments in `positional_args` and `unrecognized_flags` output parameters. +// +// This function classifies all the arguments (including content of the +// flagfiles, if any) into one of the following groups: +// +// * arguments specified as "--flag=value" or "--flag value" that match +// registered or built-in Abseil Flags. These are "Abseil Flag arguments." +// * arguments specified as "--flag" that are unrecognized as Abseil Flags +// * arguments that are not specified as "--flag" are positional arguments +// * arguments that follow the flag-terminating delimiter (`--`) are also +// treated as positional arguments regardless of their syntax. +// +// All of the deduced Abseil Flag arguments are then parsed into their +// corresponding flag values. If any syntax errors are found in these arguments, +// the binary exits with code 1. +// +// This function also handles Abseil Flags built-in usage flags (e.g. --help) +// if any were present on the command line. +// +// All the remaining positional arguments including original program name +// (argv[0]) are are returned in the `positional_args` output parameter. +// +// All unrecognized flags that are not otherwise ignored are returned in the +// `unrecognized_flags` output parameter. Note that the special `undefok` +// flag allows you to specify flags which can be safely ignored; `undefok` +// specifies these flags as a comma-separated list. Any unrecognized flags +// that appear within `undefok` will therefore be ignored and not included in +// the `unrecognized_flag` output parameter. +// +void ParseAbseilFlagsOnly(int argc, char* argv[], + std::vector<char*>& positional_args, + std::vector<UnrecognizedFlag>& unrecognized_flags); + +// ReportUnrecognizedFlags() +// +// Reports an error to `stderr` for all non-ignored unrecognized flags in +// the provided `unrecognized_flags` list. +void ReportUnrecognizedFlags( + const std::vector<UnrecognizedFlag>& unrecognized_flags); + // ParseCommandLine() // -// Parses the set of command-line arguments passed in the `argc` (argument -// count) and `argv[]` (argument vector) parameters from `main()`, assigning -// values to any defined Abseil flags. (Any arguments passed after the -// flag-terminating delimiter (`--`) are treated as positional arguments and -// ignored.) -// -// Any command-line flags (and arguments to those flags) are parsed into Abseil -// Flag values, if those flags are defined. Any undefined flags will either -// return an error, or be ignored if that flag is designated using `undefok` to -// indicate "undefined is OK." -// -// Any command-line positional arguments not part of any command-line flag (or -// arguments to a flag) are returned in a vector, with the program invocation -// name at position 0 of that vector. (Note that this includes positional -// arguments after the flag-terminating delimiter `--`.) -// -// After all flags and flag arguments are parsed, this function looks for any -// built-in usage flags (e.g. `--help`), and if any were specified, it reports -// help messages and then exits the program. +// First parses Abseil Flags only from the command line according to the +// description in `ParseAbseilFlagsOnly`. In addition this function handles +// unrecognized and usage flags. +// +// If any unrecognized flags are located they are reported using +// `ReportUnrecognizedFlags`. +// +// If any errors detected during command line parsing, this routine reports a +// usage message and aborts the program. +// +// If any built-in usage flags were specified on the command line (e.g. +// `--help`), this function reports help messages and then gracefully exits the +// program. +// +// This function returns all the remaining positional arguments collected by +// `ParseAbseilFlagsOnly`. std::vector<char*> ParseCommandLine(int argc, char* argv[]); ABSL_NAMESPACE_END diff --git a/absl/flags/parse_test.cc b/absl/flags/parse_test.cc index 418b0e55..97b78980 100644 --- a/absl/flags/parse_test.cc +++ b/absl/flags/parse_test.cc @@ -17,20 +17,19 @@ #include <stdlib.h> -#include <cstddef> #include <fstream> +#include <iostream> #include <string> #include <vector> #include "gmock/gmock.h" #include "gtest/gtest.h" -#include "absl/base/internal/raw_logging.h" #include "absl/base/internal/scoped_set_env.h" -#include "absl/flags/declare.h" #include "absl/flags/flag.h" #include "absl/flags/internal/parse.h" #include "absl/flags/internal/usage.h" #include "absl/flags/reflection.h" +#include "absl/log/log.h" #include "absl/strings/str_cat.h" #include "absl/strings/string_view.h" #include "absl/strings/substitute.h" @@ -151,8 +150,7 @@ const std::string& GetTestTempDir() { } if (res->empty()) { - ABSL_INTERNAL_LOG(FATAL, - "Failed to make temporary directory for data files"); + LOG(FATAL) << "Failed to make temporary directory for data files"; } #ifdef _WIN32 @@ -199,7 +197,7 @@ constexpr const char* const ff2_data[] = { // Builds flagfile flag in the flagfile_flag buffer and returns it. This // function also creates a temporary flagfile based on FlagfileData input. // We create a flagfile in a temporary directory with the name specified in -// FlagfileData and populate it with lines specifed in FlagfileData. If $0 is +// FlagfileData and populate it with lines specified in FlagfileData. If $0 is // referenced in any of the lines in FlagfileData they are replaced with // temporary directory location. This way we can test inclusion of one flagfile // from another flagfile. @@ -237,7 +235,9 @@ ABSL_RETIRED_FLAG(std::string, legacy_str, "l", ""); namespace { namespace flags = absl::flags_internal; +using testing::AllOf; using testing::ElementsAreArray; +using testing::HasSubstr; class ParseTest : public testing::Test { public: @@ -250,6 +250,38 @@ class ParseTest : public testing::Test { // -------------------------------------------------------------------- template <int N> +flags::HelpMode InvokeParseAbslOnlyImpl(const char* (&in_argv)[N]) { + std::vector<char*> positional_args; + std::vector<absl::UnrecognizedFlag> unrecognized_flags; + + return flags::ParseAbseilFlagsOnlyImpl(N, const_cast<char**>(in_argv), + positional_args, unrecognized_flags, + flags::UsageFlagsAction::kHandleUsage); +} + +// -------------------------------------------------------------------- + +template <int N> +void InvokeParseAbslOnly(const char* (&in_argv)[N]) { + std::vector<char*> positional_args; + std::vector<absl::UnrecognizedFlag> unrecognized_flags; + + absl::ParseAbseilFlagsOnly(2, const_cast<char**>(in_argv), positional_args, + unrecognized_flags); +} + +// -------------------------------------------------------------------- + +template <int N> +std::vector<char*> InvokeParseCommandLineImpl(const char* (&in_argv)[N]) { + return flags::ParseCommandLineImpl( + N, const_cast<char**>(in_argv), flags::UsageFlagsAction::kHandleUsage, + flags::OnUndefinedFlag::kAbortIfUndefined, std::cerr); +} + +// -------------------------------------------------------------------- + +template <int N> std::vector<char*> InvokeParse(const char* (&in_argv)[N]) { return absl::ParseCommandLine(N, const_cast<char**>(in_argv)); } @@ -854,129 +886,66 @@ TEST_F(ParseTest, TestReadingFlagsFromEnvMoxedWithRegularFlags) { // -------------------------------------------------------------------- -TEST_F(ParseTest, TestKeepParsedArgs) { - const char* in_args1[] = { - "testbin", "arg1", "--bool_flag", - "--int_flag=211", "arg2", "--double_flag=1.1", - "--string_flag", "asd", "--", - "arg3", "arg4", - }; - - auto out_args1 = InvokeParse(in_args1); - - EXPECT_THAT( - out_args1, - ElementsAreArray({absl::string_view("testbin"), absl::string_view("arg1"), - absl::string_view("arg2"), absl::string_view("arg3"), - absl::string_view("arg4")})); - - auto out_args2 = flags::ParseCommandLineImpl( - 11, const_cast<char**>(in_args1), flags::ArgvListAction::kKeepParsedArgs, - flags::UsageFlagsAction::kHandleUsage, - flags::OnUndefinedFlag::kAbortIfUndefined); - - EXPECT_THAT( - out_args2, - ElementsAreArray({absl::string_view("testbin"), - absl::string_view("--bool_flag"), - absl::string_view("--int_flag=211"), - absl::string_view("--double_flag=1.1"), - absl::string_view("--string_flag"), - absl::string_view("asd"), absl::string_view("--"), - absl::string_view("arg1"), absl::string_view("arg2"), - absl::string_view("arg3"), absl::string_view("arg4")})); -} - -// -------------------------------------------------------------------- - -TEST_F(ParseTest, TestIgnoreUndefinedFlags) { +TEST_F(ParseDeathTest, TestSimpleHelpFlagHandling) { const char* in_args1[] = { "testbin", - "arg1", - "--undef_flag=aa", - "--int_flag=21", + "--help", }; - auto out_args1 = flags::ParseCommandLineImpl( - 4, const_cast<char**>(in_args1), flags::ArgvListAction::kRemoveParsedArgs, - flags::UsageFlagsAction::kHandleUsage, - flags::OnUndefinedFlag::kIgnoreUndefined); - - EXPECT_THAT(out_args1, ElementsAreArray({absl::string_view("testbin"), - absl::string_view("arg1")})); - - EXPECT_EQ(absl::GetFlag(FLAGS_int_flag), 21); + EXPECT_EQ(InvokeParseAbslOnlyImpl(in_args1), flags::HelpMode::kImportant); + EXPECT_EXIT(InvokeParse(in_args1), testing::ExitedWithCode(1), ""); const char* in_args2[] = { "testbin", - "arg1", - "--undef_flag=aa", - "--string_flag=AA", + "--help", + "--int_flag=3", }; - auto out_args2 = flags::ParseCommandLineImpl( - 4, const_cast<char**>(in_args2), flags::ArgvListAction::kKeepParsedArgs, - flags::UsageFlagsAction::kHandleUsage, - flags::OnUndefinedFlag::kIgnoreUndefined); + EXPECT_EQ(InvokeParseAbslOnlyImpl(in_args2), flags::HelpMode::kImportant); + EXPECT_EQ(absl::GetFlag(FLAGS_int_flag), 3); - EXPECT_THAT( - out_args2, - ElementsAreArray( - {absl::string_view("testbin"), absl::string_view("--undef_flag=aa"), - absl::string_view("--string_flag=AA"), absl::string_view("arg1")})); + const char* in_args3[] = {"testbin", "--help", "some_positional_arg"}; - EXPECT_EQ(absl::GetFlag(FLAGS_string_flag), "AA"); + EXPECT_EQ(InvokeParseAbslOnlyImpl(in_args3), flags::HelpMode::kImportant); } // -------------------------------------------------------------------- -TEST_F(ParseDeathTest, TestSimpleHelpFlagHandling) { +TEST_F(ParseTest, TestSubstringHelpFlagHandling) { const char* in_args1[] = { "testbin", - "--help", - }; - - EXPECT_EXIT(InvokeParse(in_args1), testing::ExitedWithCode(1), ""); - - const char* in_args2[] = { - "testbin", - "--help", - "--int_flag=3", + "--help=abcd", }; - auto out_args2 = flags::ParseCommandLineImpl( - 3, const_cast<char**>(in_args2), flags::ArgvListAction::kRemoveParsedArgs, - flags::UsageFlagsAction::kIgnoreUsage, - flags::OnUndefinedFlag::kAbortIfUndefined); - - EXPECT_EQ(flags::GetFlagsHelpMode(), flags::HelpMode::kImportant); - EXPECT_EQ(absl::GetFlag(FLAGS_int_flag), 3); + EXPECT_EQ(InvokeParseAbslOnlyImpl(in_args1), flags::HelpMode::kMatch); + EXPECT_EQ(flags::GetFlagsHelpMatchSubstr(), "abcd"); } // -------------------------------------------------------------------- -TEST_F(ParseDeathTest, TestSubstringHelpFlagHandling) { +TEST_F(ParseDeathTest, TestVersionHandling) { const char* in_args1[] = { "testbin", - "--help=abcd", + "--version", }; - auto out_args1 = flags::ParseCommandLineImpl( - 2, const_cast<char**>(in_args1), flags::ArgvListAction::kRemoveParsedArgs, - flags::UsageFlagsAction::kIgnoreUsage, - flags::OnUndefinedFlag::kAbortIfUndefined); + EXPECT_EQ(InvokeParseAbslOnlyImpl(in_args1), flags::HelpMode::kVersion); +} - EXPECT_EQ(flags::GetFlagsHelpMode(), flags::HelpMode::kMatch); - EXPECT_EQ(flags::GetFlagsHelpMatchSubstr(), "abcd"); +// -------------------------------------------------------------------- - const char* in_args2[] = {"testbin", "--help", "some_positional_arg"}; +TEST_F(ParseTest, TestCheckArgsHandling) { + const char* in_args1[] = {"testbin", "--only_check_args", "--int_flag=211"}; - auto out_args2 = flags::ParseCommandLineImpl( - 3, const_cast<char**>(in_args2), flags::ArgvListAction::kRemoveParsedArgs, - flags::UsageFlagsAction::kIgnoreUsage, - flags::OnUndefinedFlag::kAbortIfUndefined); + EXPECT_EQ(InvokeParseAbslOnlyImpl(in_args1), flags::HelpMode::kOnlyCheckArgs); + EXPECT_EXIT(InvokeParseAbslOnly(in_args1), testing::ExitedWithCode(0), ""); + EXPECT_EXIT(InvokeParse(in_args1), testing::ExitedWithCode(0), ""); - EXPECT_EQ(flags::GetFlagsHelpMode(), flags::HelpMode::kImportant); + const char* in_args2[] = {"testbin", "--only_check_args", "--unknown_flag=a"}; + + EXPECT_EQ(InvokeParseAbslOnlyImpl(in_args2), flags::HelpMode::kOnlyCheckArgs); + EXPECT_EXIT(InvokeParseAbslOnly(in_args2), testing::ExitedWithCode(0), ""); + EXPECT_EXIT(InvokeParse(in_args2), testing::ExitedWithCode(1), ""); } // -------------------------------------------------------------------- @@ -1001,4 +970,118 @@ TEST_F(ParseTest, WasPresentOnCommandLine) { // -------------------------------------------------------------------- +TEST_F(ParseTest, ParseAbseilFlagsOnlySuccess) { + const char* in_args[] = { + "testbin", + "arg1", + "--bool_flag", + "--int_flag=211", + "arg2", + "--double_flag=1.1", + "--undef_flag1", + "--undef_flag2=123", + "--string_flag", + "asd", + "--", + "--some_flag", + "arg4", + }; + + std::vector<char*> positional_args; + std::vector<absl::UnrecognizedFlag> unrecognized_flags; + + absl::ParseAbseilFlagsOnly(13, const_cast<char**>(in_args), positional_args, + unrecognized_flags); + EXPECT_THAT(positional_args, + ElementsAreArray( + {absl::string_view("testbin"), absl::string_view("arg1"), + absl::string_view("arg2"), absl::string_view("--some_flag"), + absl::string_view("arg4")})); + EXPECT_THAT(unrecognized_flags, + ElementsAreArray( + {absl::UnrecognizedFlag(absl::UnrecognizedFlag::kFromArgv, + "undef_flag1"), + absl::UnrecognizedFlag(absl::UnrecognizedFlag::kFromArgv, + "undef_flag2")})); +} + +// -------------------------------------------------------------------- + +TEST_F(ParseDeathTest, ParseAbseilFlagsOnlyFailure) { + const char* in_args[] = { + "testbin", + "--int_flag=21.1", + }; + + EXPECT_DEATH_IF_SUPPORTED( + InvokeParseAbslOnly(in_args), + "Illegal value '21.1' specified for flag 'int_flag'"); +} + +// -------------------------------------------------------------------- + +TEST_F(ParseTest, UndefOkFlagsAreIgnored) { + const char* in_args[] = { + "testbin", "--undef_flag1", + "--undef_flag2=123", "--undefok=undef_flag2", + "--undef_flag3", "value", + }; + + std::vector<char*> positional_args; + std::vector<absl::UnrecognizedFlag> unrecognized_flags; + + absl::ParseAbseilFlagsOnly(6, const_cast<char**>(in_args), positional_args, + unrecognized_flags); + EXPECT_THAT(positional_args, ElementsAreArray({absl::string_view("testbin"), + absl::string_view("value")})); + EXPECT_THAT(unrecognized_flags, + ElementsAreArray( + {absl::UnrecognizedFlag(absl::UnrecognizedFlag::kFromArgv, + "undef_flag1"), + absl::UnrecognizedFlag(absl::UnrecognizedFlag::kFromArgv, + "undef_flag3")})); +} + +// -------------------------------------------------------------------- + +TEST_F(ParseTest, AllUndefOkFlagsAreIgnored) { + const char* in_args[] = { + "testbin", + "--undef_flag1", + "--undef_flag2=123", + "--undefok=undef_flag2,undef_flag1,undef_flag3", + "--undef_flag3", + "value", + "--", + "--undef_flag4", + }; + + std::vector<char*> positional_args; + std::vector<absl::UnrecognizedFlag> unrecognized_flags; + + absl::ParseAbseilFlagsOnly(8, const_cast<char**>(in_args), positional_args, + unrecognized_flags); + EXPECT_THAT(positional_args, + ElementsAreArray({absl::string_view("testbin"), + absl::string_view("value"), + absl::string_view("--undef_flag4")})); + EXPECT_THAT(unrecognized_flags, testing::IsEmpty()); +} + +// -------------------------------------------------------------------- + +TEST_F(ParseDeathTest, ExitOnUnrecognizedFlagPrintsHelp) { + const char* in_args[] = { + "testbin", + "--undef_flag1", + "--help=int_flag", + }; + + EXPECT_EXIT(InvokeParseCommandLineImpl(in_args), testing::ExitedWithCode(1), + AllOf(HasSubstr("Unknown command line flag 'undef_flag1'"), + HasSubstr("Try --helpfull to get a list of all flags"))); +} + +// -------------------------------------------------------------------- + } // namespace diff --git a/absl/flags/usage.cc b/absl/flags/usage.cc index 452f6675..267a5039 100644 --- a/absl/flags/usage.cc +++ b/absl/flags/usage.cc @@ -21,6 +21,7 @@ #include "absl/base/attributes.h" #include "absl/base/config.h" #include "absl/base/const_init.h" +#include "absl/base/internal/raw_logging.h" #include "absl/base/thread_annotations.h" #include "absl/flags/internal/usage.h" #include "absl/strings/string_view.h" |