From: g0dil Date: Thu, 10 Apr 2008 15:35:57 +0000 (+0000) Subject: Console: More extensible keyword parameter dispatching in arg() attribute X-Git-Url: http://g0dil.de/git?p=senf.git;a=commitdiff_plain;h=f47679431aa3461936ee4a85c0c4216e44292b55 Console: More extensible keyword parameter dispatching in arg() attribute Console: Documentation git-svn-id: https://svn.berlios.de/svnroot/repos/senf/trunk@802 270642c3-0616-0410-b53a-bc976706d245 --- diff --git a/Console/Mainpage.dox b/Console/Mainpage.dox index 9c0ea9a..b63300e 100644 --- a/Console/Mainpage.dox +++ b/Console/Mainpage.dox @@ -42,6 +42,7 @@ To get started using the config/console library, see \li \ref node_tree \li \ref console_parser + \li \ref console_commands \section console_example Example @@ -93,6 +94,294 @@ */ +/** \defgroup console_commands Supported command types + + The Console/config library supports quite a number of different command types. All these types + of command are registered, by passing them to DirectoryNode::add() + + \autotoc + + \section console_manualparse Manually parsing command arguments + + This is the most primitive type of command. It will be called with an output stream and with a + senf::console::ParseCommandInfo reference which holds information about the command parsed. + + From this information the command callback gets a list of arguments or tokens which then can be + interpreted in an arbitrary way. + \code + void test1(std::ostream & os, senf::console::ParseCommandInfo const & command) + { + // We take exactly one argument + if (command.arguments().size() != 1) + raise senf::console::SyntaxErrorException("invalid number of arguments"); + + senf::console::ParseCommandInfo::TokenRange & argTokens ( + command.arguments()[0]); + + // The argument must have exactly one token + if (argTokens.size() != 1) + raise senf::console::SyntaxErrorException("argument syntax error"); + + // Retrieve the token value + std::string arg (argTokens[0].value()); + + // In this example, we just write the argument to the output stream + os << arg << std::endl; + } + \endcode + + Registering this callback is done by simply adding it. To provide online help, pass it to + 'doc()': + \code + senf::console::root() + .add("test1", &test1) + .doc("Usage:\n" + " test1 arg\n" + "\n" + "Echo 'arg' to the console"); + \endcode + + The callback may now be called interactively on the console by it's registered name: + \htmlonly +
+    server:/$ test1
+    invalid number of arguments
+    server:/$ test1 stefan@j32.de
+    stefan@j32.de
+    server:/$ test1 (echo me)
+    argument syntax error
+    server:/$ help test1
+    Usage:
+        test1 arg
+
+    Echo 'arg' to the console
+    server:/$
+    
+ \endhtmlonly + + As you can see above, the arguments and tokens are returned as + boost::iterator_range instances. These behave much like containers: They have \c begin() and + \c end() and some other useful members. + + The parser will have divided the argument tokens into arguments already. This simplifies further + parsing. If however you want to access the list of argument tokens as a single list, you can do + so using senf::console::ParseCommandInfo::tokens(). + + Parsing arguments is quite simple but can get very tedious. To simplify this task, the parsing + can be delegated to the Console/config library. See the next section. + + + \section console_autoparse Automatic argument parsing + + To greatly simplify parsing complex commands, we turn to automatic argument parsing. This + feature allows to register (almost) arbitrary callbacks. + + \code + std::string test2(std::string const & arg) + { + return arg; + } + \endcode + + This extremely simple callback may be registered by adding it to a + senf::console::DirectoryNode. + \code + senf::console::root() + .add("test2", &test2); + \endcode + The functionality is now identical to \c test1: + \htmlonly +
+    server:/$ test2
+    invalid number of arguments
+    server:/$ test2 stefan@j32.de
+    stefan@j32.de
+    server:/$ test2 (echo me)
+    argument syntax error
+    server:/$ help test2
+    Usage:
+        test2 arg11:string
+    server:/$
+    
+ \endhtmlonly + + As we can see, some documentation is automatically provided. To add more info, we need to add + some additional attributes when registering the command: + \code + namespace kw = senf::console::kw; + + senf::console::root() + .add("test2", &test2) + .doc("Echo 'arg' to the console") + .arg( kw::name = "arg", + kw::description = "Message to output" ); + \endcode + + (Sadly, there is no way to automatically find out the \e name of an argument, just it's type.) + Every callback argument corresponds with a call of the \c arg() attribute. Argument attributes + are set using keywords from the \ref senf::console::kw namespace. You will probably wither use + this namespace via a namespace alias (as above) or via a using namespace + senf::console::kw declaration (as in all the following examples) + + You don't need + to specify any information for an argument: To skip an argument, just call \c arg() without + attributes for this argument. + + After adding this information, the online help is much more intelligible + \htmlonly +
+    server:/$ help test2
+    Usage:
+        test2 arg:string
+
+    With:
+        arg       Message to output
+
+    Echo 'arg' to the console
+    server:/$
+    
+ \endhtmlonly + + \subsection command_ostream Accessing the console stream + + Commands may have an optional first argument of type std::ostream &. This argument is + not considered part of the real interface. When the command is executed, the callback will be + passed the current console's output stream object in this argument. With this, the callback can + output arbitrary messages to the network console. See the next section for an example. + + \subsection command_overload Command overloading + + Automatically parsed commands can be overloaded: You can register multiple commands under the + same name. If this happens, each command is tried in turn until no SyntaxErrorException is + raised. + + \code + void test3(std::ostream & os, unsigned n, std::string text) + { + // It's perfectly valid to check additional constraints here and throw a + // SyntaxErrorException. In this case, the next overload will be tried. However, you must + // ensure, That no action takes place before this check ! + if ( n==0 ) throw senf::console::SyntaxErrorException("invalid value for parameter 'n'"); + while (n-- > 0) os << text << std::endl; + } + + using namespace senf::console::kw; + + senf::console::root() + .add("test3", &test3) + .doc("Echo text to the console") + .overloadDoc("Repeat 'text' for 'n' lines") + .arg( name = "n", description = "Number of repetitions" ) + .arg( name = "text", description = "Message to output" ); + senf::console::root() + .add("test3", &test2) + .overloadDoc("Echo the 'text' argument") + .arg( name = "text", description = "Message to output" ); + \endcode + + We can now call \c test2 with one or two arguments: + + \htmlonly +
+    server:/$ test3 "The sky is blue"
+    The sky is blue
+    server:/$ test3 4 ok
+    ok
+    ok
+    ok
+    ok
+    server:/$ help test3
+    Usage:
+        1- test3 n:unsigned text:string
+        2- test3 text:string
+    
+    With:
+        n         Numer of repetitions
+        text      Messsage to output
+
+    Echo text to the console
+
+    Variant 1:
+    Repeat 'text' for 'n' lines
+    
+    Variant 2:
+    Echo the 'text' argument
+    senf:/$    And 
+
+    
+ \endhtmlonly + + \subsection console_defaults Default values + + Another information which can not be automatically gathered from the type system is default + values. These have to be explicitly declared: + \code + using namespace senf::console::kw; + + senf::console::root() + .add("test4", &test2b) + .arg() + .arg( default_value = "ok" ); + \endcode + (Default values must not necessarily be declared in the callback function too.) Of course, + default values can be used together with overloading. + + There must be no argument without default value after an argument with a default value + declared. This fill fail at compile time. + + \subsection console_auto_summary Attribute summary + + Here a summary of all the attributes available for automatically parsed command nodes: + + + + + + + + + +
\c doc ( \e text )Documentation for all overloads
\c overloadDoc ( \e text )Documentation for a specific overliad
\c arg ( \e attributes )Set parameter attributes. All attributes are + optional. The attribute keywords are defined in the \ref senf::console::kw namespace. Valid + Attributes are: + \li \e name: Parameter name + \li \e description: One-line description of the argument + \li \e default_value: Arguments default value
+ + \section console_memberfn Registering member functions + + Member functions are supported like non-member functions. They must however be added through a + senf::console::ScopedDirectory<> instance to bind them to their instance. + \code + class Test + { + public: + ScopedDirectory dir; + + Test(std::string label) : dir(this), label_ (label) { + dir.add("test4", &Test::test2); + dir.add("test4", &Test::test3); + } + + std::string test2(std::string const & text) { return label_ + ": " + text; } + void test3(std::ostream & os, unsigned n, std::string const & text) { + while (n-- > 0) os << label << ": " << text << std::endl; } + + private: + std::string label_; + }; + + // ... + + Test testOb ("test"); + senf::console::root().add("testobj", testOb.dir); + \endcode + + Binding via senf::console::ScopedDirectory ensures, that the commands are automatically removed + from the tree when an object is destroyed. + */ + // Local Variables: // mode: c++ @@ -102,6 +391,6 @@ // indent-tabs-mode: nil // ispell-local-dictionary: "american" // compile-command: "scons -u test" -// mode: flyspell // mode: auto-fill // End: + diff --git a/Console/Node.hh b/Console/Node.hh index 9bf37ec..853cc1b 100644 --- a/Console/Node.hh +++ b/Console/Node.hh @@ -23,7 +23,7 @@ /** \file \brief Node public header */ -/** \defgroup node_tree The console/config file-system node tree +/** \defgroup node_tree The node tree The console/config node tree is the central data-structure of the library. Into this tree, all commands and parameters are entered. The tree is then exposed using a file-system like @@ -117,7 +117,8 @@ \li An arbitrary node can be created and then (possibly later) added to the tree using the corresponding senf::console::DirectoryNode::add() overload. \li A senf::console::CommandNode is normally added to the tree by directly adding a callback - using one of the overloaded senf::console::DirectoryNode::add() members. + using one of the overloaded senf::console::DirectoryNode::add() members. See \ref + console_commands. When directly adding a node callback, the type of node added depends on the type of callback. The callback types which can be added are listed at \ref console_callbacks. @@ -520,6 +521,9 @@ namespace console { To execute a command, CommandNode::operator()() or CommandNode::execute() is called. + Subclass instances of this node type are automatically created when adding commands to the + tree. See \ref console_commands. + \ingroup node_tree */ class CommandNode : public GenericNode @@ -573,9 +577,14 @@ namespace console { /** \brief Most simple CommandNode implementation This CommandNode implementation simply forwards the \a output and \a arguments arguments to - an arbitrary callback. + an arbitrary callback. Thus, it allows to add callbacks with the signature + \code + void callback(std::ostream & os, senf::console::ParseCommandInfo const & command) + { ... } + \endcode + to the tree. - \ingroup node_tree + \ingroup console_commands */ class SimpleCommandNode : public CommandNode { diff --git a/Console/OverloadedCommand.cc b/Console/OverloadedCommand.cc index 47a08dc..921ce8f 100644 --- a/Console/OverloadedCommand.cc +++ b/Console/OverloadedCommand.cc @@ -80,7 +80,9 @@ prefix_ void senf::console::OverloadedCommandNode::v_help(std::ostream & os) Overloads::const_iterator const i_end (overloads_.end()); unsigned index (1); for (; i != i_end; ++i, ++index) { - os << " " << index << "- " << name(); + os << " "; + if (overloads_.size() > 1) os << index << "- "; + os << name(); for (unsigned j (0); j < (*i)->numArguments(); ++j) { ArgumentDoc arg; (*i)->argumentDoc(j, arg); @@ -143,9 +145,12 @@ prefix_ void senf::console::OverloadedCommandNode::v_help(std::ostream & os) unsigned index (1); for (; i != i_end; ++i, ++index) { std::string overloadDoc ((*i)->doc()); - if (! overloadDoc.empty()) - os << "\n" << "Variant " << index << ":\n" - << overloadDoc << "\n"; + if (! overloadDoc.empty()) { + os << "\n"; + if (overloads_.size() > 1) + os << "Variant " << index << ":\n"; + os << overloadDoc << "\n"; + } } } } diff --git a/Console/OverloadedCommand.hh b/Console/OverloadedCommand.hh index e7b5692..1363d52 100644 --- a/Console/OverloadedCommand.hh +++ b/Console/OverloadedCommand.hh @@ -119,13 +119,13 @@ namespace console { cmd.add(senf::console::SimpleCommandOverload::create(&anotherCallback)); \endcode - However, this facility is mostly used not directly but indirectly (and automatically) when + However, this facility is normally used not directly but indirectly (and automatically) when adding argument parsing callbacks. \warning For this to work, the commands must do all syntax checking before doing any operation - \ingroup node_tree + \ingroup console_commands */ class OverloadedCommandNode : public CommandNode diff --git a/Console/Parse.hh b/Console/Parse.hh index 1dc7498..91a1467 100644 --- a/Console/Parse.hh +++ b/Console/Parse.hh @@ -26,7 +26,7 @@ #ifndef HH_Parse_ #define HH_Parse_ 1 -/** \defgroup console_parser The console/config parser +/** \defgroup console_parser The parser The console/config library defines a simple language used to interact with the console or to configure the application. The parser is not concerned about interpreting commands or diff --git a/Console/ParsedCommand.cti b/Console/ParsedCommand.cti index 72f372e..9abe00d 100644 --- a/Console/ParsedCommand.cti +++ b/Console/ParsedCommand.cti @@ -118,63 +118,67 @@ ParsedAttributeAttributorBase(Overload & overload, unsigned index) // senf::console::ParsedAttributeAttributor template -template prefix_ typename senf::console::ParsedAttributeAttributor::next_type -senf::console::ParsedAttributeAttributor:: -argInfo(ArgumentPack const & args) +senf::console::ParsedAttributeAttributor::arg() const { - typedef typename boost::parameter::binding< - ArgumentPack, tag::detail::default_value_>::type default_value_t; - return argInfo( args, boost::is_same() ); + return next(); } template template prefix_ typename senf::console::ParsedAttributeAttributor::next_type senf::console::ParsedAttributeAttributor:: -argInfo(ArgumentPack const & args, boost::mpl::true_) +argInfo(ArgumentPack const & args) const { - return argInfo( args[tag::name_ | ""], - args[tag::description_ | ""] ); +# define HaveArg(tag) boost::is_same< \ + typename boost::parameter::binding::type, void >() + + argInfo( kw::name, args, HaveArg(kw::type::name) ); + argInfo( kw::description, args, HaveArg(kw::type::description) ); + argInfo( kw::default_value, args, HaveArg(kw::type::default_value) ); + return next(); + +# undef HaveArg } template +template +prefix_ void senf::console::ParsedAttributeAttributor:: +argInfo(Kw const &, ArgumentPack const &, boost::mpl::true_) + const +{} + +template template -prefix_ typename senf::console::ParsedAttributeAttributor::next_type -senf::console::ParsedAttributeAttributor:: -argInfo(ArgumentPack const & args, boost::mpl::false_) +prefix_ void senf::console::ParsedAttributeAttributor:: +argInfo(boost::parameter::keyword const &, ArgumentPack const & args, + boost::mpl::false_) const { - return argInfo( args[tag::name_ | ""], - args[tag::description_ | ""], - args[tag::default_value_ | value_type()] ); + this->argName(args[kw::name]); } template -prefix_ typename senf::console::ParsedAttributeAttributor::next_type -senf::console::ParsedAttributeAttributor::argInfo(std::string const & name, - std::string const & doc) +template +prefix_ void senf::console::ParsedAttributeAttributor:: +argInfo(boost::parameter::keyword const &, ArgumentPack const & args, + boost::mpl::false_) const { - this->argName(name); - this->argDoc(doc); - return next(); + this->argDoc(args[kw::description]); } template -prefix_ typename senf::console::ParsedAttributeAttributor::next_type -senf::console::ParsedAttributeAttributor::argInfo(std::string const & name, - std::string const & doc, - value_type const & value) +template +prefix_ void senf::console::ParsedAttributeAttributor:: +argInfo(boost::parameter::keyword const &, ArgumentPack const & args, + boost::mpl::false_) const { - this->argName(name); - this->argDoc(doc); - defaultValue(value); - return next(); + this->defaultValue(args[kw::default_value]); } template @@ -185,7 +189,7 @@ ParsedAttributeAttributor(Overload & overload) {} template -prefix_ senf::console::ParsedAttributeAttributor +prefix_ typename senf::console::ParsedAttributeAttributor::next_type senf::console::ParsedAttributeAttributor::next() const { @@ -207,7 +211,8 @@ template prefix_ senf::console::ParsedAttributeAttributor:: ParsedAttributeAttributor(Overload & overload) - : ParsedCommandAttributor (overload, index) + : ParsedAttributeAttributorBase< Overload, + ParsedAttributeAttributor > (overload, index) {} /////////////////////////////////////////////////////////////////////////// diff --git a/Console/ParsedCommand.hh b/Console/ParsedCommand.hh index 14d63a0..7df4366 100644 --- a/Console/ParsedCommand.hh +++ b/Console/ParsedCommand.hh @@ -47,6 +47,85 @@ namespace senf { namespace console { + /** \brief CommandOverload implementation with automatic argument parsing + + ParsedCommandOverloadBase implements a CommandOverload implementation supporting automatic + parsing of arguments. This is \e not a node, it's a CommandOverload which is then added to + an OverloadedCommandNode instance. + + Automatic argument parsing and return value processing consists of several components: + \li \ref overload_add Adding overload instances to the tree + \li (Advanced) \ref overload_parse + \li (Advanced) \ref overload_format + + \section overload_add Adding argument parsing callbacks to the tree + + Adding appropriate callbacks to the tree is very simple: just path a function pointer to + DirectoryNode::add() or a member function pointer to ScopedDirectory::add(). + \code + std::string taskStatus(int id); + + senf::console::root().add("taskStatus", &taskStatus); + \endcode + + There are quite a number of additional parameters available to be set. These parameters are + documented in ParsedAttributeAttributor. Parameters are set by adding them as additional + calls after adding the node: + + \code + senf::console::root().add("taskStatus", &taskStatus) + .doc("Query the current task status") + .arg( name = "id", + description = "numeric id of task to check, -1 for the current task." + default_value = -1 ); + \endcode + + You may also add an additional \c std::ostream & Argument as first argument to the + callback. If this argument is present, the stream connected to the console which issued the + command will be passed there. This allows writing arbitrary messages to the console. + + Additionally, overloading is supported by registering multiple commands under the same + name. So, elaborating on above example: + \code + std::string taskStatus(int id); + std::string taskStatus(std::string const & name); + + senf::console::root() + .add("taskStatus", static_cast(&taskStatus)) + .doc("Query the current task status") + .overloadDoc("Query status by id") + .arg( name = "id", + description = "numeric id of task to check, -1 for the current task." + default_value = -1 ); + senf::console::root() + .add("taskStatus", static_cast(&taskStatus)) + .overloadDoc("Query status by name") + .arg( name = "name", + description = "name of task to check" ); + \endcode + + We can see here, that taking the address of an overloaded function requires a cast. If you + can give unique names to each of the C++ overloads (not the overloads in the console), you + should do so to make the unwieldy casts unnecessary. + + \section overload_parse Custom parameter parsers + + By default, parameters are parsed using \c boost::lexical_cast and therefore using \c + iostreams. This means, that any type which can be read from a stream can automatically be + used as argument type. + + However, argument parsing can be configured by specializing + senf::console::detail::ParameterTraits. See that class for more information. + + \section overload_format Custom return-value formatters + + By default, return values are streamed to an ostream. This automatically allows any + streamable type to be used as return value. To add new types or customize the formating, the + senf::console::detail::ReturnValueTraits template needs to be specialized for that type. See + that class for more information. + + \ingroup console_commands + */ class ParsedCommandOverloadBase : public CommandOverload { @@ -118,10 +197,10 @@ namespace console { private: }; - namespace tag { - BOOST_PARAMETER_KEYWORD(detail, name_); - BOOST_PARAMETER_KEYWORD(detail, description_); - BOOST_PARAMETER_KEYWORD(detail, default_value_); + namespace kw { + BOOST_PARAMETER_KEYWORD(type, name); + BOOST_PARAMETER_KEYWORD(type, description); + BOOST_PARAMETER_KEYWORD(type, default_value); } template @@ -155,9 +234,9 @@ namespace console { typedef ParsedAttributeAttributor return_type; typedef boost::parameter::parameters< - tag::detail::name_, - tag::detail::description_, - tag::detail::default_value_> arg_params; + kw::type::name, + kw::type::description, + kw::type::default_value> arg_params; next_type arg() const; @@ -171,16 +250,23 @@ namespace console { template next_type argInfo(ArgumentPack const & args) const; + template + void argInfo(Kw const &, ArgumentPack const &, boost::mpl::true_) + const; template - next_type argInfo(ArgumentPack const & args, boost::mpl::true_) const; + void argInfo(boost::parameter::keyword const &, + ArgumentPack const & args, boost::mpl::false_) + const; template - next_type argInfo(ArgumentPack const & args, boost::mpl::false_) const; - - next_type argInfo(std::string const & name, std::string const & doc) const; - next_type argInfo(std::string const & name, std::string const & doc, - value_type const & value) const; + void argInfo(boost::parameter::keyword const &, + ArgumentPack const & args, boost::mpl::false_) + const; + template + void argInfo(boost::parameter::keyword const &, + ArgumentPack const & args, boost::mpl::false_) + const; - ParsedAttributeAttributor next() const; + next_type next() const; void defaultValue(value_type const & value) const; @@ -203,7 +289,8 @@ namespace console { template class ParsedAttributeAttributor - : public ParsedCommandAttributor< Overload > + : public ParsedAttributeAttributorBase< Overload, + ParsedAttributeAttributor > { public: typedef OverloadedCommandNode node_type; diff --git a/Console/ParsedCommand.test.cc b/Console/ParsedCommand.test.cc index 94ab8fa..c850ca8 100644 --- a/Console/ParsedCommand.test.cc +++ b/Console/ParsedCommand.test.cc @@ -118,7 +118,7 @@ BOOST_AUTO_UNIT_TEST(parsedCommand) { std::stringstream ss; - using namespace senf::console::tag; + using namespace senf::console::kw; dir.add("cb", &cb1) .doc( @@ -133,11 +133,11 @@ BOOST_AUTO_UNIT_TEST(parsedCommand) "Lo nam balnearius Opprimo Pennatus, no decentia sui, dicto esse se pulchritudo,\n" "pupa Sive res indifferenter. Captivo pa." ) - .arg( description_ = "Bar didelfrump di desgorb. Nu widsoflar brimeldrgf." ) + .arg( description = "Bar didelfrump di desgorb. Nu widsoflar brimeldrgf." ) - .arg( name_ = "checkup", - description_ = "Florgel, dargel and durgel", - default_value_ = 2.1 ); + .arg( name = "checkup", + description = "Florgel, dargel and durgel", + default_value = 2.1 ); senf::console::OverloadedCommandNode & cbNode ( dir.add("cb", &cb5) diff --git a/HowTos/NewPacket/Mainpage.dox b/HowTos/NewPacket/Mainpage.dox index e519e91..aa400f9 100644 --- a/HowTos/NewPacket/Mainpage.dox +++ b/HowTos/NewPacket/Mainpage.dox @@ -141,7 +141,7 @@ \subsection howto_newpacket_parser_simple Simple field definitions - Packet parser fields are defined using special \ref packetpasermacros. We take the fields + Packet parser fields are defined using special \ref packetparsermacros. We take the fields directly from the packet definition (the GRE RFC in this case). This will give us to the following code fragment: diff --git a/senf.dict b/senf.dict index 755321b..d853092 100644 --- a/senf.dict +++ b/senf.dict @@ -105,6 +105,7 @@ enableChecksum endcode endif endl +endlink ENOFILE enum env @@ -146,6 +147,7 @@ FileHandle FileTarget findNext findPrev +fixedcolumn fixme fixvariant flurgle