|
Boost : |
From: Daryle Walker (dwalker07_at_[hidden])
Date: 2003-05-29 14:04:42
[I wrote this half-asleep a few days ago. You could add things like
signals/slots, better list management, etc. Take this suggestion as a
very general guideline, not as actual code to use.]
I haven't looked at the command-line/config library under review right
now. I have seen recent discussions over its design, though. Here's
my idea for such a library; maybe it'll inspire somebody:
[NOTE: severe rambling ahead]
1. Let's start with a container for the actual command line arguments:
template < typename Ch = char, class Tr = std::char_traits<Ch> >
struct arguments
{
typedef std::basic_string<Ch, Tr> string_type;
std::vector<string_type> arg_;
bool has_trigger_;
};
The "arg_" member holds the actual arguments. The "has_trigger_"
member indicates if the program name (or equivalent) is present as the
first element of "arg_". The trigger would be present in most command
line systems and in subcommands (e.g. CVS) but not in configuration
files.
The programmer has to write code to convert his/her configuration data
to this format. We could provide standard functions to do this for
regular command lines, lame command lines that are fused together,
segments from previous command line parsing, standard configuration
file formats, operating system registries, etc.
2. The programmer has some sort of object that will distribute the
final configuration data. The meat of the program will read that
object's data and do its thing. We really don't care about this
object's type or its interface.
3. The configuration library just takes the data from [1] and loads the
converted data to [2]. We can have a set of rules for conversions.
The meat of the library is these rules. There is also the connecting
object that puts all the other parts together.
4. The rules could use a virtual interface:
template
<
typename Configurator,
typename Ch = char,
class Tr = std::char_traits<Ch>
>
class command_rule
{
public:
// We hold pointers, so ensure proper destruction
virtual ~cmd_rule() {}
// The handler, h, calls this w/ index of first unclaimed arg
virtual bool process_arg( int index, command_handler &h ) = 0;
// If processing succeeded, then rule can change configuration
// and claim all the arguments it used
virtual void finalize( int index, Configurator &c ) = 0;
};
When a rule is called for processing, it asks the handler to look at
the arguments and comes with an appropriate response, if any. If the
rule wants to do work, it returns TRUE from "process_arg" then the
handler calls it again with "finalize," where the rule should make the
appropriate changes to the configuration object and claim the arguments
it used.
The handler has this interface:
template
<
typename Configurator,
typename Ch = char,
class Tr = std::char_traits<Ch>
>
class command_handler
{
public:
// Call these methods to set everything up
// (Rules can call these too to change config on the fly)
// Arguments are copied, rules and configs are referenced
// by pointer, so keep their lifetimes up.
// For first run, handler initializes its argument
// list with copy of "a". Later calls append the
// contents of "a" to the current argument list.
// (The trigger attribute is ignored except for
// the first set of arguments added.) Each added
// argument gets a claim count attached to it,
// starting at zero.
void add_arguments( arguments<Ch, Tr> const &a );
// Rules added with this method get called if the
// first argument is marked as a trigger.
void add_trigger_rule( command_rule<Configurator, Ch, Tr> &r );
// Adds rule to the normal rule list.
void add_rule( command_rule<Configurator, Ch, Tr> &r );
// Determines the (at most 1) rule to be called if all
// the other rules rejected the argument. Every call
// to this method replaces the previous rule pointer.
void set_default_rule( command_rule<Configurator, Ch, Tr> &r );
// Sets the configuration object to be changed by the rules
// Subsequent calls replace the previous object pointer.
void set_configuration_object( Configurator &c );
// Methods that rules can use
// Gets the configuration object to be changed by the rules
Configurator const & get_configuration_object() const;
// Gets the argument count (valid indices: 0 <= i < arg_count)
int arg_count() const;
// Gets the argument at this index
basic_string<Ch, Tr> get_arg( int index ) const;
// Get the claim count for an argument. The number of
// claims start at zero and goes up whenever a rule
// want to use up an argument.
int get_arg_claim_count( int index ) const;
// Rules call this up add a claim
void claim_arg( int index );
// To support "--"
void clear_normal_rules();
// The actual meat of the program
//1. If the first argument is a trigger, and there
// are trigger rules
// a. process the first argument for each trigger rule
// b. call finalize for each rule that accepted it
// c. mark the first argument as claimed
//2. For every unclaimed rule (from lowest to highest)
// a. call the current argument with each normal rule
// b. but stop at the first rule to return TRUE
// c. if no normal rules accept the argument, then
// use the default rule
// d. if the argument if accepted, then call finalize
// e. mark the argument as claimed if not already done
// (it's possible that not even the default rule took it)
void parse();
};
The programmer would create the argument list, the rule objects, and
the configuration object. Then the programmer will load those objects
up to the handler, then call "parse". Afterwards, use the
configuration object as needed. (That object should have a flag
indicating if it didn't get enough information.)
Daryle
Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk