From 0d7162af346ecc0859f3736f5965fcdf7d28eb20 Mon Sep 17 00:00:00 2001 From: Fytch Date: Sat, 3 Jun 2017 01:21:42 +0200 Subject: [PATCH 1/4] added methods to iterate over options --- ProgramOptions.hxx | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/ProgramOptions.hxx b/ProgramOptions.hxx index 9636c09..8a0e211 100644 --- a/ProgramOptions.hxx +++ b/ProgramOptions.hxx @@ -1920,6 +1920,49 @@ namespace po { } public: + using iterator = options_t::iterator; + using const_iterator = options_t::const_iterator; + using reverse_iterator = std::reverse_iterator< iterator >; + using const_reverse_iterator = std::reverse_iterator< const_iterator >; + + iterator begin() { + return m_options.begin(); + } + const_iterator begin() const { + return m_options.begin(); + } + const_iterator cbegin() const { + return begin(); + } + reverse_iterator rbegin() { + return reverse_iterator{ end() }; + } + const_reverse_iterator rbegin() const { + return const_reverse_iterator{ end() }; + } + const_reverse_iterator crbegin() const { + return rbegin(); + } + + iterator end() { + return m_options.end(); + } + const_iterator end() const { + return m_options.end(); + } + const_iterator cend() const { + return end(); + } + reverse_iterator rend() { + return reverse_iterator{ begin() }; + } + const_reverse_iterator rend() const { + return const_reverse_iterator{ begin() }; + } + const_reverse_iterator crend() const { + return rend(); + } + option& operator[]( std::string const& designator ) { return operator_brackets_helper( std::string{ designator } ); } From ce27c928ca758d1cc0a8f32696dba6325d746c25 Mon Sep 17 00:00:00 2001 From: Fytch Date: Sun, 4 Jun 2017 19:46:22 +0200 Subject: [PATCH 2/4] [BREAKING] replace PROGRAMOPTIONS_SILENT with .silent() --- ProgramOptions.hxx | 158 ++++++++++++++++++++++++++---------------- test/callback.cxx | 2 +- test/defaults.cxx | 3 +- test/errors.cxx | 3 +- test/exceptions.cxx | 3 +- test/integer.cxx | 3 +- test/option_packs.cxx | 3 +- test/str2flt.cxx | 1 - test/str2int.cxx | 1 - test/str2uint.cxx | 1 - test/string.cxx | 3 +- test/wellformed.cxx | 1 - 12 files changed, 112 insertions(+), 70 deletions(-) diff --git a/ProgramOptions.hxx b/ProgramOptions.hxx index 9636c09..77a2264 100644 --- a/ProgramOptions.hxx +++ b/ProgramOptions.hxx @@ -168,23 +168,6 @@ namespace po { } #endif // !PROGRAMOPTIONS_NO_COLORS -#ifdef PROGRAMOPTIONS_SILENT - inline std::ostream& err() { - struct dummy_buf_t : public std::streambuf { - int overflow( int c ) { - return c; - } - }; - static dummy_buf_t dummy_buf; - static std::ostream dummy_str{ &dummy_buf }; - return dummy_str; - } -#else // PROGRAMOPTIONS_SILENT - inline std::ostream& err() { - return std::cerr; - } -#endif // PROGRAMOPTIONS_SILENT - struct error_t { friend std::ostream& operator<<( std::ostream& stream, error_t const& /* object */ ) { // -Wunused-parameter return stream << red << "error: "; @@ -224,15 +207,6 @@ namespace po { return { arg }; } - template< typename arg_t > - void error_nonoption_arguments( arg_t const& arg ) { - err() << error() << "non-option arguments not allowed" << ignoring( arg ); - } - template< typename arg_t > - void error_unrecognized_option( arg_t const& arg ) { - err() << error() << "unrecognized option" << ignoring( arg ); - } - // Compatibility stuff for the lack of C++14 support template< typename T, typename... args_t > std::unique_ptr< T > make_unique( args_t&&... args ) { @@ -1654,6 +1628,52 @@ namespace po { using options_t = std::unordered_map< std::string, option >; options_t m_options; char const* m_program_name = ""; + std::ostream* m_output_destination; + + public: + parser() { + verbose( std::cerr ); + } + + void silent() { + m_output_destination = nullptr; + } + bool is_silent() const { + return m_output_destination == nullptr; + } + void verbose( std::ostream& destination ) { + m_output_destination = &destination; + } + bool is_verbose() const { + return !is_silent(); + } + + private: + std::ostream& err() { + if( m_output_destination ) { + return *m_output_destination; + } else { + struct dummy_buf_t : public std::streambuf { + int overflow( int c ) { + return c; + } + }; + static dummy_buf_t dummy_buf; + static std::ostream dummy_str{ &dummy_buf }; + return dummy_str; + } + } + + template< typename arg_t > + void error_nonoption_arguments( arg_t const& arg ) { + assert( is_verbose() ); + *m_output_destination << error() << "non-option arguments not allowed" << ignoring( arg ); + } + template< typename arg_t > + void error_unrecognized_option( arg_t const& arg ) { + assert( is_verbose() ); + *m_output_destination << error() << "unrecognized option" << ignoring( arg ); + } options_t::iterator find_abbreviation( char value ) { auto iter = m_options.begin(); @@ -1664,6 +1684,7 @@ namespace po { } void check_spelling( char short_option ) { + assert( is_verbose() ); if( !std::isalpha( short_option ) ) return; if( std::islower( short_option ) ) @@ -1671,9 +1692,10 @@ namespace po { else // if( std::isupper( short_option ) ) short_option = std::tolower( short_option ); if( find_abbreviation( short_option ) != m_options.end() ) - err() << suggest( std::string{ '-', short_option } ); + *m_output_destination << suggest( std::string{ '-', short_option } ); } void check_spelling( char const* long_option ) { + assert( is_verbose() ); assert( long_option[ 0 ] == '-' && long_option[ 1 ] == '-' && long_option[ 2 ] != '\0' ); enum : std::size_t { distance_cutoff = 4 @@ -1698,30 +1720,32 @@ namespace po { } } if( min_distance < distance_cutoff ) - err() << suggest( std::string( "--" ) + nearest_option->first ); + *m_output_destination << suggest( std::string( "--" ) + nearest_option->first ); } template< typename expression_t, typename... args_t > bool parse_argument( options_t::iterator option, expression_t const& expression, args_t&&... args ) const { const error_code code = option->second.parse( std::forward< args_t >( args )... ); if( code == error_code::none ) return true; - err() << error() << "option \'"; - err() << blue << option->first; - err() << "\' "; - switch( code ) { - case error_code::argument_expected: - case error_code::conversion_error: - err() << "expects an argument of type " << vt2str( option->second.get_type() ); - break; - case error_code::no_argument_expected: - err() << "doesn't expect any arguments"; - break; - case error_code::out_of_range: - err() << "has an argument that caused an out of range error"; - default: // -Wswitch - ; + if( is_verbose() ) { + *m_output_destination << error() << "option \'"; + *m_output_destination << blue << option->first; + *m_output_destination << "\' "; + switch( code ) { + case error_code::argument_expected: + case error_code::conversion_error: + *m_output_destination << "expects an argument of type " << vt2str( option->second.get_type() ); + break; + case error_code::no_argument_expected: + *m_output_destination << "doesn't expect any arguments"; + break; + case error_code::out_of_range: + *m_output_destination << "has an argument that caused an out of range error"; + default: // -Wswitch + ; + } + *m_output_destination << ignoring( expression ) << '\n'; } - err() << ignoring( expression ) << '\n'; return false; } bool dashed_non_option( char* arg ) { @@ -1751,7 +1775,12 @@ namespace po { // -vdata argument = &argv[ i ][ j ]; } else { - err() << error() << "unexpected character \'" << argv[ i ][ j ] << "\'" << ignoring( argv[ i ] ) << suggest( std::string{ &argv[ i ][ 0 ], &argv[ i ][ j ] } + "=" + std::string{ &argv[ i ][ j ] } ) << '\n'; + if( is_verbose() ) + *m_output_destination + << error() + << "unexpected character \'" << argv[ i ][ j ] << "\'" + << ignoring( argv[ i ] ) + << suggest( std::string{ &argv[ i ][ 0 ], &argv[ i ][ j ] } + "=" + std::string{ &argv[ i ][ j ] } ) << '\n'; return false; } return parse_argument( option, std::move( expression ), argument ); @@ -1761,7 +1790,8 @@ namespace po { if( !result ) { good = false; error_nonoption_arguments( arg ); - err() << '\n'; + if( is_verbose() ) + *m_output_destination << '\n'; } return result; } @@ -1832,15 +1862,19 @@ namespace po { for( ; valid_designator_character( *last ); ++last ); if( first == last ) { good = false; - error_unrecognized_option( argv[ i ] ); - err() << '\n'; + if( is_verbose() ) { + error_unrecognized_option( argv[ i ] ); + *m_output_destination << '\n'; + } } else { const auto opt = m_options.find( std::string{ first, last } ); if( opt == m_options.end() ) { good = false; - error_unrecognized_option( argv[ i ] ); - check_spelling( argv[ i ] ); - err() << '\n'; + if( is_verbose() ) { + error_unrecognized_option( argv[ i ] ); + check_spelling( argv[ i ] ); + *m_output_destination << '\n'; + } } else { good &= extract_argument( opt, argc, argv, i, last - argv[ i ] ); } @@ -1851,29 +1885,35 @@ namespace po { const auto head = find_abbreviation( argv[ i ][ 1 ] ); if( head == m_options.end() ) { good = false; - error_unrecognized_option( argv[ i ] ); - check_spelling( argv[ i ][ 1 ] ); - err() << '\n'; + if( is_verbose() ) { + error_unrecognized_option( argv[ i ] ); + check_spelling( argv[ i ][ 1 ] ); + *m_output_destination << '\n'; + } } else if( head->second.get_type() == void_ ) { // -fgh char c; for( std::size_t j = 1; ( c = argv[ i ][ j ] ) != '\0'; ++j ) { if( !std::isprint( c ) || c == '-' ) { good = false; - err() << error() << "invalid character \'" << c << "\'" << ignoring( &argv[ i ][ j ] ) << '\n'; + if( is_verbose() ) + *m_output_destination << error() << "invalid character \'" << c << "\'" << ignoring( &argv[ i ][ j ] ) << '\n'; break; } const auto opt = find_abbreviation( c ); if( opt == m_options.end() ) { good = false; - error_unrecognized_option( c ); - check_spelling( c ); - err() << '\n'; + if( is_verbose() ) { + error_unrecognized_option( c ); + check_spelling( c ); + *m_output_destination << '\n'; + } continue; } if( opt->second.get_type() != void_ ) { good = false; - err() << error() << "non-void options not allowed in option packs" << ignoring( c ) << '\n'; + if( is_verbose() ) + *m_output_destination << error() << "non-void options not allowed in option packs" << ignoring( c ) << '\n'; continue; } good &= parse_argument( opt, c ); diff --git a/test/callback.cxx b/test/callback.cxx index 2d82d69..c72b221 100644 --- a/test/callback.cxx +++ b/test/callback.cxx @@ -1,10 +1,10 @@ #include -#define PROGRAMOPTIONS_SILENT #include #include "arg_provider.hxx" TEST_CASE( "callback", "[ProgramOptions]" ) { po::parser parser; + parser.silent(); po::i32_t a_value{}; po::string_t a_value_str{}; diff --git a/test/defaults.cxx b/test/defaults.cxx index 4a5b749..47bd495 100644 --- a/test/defaults.cxx +++ b/test/defaults.cxx @@ -1,9 +1,10 @@ #include -#define PROGRAMOPTIONS_SILENT #include TEST_CASE( "defaults", "[ProgramOptions]" ) { po::parser parser; + parser.silent(); + auto&& _1 = parser[ "abacus" ]; auto&& _2 = parser[ "a" ]; auto&& _3 = parser[ "" ]; diff --git a/test/errors.cxx b/test/errors.cxx index a86176b..89c7531 100644 --- a/test/errors.cxx +++ b/test/errors.cxx @@ -1,10 +1,11 @@ #include -#define PROGRAMOPTIONS_SILENT #include #include "arg_provider.hxx" TEST_CASE( "errors", "[ProgramOptions]" ) { po::parser parser; + parser.silent(); + auto&& sum = parser[ "sum" ] .abbreviation( 's' ) .type( po::i64 ) diff --git a/test/exceptions.cxx b/test/exceptions.cxx index dccc4e3..0157f2e 100644 --- a/test/exceptions.cxx +++ b/test/exceptions.cxx @@ -1,5 +1,4 @@ #include -#define PROGRAMOPTIONS_SILENT #undef NDEBUG #undef PROGRAMOPTIONS_NO_EXCEPTIONS #include @@ -8,6 +7,8 @@ TEST_CASE( "exceptions", "[ProgramOptions]" ) { po::parser parser; + parser.silent(); + auto&& a = parser[ "a" ] .type( po::f32 ); auto&& bc = parser[ "bc" ] diff --git a/test/integer.cxx b/test/integer.cxx index c7f6dac..cf55893 100644 --- a/test/integer.cxx +++ b/test/integer.cxx @@ -1,10 +1,11 @@ #include -#define PROGRAMOPTIONS_SILENT #include #include "arg_provider.hxx" TEST_CASE( "integer", "[ProgramOptions]" ) { po::parser parser; + parser.silent(); + auto&& a = parser[ "a" ] .type( po::i64 ); auto&& b = parser[ "bot" ] diff --git a/test/option_packs.cxx b/test/option_packs.cxx index ad8a40d..d83b497 100644 --- a/test/option_packs.cxx +++ b/test/option_packs.cxx @@ -1,10 +1,11 @@ #include -#define PROGRAMOPTIONS_SILENT #include #include "arg_provider.hxx" TEST_CASE( "option_packs", "[ProgramOptions]" ) { po::parser parser; + parser.silent(); + auto&& g = parser[ "g" ]; auto&& h = parser[ "home" ] .abbreviation( 'h' ) diff --git a/test/str2flt.cxx b/test/str2flt.cxx index e81baa0..25daa11 100644 --- a/test/str2flt.cxx +++ b/test/str2flt.cxx @@ -1,5 +1,4 @@ #include -#define PROGRAMOPTIONS_SILENT #include #include #include diff --git a/test/str2int.cxx b/test/str2int.cxx index 0397954..9c7f3ff 100644 --- a/test/str2int.cxx +++ b/test/str2int.cxx @@ -1,5 +1,4 @@ #include -#define PROGRAMOPTIONS_SILENT #include #include #include diff --git a/test/str2uint.cxx b/test/str2uint.cxx index 1e980da..fde6109 100644 --- a/test/str2uint.cxx +++ b/test/str2uint.cxx @@ -1,5 +1,4 @@ #include -#define PROGRAMOPTIONS_SILENT #include #include #include diff --git a/test/string.cxx b/test/string.cxx index 209e5b4..2bc040b 100644 --- a/test/string.cxx +++ b/test/string.cxx @@ -1,10 +1,11 @@ #include -#define PROGRAMOPTIONS_SILENT #include #include "arg_provider.hxx" TEST_CASE( "string", "[ProgramOptions]" ) { po::parser parser; + parser.silent(); + auto&& a = parser[ "a" ] .type( po::string ); auto&& b = parser[ "bot" ] diff --git a/test/wellformed.cxx b/test/wellformed.cxx index e8d598a..b7a4a9e 100644 --- a/test/wellformed.cxx +++ b/test/wellformed.cxx @@ -1,5 +1,4 @@ #include -#define PROGRAMOPTIONS_SILENT #include TEST_CASE( "wellformed", "[ProgramOptions]" ) { From d8dc1d09b5d97121010e86c5ff80a6c231408500 Mon Sep 17 00:00:00 2001 From: Fytch Date: Sun, 4 Jun 2017 19:59:39 +0200 Subject: [PATCH 3/4] README: fix documentation as to silencing the parser --- README.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 6e61050..8282ad5 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ - [Example 2 (`fallback`, `was_set`, `string`, `multi`)](#example-2-fallback-was_set-string-multi) - [Example 3 (`description`, `callback`, unnamed parameter)](#example-3-description-callback-unnamed-parameter) - [Example 4 (more `callback`s, more `fallback`s, `f64`, `to_vector`)](#example-4-more-callbacks-more-fallbacks-f64-to_vector) + - [Miscellaneous functions](#miscellaneous-functions) - [Defaults](#defaults) - [Flags](#flags) - [Third-party libraries](#third-party-libraries) @@ -302,6 +303,11 @@ successfully parsed 12 which equals 12 successfully parsed NaN which equals nan ( + 12 nan ) = nan ``` +### Miscellaneous functions + +#### `void po::parser::silent()` +Suppresses all communication via `stderr`. Without this flag, *ProgramOptions.hxx* notifies the user in case of warnings or errors occurring while parsing. For instance, if an option requires an argument of type `i32` and it couldn't be parsed, overflowed or wasn't provided at all, *ProgramOptions.hxx* would print an error saying that the option's argument is invalid and that it hence was ignored. + ## Defaults This small table helps clarifying the defaults for the different kinds of options. @@ -314,9 +320,6 @@ This small table helps clarifying the defaults for the different kinds of option ## Flags All flags have to be `#define`d before including *ProgramOptions.hxx*. Different translation units may include *ProgramOptions.hxx* using different flags. -### `#define PROGRAMOPTIONS_SILENT` -Suppresses all communication via `stderr`. Without this flag, *ProgramOptions.hxx* notifies the user in case of warnings or errors occurring while parsing. For instance, if an option requires an argument of type `i32` and it couldn't be parsed or wasn't provided at all, *ProgramOptions.hxx* would print a warning that the option must have an argument and that it hence was ignored. - ### `#define PROGRAMOPTIONS_NO_EXCEPTIONS` Disables all exceptions and thus allows compilation with `-fno-exceptions`. However, incorrect use of the library and unmet preconditions entail `abort()` via `assert(...)`. This flag is implied by `NDEBUG`. From e9a7a251ecdc7f65de6e09faaf6ba11a401b3421 Mon Sep 17 00:00:00 2001 From: Fytch Date: Mon, 5 Jun 2017 00:14:32 +0200 Subject: [PATCH 4/4] add option::mandatory() for compulsory options --- ProgramOptions.hxx | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/ProgramOptions.hxx b/ProgramOptions.hxx index 44d0ddb..592aa0e 100644 --- a/ProgramOptions.hxx +++ b/ProgramOptions.hxx @@ -1122,6 +1122,7 @@ namespace po { std::string m_description; value_type m_type = void_; bool m_multi = false; + bool m_mandatory = false; std::unique_ptr< value_vector_base > m_fallback; std::unique_ptr< value_vector_base > m_data; @@ -1501,6 +1502,18 @@ namespace po { return m_multi; } + option& mandatory( bool value = true ) { + m_mandatory = value; + return *this; + } + option& non_mandatory() { + m_mandatory = false; + return *this; + } + bool is_mandatory() const { + return m_mandatory; + } + private: template< typename T > parsing_report< value > fallback_parse( T&& arg ) { @@ -1924,6 +1937,20 @@ namespace po { } } } + for( auto&& i : m_options ) + if( i.second.is_mandatory() && !i.second.was_set() ) { + good = false; + if( is_verbose() ) { + auto&& name = i.first; + if( !name.empty() ) { + *m_output_destination << error() << "option \'"; + *m_output_destination << blue << i.first; + *m_output_destination << "\' has not been set\n"; + } else { + *m_output_destination << error() << "no argument provided\n"; + } + } + } for( auto&& i : m_options ) i.second.make_immutable(); return good;