From 970e377710883fc5d5fb4e0b958e7e720f64c6cf Mon Sep 17 00:00:00 2001 From: Sascha Ochsenknecht Date: Thu, 10 Dec 2009 08:46:44 +0000 Subject: [PATCH] Enhancement to flag options as required, Fixes #2982 [SVN r58263] --- include/boost/program_options/errors.hpp | 17 ++++ .../boost/program_options/value_semantic.hpp | 19 +++- .../boost/program_options/variables_map.hpp | 9 +- src/value_semantic.cpp | 6 ++ src/variables_map.cpp | 62 ++++++++---- test/Jamfile.v2 | 1 + test/required_test.cfg | 1 + test/required_test.cpp | 97 +++++++++++++++++++ 8 files changed, 191 insertions(+), 21 deletions(-) create mode 100644 test/required_test.cfg create mode 100644 test/required_test.cpp diff --git a/include/boost/program_options/errors.hpp b/include/boost/program_options/errors.hpp index 51b22ee..116af58 100644 --- a/include/boost/program_options/errors.hpp +++ b/include/boost/program_options/errors.hpp @@ -213,6 +213,23 @@ namespace boost { namespace program_options { : error(std::string("can not read file ").append(filename)) {} }; + + /** Class thrown when a required/mandatory option is missing */ + class BOOST_PROGRAM_OPTIONS_DECL required_option : public error { + public: + required_option(const std::string& name) + : error(std::string("missing required option ").append(name)) + , m_option_name(name) + {} + + ~required_option() throw() {} + + const std::string& get_option_name() const throw(); + + private: + std::string m_option_name; // The name of the option which + // caused the exception. + }; }} diff --git a/include/boost/program_options/value_semantic.hpp b/include/boost/program_options/value_semantic.hpp index 40132cd..033009e 100644 --- a/include/boost/program_options/value_semantic.hpp +++ b/include/boost/program_options/value_semantic.hpp @@ -43,6 +43,11 @@ namespace boost { namespace program_options { other sources are discarded. */ virtual bool is_composing() const = 0; + + /** Returns true if value must be given. Non-optional value + + */ + virtual bool is_required() const = 0; /** Parses a group of tokens that specify a value of option. Stores the result in 'value_store', using whatever representation @@ -131,6 +136,8 @@ namespace boost { namespace program_options { unsigned max_tokens() const; bool is_composing() const { return false; } + + bool is_required() const { return false; } /** If 'value_store' is already initialized, or new_tokens has more than one elements, throws. Otherwise, assigns @@ -177,7 +184,8 @@ namespace boost { namespace program_options { the value when it's known. The parameter can be NULL. */ typed_value(T* store_to) : m_store_to(store_to), m_composing(false), - m_multitoken(false), m_zero_tokens(false) + m_multitoken(false), m_zero_tokens(false), + m_required(false) {} /** Specifies default value, which will be used @@ -266,6 +274,12 @@ namespace boost { namespace program_options { return this; } + /** Specifies that the value must occur. */ + typed_value* required() + { + m_required = true; + return this; + } public: // value semantic overrides @@ -292,6 +306,7 @@ namespace boost { namespace program_options { } } + bool is_required() const { return m_required; } /** Creates an instance of the 'validator' class and calls its operator() to perform the actual conversion. */ @@ -335,7 +350,7 @@ namespace boost { namespace program_options { std::string m_default_value_as_text; boost::any m_implicit_value; std::string m_implicit_value_as_text; - bool m_composing, m_implicit, m_multitoken, m_zero_tokens; + bool m_composing, m_implicit, m_multitoken, m_zero_tokens, m_required; boost::function1 m_notifier; }; diff --git a/include/boost/program_options/variables_map.hpp b/include/boost/program_options/variables_map.hpp index cea4d7c..02c4af2 100644 --- a/include/boost/program_options/variables_map.hpp +++ b/include/boost/program_options/variables_map.hpp @@ -92,7 +92,8 @@ namespace boost { namespace program_options { friend BOOST_PROGRAM_OPTIONS_DECL void store(const basic_parsed_options& options, variables_map& m, bool); - friend BOOST_PROGRAM_OPTIONS_DECL void notify(variables_map& m); + + friend BOOST_PROGRAM_OPTIONS_DECL class variables_map; }; /** Implements string->string mapping with convenient value casting @@ -147,6 +148,8 @@ namespace boost { namespace program_options { // Resolve conflict between inherited operators. const variable_value& operator[](const std::string& name) const { return abstract_variables_map::operator[](name); } + + void notify(); private: /** Implementation of abstract_variables_map::get @@ -161,6 +164,10 @@ namespace boost { namespace program_options { void store(const basic_parsed_options& options, variables_map& xm, bool utf8); + + /** Names of required options, filled by parser which has + access to options_description. */ + std::set m_required; }; diff --git a/src/value_semantic.cpp b/src/value_semantic.cpp index 3da5baf..f5770f1 100644 --- a/src/value_semantic.cpp +++ b/src/value_semantic.cpp @@ -325,5 +325,11 @@ namespace boost { namespace program_options { } return m_message.c_str(); } + + const std::string& + required_option::get_option_name() const throw() + { + return m_option_name; + } }} diff --git a/src/variables_map.cpp b/src/variables_map.cpp index bac6b0a..449d23c 100644 --- a/src/variables_map.cpp +++ b/src/variables_map.cpp @@ -105,7 +105,7 @@ namespace boost { namespace program_options { - // Second, apply default values. + // Second, apply default values and store required options. const vector >& all = desc.options(); for(i = 0; i < all.size(); ++i) { @@ -127,7 +127,12 @@ namespace boost { namespace program_options { m[key] = variable_value(def, true); m[key].m_value_semantic = d.semantic(); } - } + } + + // add empty value if this is an required option + if (d.semantic()->is_required()) { + xm.m_required.insert(key); + } } } @@ -140,22 +145,7 @@ namespace boost { namespace program_options { BOOST_PROGRAM_OPTIONS_DECL void notify(variables_map& vm) { - // Lastly, run notify actions. - for (map::iterator k = vm.begin(); - k != vm.end(); - ++k) - { - /* Users might wish to use variables_map to store their own values - that are not parsed, and therefore will not have value_semantics - defined. Do no crash on such values. In multi-module programs, - one module might add custom values, and the 'notify' function - will be called after that, so we check that value_sematics is - not NULL. See: - https://svn.boost.org/trac/boost/ticket/2782 - */ - if (k->second.m_value_semantic) - k->second.m_value_semantic->notify(k->second.value()); - } + vm.notify(); } abstract_variables_map::abstract_variables_map() @@ -206,4 +196,40 @@ namespace boost { namespace program_options { else return i->second; } + + void + variables_map::notify() + { + // This checks if all required options occur + for (set::const_iterator r = m_required.begin(); + r != m_required.end(); + ++r) + { + const string& opt = *r; + map::const_iterator iter = find(opt); + if (iter == end() || iter->second.empty()) + { + boost::throw_exception(required_option(opt)); + + } + } + + // Lastly, run notify actions. + for (map::iterator k = begin(); + k != end(); + ++k) + { + /* Users might wish to use variables_map to store their own values + that are not parsed, and therefore will not have value_semantics + defined. Do no crash on such values. In multi-module programs, + one module might add custom values, and the 'notify' function + will be called after that, so we check that value_sematics is + not NULL. See: + https://svn.boost.org/trac/boost/ticket/2782 + */ + if (k->second.m_value_semantic) + k->second.m_value_semantic->notify(k->second.value()); + } + } + }} diff --git a/test/Jamfile.v2 b/test/Jamfile.v2 index 83625e9..809c9cb 100644 --- a/test/Jamfile.v2 +++ b/test/Jamfile.v2 @@ -30,6 +30,7 @@ test-suite program_options : [ po-test exception_test.cpp ] [ po-test split_test.cpp ] [ po-test unrecognized_test.cpp ] + [ po-test required_test.cpp ] ; exe test_convert : test_convert.cpp ; diff --git a/test/required_test.cfg b/test/required_test.cfg new file mode 100644 index 0000000..4b25dd6 --- /dev/null +++ b/test/required_test.cfg @@ -0,0 +1 @@ +cfgfile = file.cfg diff --git a/test/required_test.cpp b/test/required_test.cpp new file mode 100644 index 0000000..15e6a61 --- /dev/null +++ b/test/required_test.cpp @@ -0,0 +1,97 @@ +// Copyright Sascha Ochsenknecht 2009. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include +using namespace boost::program_options; + +#include +#include +#include +using namespace std; + +#include "minitest.hpp" + + +void required_throw_test() +{ + options_description opts; + opts.add_options() + ("cfgfile,c", value()->required(), "the configfile") + ("fritz,f", value()->required(), "the output file") + ; + + variables_map vm; + bool throwed = false; + { + // This test must throw exception + string cmdline = "prg -f file.txt"; + vector< string > tokens = split_unix(cmdline); + throwed = false; + try { + store(command_line_parser(tokens).options(opts).run(), vm); + notify(vm); + } + catch (required_option& e) { + throwed = true; + } + BOOST_CHECK(throwed); + } + + { + // This test mustn't throw exception + string cmdline = "prg -c config.txt"; + vector< string > tokens = split_unix(cmdline); + throwed = false; + try { + store(command_line_parser(tokens).options(opts).run(), vm); + notify(vm); + } + catch (required_option& e) { + throwed = true; + } + BOOST_CHECK(!throwed); + } +} + + + +void simple_required_test() +{ + options_description opts; + opts.add_options() + ("cfgfile,c", value()->required(), "the configfile") + ("fritz,f", value()->required(), "the output file") + ; + + variables_map vm; + bool throwed = false; + { + // This test must throw exception + string cmdline = "prg -f file.txt"; + vector< string > tokens = split_unix(cmdline); + throwed = false; + try { + // options coming from different sources + store(command_line_parser(tokens).options(opts).run(), vm); + store(parse_config_file("required_test.cfg", opts), vm); + notify(vm); + } + catch (required_option& e) { + throwed = true; + } + BOOST_CHECK(!throwed); + } +} + + + +int main(int /*argc*/, char** /*argv*/) +{ + required_throw_test(); + simple_required_test(); + + return 0; +} +