1 | // (C) Copyright Gennadiy Rozental 2001. |
2 | // Distributed under the Boost Software License, Version 1.0. |
3 | // (See accompanying file LICENSE_1_0.txt or copy at |
4 | // http://www.boost.org/LICENSE_1_0.txt) |
5 | |
6 | // See http://www.boost.org/libs/test for the library home page. |
7 | // |
8 | // File : $RCSfile$ |
9 | // |
10 | // Version : $Revision$ |
11 | // |
12 | // Description : formal parameter definition |
13 | // *************************************************************************** |
14 | |
15 | #ifndef BOOST_TEST_UTILS_RUNTIME_PARAMETER_HPP |
16 | #define BOOST_TEST_UTILS_RUNTIME_PARAMETER_HPP |
17 | |
18 | // Boost.Test Runtime parameters |
19 | #include <boost/test/utils/runtime/fwd.hpp> |
20 | #include <boost/test/utils/runtime/modifier.hpp> |
21 | #include <boost/test/utils/runtime/argument.hpp> |
22 | #include <boost/test/utils/runtime/argument_factory.hpp> |
23 | |
24 | // Boost.Test |
25 | #include <boost/test/utils/class_properties.hpp> |
26 | #include <boost/test/utils/foreach.hpp> |
27 | |
28 | // Boost |
29 | #include <boost/function/function2.hpp> |
30 | #include <boost/algorithm/cxx11/all_of.hpp> |
31 | |
32 | // STL |
33 | #include <algorithm> |
34 | |
35 | #include <boost/test/detail/suppress_warnings.hpp> |
36 | |
37 | namespace boost { |
38 | namespace runtime { |
39 | |
40 | // ************************************************************************** // |
41 | // ************** runtime::parameter_cla_id ************** // |
42 | // ************************************************************************** // |
43 | // set of attributes identifying the parameter in the command line |
44 | |
45 | struct parameter_cla_id { |
46 | parameter_cla_id( cstring prefix, cstring tag, cstring value_separator, bool negatable ) |
47 | : m_prefix( prefix.begin(), prefix.size() ) |
48 | , m_tag( tag.begin(), tag.size() ) |
49 | , m_value_separator( value_separator.begin(), value_separator.size() ) |
50 | , m_negatable( negatable ) |
51 | { |
52 | |
53 | BOOST_TEST_I_ASSRT( algorithm::all_of( m_prefix.begin(), m_prefix.end(), valid_prefix_char ), |
54 | invalid_cla_id() << "Parameter " << m_tag |
55 | << " has invalid characters in prefix." ); |
56 | |
57 | BOOST_TEST_I_ASSRT( algorithm::all_of( m_tag.begin(), m_tag.end(), valid_name_char ), |
58 | invalid_cla_id() << "Parameter " << m_tag |
59 | << " has invalid characters in name." ); |
60 | |
61 | BOOST_TEST_I_ASSRT( algorithm::all_of( m_value_separator.begin(), m_value_separator.end(), valid_separator_char ), |
62 | invalid_cla_id() << "Parameter " << m_tag |
63 | << " has invalid characters in value separator." ); |
64 | } |
65 | |
66 | static bool valid_prefix_char( char c ) |
67 | { |
68 | return c == '-' || c == '/' ; |
69 | } |
70 | static bool valid_separator_char( char c ) |
71 | { |
72 | return c == '=' || c == ':' || c == ' ' || c == '\0'; |
73 | } |
74 | static bool valid_name_char( char c ) |
75 | { |
76 | return std::isalnum( c ) || c == '+' || c == '_' || c == '?'; |
77 | } |
78 | |
79 | std::string m_prefix; |
80 | std::string m_tag; |
81 | std::string m_value_separator; |
82 | bool m_negatable; |
83 | }; |
84 | |
85 | typedef std::vector<parameter_cla_id> param_cla_ids; |
86 | |
87 | // ************************************************************************** // |
88 | // ************** runtime::basic_param ************** // |
89 | // ************************************************************************** // |
90 | |
91 | cstring const help_prefix("////" ); |
92 | |
93 | class basic_param { |
94 | typedef function<void (cstring)> callback_type; |
95 | typedef unit_test::readwrite_property<bool> bool_property; |
96 | |
97 | protected: |
98 | /// Constructor with modifiers |
99 | template<typename Modifiers> |
100 | basic_param( cstring name, bool is_optional, bool is_repeatable, Modifiers const& m ) |
101 | : p_name( name.begin(), name.end() ) |
102 | , p_description( nfp::opt_get( m, description, std::string() ) ) |
103 | , p_help( nfp::opt_get( m, runtime::help, std::string() ) ) |
104 | , p_env_var( nfp::opt_get( m, env_var, std::string() ) ) |
105 | , p_value_hint( nfp::opt_get( m, value_hint, std::string() ) ) |
106 | , p_optional( is_optional ) |
107 | , p_repeatable( is_repeatable ) |
108 | , p_has_optional_value( m.has( optional_value ) ) |
109 | , p_has_default_value( m.has( default_value ) || is_repeatable ) |
110 | , p_callback( nfp::opt_get( m, callback, callback_type() ) ) |
111 | { |
112 | add_cla_id( prefix: help_prefix, tag: name, value_separator: ":" ); |
113 | } |
114 | |
115 | public: |
116 | virtual ~basic_param() {} |
117 | |
118 | // Pubic properties |
119 | std::string const p_name; |
120 | std::string const p_description; |
121 | std::string const p_help; |
122 | std::string const p_env_var; |
123 | std::string const p_value_hint; |
124 | bool const p_optional; |
125 | bool const p_repeatable; |
126 | bool_property p_has_optional_value; |
127 | bool_property p_has_default_value; |
128 | callback_type const p_callback; |
129 | |
130 | /// interface for cloning typed parameters |
131 | virtual basic_param_ptr clone() const = 0; |
132 | |
133 | /// Access methods |
134 | param_cla_ids const& cla_ids() const { return m_cla_ids; } |
135 | void add_cla_id( cstring prefix, cstring tag, cstring value_separator ) |
136 | { |
137 | add_cla_id_impl( prefix, tag, value_separator, negatable: false, validate_value_separator: true ); |
138 | } |
139 | |
140 | /// interface for producing argument values for this parameter |
141 | virtual void produce_argument( cstring token, bool negative_form, arguments_store& store ) const = 0; |
142 | virtual void produce_default( arguments_store& store ) const = 0; |
143 | |
144 | /// interfaces for help message reporting |
145 | virtual void usage( std::ostream& ostr, cstring negation_prefix_ ) |
146 | { |
147 | ostr << "Parameter: " << p_name << '\n'; |
148 | if( !p_description.empty() ) |
149 | ostr << ' ' << p_description << '\n'; |
150 | |
151 | ostr << " Command line formats:\n" ; |
152 | BOOST_TEST_FOREACH( parameter_cla_id const&, id, cla_ids() ) { |
153 | if( id.m_prefix == help_prefix ) |
154 | continue; |
155 | |
156 | ostr << " " << id.m_prefix; |
157 | if( id.m_negatable ) |
158 | cla_name_help( ostr, cla_tag: id.m_tag, negation_prefix_ ); |
159 | else |
160 | cla_name_help( ostr, cla_tag: id.m_tag, negation_prefix_: "" ); |
161 | |
162 | bool optional_value_ = false; |
163 | |
164 | if( p_has_optional_value ) { |
165 | optional_value_ = true; |
166 | ostr << '['; |
167 | } |
168 | |
169 | if( id.m_value_separator.empty() ) |
170 | ostr << ' '; |
171 | else { |
172 | ostr << id.m_value_separator; |
173 | } |
174 | |
175 | value_help( ostr ); |
176 | |
177 | if( optional_value_ ) |
178 | ostr << ']'; |
179 | |
180 | ostr << '\n'; |
181 | } |
182 | if( !p_env_var.empty() ) |
183 | ostr << " Environment variable: " << p_env_var << '\n'; |
184 | } |
185 | |
186 | virtual void help( std::ostream& ostr, cstring negation_prefix_ ) |
187 | { |
188 | usage( ostr, negation_prefix_ ); |
189 | |
190 | if( !p_help.empty() ) |
191 | ostr << '\n' << p_help << '\n'; |
192 | } |
193 | |
194 | protected: |
195 | void add_cla_id_impl( cstring prefix, |
196 | cstring tag, |
197 | cstring value_separator, |
198 | bool negatable, |
199 | bool validate_value_separator ) |
200 | { |
201 | BOOST_TEST_I_ASSRT( !tag.is_empty(), |
202 | invalid_cla_id() << "Parameter can't have an empty name." ); |
203 | |
204 | BOOST_TEST_I_ASSRT( !prefix.is_empty(), |
205 | invalid_cla_id() << "Parameter " << tag |
206 | << " can't have an empty prefix." ); |
207 | |
208 | BOOST_TEST_I_ASSRT( !value_separator.is_empty(), |
209 | invalid_cla_id() << "Parameter " << tag |
210 | << " can't have an empty value separator." ); |
211 | |
212 | // We trim value separator from all the spaces, so token end will indicate separator |
213 | value_separator.trim(); |
214 | BOOST_TEST_I_ASSRT( !validate_value_separator || !value_separator.is_empty() || !p_has_optional_value, |
215 | invalid_cla_id() << "Parameter " << tag |
216 | << " with optional value attribute can't use space as value separator." ); |
217 | |
218 | m_cla_ids.push_back( x: parameter_cla_id( prefix, tag, value_separator, negatable ) ); |
219 | } |
220 | |
221 | private: |
222 | /// interface for usage/help customization |
223 | virtual void cla_name_help( std::ostream& ostr, cstring cla_tag, cstring negation_prefix_ ) const |
224 | { |
225 | ostr << cla_tag; |
226 | } |
227 | virtual void value_help( std::ostream& ostr ) const |
228 | { |
229 | if( p_value_hint.empty() ) |
230 | ostr << "<value>" ; |
231 | else |
232 | ostr << p_value_hint; |
233 | } |
234 | |
235 | // Data members |
236 | param_cla_ids m_cla_ids; |
237 | }; |
238 | |
239 | // ************************************************************************** // |
240 | // ************** runtime::parameter ************** // |
241 | // ************************************************************************** // |
242 | |
243 | enum args_amount { |
244 | OPTIONAL_PARAM, // 0-1 |
245 | REQUIRED_PARAM, // exactly 1 |
246 | REPEATABLE_PARAM // 0-N |
247 | }; |
248 | |
249 | //____________________________________________________________________________// |
250 | |
251 | template<typename ValueType, args_amount a = runtime::OPTIONAL_PARAM, bool is_enum = false> |
252 | class parameter : public basic_param { |
253 | public: |
254 | /// Constructor with modifiers |
255 | #ifndef BOOST_NO_CXX11_FUNCTION_TEMPLATE_DEFAULT_ARGS |
256 | template<typename Modifiers=nfp::no_params_type> |
257 | parameter( cstring name, Modifiers const& m = nfp::no_params ) |
258 | #else |
259 | template<typename Modifiers> |
260 | parameter( cstring name, Modifiers const& m ) |
261 | #endif |
262 | : basic_param( name, a != runtime::REQUIRED_PARAM, a == runtime::REPEATABLE_PARAM, m ) |
263 | , m_arg_factory( m ) |
264 | { |
265 | BOOST_TEST_I_ASSRT( !m.has( default_value ) || a == runtime::OPTIONAL_PARAM, |
266 | invalid_param_spec() << "Parameter " << name |
267 | << " is not optional and can't have default_value." ); |
268 | |
269 | BOOST_TEST_I_ASSRT( !m.has( optional_value ) || !this->p_repeatable, |
270 | invalid_param_spec() << "Parameter " << name |
271 | << " is repeatable and can't have optional_value." ); |
272 | } |
273 | |
274 | private: |
275 | virtual basic_param_ptr clone() const |
276 | { |
277 | return basic_param_ptr( new parameter( *this ) ); |
278 | } |
279 | virtual void produce_argument( cstring token, bool , arguments_store& store ) const |
280 | { |
281 | m_arg_factory.produce_argument( token, this->p_name, store ); |
282 | } |
283 | virtual void produce_default( arguments_store& store ) const |
284 | { |
285 | if( !this->p_has_default_value ) |
286 | return; |
287 | |
288 | m_arg_factory.produce_default( this->p_name, store ); |
289 | } |
290 | |
291 | // Data members |
292 | typedef argument_factory<ValueType, is_enum, a == runtime::REPEATABLE_PARAM> factory_t; |
293 | factory_t m_arg_factory; |
294 | }; |
295 | |
296 | //____________________________________________________________________________// |
297 | |
298 | class option : public basic_param { |
299 | public: |
300 | /// Constructor with modifiers |
301 | #ifndef BOOST_NO_CXX11_FUNCTION_TEMPLATE_DEFAULT_ARGS |
302 | template<typename Modifiers=nfp::no_params_type> |
303 | option( cstring name, Modifiers const& m = nfp::no_params ) |
304 | #else |
305 | template<typename Modifiers> |
306 | option( cstring name, Modifiers const& m ) |
307 | #endif |
308 | : basic_param( name, true, false, nfp::opt_append( nfp::opt_append( m, optional_value = true), default_value = false) ) |
309 | , m_arg_factory( nfp::opt_append( nfp::opt_append( m, optional_value = true), default_value = false) ) |
310 | { |
311 | } |
312 | |
313 | void add_cla_id( cstring prefix, cstring tag, cstring value_separator, bool negatable = false ) |
314 | { |
315 | add_cla_id_impl( prefix, tag, value_separator, negatable, validate_value_separator: false ); |
316 | } |
317 | |
318 | private: |
319 | virtual basic_param_ptr clone() const |
320 | { |
321 | return basic_param_ptr( new option( *this ) ); |
322 | } |
323 | |
324 | virtual void produce_argument( cstring token, bool negative_form, arguments_store& store ) const |
325 | { |
326 | if( token.empty() ) |
327 | store.set( parameter_name: p_name, value: !negative_form ); |
328 | else { |
329 | BOOST_TEST_I_ASSRT( !negative_form, |
330 | format_error( p_name ) << "Can't set value to negative form of the argument." ); |
331 | |
332 | m_arg_factory.produce_argument( source: token, param_name: p_name, store ); |
333 | } |
334 | } |
335 | |
336 | virtual void produce_default( arguments_store& store ) const |
337 | { |
338 | m_arg_factory.produce_default( param_name: p_name, store ); |
339 | } |
340 | virtual void cla_name_help( std::ostream& ostr, cstring cla_tag, cstring negation_prefix_ ) const |
341 | { |
342 | if( negation_prefix_.is_empty() ) |
343 | ostr << cla_tag; |
344 | else |
345 | ostr << '[' << negation_prefix_ << ']' << cla_tag; |
346 | } |
347 | virtual void value_help( std::ostream& ostr ) const |
348 | { |
349 | if( p_value_hint.empty() ) |
350 | ostr << "<boolean value>" ; |
351 | else |
352 | ostr << p_value_hint; |
353 | } |
354 | |
355 | // Data members |
356 | typedef argument_factory<bool, false, false> factory_t; |
357 | factory_t m_arg_factory; |
358 | }; |
359 | |
360 | //____________________________________________________________________________// |
361 | |
362 | template<typename EnumType, args_amount a = runtime::OPTIONAL_PARAM> |
363 | class enum_parameter : public parameter<EnumType, a, true> { |
364 | typedef parameter<EnumType, a, true> base; |
365 | public: |
366 | /// Constructor with modifiers |
367 | #ifndef BOOST_NO_CXX11_FUNCTION_TEMPLATE_DEFAULT_ARGS |
368 | template<typename Modifiers=nfp::no_params_type> |
369 | enum_parameter( cstring name, Modifiers const& m = nfp::no_params ) |
370 | #else |
371 | template<typename Modifiers> |
372 | enum_parameter( cstring name, Modifiers const& m ) |
373 | #endif |
374 | : base( name, m ) |
375 | { |
376 | #ifdef BOOST_TEST_CLA_NEW_API |
377 | auto const& values = m[enum_values<EnumType>::value]; |
378 | auto it = values.begin(); |
379 | #else |
380 | std::vector<std::pair<cstring, EnumType> > const& values = m[enum_values<EnumType>::value]; |
381 | typename std::vector<std::pair<cstring, EnumType> >::const_iterator it = values.begin(); |
382 | #endif |
383 | while( it != values.end() ) { |
384 | m_valid_names.push_back( it->first ); |
385 | ++it; |
386 | } |
387 | } |
388 | |
389 | private: |
390 | virtual basic_param_ptr clone() const |
391 | { |
392 | return basic_param_ptr( new enum_parameter( *this ) ); |
393 | } |
394 | |
395 | virtual void value_help( std::ostream& ostr ) const |
396 | { |
397 | if( this->p_value_hint.empty() ) { |
398 | ostr << "<" ; |
399 | bool first = true; |
400 | BOOST_TEST_FOREACH( cstring, name, m_valid_names ) { |
401 | if( first ) |
402 | first = false; |
403 | else |
404 | ostr << '|'; |
405 | ostr << name; |
406 | } |
407 | ostr << ">" ; |
408 | } |
409 | else |
410 | ostr << this->p_value_hint; |
411 | } |
412 | |
413 | // Data members |
414 | std::vector<cstring> m_valid_names; |
415 | }; |
416 | |
417 | |
418 | // ************************************************************************** // |
419 | // ************** runtime::parameters_store ************** // |
420 | // ************************************************************************** // |
421 | |
422 | class parameters_store { |
423 | struct lg_compare { |
424 | bool operator()( cstring lh, cstring rh ) const |
425 | { |
426 | return std::lexicographical_compare(first1: lh.begin(), last1: lh.end(), |
427 | first2: rh.begin(), last2: rh.end()); |
428 | } |
429 | }; |
430 | public: |
431 | |
432 | typedef std::map<cstring, basic_param_ptr, lg_compare> storage_type; |
433 | |
434 | /// Adds parameter into the persistent store |
435 | void add( basic_param const& in ) |
436 | { |
437 | basic_param_ptr p = in.clone(); |
438 | |
439 | BOOST_TEST_I_ASSRT( m_parameters.insert( std::make_pair( cstring(p->p_name), p ) ).second, |
440 | duplicate_param() << "Parameter " << p->p_name << " is duplicate." ); |
441 | } |
442 | |
443 | /// Returns true if there is no parameters registered |
444 | bool is_empty() const { return m_parameters.empty(); } |
445 | /// Returns map of all the registered parameter |
446 | storage_type const& all() const { return m_parameters; } |
447 | /// Returns true if parameter with psecified name is registered |
448 | bool has( cstring name ) const |
449 | { |
450 | return m_parameters.find( x: name ) != m_parameters.end(); |
451 | } |
452 | /// Returns map of all the registered parameter |
453 | basic_param_ptr get( cstring name ) const |
454 | { |
455 | storage_type::const_iterator const& found = m_parameters.find( x: name ); |
456 | BOOST_TEST_I_ASSRT( found != m_parameters.end(), |
457 | unknown_param() << "Parameter " << name << " is unknown." ); |
458 | |
459 | return found->second; |
460 | } |
461 | |
462 | private: |
463 | // Data members |
464 | storage_type m_parameters; |
465 | }; |
466 | |
467 | } // namespace runtime |
468 | } // namespace boost |
469 | |
470 | #include <boost/test/detail/enable_warnings.hpp> |
471 | |
472 | #endif // BOOST_TEST_UTILS_RUNTIME_PARAMETER_HPP |
473 | |