|
Boost : |
From: Gennadiy Rozental (gennadiy.rozental_at_[hidden])
Date: 2003-01-15 02:21:26
Hi, Volodya.
About month ago, while working on Boost.Test issues I was faced with the
need for the more or less full featured command line argument parser. I
recall that you were working on one and took a look on some of your
preliminary code in vault area. And ... was really disappointed. I do not
believe that any "solid" design could fit for variety of different
needs/expectations that exist. One big class with many many options/styles
does not look like good design decision. I was not able to look into deep
details with your current submission, but from what I view even if it better
it still has similar design. While we are at it I could make 2 remarks about
your submission: a) None of 5 compiler configurations installed on my XP
could not compile it. b) see separate post regarding you config file design
with is also unsatisfactory IMO.
So I made my move and implemented my own solution, that I will be call:
CLA framework. It's still missing some bits and pieces, particularly docs is
not started yet. But I will present it as an alternative to your submission.
(Though I may not be able to work on this within following month due to
extreme load with other activities). Here is short description of my design.
Rationale.
While working on this design I was trying to concentrate on:
1. Usability
2. Flexibility
3. Usability
4. Flexibility
5. Minimal dependencies (specifically on other boost libs)
6. Clarity/simplicity
All types are kept simple and defined in namespace boost::cla.
Terminology:
Parameter - format parameter defined by programmer for it's program
Argument - actual argument supplied by the end used to the program
Option - boolean parameter
cla::parser
Class cla::parser is core class in a framework. It's definition looks
like this:
template<typename LookupPolicy>
class parser : public LookupPolicy {
typedef typename LookupPolicy::policy_base_parameter
policy_base_parameter;
public:
parser& register_parameter( policy_base_parameter const& parameter );
template<typename Modifier>
parser& accept_modifier( Modifier const& m )
iterator first_argument() const;
iterator last_argument() const;
std::string const& error_msg() const;
bool is_valid() const;
bool parse( int& argc, char** argv );
};
template<typename LookupPolicy, typename Parameter_or_Modifier>
inline parser<LookupPolicy>&
operator<<( parser<LookupPolicy>& p, Parameter_or_Modifier const& rhs );
This is in fact almost hollow class that in most part forward real work to
the policy. It's main purpose to define framework interface and store the
arguments. (What is the deal with modifiers see below). cla::parser design
similar the Pluggable Factory Design pattern and implemented close to my
implementation of the pattern in vault area.
Lookup policy
Lookup policy is in a sense an algorithm for parsing the arguments from
the input provided. Each specific lookup policy could incur what type of the
parameters/modifiers it's supports. Currently framework itself present
following lookup policies;
key_lookup_policy - each parameter is identified by unique key
positional_lookup policy - each parameter is identified by position in
input
dual_lookup_policy - each parameter is identified by dual (most
probably long/short) key
getopt_lookup_policy - getopt style option parsing (with sticky
options)
simple_xml_lookup_policy - as a prove of concept lookup policy that allows
to parse parameters passed in XML format
See below for details how one of them is implemented
Parameters definition.
So, how the programmer will define parameters. Here an example:
cla::parser<cla::key_lookup_policy<> > cla_parser;
cla_parser << cla::named_parameter<>( "arg1" ) << cla::optional
<< cla::handler<std::string>( &arg1_handler )
<< cla::named_parameter<std::list<int> >( "arg2" ) <<
cla::required
<< cla::named_option( "debug" ) << cla::required <<
cla::default_value( false ) << cla::place_to( is_debug );
Parameter definition format is defined by cla::parser class and always looks
like this:
<parser> [<< lookup policy modifiers]
[<< parameter [<< parameter modifiers]]*;
You could define as many parameters as you want. Allowed types of the
parameters are defined by the lookup policy (you will need to see the code
to see how it's working). Any policy may define and support any modifiers
(modifier is similar to iostream manipulator concept). For example
key_lookup_policy supports ignore_mismatch modifier, in which case lookup
algorithm will ignore the errors in input and skip them.
Any parameter type could also define modifiers. Some of the modifiers are
supported by all parameters. For example:
optional,
requires,
description( string )
Note again that any custom policy could define it's own parameters and
modifiers
Argument access.
The framework provide several ways to access the parsed argument values
1. Through first_argument/last_argument interface. It will work if parameter
will create typed formal argument (inherited from typed_argument<T> )
It will look like this:
cla::parser<...>::iterator it = parser.first_argument();
cla::argument* arg = *it
long value = cla::get_value<long>( arg );
2. Most parameter types supports cla::place_to modifier that allows to set
the argument value directly to the variable supplied.
3. Most parameter types supports cla::handler modifier that allows to set
specific handler (any handler with predefined interface) for any parameter
parsing event. That allows to implement event driven parameters parsing.
4. Since parser is publicly inherited from LookupPolicy it could always
extend it's interface. Particularly key_lookup_policy add operator[] access
with key argument. You will need to use cla::get_value then to get the
value.
Public interfaces
Even though I was trying to minimize the dependencies. class framework
may still be a burden to include in many compilation units. In case if one
wants to access cla::parser in different (from where it was created/parsed)
file, lookup policies supports public virtual interface that defined in one
header with no dependencies. So including namely this one header is enough
in most case to get an access to the argument values.
Key lookup policy design
class template key_lookup_policy defined like this:
template<typename KeyPolicy = default_key_policy<> >
class key_lookup_policy : public KeyPolicy, public key_based_interface {
public:
argument const* operator[]( std::string arg_name ) const;
protected:
// lookup policy implementation
typedef named_parameter_base policy_base_parameter;
void register_parameter(
policy_base_parameter const& parameter );
policy_base_parameter const* locate_parameter( raw_argv_traverser& );
// support for modifiers
void accept_modifier( separator );
void accept_modifier( prefix );
void accept_modifier( ignore_mismatch );
void accept_modifier( mismatch_handler );
};
This class is parameterized with Key policy that encapsulate logic with Key
identification, key prefix and key/value separator. This policy allows to
set arbitrary string/char as allowed prefix/separator (or even several of
them), may successfully skip mismatched tokens and more.
Usage/Help
cla::parser also supply usage and help methods. First one supposed to give
usage information, second - detailed information about all parameters. To
define the usage/help format programmer could supply Usage/HelpFormattter
implementation.
Hope this overview gave you some idea how the framework will work.
Any comments appreciated,
Gennadiy.
Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk