diff --git a/ProgramOptions.cc b/ProgramOptions.cc index 13b489e..aeebd2b 100644 --- a/ProgramOptions.cc +++ b/ProgramOptions.cc @@ -4,6 +4,7 @@ static_assert( __cplusplus > 2020'99 ); #include #include +#include #include #include @@ -43,6 +44,8 @@ namespace Alepha::Hydrogen ::detail:: ProgramOptions_m std::function< std::string () > defaultBuilder= [] { return ""; }; std::map< std::type_index, std::set< const DomainBase * > > domains; + + ArgumentStance argumentStance; }; namespace @@ -102,7 +105,7 @@ namespace Alepha::Hydrogen ::detail:: ProgramOptions_m } return core(); }; - return registerHandler( handler ); + return registerHandler( handler, ArgumentStance::None ); } std::ostream & @@ -115,7 +118,7 @@ namespace Alepha::Hydrogen ::detail:: ProgramOptions_m impl::checkArgument( argument, name ); return core( argument.value() ); }; - return registerHandler( handler ); + return registerHandler( handler, ArgumentStance::Required ); } void @@ -125,9 +128,10 @@ namespace Alepha::Hydrogen ::detail:: ProgramOptions_m } std::ostream & - OptionBinding::registerHandler( std::function< void ( std::optional< std::string > ) > handler ) const + OptionBinding::registerHandler( std::function< void ( std::optional< std::string > ) > handler, ArgumentStance argumentStance ) const { option->handler= handler; + option->argumentStance= argumentStance; return option->help; } @@ -198,7 +202,7 @@ namespace Alepha::Hydrogen ::detail:: ProgramOptions_m AutoRAII endline{ []{}, []{ std::cout << std::endl; } }; auto wrapping= adaptStream( StartWrap{ width, alignmentWidth }, std::cout ); - const auto &[ _, helpText, defaultBuilder, domains ]= def; + const auto &[ _, helpText, defaultBuilder, domains, argumentStance ]= def; VariableMap substitutions= { @@ -291,9 +295,69 @@ namespace Alepha::Hydrogen ::detail:: ProgramOptions_m ::exit( EXIT_SUCCESS ); } - std::vector< std::string > - impl::handleOptions( const std::vector< std::string > &args, const std::function< void () > usageFunction ) + namespace { + void + validateShortAliases( const ShortAliases &shortAliases ) + { + for( const auto &[ key, name ]: shortAliases ) + { + if( not programOptions().contains( "--" + name ) ) + { + throw std::logic_error{ "Short form alias mapping of `-"s + key + "` for option `--" + + name + "` is invalid, because the long name doesn't exist." }; + } + const auto &[ _, help, builder, domains, stance ]= programOptions().at( "--" + name ); + if( stance == ArgumentStance::Required ) + { + throw std::logic_error{ "Short form alias mapping of `-"s + key + "` for option `--" + + name + "` is invalid, because the long name takes an argument and the argument parser " + + "does not support short options with arguments at this time." }; + } + } + } + + void + importShortAliases( const ShortAliases &shortAliases ) + { + validateShortAliases( shortAliases ); + + // TODO: Incorporate short aliases into the help... + } + + std::vector< std::string > + expandAliases( const std::vector< std::string > &args, const ShortAliases &shortAliases ) + { + std::vector< std::string > rv; + + for( const auto &arg: args ) + { + if( not std::regex_match( arg, std::regex{ "-[^-]" } ) ) + { + rv.push_back( arg ); + continue; + } + + for( const auto &key: std::string_view{ arg }.substr( 1 ) ) + { + if( not shortAliases.contains( key ) ) + { + throw std::runtime_error( "`-"s + key + "` is an unrecognized option." ); + } + + rv.push_back( "--" + shortAliases.at( key ) ); + } + } + return rv; + } + } + + std::vector< std::string > + impl::handleOptions( const std::vector< std::string > &args, const std::function< void () > usageFunction, + const ShortAliases &shortAliases ) + { + importShortAliases( shortAliases ); + --"help"_option << usageFunction << "Print this help message (program usage)."; // The unprocessed program arguments will be collected into this vector @@ -313,7 +377,7 @@ namespace Alepha::Hydrogen ::detail:: ProgramOptions_m // required option domains. std::set< const DomainBase * > requiredOptionsSeen; - const std::vector< std::string > argsToProcess{ begin( args ), endOfArgs }; + const std::vector< std::string > argsToProcess= expandAliases( { begin( args ), endOfArgs }, shortAliases ); // An option that requires an argument might have been type-o'ed as `--option arg` // instead of `--option=arg`. By tracking the next option, we can print helpful diff --git a/ProgramOptions.h b/ProgramOptions.h index 134b9da..74f169b 100644 --- a/ProgramOptions.h +++ b/ProgramOptions.h @@ -142,6 +142,9 @@ namespace Alepha::Hydrogen ::detail:: ProgramOptions_m namespace exports { + enum class ArgumentStance { None, Required }; + using ShortAliases= std::map< char, std::string >; + /*! * This is used to build groups of mutually exclusive options. * @@ -199,7 +202,7 @@ namespace Alepha::Hydrogen ::detail:: ProgramOptions_m const auto &self() const { return *this; } using option_handler= std::function< void ( std::optional< std::string > ) >; - [[nodiscard]] std::ostream ®isterHandler( option_handler handler ) const; + [[nodiscard]] std::ostream ®isterHandler( option_handler handler, ArgumentStance argumentStance ) const; void setDefaultBuilder( std::function< std::string () > ) const; @@ -311,7 +314,7 @@ namespace Alepha::Hydrogen ::detail:: ProgramOptions_m }; handler( parsed ); }; - return registerHandler( wrapped ); + return registerHandler( wrapped, ArgumentStance::Required ); } else { @@ -322,7 +325,7 @@ namespace Alepha::Hydrogen ::detail:: ProgramOptions_m const auto value= argumentFromString< arg_type >( argument.value(), name, name + "=" + argument.value() ); return handler( value ); }; - return registerHandler( wrapped ); + return registerHandler( wrapped, ArgumentStance::Required ); } } }; @@ -348,7 +351,7 @@ namespace Alepha::Hydrogen ::detail:: ProgramOptions_m namespace impl { [[noreturn]] void usage( const std::string &, const std::optional< std::string > & ); - [[nodiscard]] std::vector< std::string > handleOptions( const std::vector< std::string > &, std::function< void () > ); + [[nodiscard]] std::vector< std::string > handleOptions( const std::vector< std::string > &, std::function< void () >, const ShortAliases &shortAliases ); } template< typename Supplement > @@ -364,28 +367,28 @@ namespace Alepha::Hydrogen ::detail:: ProgramOptions_m template< typename Supplement > auto - handleOptions( const std::vector< std::string > &args ) + handleOptions( const std::vector< std::string > &args, const ShortAliases &shortAliases= {} ) { - return impl::handleOptions( args, usageWrap< Supplement > ); + return impl::handleOptions( args, usageWrap< Supplement >, shortAliases ); } template< typename Supplement > auto - handleOptions( const int argcnt, const char *const *const argvec ) + handleOptions( const int argcnt, const char *const *const argvec, const ShortAliases &shortAliases= {} ) { - return handleOptions< Supplement >( { argvec + 1, argvec + argcnt } ); + return handleOptions< Supplement >( { argvec + 1, argvec + argcnt }, shortAliases ); } inline auto - handleOptions( const std::vector< std::string > &args ) + handleOptions( const std::vector< std::string > &args, const ShortAliases &shortAliases= {} ) { - return handleOptions< ProgramDescription >( args ); + return handleOptions< ProgramDescription >( args, shortAliases ); } inline auto - handleOptions( const int argcnt, const char *const *const argvec ) + handleOptions( const int argcnt, const char *const *const argvec, const ShortAliases &shortAliases= {} ) { - return handleOptions< ProgramDescription >( argcnt, argvec ); + return handleOptions< ProgramDescription >( argcnt, argvec, shortAliases ); } } } diff --git a/dumbhash.test/0.cc b/dumbhash.test/0.cc index 3eef334..6cddead 100644 --- a/dumbhash.test/0.cc +++ b/dumbhash.test/0.cc @@ -11,14 +11,14 @@ static_assert( __cplusplus > 2020'99 ); #include #include -#include +#include #include namespace { using namespace Alepha::Testing::exports; - using namespace Alepha::Utility::exports::evaluation; - using namespace Alepha::exports::types; + using namespace Alepha::Utility::exports::enroll_m; + using namespace Alepha::exports::types_m; using hash_type= std::uint64_t; @@ -65,6 +65,10 @@ namespace typesig() { const char *p= __PRETTY_FUNCTION__; + if not consteval + { + std::cerr << p << std::endl; + } while( *p++ != '[' ); return p + 5 + 4; } diff --git a/example.cc b/example.cc index 9ae84fc..273ebd8 100644 --- a/example.cc +++ b/example.cc @@ -14,10 +14,13 @@ namespace int optionA= 42; std::optional< std::string > optionB; + bool optionC= false; + auto init= enroll <=[] { --"set-a"_option << optionA << "The option is an integer. !default!"; --"set-b"_option << optionB << "The option is a string, no defaults."; + --"set-c"_option << optionC << "This sets the 'C' flag."; }; } @@ -25,10 +28,11 @@ int main( const int argcnt, const char *const *const argvec ) try { - const auto args= Alepha::handleOptions( argcnt, argvec ); + const auto args= Alepha::handleOptions( argcnt, argvec, { { 'c', "set-c" }, { 'C', "no-set-c" } } ); std::cout << "A is set to: " << optionA << std::endl; std::cout << "B is set to: " << ( optionB.has_value() ? optionB.value() : "nullopt"s ) << std::endl; + std::cout << "C is set to: " << std::boolalpha << optionC << std::endl; return EXIT_SUCCESS; }