From e41198c294b62dba59adac48cecb1d83014689b9 Mon Sep 17 00:00:00 2001 From: ADAM David Alan Martin Date: Sat, 6 Sep 2025 01:22:53 -0400 Subject: [PATCH] Initial draft of how short options work. I went with expansion here, as it was easier to implement, given the complexities of how the options parsing code works. Rather than try to maintain state machines and parsing for both forms of argument, we can transform the short options into the long form options. This, then, might lead to some issues when the code is expanded to handle arguments to those options. I'll probably just add a state tracking bit to that parameter to say that it was expanded from a specific short form. It might be worth it to permit a short form to expand to a long form _with_ specific hardcoded option. This gets into defaults, which might be the better way to underpin that. For expanding these into the automatic help documentation, the Long options (the main option definition struct) should maintain a list of the short forms that it supports. I also need to add a neat syntax. Something like: ``` -'o'_option <= --"long-option"_option ``` It might be beneficial to auto generate something like: ``` -'O'_option <= --"no-long-option"_option ``` for boolean toggles. Should it always be so? Maybe an extra sigil to allow both? --- ProgramOptions.cc | 78 +++++++++++++++++++++++++++++++++++++++++----- ProgramOptions.h | 27 +++++++++------- dumbhash.test/0.cc | 10 ++++-- example.cc | 6 +++- 4 files changed, 98 insertions(+), 23 deletions(-) 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; }