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 #include <boost/test/utils/setcolor.hpp>
28 
29 // Boost
30 #include <boost/function.hpp>
31 #include <boost/algorithm/cxx11/all_of.hpp>
32 
33 // STL
34 #include <algorithm>
35 
36 #include <boost/test/detail/suppress_warnings.hpp>
37 
38 namespace boost {
39 namespace runtime {
40 
41 inline
commandline_pretty_print(std::ostream & ostr,std::string const & prefix,std::string const & to_print)42 std::ostream& commandline_pretty_print(
43     std::ostream& ostr,
44     std::string const& prefix,
45     std::string const& to_print) {
46 
47     const int split_at = 80;
48 
49     std::string::size_type current = 0;
50 
51     while(current < to_print.size()) {
52 
53         // discards spaces at the beginning
54         std::string::size_type startpos = to_print.find_first_not_of(" \t\n", current);
55         current += startpos - current;
56 
57         bool has_more_lines = (current + split_at) < to_print.size();
58 
59         if(has_more_lines) {
60           std::string::size_type endpos = to_print.find_last_of(" \t\n", current + split_at);
61           std::string sub(to_print.substr(current, endpos - current));
62           ostr << prefix << sub;
63           ostr << "\n";
64           current += endpos - current;
65         }
66         else
67         {
68           ostr << prefix << to_print.substr(current, split_at);
69           current += split_at;
70         }
71     }
72     return ostr;
73 }
74 
75 // ************************************************************************** //
76 // **************           runtime::parameter_cla_id          ************** //
77 // ************************************************************************** //
78 // set of attributes identifying the parameter in the command line
79 
80 struct parameter_cla_id {
parameter_cla_idboost::runtime::parameter_cla_id81     parameter_cla_id( cstring prefix, cstring tag, cstring value_separator, bool negatable )
82     : m_prefix( prefix.begin(), prefix.size() )
83     , m_tag( tag.begin(), tag.size() )
84     , m_value_separator( value_separator.begin(), value_separator.size() )
85     , m_negatable( negatable )
86     {
87 
88         BOOST_TEST_I_ASSRT( algorithm::all_of( m_prefix.begin(), m_prefix.end(), valid_prefix_char ),
89                             invalid_cla_id() << "Parameter " << m_tag
90                                              << " has invalid characters in prefix." );
91 
92         BOOST_TEST_I_ASSRT( algorithm::all_of( m_tag.begin(), m_tag.end(), valid_name_char ),
93                             invalid_cla_id() << "Parameter " << m_tag
94                                              << " has invalid characters in name." );
95 
96         BOOST_TEST_I_ASSRT( algorithm::all_of( m_value_separator.begin(), m_value_separator.end(), valid_separator_char ),
97                             invalid_cla_id() << "Parameter " << m_tag
98                                              << " has invalid characters in value separator." );
99     }
100 
valid_prefix_charboost::runtime::parameter_cla_id101     static bool             valid_prefix_char( char c )
102     {
103         return c == '-' || c == '/' ;
104     }
valid_separator_charboost::runtime::parameter_cla_id105     static bool             valid_separator_char( char c )
106     {
107         return c == '=' || c == ':' || c == ' ' || c == '\0';
108     }
valid_name_charboost::runtime::parameter_cla_id109     static bool             valid_name_char( char c )
110     {
111         return std::isalnum( c ) || c == '+' || c == '_' || c == '?';
112     }
113 
114     std::string             m_prefix;
115     std::string             m_tag;
116     std::string             m_value_separator;
117     bool                    m_negatable;
118 };
119 
120 typedef std::vector<parameter_cla_id> param_cla_ids;
121 
122 // ************************************************************************** //
123 // **************             runtime::basic_param             ************** //
124 // ************************************************************************** //
125 
126 cstring const help_prefix("////");
127 
128 class basic_param {
129     typedef function<void (cstring)>            callback_type;
130     typedef unit_test::readwrite_property<bool> bool_property;
131 
132 protected:
133     /// Constructor with modifiers
134     template<typename Modifiers>
basic_param(cstring name,bool is_optional,bool is_repeatable,Modifiers const & m)135     basic_param( cstring name, bool is_optional, bool is_repeatable, Modifiers const& m )
136     : p_name( name.begin(), name.end() )
137     , p_description( nfp::opt_get( m, description, std::string() ) )
138     , p_help( nfp::opt_get( m, runtime::help, std::string() ) )
139     , p_env_var( nfp::opt_get( m, env_var, std::string() ) )
140     , p_value_hint( nfp::opt_get( m, value_hint, std::string() ) )
141     , p_optional( is_optional )
142     , p_repeatable( is_repeatable )
143     , p_has_optional_value( m.has( optional_value ) )
144     , p_has_default_value( m.has( default_value ) || is_repeatable )
145     , p_callback( nfp::opt_get( m, callback, callback_type() ) )
146     {
147         add_cla_id( help_prefix, name, ":" );
148     }
149 
150 public:
~basic_param()151     virtual                 ~basic_param() {}
152 
153     // Pubic properties
154     std::string const       p_name;
155     std::string const       p_description;
156     std::string const       p_help;
157     std::string const       p_env_var;
158     std::string const       p_value_hint;
159     bool const              p_optional;
160     bool const              p_repeatable;
161     bool_property           p_has_optional_value;
162     bool_property           p_has_default_value;
163     callback_type const     p_callback;
164 
165     /// interface for cloning typed parameters
166     virtual basic_param_ptr clone() const = 0;
167 
168     /// Access methods
cla_ids() const169     param_cla_ids const&    cla_ids() const { return m_cla_ids; }
add_cla_id(cstring prefix,cstring tag,cstring value_separator)170     void                    add_cla_id( cstring prefix, cstring tag, cstring value_separator )
171     {
172         add_cla_id_impl( prefix, tag, value_separator, false, true );
173     }
174 
175     /// interface for producing argument values for this parameter
176     virtual void            produce_argument( cstring token, bool negative_form, arguments_store& store ) const = 0;
177     virtual void            produce_default( arguments_store& store ) const = 0;
178 
179     /// interfaces for help message reporting
usage(std::ostream & ostr,cstring negation_prefix_,bool use_color=true)180     virtual void            usage( std::ostream& ostr, cstring negation_prefix_, bool use_color = true )
181     {
182         namespace utils = unit_test::utils;
183         namespace ut_detail = unit_test::ut_detail;
184 
185         //
186         ostr  << "  ";
187         {
188 
189           BOOST_TEST_SCOPE_SETCOLOR( use_color, ostr, term_attr::BRIGHT, term_color::GREEN );
190           ostr << p_name;
191         }
192 
193         ostr << '\n';
194 
195         if( !p_description.empty() ) {
196           commandline_pretty_print(ostr, "    ", p_description) << '\n';
197         }
198 
199         BOOST_TEST_FOREACH( parameter_cla_id const&, id, cla_ids() ) {
200             if( id.m_prefix == help_prefix )
201                 continue;
202 
203             ostr << "    " << id.m_prefix;
204 
205             if( id.m_negatable )
206                 cla_name_help( ostr, id.m_tag, negation_prefix_, use_color );
207             else
208                 cla_name_help( ostr, id.m_tag, "", use_color );
209 
210             BOOST_TEST_SCOPE_SETCOLOR( use_color, ostr, term_attr::BRIGHT, term_color::YELLOW );
211             bool optional_value_ = false;
212 
213             if( p_has_optional_value ) {
214                 optional_value_ = true;
215                 ostr << '[';
216             }
217 
218 
219             if( id.m_value_separator.empty() )
220                 ostr << ' ';
221             else {
222                 ostr << id.m_value_separator;
223             }
224 
225             value_help( ostr );
226 
227             if( optional_value_ )
228                 ostr << ']';
229 
230             ostr << '\n';
231         }
232     }
233 
help(std::ostream & ostr,cstring negation_prefix_,bool use_color=true)234     virtual void            help( std::ostream& ostr, cstring negation_prefix_, bool use_color = true )
235     {
236         usage( ostr, negation_prefix_, use_color );
237 
238         if( !p_help.empty() ) {
239             ostr << '\n';
240             commandline_pretty_print(ostr, "  ", p_help);
241         }
242     }
243 
244 protected:
add_cla_id_impl(cstring prefix,cstring tag,cstring value_separator,bool negatable,bool validate_value_separator)245     void                    add_cla_id_impl( cstring prefix,
246                                              cstring tag,
247                                              cstring value_separator,
248                                              bool negatable,
249                                              bool validate_value_separator )
250     {
251         BOOST_TEST_I_ASSRT( !tag.is_empty(),
252                             invalid_cla_id() << "Parameter can't have an empty name." );
253 
254         BOOST_TEST_I_ASSRT( !prefix.is_empty(),
255                             invalid_cla_id() << "Parameter " << tag
256                                              << " can't have an empty prefix." );
257 
258         BOOST_TEST_I_ASSRT( !value_separator.is_empty(),
259                             invalid_cla_id() << "Parameter " << tag
260                                              << " can't have an empty value separator." );
261 
262         // We trim value separator from all the spaces, so token end will indicate separator
263         value_separator.trim();
264         BOOST_TEST_I_ASSRT( !validate_value_separator || !value_separator.is_empty() || !p_has_optional_value,
265                             invalid_cla_id() << "Parameter " << tag
266                                              << " with optional value attribute can't use space as value separator." );
267 
268         m_cla_ids.push_back( parameter_cla_id( prefix, tag, value_separator, negatable ) );
269     }
270 
271 private:
272     /// interface for usage/help customization
cla_name_help(std::ostream & ostr,cstring cla_tag,cstring,bool=true) const273     virtual void            cla_name_help( std::ostream& ostr, cstring cla_tag, cstring /*negation_prefix_*/, bool /*use_color*/ = true) const
274     {
275         ostr << cla_tag;
276     }
value_help(std::ostream & ostr) const277     virtual void            value_help( std::ostream& ostr ) const
278     {
279         if( p_value_hint.empty() )
280             ostr << "<value>";
281         else
282             ostr << p_value_hint;
283     }
284 
285     // Data members
286     param_cla_ids       m_cla_ids;
287 };
288 
289 // ************************************************************************** //
290 // **************              runtime::parameter              ************** //
291 // ************************************************************************** //
292 
293 enum args_amount {
294     OPTIONAL_PARAM,   // 0-1
295     REQUIRED_PARAM,   // exactly 1
296     REPEATABLE_PARAM  // 0-N
297 };
298 
299 //____________________________________________________________________________//
300 
301 template<typename ValueType, args_amount a = runtime::OPTIONAL_PARAM, bool is_enum = false>
302 class parameter : public basic_param {
303 public:
304     /// Constructor with modifiers
305 #ifndef BOOST_NO_CXX11_FUNCTION_TEMPLATE_DEFAULT_ARGS
306     template<typename Modifiers=nfp::no_params_type>
parameter(cstring name,Modifiers const & m=nfp::no_params)307     parameter( cstring name, Modifiers const& m = nfp::no_params )
308 #else
309     template<typename Modifiers>
310     parameter( cstring name, Modifiers const& m )
311 #endif
312     : basic_param( name, a != runtime::REQUIRED_PARAM, a == runtime::REPEATABLE_PARAM, m )
313     , m_arg_factory( m )
314     {
315         BOOST_TEST_I_ASSRT( !m.has( default_value ) || a == runtime::OPTIONAL_PARAM,
316                             invalid_param_spec() << "Parameter " << name
317                                                  << " is not optional and can't have default_value." );
318 
319         BOOST_TEST_I_ASSRT( !m.has( optional_value ) || !this->p_repeatable,
320                             invalid_param_spec() << "Parameter " << name
321                                                  << " is repeatable and can't have optional_value." );
322     }
323 
324 private:
clone() const325     basic_param_ptr clone() const BOOST_OVERRIDE
326     {
327         return basic_param_ptr( new parameter( *this ) );
328     }
produce_argument(cstring token,bool,arguments_store & store) const329     void    produce_argument( cstring token, bool , arguments_store& store ) const BOOST_OVERRIDE
330     {
331         m_arg_factory.produce_argument( token, this->p_name, store );
332     }
produce_default(arguments_store & store) const333     void    produce_default( arguments_store& store ) const BOOST_OVERRIDE
334     {
335         if( !this->p_has_default_value )
336             return;
337 
338         m_arg_factory.produce_default( this->p_name, store );
339     }
340 
341     // Data members
342     typedef argument_factory<ValueType, is_enum, a == runtime::REPEATABLE_PARAM> factory_t;
343     factory_t       m_arg_factory;
344 };
345 
346 //____________________________________________________________________________//
347 
348 class option : public basic_param {
349 public:
350     /// Constructor with modifiers
351 #ifndef BOOST_NO_CXX11_FUNCTION_TEMPLATE_DEFAULT_ARGS
352     template<typename Modifiers=nfp::no_params_type>
option(cstring name,Modifiers const & m=nfp::no_params)353     option( cstring name, Modifiers const& m = nfp::no_params )
354 #else
355     template<typename Modifiers>
356     option( cstring name, Modifiers const& m )
357 #endif
358     : basic_param( name, true, false, nfp::opt_append( nfp::opt_append( m, optional_value = true), default_value = false) )
359     , m_arg_factory( nfp::opt_append( nfp::opt_append( m, optional_value = true), default_value = false) )
360     {
361     }
362 
add_cla_id(cstring prefix,cstring tag,cstring value_separator,bool negatable=false)363     void            add_cla_id( cstring prefix, cstring tag, cstring value_separator, bool negatable = false )
364     {
365         add_cla_id_impl( prefix, tag, value_separator, negatable, false );
366     }
367 
368 private:
clone() const369     basic_param_ptr clone() const BOOST_OVERRIDE
370     {
371         return basic_param_ptr( new option( *this ) );
372     }
373 
produce_argument(cstring token,bool negative_form,arguments_store & store) const374     void    produce_argument( cstring token, bool negative_form, arguments_store& store ) const BOOST_OVERRIDE
375     {
376         if( token.empty() )
377             store.set( p_name, !negative_form );
378         else {
379             BOOST_TEST_I_ASSRT( !negative_form,
380                                 format_error( p_name ) << "Can't set value to negative form of the argument." );
381 
382             m_arg_factory.produce_argument( token, p_name, store );
383         }
384     }
385 
produce_default(arguments_store & store) const386     void    produce_default( arguments_store& store ) const BOOST_OVERRIDE
387     {
388         m_arg_factory.produce_default( p_name, store );
389     }
cla_name_help(std::ostream & ostr,cstring cla_tag,cstring negation_prefix_,bool use_color=true) const390     void    cla_name_help( std::ostream& ostr, cstring cla_tag, cstring negation_prefix_, bool use_color = true ) const BOOST_OVERRIDE
391     {
392         namespace utils = unit_test::utils;
393         namespace ut_detail = unit_test::ut_detail;
394 
395         if( !negation_prefix_.is_empty() ) {
396             BOOST_TEST_SCOPE_SETCOLOR( use_color, ostr, term_attr::BRIGHT, term_color::YELLOW );
397             ostr << '[' << negation_prefix_ << ']';
398         }
399         ostr << cla_tag;
400     }
value_help(std::ostream & ostr) const401     void    value_help( std::ostream& ostr ) const BOOST_OVERRIDE
402     {
403         if( p_value_hint.empty() )
404             ostr << "<boolean value>";
405         else
406             ostr << p_value_hint;
407     }
408 
409     // Data members
410     typedef argument_factory<bool, false, false> factory_t;
411     factory_t       m_arg_factory;
412 };
413 
414 //____________________________________________________________________________//
415 
416 template<typename EnumType, args_amount a = runtime::OPTIONAL_PARAM>
417 class enum_parameter : public parameter<EnumType, a, true> {
418     typedef parameter<EnumType, a, true> base;
419 public:
420     /// Constructor with modifiers
421 #ifndef BOOST_NO_CXX11_FUNCTION_TEMPLATE_DEFAULT_ARGS
422     template<typename Modifiers=nfp::no_params_type>
enum_parameter(cstring name,Modifiers const & m=nfp::no_params)423     enum_parameter( cstring name, Modifiers const& m = nfp::no_params )
424 #else
425     template<typename Modifiers>
426     enum_parameter( cstring name, Modifiers const& m )
427 #endif
428     : base( name, m )
429     {
430 #ifdef BOOST_TEST_CLA_NEW_API
431         auto const& values = m[enum_values<EnumType>::value];
432         auto it = values.begin();
433 #else
434         std::vector<std::pair<cstring, EnumType> > const& values = m[enum_values<EnumType>::value];
435         typename std::vector<std::pair<cstring, EnumType> >::const_iterator it = values.begin();
436 #endif
437         while( it != values.end() ) {
438             m_valid_names.push_back( it->first );
439             ++it;
440         }
441     }
442 
443 private:
clone() const444     basic_param_ptr clone() const BOOST_OVERRIDE
445     {
446         return basic_param_ptr( new enum_parameter( *this ) );
447     }
448 
value_help(std::ostream & ostr) const449     void    value_help( std::ostream& ostr ) const BOOST_OVERRIDE
450     {
451         if( this->p_value_hint.empty() ) {
452             ostr << "<";
453             bool first = true;
454             BOOST_TEST_FOREACH( cstring, name, m_valid_names ) {
455                 if( first )
456                     first = false;
457                 else
458                     ostr << '|';
459                 ostr << name;
460             }
461             ostr << ">";
462         }
463         else
464             ostr << this->p_value_hint;
465     }
466 
467     // Data members
468     std::vector<cstring>    m_valid_names;
469 };
470 
471 
472 // ************************************************************************** //
473 // **************           runtime::parameters_store          ************** //
474 // ************************************************************************** //
475 
476 class parameters_store {
477     struct lg_compare {
operator ()boost::runtime::parameters_store::lg_compare478         bool operator()( cstring lh, cstring rh ) const
479         {
480             return std::lexicographical_compare(lh.begin(), lh.end(),
481                                                 rh.begin(), rh.end());
482         }
483     };
484 public:
485 
486     typedef std::map<cstring, basic_param_ptr, lg_compare> storage_type;
487 
488     /// Adds parameter into the persistent store
add(basic_param const & in)489     void                    add( basic_param const& in )
490     {
491         basic_param_ptr p = in.clone();
492 
493         BOOST_TEST_I_ASSRT( m_parameters.insert( std::make_pair( cstring(p->p_name), p ) ).second,
494                             duplicate_param() << "Parameter " << p->p_name << " is duplicate." );
495     }
496 
497     /// Returns true if there is no parameters registered
is_empty() const498     bool                    is_empty() const    { return m_parameters.empty(); }
499     /// Returns map of all the registered parameter
all() const500     storage_type const&     all() const         { return m_parameters; }
501     /// Returns true if parameter with specified name is registered
has(cstring name) const502     bool                    has( cstring name ) const
503     {
504         return m_parameters.find( name ) != m_parameters.end();
505     }
506     /// Returns map of all the registered parameter
get(cstring name) const507     basic_param_ptr         get( cstring name ) const
508     {
509         storage_type::const_iterator const& found = m_parameters.find( name );
510         BOOST_TEST_I_ASSRT( found != m_parameters.end(),
511                             unknown_param() << "Parameter " << name << " is unknown." );
512 
513         return found->second;
514     }
515 
516 private:
517     // Data members
518     storage_type            m_parameters;
519 };
520 
521 } // namespace runtime
522 } // namespace boost
523 
524 #include <boost/test/detail/enable_warnings.hpp>
525 
526 #endif // BOOST_TEST_UTILS_RUNTIME_PARAMETER_HPP
527