From: g0dil Date: Sun, 13 Apr 2008 17:59:07 +0000 (+0000) Subject: Console: Overhaul documentation X-Git-Url: http://g0dil.de/git?a=commitdiff_plain;h=8e708e9784c76461252c3bdf646d291c593a49be;p=senf.git Console: Overhaul documentation Console: Implement custom argument parser support Utils: Add Boost.Parameter has_parameter helper senfscons: Show reason (error message) in the CompileCheck builder git-svn-id: https://svn.berlios.de/svnroot/repos/senf/trunk@810 270642c3-0616-0410-b53a-bc976706d245 --- diff --git a/Console/Mainpage.dox b/Console/Mainpage.dox index 42187b7..6929f25 100644 --- a/Console/Mainpage.dox +++ b/Console/Mainpage.dox @@ -20,29 +20,30 @@ // Free Software Foundation, Inc., // 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -/** \mainpage The Configuration and Runtime Control Framework +/** \mainpage The Configuration and Runtime Control Library The Console library implements a runtime interactive (network) console which allows to configure, control and manipulate a running application in any way. Additionally this library provides support for configuration files and command line parsing which can be used with or without the network console. - \section console_intro Introduction + \autotoc - There are two components to the Config/console framework: + \section console_intro Introduction - \li Building the node tree by registering objects and callbacks - \li Utilizing the config/console framework by writing configuration files or using the - interactive console. + There are three parts to the Config/console library: - Basic data structure of the console and config framework is the config/console node tree. This - tree. This tree works like a file-system. Commands are added to this tree and can then be called - from configuration files or from the interactive console. + \li The console/config library is based on a \link node_tree tree of console/config + nodes. \endlink + \li Besides directories, the node contains command nodes. Commands are based on \link + console_commands variables or callbacks.\endlink + \li The console/config library is utilized by writing configuration files or interactive + commands in \link console_parser the console/config language.\endlink - To get started using the config/console library, see - \li \ref node_tree - \li \ref console_parser - \li \ref console_commands + The node tree works like a directory structure. Commands are entered into this directory + structure and can be called passing arbitrary arguments. Configuration parameters are just + commands which set their respective parameter, however the library allows commands to do much + more than just that. \section console_example Example @@ -51,12 +52,14 @@ \code // Define callback function. - void mycommand(std::ostream & os, senf::console::ParseCommandInfo const & command) + void mycommand(std::ostream & os, int foo, int bar) { // ... os << "!! Important message ...\n"; } + namespace kw = senf::console::kw; + int main(int, char**) { // Provide global documentation @@ -66,8 +69,9 @@ // Add a command senf::console::root() .add("mycommand", &mycommand) - .doc("mycommand []\n\n" - "If is given, flurgle the , otherwise burgle it"); + .doc("If is given, flurgle the , otherwise burgle it") + .arg("foo") + .arg(kw::name = "bar", kw::default_value = 0); // Start the interactive console server senf::console::Server::start(senf::INet4SocketAddress(senf::INet4Address::None, 23232u)) @@ -92,6 +96,53 @@ Connection closed by foreign host. $ + + + \section intro_nodes The node tree + + The basic idea is, that the console/config library manages a directory structure of parameters + and auxiliary commands. Parameters are just commands which set a parameter value so everything + is either a directory entry (senf::console::DirectoryNode) or a command + (senf::console::CommandNode). + + \see \ref node_tree + + + \section intro_commands Console/config commands + + The console/config language does not define, how arguments are passed to the commands, it just + tokenizes the input and passes the tokens to the commands which then handle the + conversion. + + Since parsing the tokens into something usable is quite tedious and error prone, the library + implements automatic argument parsing where the argument tokens are automatically parsed + depending on argument types. This enables you to register a command taking an integer argument + which will be called with an already parsed integer value (or throw a + senf::console::SyntaxErrorException if the conversion fails). This will be the most often used + command. + + \see \ref console_commands + + + \section intro_language The console/config language + + To call the commands and set parameters, a very simple language is defined. The language is + almost declarative (e.g. it does not have any control-flow statements) but is processed + imperatively from top to bottom. This is very simple and flexible. + + Commands are referenced by their path in the node tree. To simplify working with deeply nested + directory structures, the current directory may be changed persistently or temporarily for some + commands. + \code + /server/port 1234; + + /logger/targets/console { + accept senf::log::Debug IMPORTANT; + accept server::ServerLog CRITICAL; + } + \endcode + + \see \ref console_parser */ /** \defgroup console_commands Supported command types @@ -101,6 +152,43 @@ \autotoc + \section console_cmdadd Adding commands and setting attributes + + Basically, all commands are added using senf::console::DirectoryNode::add(). What exactly + happens depends on the type of object added. + \code + dir.add("name", callback) + \endcode + will add a command 'name' which will execute 'callback' when called, where 'callback' can be a + lot of things as documented in the following chapters. + + The add call always returns (something which can be used as) a reference to the command node + added: + \code + senf::console::CommandNode & node ( dir.add( ... ) ); + \endcode + + Depending on the object added, you can also bind to a more specific node type + (e.g. senf::console::SimpleCommand) if you know the type of node returned. + + Depending on the type of object added, there are additional attributes which can be set. These + attributes are always set by calling them on the return value before saving that value as a + node reference. It is \e not guaranteed, you can call these members on the node + reference. + \code + dir.add("name", callback) + .doc("The documentation"); + \endcode + sets the \e doc attribute (if that is available, otherwise this will fail to compile). The + attribute members return value is again (something which can be used as) a reference to the + command node + \code + senf::console::CommandNode & node ( + dir.add("name", callback) + .doc("The documentation") ); + \endcode + + \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 @@ -171,21 +259,25 @@ 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. + This type of command has only a single attribute, \e doc to set the commands documentation. + \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. + To greatly simplify parsing complex commands, we turn to automatic argument parsing. + \subsection console_autoadd Adding automatically parsed commands + + Automatically parsed commands are registered by just adding a callback which has the correct + arguments and return-value defined: \code - std::string fun(std::string const & arg) + std::string fun2(std::string const & arg) { return arg; } \endcode - This extremely simple callback may be registered by adding it to a - senf::console::DirectoryNode. + This extremely simple callback may be registered by adding it to a senf::console::DirectoryNode. \code senf::console::root() .add("test2", &fun2); @@ -206,172 +298,257 @@ \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: + + \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. \code - namespace kw = senf::console::kw; + void fun3(std::ostream & os, unsigned n, std::string text) + { + while (n-- > 0) os << text << std::endl; + } senf::console::root() - .add("test2", &test2) - .doc("Echo 'arg' to the console") - .arg( kw::name = "arg", - kw::description = "Message to output" ); + .add("test3", &fun3); \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) + This simple command can now be used thus: + \htmlonly +
+    server:/$ test3
+    invalid number of arguments
+    server:/$ test3 stefan@j32.de
+    invalid number of arguments
+    server:/$ test3 2 ok
+    ok
+    ok
+    server:/$ help test3
+    Usage:
+        test3 arg11:int arg12:string
+    server:/$
+    
+ \endhtmlonly + + \subsection command_overload Command overloading + + Automatically parsed commands can be overloaded: You can register multiple commands under the + same name. Each overload is tried in turn until no SyntaxErrorException is raised. + \code + senf::console::root() + .add("test4", &fun3); + senf::console::root() + .add("test4", &fun2); + \endcode + And now, we can call \c test4 with one or two args: +
+    server:/$ test4
+    invalid number of arguments
+    server:/$ test4 stefan@j32.de
+    stefan@j32.de
+    server:/$ test4 2 ok
+    ok
+    ok
+    server:/$ help test4
+    Usage:
+        1- test4 arg11:int arg12:string
+        2- test4 arg21:string
+    server:/$
+    
- You don't need - to specify any information for an argument: To skip an argument, just call \c arg() without - attributes for this argument. + \subsection console_attributes Attributes of automatically parsed commands - After adding this information, the online help is much more intelligible + As have seen so far, some documentation is automatically provided. We can add more info, by + setting additional attributes. + \code + senf::console::root() + .add("test5", &fun3) + .doc("Echo text to the console") + .overloadDoc("Repeat {arg12} for {arg11} lines"); + senf::console::root() + .add("test4", &fun2) + .overloadDoc("Echo the {arg21} argument") + \endcode + + This additional info is used to provide more documentation: \htmlonly
-    server:/$ help test2
+    server:/$ help test5
     Usage:
-        test2 arg:string
+        1- test5 arg11:int arg12:string
+        2- test5 arg21:string
 
-    With:
-        arg       Message to output
+    Echo text to the console
 
-    Echo 'arg' to the console
-    server:/$
+    Variant 1:
+    Repeat {arg12} for {arg11} lines
+    
+    Variant 2:
+    Echo the {arg21} argument
+    senf:/$
     
\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 + \subsection console_argattributes Argument attributes - 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. + Additional attributes can be set for each parameter. They are all passed to the + senf::console::ParsedArgumentAttributor::arg() attribute. \code - void fun3(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; - } + namespace kw = senf::console::kw; - using namespace senf::console::kw; - senf::console::root() - .add("test3", &fun3) + .add("test6", &fun3) .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" ); + .overloadDoc("Repeat {text} for {n} lines"); + .arg( kw::name = "n", kw::description="Number of repetitions" ) + .arg( kw::name = "text", kw::description="Text to output" ); senf::console::root() - .add("test3", &fun2) - .overloadDoc("Echo the 'text' argument") - .arg( name = "text" ); + .add("test6", &fun2) + .overloadDoc("Echo the {text} argument") + .arg( kw::name = "text" ); \endcode - We can now call \c test3 with one or two arguments: + (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 either use + this namespace via a namespace alias (as above) or via a using namespace + senf::console::kw declaration (but beware of name collisions). - \htmlonly + 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 readable + \htmlonly
-    server:/$ test3 "The sky is blue"
-    The sky is blue
-    server:/$ test3 4 ok
-    ok
-    ok
-    ok
-    ok
-    server:/$ help test3
+    server:/$ help test6
     Usage:
-        1- test3 n:unsigned text:string
-        2- test3 text:string
-    
+        1- test6 n:int text:string
+        2- test6 text:string
+
     With:
-        n         Numer of repetitions
-        text      Messsage to output
+        n         Number of repetitions
+        text      Text to output
 
     Echo text to the console
 
     Variant 1:
-    Repeat 'text' for 'n' lines
+    Repeat {text} for {n} lines
     
     Variant 2:
-    Echo the 'text' argument
-    senf:/$    And 
-
+    Echo the {text} argument
+    senf:/$
     
\endhtmlonly + + \subsection console_argattribpos Passing argument attributes as positional arguments + + Since most of the time, we only need to set the name and possibly a description for arguments, + there is a shortcut: name and description can be specified as positional arguments in this + order. So the following will give the exactly same result as the example in the previous section + + \code + namespace kw = senf::console::kw; + + senf::console::root() + .add("test6", &fun3) + .doc("Echo text to the console") + .overloadDoc("Repeat for lines"); + .arg("n", "Number of repetitions") + .arg("text", "Text to output"); + senf::console::root() + .add("test6", &fun2) + .overloadDoc("Echo the argument") + .arg("text"); + \endcode + + Keyword arguments should always be used if additional attributes are set. You can however mix + positional and keyword arguments. + + \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: + values. These have to be declared explicitly: \code - using namespace senf::console::kw; + namespace kw = senf::console::kw; senf::console::root() - .add("test4", &fun3) - .arg( name = "n", description = "Number of repetitions", default_value = 1 ) - .arg( name = "text", description = "Message to output" ); + .add("test7", &fun3) + .doc("Echo {text} to the console, repeating {text} for {n} lines") + .arg("n", "Number of repetitions", kw::default_value=1) + .arg("text", "Text to output"); \endcode - (Default values must not necessarily be declared in the callback function too.) Of course, - default values can be used together with overloading. Default (optional) value support is quite + Default values can be used together with overloading. Default (optional) value support is quite flexible, it is not mandatory, for default values to be specified only for the trailing - arguments. + arguments. For the exact definition, how parsed argument values are assigned to overload + arguments in the presence of default values, see \ref senf::console::kw::default_value. \htmlonly
-    server:/$ test4 echo
+    server:/$ test7 echo
     echo
-    server:/$ test4 4 ok
+    server:/$ test7 4 ok
     ok
     ok
     ok
     ok
-    server:/$ help test4
+    server:/$ help test7
     Usage:
         test4 [n:unsigned] text:string
     
     With:
         n         Number of repetitions
-        text      Message to output
-            default value: ok
+            default: 1
+        text      Text to output
+
+    Echo {text} to the console, repeating {text} for {n} lines
     server:/$
     
\endhtmlonly - \subsection console_auto_summary Attribute summary + \subsection console_attr_summary Attribute summary + + Here a summary of the most common attributes - 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
\link senf::console::ParsedArgumentAttributorBase::doc() .doc\endlink ( \e doc )Set + documentation for all overloads
\link senf::console::ParsedArgumentAttributorBase::overloadDoc() + .overloadDoc\endlink ( \e doc )Set documentation for a specific overload
\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
\link senf::console::ParsedArgumentAttributor::arg() .arg\endlink ( \e argument \e + attributes )Set argument attributes (see below)
- See senf::console::ParsedArgumentAttributor 'List of all members' + The most important argument attributes (all defined in the senf::console::kw namespace) are: + + + + + + + + +
\link senf::console::kw::name kw::name\endlinkParameter name
\link senf::console::kw::description kw::description\endlinkOne-line + description of the argument
\link senf::console::kw::default_value kw::default_value\endlinkArguments + default value
+ + \see senf::console::ParsedArgumentAttributor + / List of all members for the complete attribute interface \n + \ref senf::console::kw for a list of all argument attribute keywords + + \section console_memberfn Registering member functions Member functions are supported like non-member functions. They must however be added through a @@ -382,14 +559,17 @@ public: senf::console::ScopedDirectory dir; - Test(std::string label) : dir(this), label_ (label) { + 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; } + 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_; diff --git a/Console/OverloadedCommand.cc b/Console/OverloadedCommand.cc index 921ce8f..ecc5c44 100644 --- a/Console/OverloadedCommand.cc +++ b/Console/OverloadedCommand.cc @@ -96,7 +96,7 @@ prefix_ void senf::console::OverloadedCommandNode::v_help(std::ostream & os) if (! arg.defaultValue.empty()) os << ']'; - if (! arg.name.empty()) { + if (! arg.name.empty() || ! arg.defaultValue.empty()) { ArgumentDocs::iterator k (argumentDocs.begin()); ArgumentDocs::iterator const k_end (argumentDocs.end()); for (; k != k_end; ++k) @@ -123,7 +123,7 @@ prefix_ void senf::console::OverloadedCommandNode::v_help(std::ostream & os) ArgumentDocs::const_iterator i (argumentDocs.begin()); ArgumentDocs::const_iterator const i_end (argumentDocs.end()); for (; i != i_end; ++i) { - if (! i->doc.empty()) { + if (! i->doc.empty() || ! i->defaultValue.empty()) { os << " " << i->name << std::string(i->name.length()<8 ? 8-i->name.length() : 0, ' ') diff --git a/Console/ParsedCommand.cc b/Console/ParsedCommand.cc index 0ef2347..d593eb1 100644 --- a/Console/ParsedCommand.cc +++ b/Console/ParsedCommand.cc @@ -51,7 +51,13 @@ prefix_ void senf::console::ParsedCommandOverloadBase::v_argumentDoc(unsigned in ? (boost::format("arg%d%d") % overloadIndex() % (index+1)).str() : arg.name; doc.type = arg.type; - doc.defaultValue = arg.defaultValueStr(); + if (arg.hasDefault) { + doc.defaultValue = arg.defaultDoc.empty() + ? arg.defaultValueStr() + : arg.defaultDoc; + if (doc.defaultValue.empty()) + doc.defaultValue = "(empty)"; + } doc.doc = arg.doc; } diff --git a/Console/ParsedCommand.cci b/Console/ParsedCommand.cci index 768be07..1cd764b 100644 --- a/Console/ParsedCommand.cci +++ b/Console/ParsedCommand.cci @@ -82,6 +82,18 @@ prefix_ void senf::console::ParsedCommandAttributorBase::argDoc(std::string cons overload().arg(index_).doc = doc; } +prefix_ void senf::console::ParsedCommandAttributorBase::typeName(std::string const & doc) + const +{ + overload().arg(index_).type = doc; +} + +prefix_ void senf::console::ParsedCommandAttributorBase::defaultDoc(std::string const & doc) + const +{ + overload().arg(index_).defaultDoc = doc; +} + prefix_ senf::console::ParsedCommandOverloadBase & senf::console::ParsedCommandAttributorBase::overload() const diff --git a/Console/ParsedCommand.cti b/Console/ParsedCommand.cti index e37906c..f4b523f 100644 --- a/Console/ParsedCommand.cti +++ b/Console/ParsedCommand.cti @@ -28,7 +28,7 @@ // Custom includes #include "../Utils/membind.hh" #include -#include +#include "../Utils/parameter.hh" #define prefix_ inline ///////////////////////////////cti.p/////////////////////////////////////// @@ -99,6 +99,13 @@ prefix_ void senf::console::ParsedCommandAttributorBase::defaultValue(Type const overload().arg(index_).hasDefault = true; } +template +prefix_ void senf::console::ParsedCommandAttributorBase::parser(Fn fn) + const +{ + overload().arg(index_).parser = fn; +} + /////////////////////////////////////////////////////////////////////////// // senf::console::ParsedCommandAttributor @@ -162,12 +169,15 @@ senf::console::ParsedArgumentAttributor:: argInfo(ArgumentPack const & args) const { -# define HaveArg(tag) boost::is_same< \ - typename boost::parameter::binding::type, void >() +# define ProcessArg(tag) \ + argInfo( kw:: tag, args, senf::has_parameter< ArgumentPack, kw::type:: tag >() ) - 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) ); + ProcessArg(name); + ProcessArg(description); + ProcessArg(default_value); + ProcessArg(type_name); + ProcessArg(default_doc); + ProcessArg(parser); return next(); @@ -177,7 +187,7 @@ argInfo(ArgumentPack const & args) template template prefix_ void senf::console::ParsedArgumentAttributor:: -argInfo(Kw const &, ArgumentPack const &, boost::mpl::true_) +argInfo(Kw const &, ArgumentPack const &, boost::mpl::false_) const {} @@ -185,7 +195,7 @@ template template prefix_ void senf::console::ParsedArgumentAttributor:: argInfo(boost::parameter::keyword const &, ArgumentPack const & args, - boost::mpl::false_) + boost::mpl::true_) const { this->argName(args[kw::name]); @@ -195,7 +205,7 @@ template template prefix_ void senf::console::ParsedArgumentAttributor:: argInfo(boost::parameter::keyword const &, ArgumentPack const & args, - boost::mpl::false_) + boost::mpl::true_) const { this->argDoc(args[kw::description]); @@ -205,13 +215,44 @@ template template prefix_ void senf::console::ParsedArgumentAttributor:: argInfo(boost::parameter::keyword const &, ArgumentPack const & args, - boost::mpl::false_) + boost::mpl::true_) const { this->defaultValue(args[kw::default_value]); } template +template +prefix_ void senf::console::ParsedArgumentAttributor:: +argInfo(boost::parameter::keyword const &, ArgumentPack const & args, + boost::mpl::true_) + const +{ + this->typeName(args[kw::type_name]); +} + +template +template +prefix_ void senf::console::ParsedArgumentAttributor:: +argInfo(boost::parameter::keyword const &, ArgumentPack const & args, + boost::mpl::true_) + const +{ + BOOST_STATIC_ASSERT(( senf::has_parameter::value )); + this->defaultDoc(args[kw::default_doc]); +} + +template +template +prefix_ void senf::console::ParsedArgumentAttributor:: +argInfo(boost::parameter::keyword const &, ArgumentPack const & args, + boost::mpl::true_) + const +{ + this->parser(args[kw::parser]); +} + +template prefix_ senf::console::ParsedArgumentAttributor:: ParsedArgumentAttributor(Overload & overload) @@ -231,7 +272,15 @@ prefix_ void senf::console::ParsedArgumentAttributor:: defaultValue(value_type const & value) const { - ParsedCommandAttributorBase::defaultValue(value); + ParsedCommandAttributorBase::defaultValue(value); +} + +template +template +prefix_ void senf::console::ParsedArgumentAttributor::parser(Fn fn) + const +{ + ParsedCommandAttributorBase::parser(fn); } /////////////////////////////////////////////////////////////////////////// diff --git a/Console/ParsedCommand.hh b/Console/ParsedCommand.hh index 9f43d15..75724a8 100644 --- a/Console/ParsedCommand.hh +++ b/Console/ParsedCommand.hh @@ -27,10 +27,12 @@ #define HH_ParsedCommand_ 1 // Custom includes + +#define BOOST_PARAMETER_MAX_ARITY 6 + #include #include #include -#include #include #include #include @@ -194,6 +196,8 @@ namespace console { This class is the base class for those attributors. It provides members which do not depend in any way on the exact type of command added. + + \see \ref console_autoparse */ class ParsedCommandAttributorBase { @@ -207,6 +211,9 @@ namespace console { void argName(std::string const & name) const; void argDoc(std::string const & doc) const; template void defaultValue(Type const & value) const; + void typeName(std::string const & doc) const; + void defaultDoc(std::string const & doc) const; + template void parser(Fn fn) const; ParsedCommandOverloadBase & overload() const; void overloadDoc(std::string const & doc) const; @@ -225,9 +232,8 @@ namespace console { This class adds all those members, which do depend on the type of command added (and thereby on that commands signature) but do not depend on the type of any single argument. - - \fixme Implement compile-time checking, that after a defaulted arg only defaulted args are - allowed. + + \see \ref console_autoparse */ template class ParsedCommandAttributor @@ -248,21 +254,144 @@ namespace console { href="http://www.boost.org/doc/libs/1_33_1/libs/parameter/doc/html/index.html">Boost.Parameter library. - For the keyword tags, the standard C++ scoping rules apply: - \li Either qualify them with their complete namespace: arg( senf::console::kw::name = - "name" ) - \li or use a namespace alias: namespace kw = senf::console::kw; arg( kw::name = "name" - ); - \li import the keywords into your namespace: using namespace senf::console::kw; arg( - name = "name"); + For the keyword tags, the standard C++ scoping rules apply + \code + // Either qualify them with their complete namespace + dir.add(...) + .arg( senf::console::kw::name = "name" ); + + // Or use a namespace alias + namespace kw = senf::console::kw; + dir.add(...) + .arg( kw::name = "name" ); + + // Or import the keywords into the current namespace (beware of name collisions) + using namespace senf::console::kw; + dir.add(...) + .arg( name = "name" ); + \endcode The second alternative is preferred, the using namespace directive may be used as long as the keyword names do not clash with another visible symbol. + + \section kw_attributes Argument attribute values + + The keywords are used to set argument attributes. The keywords \ref default_value and \ref + parser influence, how an argument is parsed/interpreted whereas \ref name, \ref description, + \ref type_name and \ref default_doc are used to change the arguments documentation: + \code + void command(int); + + dir.add("command", &command) + .arg( kw::name = "name", + kw::description = "description", + kw::default_value = 1, + kw::type_name = "type_name", + kw::default_doc = "default_doc" ); + \endcode + Will create the following documentation: + \htmlonly +
+        Usage:
+            command [name:type_name]
+        
+        With:
+            name      description
+                default: default_doc
+        
+ \endhtmlonly + + \see \ref senf::console::ParsedArgumentAttributor::arg() */ namespace kw { BOOST_PARAMETER_KEYWORD(type, name) ///< Argument name - BOOST_PARAMETER_KEYWORD(type, description) ///< One-line Argument description + /**< Sets the displayed name of the argument. */ + BOOST_PARAMETER_KEYWORD(type, description) ///< One-line argument description + /**< This description is shown in the argument + reference. If several overloads have same-named + arguments, only one of them should be documented. This + documentation then applies to all arguments of that + name. */ BOOST_PARAMETER_KEYWORD(type, default_value) ///< Argument default value + /**< If a default value is specified for an argument, that + argument is optional. If an overload is called with + fewer arguments than defined, optional values will be + used beginning at the last optional argument and going + forward until all arguments have values assigned. E.g., + an overload with 5 parameters \a a - \a e with two + defaults attached: +
+                                             command a:int [b:int] c:int [d:int] e:int
+                                             
+ When calling the overload, the arguments will be + assigned in the following way: + + + + + + + + + + + + + + + + + + + + + +
command 1 2SyntaxErrorException: invalid number of + arguments
command 1 2 3\a a = 1, \n + \a b = \e default, \n + \a c = 2, \n + \a d = \e default, \n + \a e = 3
command 1 2 3 4\a a = 1, \n + \a b = 2, \n + \a c = 3, \n + \a d = \e default, \n + \a e = 4
command 1 2 3 4 5\a a = 1, \n + \a b = 2, \n + \a c = 3, \n + \a d = 4, \n + \a e = 5
command 1 2 3 4 5 6SyntaxErrorException: invalid number of + arguments
+ So, if you assign default values as you are used to + they will work like in C++ and most other languages */ + BOOST_PARAMETER_KEYWORD(type, type_name) ///< Type name of this arguments type + /**< By default, the type of an argument is extracted from + the C++ type name by taking the last component of the + fully scoped name. This value can be changed by setting + this attribute. */ + BOOST_PARAMETER_KEYWORD(type, default_doc) ///< String rep of default value + /**< By default, the default value is documented by + converting the value to it's string representation + using \c boost::lexical_cast / \c iostreams. The + displayed value can be changed by setting this + attribute. */ + BOOST_PARAMETER_KEYWORD(type, parser) ///< Argument parser + /**< The argument parser is used to convert the argument + token list returned by the console/config parser into + the appropriate value. If not set explicitly, this + conversion is supplied by the ArgumentTraits + class. + + Setting the \a parser attribute allows to use a custom + parser. The parser is an arbitrary callable object with + the signature + \code + void parser(senf::console::ParseCommandInfo::TokensRange const & tokens, value_type & out); + \endcode + where \c value_type is the type of the overload + parameter. The parser must read and parse the complete + \a tokens range and return the parsed value in \a + out. If the parser fails, it must raise a + senf::console::SyntaxErrorException. */ } /** \brief Derived class dependent ParsedCommandBase attributes @@ -272,6 +401,8 @@ namespace console { This class adds all those members, which do not depend on any specific argument but which need to return the correct attributor type. + + \see \ref console_autoparse */ template class ParsedArgumentAttributorBase @@ -295,6 +426,8 @@ namespace console { This class adds all those members, which depend on a specific argument. Each call to \c arg will advance to the next argument. + + \see \ref console_autoparse */ template < class Overload, unsigned index=0, @@ -306,7 +439,10 @@ namespace console { typedef boost::parameter::parameters< kw::type::name, kw::type::description, - kw::type::default_value> arg_params; + kw::type::default_value, + kw::type::type_name, + kw::type::default_doc, + kw::type::parser> arg_params; public: typedef OverloadedCommandNode node_type; @@ -322,16 +458,26 @@ namespace console { argument. The attributes are passed to arg() as keyword arguments using the Boost.Parameter - library. The valid keywords are defined in the - senf::console::kw namespace. - - This member is only present, if there is an argument at - the current index. */ + library. + \code + ... + .arg( kw::name = "name", + kw::default_value = 1 ) + ... + \endcode + The valid keywords are defined in the senf::console::kw + namespace. + + Each call to arg() will increment the argument index + and advance to the next argument. This member is only + present, if there is an argument at the current. */ #ifndef DOXYVEN -# define BOOST_PP_ITERATION_PARAMS_1 \ - (4, (1, 3, SENF_ABSOLUTE_INCLUDE_PATH(Console/ParsedCommand.mpp), 5)) +# define BOOST_PP_ITERATION_PARAMS_1 \ + (4, (1, BOOST_PARAMETER_MAX_ARITY, \ + SENF_ABSOLUTE_INCLUDE_PATH(Console/ParsedCommand.mpp), \ + 5)) # include BOOST_PP_ITERATE() #endif @@ -342,25 +488,38 @@ namespace console { template next_type argInfo(ArgumentPack const & args) const; template - void argInfo(Kw const &, ArgumentPack const &, boost::mpl::true_) + void argInfo(Kw const &, ArgumentPack const &, boost::mpl::false_) const; template void argInfo(boost::parameter::keyword const &, - ArgumentPack const & args, boost::mpl::false_) + ArgumentPack const & args, boost::mpl::true_) const; template void argInfo(boost::parameter::keyword const &, - ArgumentPack const & args, boost::mpl::false_) + ArgumentPack const & args, boost::mpl::true_) const; template void argInfo(boost::parameter::keyword const &, - ArgumentPack const & args, boost::mpl::false_) + ArgumentPack const & args, boost::mpl::true_) + const; + template + void argInfo(boost::parameter::keyword const &, + ArgumentPack const & args, boost::mpl::true_) + const; + template + void argInfo(boost::parameter::keyword const &, + ArgumentPack const & args, boost::mpl::true_) + const; + template + void argInfo(boost::parameter::keyword const &, + ArgumentPack const & args, boost::mpl::true_) const; next_type next() const; void defaultValue(value_type const & value) const; + template void parser(Fn fn) const; template friend class ParsedArgumentAttributor; diff --git a/Console/ParsedCommand.ih b/Console/ParsedCommand.ih index d8a72d2..19a4de6 100644 --- a/Console/ParsedCommand.ih +++ b/Console/ParsedCommand.ih @@ -27,6 +27,9 @@ #define IH_ParsedCommand_ 1 // Custom includes +#include +#include +#include "Parse.hh" ///////////////////////////////ih.p//////////////////////////////////////// @@ -43,6 +46,7 @@ namespace detail { std::string type; std::string name; + std::string defaultDoc; bool hasDefault; std::string doc; @@ -56,19 +60,18 @@ namespace detail { : public ArgumentInfoBase { typedef boost::intrusive_ptr ptr; + typedef boost::function Parser; static ptr create(); ArgumentInfo(); ParameterType defaultValue; + Parser parser; virtual std::string defaultValueStr() const; }; - template ::value> - struct ParsedCommandTraits - {}; - // FirstArgType returns void, if the function has no arguments, otherwise it returns arg1_type template 0)> @@ -83,6 +86,10 @@ namespace detail { typedef typename Traits::arg1_type type; }; + template ::value> + struct ParsedCommandTraits + {}; + template struct ParsedCommandTraits { diff --git a/Console/ParsedCommand.mpp b/Console/ParsedCommand.mpp index 3d998c4..f1d6833 100644 --- a/Console/ParsedCommand.mpp +++ b/Console/ParsedCommand.mpp @@ -135,11 +135,15 @@ v_execute(std::ostream & os, ParseCommandInfo const & command) riterator const i_end (boost::rend(command.arguments())); # define mpp_l(z,n,d) \ - if (i == i_end) \ - throw SyntaxErrorException("invalid number of arguments"); \ mpp_ArgTypeN(n) mpp_ArgN(n) (arg< mpp_ArgTypeN(n) >( n ).defaultValue); \ - if (! arg(n).hasDefault || nDefaults-- <= 0) \ - ArgumentTraits< mpp_ArgTypeN(n) >::parse( *(i++), mpp_ArgN(n) ); + if (! arg(n).hasDefault || nDefaults-- <= 0) { \ + if (i == i_end) \ + throw SyntaxErrorException("invalid number of arguments"); \ + if (arg< mpp_ArgTypeN(n) >(n).parser) \ + arg< mpp_ArgTypeN(n) >(n).parser( *(i++), mpp_ArgN(n) ); \ + else \ + ArgumentTraits< mpp_ArgTypeN(n) >::parse( *(i++), mpp_ArgN(n) ); \ + } # define mpp_l_(z,n,d) mpp_l(z, BOOST_PP_SUB(BOOST_PP_DEC(BOOST_PP_ITERATION()), n), d) BOOST_PP_REPEAT( BOOST_PP_ITERATION(), mpp_l_, _ ) # undef mpp_l diff --git a/Console/ParsedCommand.test.cc b/Console/ParsedCommand.test.cc index 3487d9c..39f028d 100644 --- a/Console/ParsedCommand.test.cc +++ b/Console/ParsedCommand.test.cc @@ -46,7 +46,18 @@ namespace { double cb2(){ return 1.2; } void cb3(int i) { } std::string cb4(std::ostream & os) { os << "text\n"; return "value"; } - void cb5(std::ostream & os, std::string v) { os << "Value: " << v << "\n"; } + void cb5(std::ostream & os, std::string const & v) { os << "Value: " << v << "\n"; } + + struct TestParser + { + typedef senf::console::ParseCommandInfo::TokensRange const & first_argument_type; + typedef std::string & second_argument_type; + typedef void result_type; + + result_type operator()(first_argument_type, second_argument_type out) const + { out = "true"; } + }; + } BOOST_AUTO_UNIT_TEST(parsedCommand) @@ -116,6 +127,20 @@ BOOST_AUTO_UNIT_TEST(parsedCommand) } { + namespace kw = senf::console::kw; + std::stringstream ss; + + // Just for the fun of it, use a functor and not a function pointer as parser ... + dir.add("cb6", &cb5) + .arg( kw::parser = TestParser() ); + BOOST_CHECK_NO_THROW( + parser.parse("test/cb6 false", + boost::bind( boost::ref(executor), boost::ref(ss), _1 )) ); + BOOST_CHECK_EQUAL( ss.str(), "Value: true\n" ); + + } + + { std::stringstream ss; using namespace senf::console::kw; @@ -136,23 +161,35 @@ BOOST_AUTO_UNIT_TEST(parsedCommand) .arg( description = "Bar didelfrump di desgorb. Nu widsoflar brimeldrgf." ) .arg( name = "checkup", + type_name = "number", description = "Florgel, dargel and durgel", - default_value = 2.1 ); + default_value = 2.1, + default_doc = "(double) 2.1" ); senf::console::OverloadedCommandNode & cbNode ( dir.add("cb", &cb5) .overloadDoc( "Uus Primordia fundo falsidicus corium, diurnitas humo pro leto. Sui Ueraciter\n" "hio eruca lenis qua Agalmate ut fors penitentia. Iugum obdormio anxio nuncupo\n" - "iam, in vos nam Custodi." ) ); + "iam, in vos nam Custodi." ) + .arg( "text", default_value = "" ) ); (void) cbNode; - dir.add("cb", &cb2); - - parser.parse("test/cb 111 222.4; test/cb 222; test/cb foo; test/cb", - boost::bind( boost::ref(executor), boost::ref(ss), _1 )); - BOOST_CHECK_EQUAL( ss.str(), "333\n" "224\n" "Value: foo\n" "1.2\n" ); + BOOST_CHECK_NO_THROW( + parser.parse("test/cb 111 222.4", + boost::bind( boost::ref(executor), boost::ref(ss), _1 )) ); + BOOST_CHECK_NO_THROW( + parser.parse("test/cb 222", + boost::bind( boost::ref(executor), boost::ref(ss), _1 )) ); + BOOST_CHECK_NO_THROW( + parser.parse("test/cb foo", + boost::bind( boost::ref(executor), boost::ref(ss), _1 )) ); + BOOST_CHECK_NO_THROW( + parser.parse("test/cb", + boost::bind( boost::ref(executor), boost::ref(ss), _1 )) ); + + BOOST_CHECK_EQUAL( ss.str(), "333\n" "224\n" "Value: foo\n" "Value: \n" ); } { @@ -161,14 +198,15 @@ BOOST_AUTO_UNIT_TEST(parsedCommand) BOOST_CHECK_EQUAL( ss.str(), "Usage:\n" - " 1- cb arg11:int [checkup:double]\n" - " 2- cb arg21:string\n" - " 3- cb\n" + " 1- cb arg11:int [checkup:number]\n" + " 2- cb [text:string]\n" "\n" "With:\n" " arg11 Bar didelfrump di desgorb. Nu widsoflar brimeldrgf.\n" " checkup Florgel, dargel and durgel\n" - " default: 2.1\n" + " default: (double) 2.1\n" + " text \n" + " default: (empty)\n" "\n" "Ops fortunate, ops me ut orgia vociferatio contumax per, rudo re loco emitto\n" "intolerabiliter ita iugo. Subcribo gravo. Devenio luna fonticulus Castanea\n" @@ -229,12 +267,24 @@ BOOST_AUTO_UNIT_TEST(memberParsedCommand) COMPILE_FAIL(argParser) { senf::console::ScopedDirectory<> dir; + + // Fails, since there are only two arguments defined dir.add("cb", &cb1) .arg() .arg() .arg(); } +COMPILE_FAIL(defaultDoc) +{ + senf::console::ScopedDirectory<> dir; + using namespace senf::console::kw; + + // Fails, since default_value is missing but default_doc is given + dir.add("cb",&cb1) + .arg(default_doc = "doc"); +} + #endif ///////////////////////////////cc.e//////////////////////////////////////// diff --git a/Utils/Mainpage.dox b/Utils/Mainpage.dox index 711946c..cc20df8 100644 --- a/Utils/Mainpage.dox +++ b/Utils/Mainpage.dox @@ -36,9 +36,17 @@ namespace senf { \ref senfmplSome simple tools which help to solve common meta-programming tasks - \ref senfppExtensions to the Boost.Preprocessor library + \ref senfppExtensions to the Boost.Preprocessor + library - \ref typetraitsExtensions to the Boost.TypeTraits library + \ref typetraitsExtensions to the Boost.TypeTraits + library + + \ref boost_parameterUtilities concerning the Boost.Parameter + library diff --git a/Utils/parameter.hh b/Utils/parameter.hh new file mode 100644 index 0000000..478925a --- /dev/null +++ b/Utils/parameter.hh @@ -0,0 +1,83 @@ +// $Id$ +// +// Copyright (C) 2008 +// Fraunhofer Institute for Open Communication Systems (FOKUS) +// Competence Center NETwork research (NET), St. Augustin, GERMANY +// Stefan Bund +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the +// Free Software Foundation, Inc., +// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +/** \file + \brief parameter public header */ + +/** \defgroup boost_parameter Boost.Parameter utilities + + Here we define some utilities for the Boost.Parameter + library. + */ + +#ifndef HH_parameter_ +#define HH_parameter_ 1 + +// Custom includes +#include +#include +#include + +//#include "parameter.mpp" +///////////////////////////////hh.p//////////////////////////////////////// + +namespace senf { + + ///\addtogroup boost_parameter + ///\{ + + /** \brief Test \a ArgumentPack, whether the \a TagType argument was supplied + + This check will test, whether the argument identified by the \a TagType keyword tag type was + specified in the \a ArgumentPack. This inherits from \c boost::mpl::true_ or \c + boost::mpl::false_ accordingly. + + \tparam ArgumentPack argument pack to check + \tparam TagType keyword tag type identifying the argument to check + */ + template + struct has_parameter + : public boost::mpl::not_< + boost::is_same< typename boost::parameter::binding< ArgumentPack, TagType>::type, + void > >::type + {}; + + ///\} +} + +///////////////////////////////hh.e//////////////////////////////////////// +//#include "parameter.cci" +//#include "parameter.ct" +//#include "parameter.cti" +#endif + + +// Local Variables: +// mode: c++ +// fill-column: 100 +// comment-column: 40 +// c-file-style: "senf" +// indent-tabs-mode: nil +// ispell-local-dictionary: "american" +// compile-command: "scons -u test" +// End: diff --git a/Utils/parameter.test.cc b/Utils/parameter.test.cc new file mode 100644 index 0000000..e4b6b3d --- /dev/null +++ b/Utils/parameter.test.cc @@ -0,0 +1,53 @@ +// $Id$ +// +// Copyright (C) 2008 +// Fraunhofer Institute for Open Communication Systems (FOKUS) +// Competence Center NETwork research (NET), St. Augustin, GERMANY +// Stefan Bund +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the +// Free Software Foundation, Inc., +// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +/** \file + \brief parameter.test unit tests */ + +//#include "parameter.test.hh" +//#include "parameter.test.ih" + +// Custom includes +#include "parameter.hh" + +#include +#include + +#define prefix_ +///////////////////////////////cc.p//////////////////////////////////////// + +BOOST_AUTO_UNIT_TEST(parameter) +{} + +///////////////////////////////cc.e//////////////////////////////////////// +#undef prefix_ + + +// Local Variables: +// mode: c++ +// fill-column: 100 +// comment-column: 40 +// c-file-style: "senf" +// indent-tabs-mode: nil +// ispell-local-dictionary: "american" +// compile-command: "scons -u test" +// End: diff --git a/doclib/html-munge.xsl b/doclib/html-munge.xsl index 11b9e15..598587e 100644 --- a/doclib/html-munge.xsl +++ b/doclib/html-munge.xsl @@ -1,7 +1,8 @@ + xmlns:str="http://exslt.org/strings" + extension-element-prefixes="str"> &1', source=source, target=target)) passedTests = {} + delay_name = None for error in errf: elts = error.split(':',2) if len(elts) != 3 : continue @@ -26,13 +27,25 @@ def CompileCheck(target, source, env): if not os.path.exists(filename) : continue try: line = int(line) except ValueError : continue + message = message.strip() + + if delay_name and not message.startswith('instantiated from '): + print "Passed test '%s': %s" % (delay_name, message) + delay_name = None + continue + filename = os.path.abspath(filename) if filename != source[0].abspath : continue for name,lines in tests.iteritems(): if line >= lines[0] and line <= lines[1]: passedTests[name] = 1 - print "Passed test '%s': %s" % (name, message.strip()) + if message.startswith('instantiated from '): + delay_name = name + else: + print "Passed test '%s': %s" % (name, message) + if delay_name: + print "Passed test '%s': " % delay_name failedTests = set(tests.iterkeys()) - set(passedTests.iterkeys()) if failedTests: for test in failedTests: