Skip to content

Commit 970e377

Browse files
author
Sascha Ochsenknecht
committed
Enhancement to flag options as required, Fixes #2982
[SVN r58263]
1 parent fbb8f04 commit 970e377

File tree

8 files changed

+191
-21
lines changed

8 files changed

+191
-21
lines changed

include/boost/program_options/errors.hpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,23 @@ namespace boost { namespace program_options {
213213
: error(std::string("can not read file ").append(filename))
214214
{}
215215
};
216+
217+
/** Class thrown when a required/mandatory option is missing */
218+
class BOOST_PROGRAM_OPTIONS_DECL required_option : public error {
219+
public:
220+
required_option(const std::string& name)
221+
: error(std::string("missing required option ").append(name))
222+
, m_option_name(name)
223+
{}
224+
225+
~required_option() throw() {}
226+
227+
const std::string& get_option_name() const throw();
228+
229+
private:
230+
std::string m_option_name; // The name of the option which
231+
// caused the exception.
232+
};
216233
}}
217234

218235

include/boost/program_options/value_semantic.hpp

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,11 @@ namespace boost { namespace program_options {
4343
other sources are discarded.
4444
*/
4545
virtual bool is_composing() const = 0;
46+
47+
/** Returns true if value must be given. Non-optional value
48+
49+
*/
50+
virtual bool is_required() const = 0;
4651

4752
/** Parses a group of tokens that specify a value of option.
4853
Stores the result in 'value_store', using whatever representation
@@ -131,6 +136,8 @@ namespace boost { namespace program_options {
131136
unsigned max_tokens() const;
132137

133138
bool is_composing() const { return false; }
139+
140+
bool is_required() const { return false; }
134141

135142
/** If 'value_store' is already initialized, or new_tokens
136143
has more than one elements, throws. Otherwise, assigns
@@ -177,7 +184,8 @@ namespace boost { namespace program_options {
177184
the value when it's known. The parameter can be NULL. */
178185
typed_value(T* store_to)
179186
: m_store_to(store_to), m_composing(false),
180-
m_multitoken(false), m_zero_tokens(false)
187+
m_multitoken(false), m_zero_tokens(false),
188+
m_required(false)
181189
{}
182190

183191
/** Specifies default value, which will be used
@@ -266,6 +274,12 @@ namespace boost { namespace program_options {
266274
return this;
267275
}
268276

277+
/** Specifies that the value must occur. */
278+
typed_value* required()
279+
{
280+
m_required = true;
281+
return this;
282+
}
269283

270284
public: // value semantic overrides
271285

@@ -292,6 +306,7 @@ namespace boost { namespace program_options {
292306
}
293307
}
294308

309+
bool is_required() const { return m_required; }
295310

296311
/** Creates an instance of the 'validator' class and calls
297312
its operator() to perform the actual conversion. */
@@ -335,7 +350,7 @@ namespace boost { namespace program_options {
335350
std::string m_default_value_as_text;
336351
boost::any m_implicit_value;
337352
std::string m_implicit_value_as_text;
338-
bool m_composing, m_implicit, m_multitoken, m_zero_tokens;
353+
bool m_composing, m_implicit, m_multitoken, m_zero_tokens, m_required;
339354
boost::function1<void, const T&> m_notifier;
340355
};
341356

include/boost/program_options/variables_map.hpp

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,8 @@ namespace boost { namespace program_options {
9292
friend BOOST_PROGRAM_OPTIONS_DECL
9393
void store(const basic_parsed_options<char>& options,
9494
variables_map& m, bool);
95-
friend BOOST_PROGRAM_OPTIONS_DECL void notify(variables_map& m);
95+
96+
friend BOOST_PROGRAM_OPTIONS_DECL class variables_map;
9697
};
9798

9899
/** Implements string->string mapping with convenient value casting
@@ -147,6 +148,8 @@ namespace boost { namespace program_options {
147148
// Resolve conflict between inherited operators.
148149
const variable_value& operator[](const std::string& name) const
149150
{ return abstract_variables_map::operator[](name); }
151+
152+
void notify();
150153

151154
private:
152155
/** Implementation of abstract_variables_map::get
@@ -161,6 +164,10 @@ namespace boost { namespace program_options {
161164
void store(const basic_parsed_options<char>& options,
162165
variables_map& xm,
163166
bool utf8);
167+
168+
/** Names of required options, filled by parser which has
169+
access to options_description. */
170+
std::set<std::string> m_required;
164171
};
165172

166173

src/value_semantic.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,5 +325,11 @@ namespace boost { namespace program_options {
325325
}
326326
return m_message.c_str();
327327
}
328+
329+
const std::string&
330+
required_option::get_option_name() const throw()
331+
{
332+
return m_option_name;
333+
}
328334

329335
}}

src/variables_map.cpp

Lines changed: 44 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ namespace boost { namespace program_options {
105105

106106

107107

108-
// Second, apply default values.
108+
// Second, apply default values and store required options.
109109
const vector<shared_ptr<option_description> >& all = desc.options();
110110
for(i = 0; i < all.size(); ++i)
111111
{
@@ -127,7 +127,12 @@ namespace boost { namespace program_options {
127127
m[key] = variable_value(def, true);
128128
m[key].m_value_semantic = d.semantic();
129129
}
130-
}
130+
}
131+
132+
// add empty value if this is an required option
133+
if (d.semantic()->is_required()) {
134+
xm.m_required.insert(key);
135+
}
131136
}
132137
}
133138

@@ -140,22 +145,7 @@ namespace boost { namespace program_options {
140145
BOOST_PROGRAM_OPTIONS_DECL
141146
void notify(variables_map& vm)
142147
{
143-
// Lastly, run notify actions.
144-
for (map<string, variable_value>::iterator k = vm.begin();
145-
k != vm.end();
146-
++k)
147-
{
148-
/* Users might wish to use variables_map to store their own values
149-
that are not parsed, and therefore will not have value_semantics
150-
defined. Do no crash on such values. In multi-module programs,
151-
one module might add custom values, and the 'notify' function
152-
will be called after that, so we check that value_sematics is
153-
not NULL. See:
154-
https://svn.boost.org/trac/boost/ticket/2782
155-
*/
156-
if (k->second.m_value_semantic)
157-
k->second.m_value_semantic->notify(k->second.value());
158-
}
148+
vm.notify();
159149
}
160150

161151
abstract_variables_map::abstract_variables_map()
@@ -206,4 +196,40 @@ namespace boost { namespace program_options {
206196
else
207197
return i->second;
208198
}
199+
200+
void
201+
variables_map::notify()
202+
{
203+
// This checks if all required options occur
204+
for (set<string>::const_iterator r = m_required.begin();
205+
r != m_required.end();
206+
++r)
207+
{
208+
const string& opt = *r;
209+
map<string, variable_value>::const_iterator iter = find(opt);
210+
if (iter == end() || iter->second.empty())
211+
{
212+
boost::throw_exception(required_option(opt));
213+
214+
}
215+
}
216+
217+
// Lastly, run notify actions.
218+
for (map<string, variable_value>::iterator k = begin();
219+
k != end();
220+
++k)
221+
{
222+
/* Users might wish to use variables_map to store their own values
223+
that are not parsed, and therefore will not have value_semantics
224+
defined. Do no crash on such values. In multi-module programs,
225+
one module might add custom values, and the 'notify' function
226+
will be called after that, so we check that value_sematics is
227+
not NULL. See:
228+
https://svn.boost.org/trac/boost/ticket/2782
229+
*/
230+
if (k->second.m_value_semantic)
231+
k->second.m_value_semantic->notify(k->second.value());
232+
}
233+
}
234+
209235
}}

test/Jamfile.v2

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ test-suite program_options :
3030
[ po-test exception_test.cpp ]
3131
[ po-test split_test.cpp ]
3232
[ po-test unrecognized_test.cpp ]
33+
[ po-test required_test.cpp ]
3334
;
3435

3536
exe test_convert : test_convert.cpp ;

test/required_test.cfg

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
cfgfile = file.cfg

test/required_test.cpp

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
// Copyright Sascha Ochsenknecht 2009.
2+
// Distributed under the Boost Software License, Version 1.0.
3+
// (See accompanying file LICENSE_1_0.txt
4+
// or copy at http://www.boost.org/LICENSE_1_0.txt)
5+
6+
#include <boost/program_options.hpp>
7+
using namespace boost::program_options;
8+
9+
#include <string>
10+
#include <iostream>
11+
#include <fstream>
12+
using namespace std;
13+
14+
#include "minitest.hpp"
15+
16+
17+
void required_throw_test()
18+
{
19+
options_description opts;
20+
opts.add_options()
21+
("cfgfile,c", value<string>()->required(), "the configfile")
22+
("fritz,f", value<string>()->required(), "the output file")
23+
;
24+
25+
variables_map vm;
26+
bool throwed = false;
27+
{
28+
// This test must throw exception
29+
string cmdline = "prg -f file.txt";
30+
vector< string > tokens = split_unix(cmdline);
31+
throwed = false;
32+
try {
33+
store(command_line_parser(tokens).options(opts).run(), vm);
34+
notify(vm);
35+
}
36+
catch (required_option& e) {
37+
throwed = true;
38+
}
39+
BOOST_CHECK(throwed);
40+
}
41+
42+
{
43+
// This test mustn't throw exception
44+
string cmdline = "prg -c config.txt";
45+
vector< string > tokens = split_unix(cmdline);
46+
throwed = false;
47+
try {
48+
store(command_line_parser(tokens).options(opts).run(), vm);
49+
notify(vm);
50+
}
51+
catch (required_option& e) {
52+
throwed = true;
53+
}
54+
BOOST_CHECK(!throwed);
55+
}
56+
}
57+
58+
59+
60+
void simple_required_test()
61+
{
62+
options_description opts;
63+
opts.add_options()
64+
("cfgfile,c", value<string>()->required(), "the configfile")
65+
("fritz,f", value<string>()->required(), "the output file")
66+
;
67+
68+
variables_map vm;
69+
bool throwed = false;
70+
{
71+
// This test must throw exception
72+
string cmdline = "prg -f file.txt";
73+
vector< string > tokens = split_unix(cmdline);
74+
throwed = false;
75+
try {
76+
// options coming from different sources
77+
store(command_line_parser(tokens).options(opts).run(), vm);
78+
store(parse_config_file<char>("required_test.cfg", opts), vm);
79+
notify(vm);
80+
}
81+
catch (required_option& e) {
82+
throwed = true;
83+
}
84+
BOOST_CHECK(!throwed);
85+
}
86+
}
87+
88+
89+
90+
int main(int /*argc*/, char** /*argv*/)
91+
{
92+
required_throw_test();
93+
simple_required_test();
94+
95+
return 0;
96+
}
97+

0 commit comments

Comments
 (0)