Supported command types

Namespaces

 senf::console::factory
 Console node factories.
 
 senf::console::kw
 Keyword argument tags.
 

Classes

class  senf::console::SimpleCommandNode
 Most simple CommandNode implementation. More...
 
class  senf::console::OverloadedCommandNode
 Command node which allows multiple registered callbacks. More...
 
class  senf::console::ParsedCommandOverloadBase
 CommandOverload implementation with automatic argument parsing. More...
 

Macros

#define SENF_CONSOLE_REGISTER_ENUM(Type, Values)   SENF_CONSOLE_REGISTER_ENUM_(BOOST_PP_EMPTY(), BOOST_PP_EMPTY(), Type, Values)
 Register enum type for argument parsing. More...
 
#define SENF_CONSOLE_REGISTER_ENUM_MEMBER(Class, Type, Values)   SENF_CONSOLE_REGISTER_ENUM_(Class::, BOOST_PP_EMPTY(), Type, Values)
 Register enum type for argument parsing. More...
 

Detailed Description

The Console/config library supports quite a number of different command types. All these types of command are registered by passing an appropriate factory instance to DirectoryNode::add()

Adding commands and setting attributes

Basically, all commands are added using senf::console::DirectoryNode::add().
namespace fty = senf::console::factory;
dir.add("name", fty::Command(callback));

will add the command 'name' which will execute 'callback' when called.

The add call always returns (something which can be used as) a reference to the command node added:

senf::console::CommandNode & node ( dir.add( ... ) );
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.

Nodes are always added using a factory from the senf::console::factory namespace. The factory
has additional (type specific) attributes. These attributes are set by calling member functions
called 'attributors' on the temporary factory instance. It is \e not guaranteed, you can call
these members on the node reference returned by the \c add() call.
namespace fty = senf::console::factory;
dir.add("name", fty::Command(callback) .doc("The documentation") );

sets the doc attribute (if that is available, otherwise this will fail to compile).

See also
senf::console::factory for a list of all node factories.

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.
void fun1(std::ostream & os, senf::console::ParseCommandInfo const & command)
{
// Here we declare variables for the arguments
std::string value;
{
// We parse the arguments using the CheckedArgumentIteratorWrapper. This wrapper
// will throw a SyntaxErrorException if we access a nonexistent argument or if we
// do not parse all arguments.
// Extract the first argument. This is again a token range.
if (arg1Tokens.size() != 1)
raise senf::console::SyntaxErrorException("argument syntax error");
value = arg1Tokens[0].value();
}
os << value << std::endl;
}
Registering this callback is done by simply adding it. To provide online help, pass it to
'doc()':
namespace fty = senf::console::factory;
senf::console::root().add("test1", fty::Command(&fun1)
.doc("Usage:\n"
" test1 arg\n"
"\n"
"Echo 'arg' to the console") );
The callback may now be called interactively on the console by it's registered name:

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:/$

As you can see above, the arguments and tokens are returned as boost::iterator_range instances. These behave much like containers: They have begin() and 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.

This type of command has only a single attribute, doc to set the commands documentation.

Automatic argument parsing

To greatly simplify parsing complex commands, we turn to automatic argument parsing.

Adding

Automatically parsed commands are registered by just adding a callback which has the correct
arguments and return-value defined:
std::string fun2(std::string const & arg)
{
return arg;
}
This extremely simple callback may be registered by adding it to a senf::console::DirectoryNode.
namespace fty = senf::console::factory;
senf::console::root().add("test2", fty::Command(&fun2));

The functionality is now identical to test1:

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:/$

Accessing the console stream

Commands may have an optional first argument of type <tt>std::ostream &</tt>. This argument is
not considered part of the real interface. When the command is executed, the callback will be
passed the current consoles output stream object in this argument. With this, the callback can
output arbitrary messages to the network console.
namespace fty = senf::console::factory;
void fun3(std::ostream & os, unsigned n, std::string text)
{
while (n-- > 0) os << text << std::endl;
}
senf::console::root().add("test3", fty::Command(&fun3));
This simple command can now be used thus:

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:/$

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.
namespace fty = senf::console::factory;
senf::console::root().add("test4", fty::Command(&fun3));
senf::console::root().add("test4", fty::Command(&fun2));

And now, we can call test4 with one or two args:

server:/$ test4
invalid number of arguments
server:/$ test4 stefa.nosp@m.n@j3.nosp@m.2.de
stefa.nosp@m.n@j3.nosp@m.2.de
server:/$ test4 2 ok
ok
ok
server:/$ help test4
Usage:
    1- test4 arg11:int arg12:string
    2- test4 arg21:string
server:/$
One note: When taking the address of an overloaded function (member or non-member), the C++
language forces you to cast that address to one of the possible types so the compiler knows,
which overload is requested. So to add a function which is overloaded in C++, each overload
needs to be added explicitly, casting to the correct type. There are some macros in
Utils/membind.hh to simplify this:
namespace fty = senf::console::factory;
void over(int);
void over(int,int);
senf::console::root().add("over", fty::Command(SENF_FNP(void, over, (int))));
senf::console::root().add("over", fty::Command(SENF_FNP(void, over, (int,int)));
class SomeModule {
unsigned int overlodedMethod() const {....};
void overlodedMethod(unsigned int) {....};
void addConsoleCommands() {
dir.node()
.add("overlodedMethod", fty::Command(
SENF_MEMBINDFNP(unsigned int, SomeModule, overlodedMethod, () const)));
dir.node()
.add("overlodedMethod", fty::Command(
SENF_MEMBINDFNP(unsigned int, SomeModule, overlodedMethod, (unsigned int))));
}
}

Attributes

As have seen so far, some documentation is automatically provided. We can add more info, by
setting additional attributes.
namespace fty = senf::console::factory;
senf::console::root().add("test5", fty::Command(&fun3)
.doc("Echo text to the console")
.overloadDoc("Repeat {arg12} for {arg11} lines") );
senf::console::root().add("test4", fty::Command(&fun2)
.overloadDoc("Echo the {arg21} argument") );
This additional info is used to provide more documentation:

server:/$ help test5
Usage:
    1- test5 arg11:int arg12:string
    2- test5 arg21:string

Echo text to the console

Variant 1:
Repeat {arg12} for {arg11} lines

Variant 2:
Echo the {arg21} argument
senf:/$

Argument attributes

Additional attributes can be set for each parameter. They are all passed to the
senf::console::ParsedArgumentAttributor::arg() attribute.
namespace kw = senf::console::kw;
namespace fty = senf::console::factory;
senf::console::root().add("test6", fty::Command(&fun3)
.doc("Echo text to the console")
.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("test6", fty::Command(&fun2)
.overloadDoc("Echo the {text} argument")
.arg( kw::name = "text" ) );
(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 <tt>using namespace
senf::console::kw</tt> declaration (but beware of name collisions).

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

server:/$ help test6
Usage:
    1- test6 n:int text:string
    2- test6 text:string

With:
    n         Number of repetitions
    text      Text to output

Echo text to the console

Variant 1:
Repeat {text} for {n} lines

Variant 2:
Echo the {text} argument
senf:/$

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 above:

namespace kw = senf::console::kw;
namespace fty = senf::console::factory;
senf::console::root().add("test6", fty::Command(&fun3)
.doc("Echo text to the console")
.overloadDoc("Repeat <text> for <n> lines");
.arg("n", "Number of repetitions")
.arg("text", "Text to output") );
senf::console::root().add("test6", fty::Command(&fun2)
.overloadDoc("Echo the <text> argument") );
.arg("text");
Keyword arguments should always be used if additional attributes are set. You can however mix
positional and keyword arguments.

Default values

Another information which can not be automatically gathered from the type system is default
values. These have to be declared explicitly:
namespace kw = senf::console::kw;
namespace fty = senf::console::factory;
senf::console::root().add("test7", fty::Command(&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") );
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. 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.

server:/$ test7 echo
echo
server:/$ test7 4 ok
ok
ok
ok
ok
server:/$ help test7
Usage:
    test4 [n:unsigned] text:string

With:
    n         Number of repetitions
        default: 1
    text      Text to output

Echo {text} to the console, repeating {text} for {n} lines
server:/$

Non-function-pointer commands

It is possible to add other callable objects besides function (and member-function)
pointers. However, since it is not possible to automatically deduce the argument and return
types in this case, the signature has to be specified explicitly:
namespace fty = senf::console::factory;
.add("test8",fty::Command<void (std::ostream &, std::string const &)>(
boost::bind(&fun3, _1, 4u, _2)));
This works with any callable object where argument types cannot be deduced automatically:
Boost.Bind expressions, Boost.Lambda expressions, functors and so on.

server:/$ test8 ok
ok
ok
ok
ok
server:/$ help test8
Usage:
    test8 arg11:string
server:/$

Attribute summary

Here a summary of the most common attributes

<table class="senf fixedwidth">

<tr><td style="width:14em">\link senf::console::ParsedArgumentAttributorBase::doc() .doc\endlink
( \e doc )</td><td>Set documentation for all overloads</td></tr>

<tr><td>\link senf::console::ParsedArgumentAttributorBase::overloadDoc()
.overloadDoc\endlink ( \e doc )</td><td>Set documentation for a specific overload</td></tr>

<tr><td>\link senf::console::ParsedArgumentAttributor::arg() .arg\endlink ( \e argument \e
attributes )</td><td>Set argument attributes (see below)</td></tr>

</table>

The most important argument attributes (all defined in the senf::console::kw namespace) are:

<table class="senf fixed width">

<tr><td style="width:14em">\link senf::console::kw::name kw::name\endlink</td><td>Parameter
name</td></tr>

<tr><td>\link senf::console::kw::description kw::description\endlink</td><td>One-line
description of the argument</td></tr>

<tr><td>\link senf::console::kw::default_value kw::default_value\endlink</td><td>Arguments
default value</td></tr>

</table>

\see <a
    href="classsenf_1_1console_1_1ParsedArgumentAttributor-members.html">senf::console::ParsedArgumentAttributor
    / List of all members</a> for the complete attribute interface \n
    \ref senf::console::kw for a list of all argument attribute keywords

Member functions

Non-static member functions are supported like non-member functions (static member functions are
identical to non-members). They must however be added through a senf::console::ScopedDirectory
instance to bind them to their instance.
namespace fty = senf::console::factory;
class Test1
{
public:
Test1(std::string label) : dir(this), label_ (label)
{ dir.add("test", fty::Command(&Test::test1, this));
dir.add("test", fty::Command(&Test::test2, this)); }
std::string test1(std::string const & text)
{ return label_ + ": " + text; }
void test2(std::ostream & os, unsigned n, std::string const & text)
{ while (n-- > 0) os << label << ": " << text << std::endl; }
private:
std::string label_;
};
// ...
Test1 test1ob ("test");
senf::console::root().add("test1ob", test1ob.dir);
Binding via senf::console::ScopedDirectory ensures, that the commands are automatically removed
from the tree when the object is destroyed.

Variables

Adding

The console/config library supports the direct registration of variables as commands. A
variable command consists of two overloads, one to query the current value and one to change the
value.
namespace fty = senf::console::factory;
class Test2
{
public:
Test2() : dir(this), var_(0)
{ dir.add("var", fty::Variable(var_) ); }
private:
int var_;
};
Test2 test2ob;
senf::console::root().add("test2ob", test2ob.dir);

This shows the most common scenario: A member variable is added to a ScopedDirectory of the same class. This ensures, that the variable command node is removed from the tree when the instance (and thereby the variable) are destroyed. The variable can now be used like any other command:

server:/$ test2ob/var
0
server:/$ test2ob/var 10
server:/$ test2ob/var
10
server:/$ help test2ob
Usage:
    1- var new_value:int
    2- var
server:/$

Read-only variables

The library also supports read-only variables. To make a variable read-only, just wrap it in \c
boost::cref() (where \c cref stands for \c const reference)
namespace fty = senf::console::factory;
int var (0);
senf::console::root().add("var1", fty::Variable(boost::cref(var)));

A read-only variable only has a single overload:

server:/$ var1
0
server:/$ help var1
Usage:
    var1
server:/$

Attributes

The most important Variable command attributes are

<table class="senf fixedwidth">

<tr><td style="width:14em">\link senf::console::VariableAttributor::doc() .doc\endlink
( \e doc )</td><td>Set variable documentation</td></tr>

<tr><td>\link senf::console::VariableAttributor::onChange() .onChange\endlink
( \e handler )</td><td>Set change handler</td></tr>

</table>

\see senf::console::VariableAttributor for the complete attribute interface

Change notification

A \e handler can be set to be called, whenever the variable is changed. It will be called with a
reference to the old value. The handler is called, after the value has been changed
namespace fty = senf::console::factory;
int var (0);
// Since this is int, it would make sense to declare the argument pass-by-value (int old)
// but for more complex args, use a const & here
void varChanged(int const & old)
{
// ...
}
senf::console::root().add("var2", fty::Variable(var)
.onChange(&varChanged) );
After this setup, \c varChanged will be called, whenever the value has changed.

Console library supported types

By default, types which can be read and written using \c iostreams are automatically supported.
This includes all the C++ built-in types as well as user defined streamable types.

An exception is made for all \c char types: These types are by default parsed as \e numeric
values not single-character data. To interpret \c char values as single-char strings, use \ref
senf::console::CharAsString.

STL container support

The %console library contains support for the STL container types: \c std::vector, \c
std::list, \c std::set, \c std::multiset, \c std::map and \c std::multimap.

All container types are parsed as parenthesized list of elements. Each element is parsed as
defined for the element type:

\c vector, \c list or \c set of integers:
(1 2 3)
\c vector, \c list or \c set of strings:
("String 1" "String 2" "String 3")
\c vector, \c list or \c set of <tt>pair<int,string></tt>:
((1 "String 1") (2 "String 2") (3 "String 3"))
Empty collection:
()
Collection's with only one element may skip the parenthesis <em>if and only if</em> the element
type does not need additional parenthesis

A \c vector, \c list or \c set of integer with one element may be written with or without
parenthesis:
(1)
1
\e but a single element \c vector, \c list or \c set of <tt>pair<int,string></tt> may \e only be
written:
((1 "String 1"))
In mapping containers, the key and value are separated by \c =:
(foo=1 bar=2 "foo bar"=3)

Boolean arguments and return values

The console library by default formats boolean values using the strings \c true and \c false for
their representation. When parsing a boolean value, most sensible representations will be
accepted:

<table class="senf">
<tr><td>\c true</td>    <td>\c false</td>    <td>\ref senf::console::formatTrueFalse</td></tr>
<tr><td>\c on</td>      <td>\c off</td>      <td>\ref senf::console::formatOnOff</td></tr>
<tr><td>\c enabled</td> <td>\c disabled</td> <td>\ref senf::console::formatEnabledDisabled</td></tr>
<tr><td>\c yes</td>     <td>\c no</td>       <td>\ref senf::console::formatYesNo</td></tr>
<tr><td><em>non-zero integer</em></td><td>\c 0</td><td>\ref senf::console::formatOneZero</td></tr>
</table>

The boolean parser will accept these values in any (mixed) case and accepts any unique initial
substring (e.g. \c Y / \c N).

The last column lists explicit formatters which can be set to customize the return value
formatting of a registered overload accordingly.

Registering enum types

Enum types are a special case, since it is not possible, to find a string representation for the
enumerator values automatically. Therefore, enum types need to be registered manually.
namespace fty = senf::console::factory;
enum MyEnum { Sit, Run, Jump };
SENF_CONSOLE_REGISTER_ENUM( MyEnum, (Sit)(Run)(Jump) );
MyEnum fun4(MyEnum v) { return v }
senf::console::root().add("test9", fty::Command(&fun4));
After an enum type is registered, it can be used like any other type for arguments or
return-values:

server:/$ test9 Sit
Sit
server:/$ test9 Crawl
argument syntax error: invalid enum value
server:/$ help test9
Usage:
    test9 arg11:MyEnum
server:/$

SENF_CONSOLE_REGISTER_ENUM() can only be used, to register enums at namespace scope. To register enums defined within some class, use SENF_CONSOLE_REGISTER_ENUM_MEMBER()

namespace fty = senf::console::factory;
class Test3
{
public:
enum Color { Red, Green, Blue };
Test3();
Color mem3(Color c) { return c }
};
SENF_CONSOLE_REGISTER_ENUM_MEMBER( Test3, Color, (Red)(Green)(Blue) );
Test3::Test3() : dir(this)
{ dir.add("test", fty::Command(&Test3::mem3, this)); }
Test3 test3ob;
senf::console::root().add("test3ob", test3ob.dir);
Using this command/type is identical

server:/$ test3ob/test Red
Red
server:/$ test3ob/test White
argument syntax error: invalid enum value
server:/$ help test3ob/test
Usage:
    test arg11:Color

Handling argument types by conversion

Sometimes an argument type is best handled by just pretending it to be of some other type. The
basic idea is, to provide an explicit signature with different (but compatible) types to the
factory:
namespace fty = senf::console::factory;
int fun4(int value)
{
return value;
}
.add("test8", fty::Command<bool (bool)>(&fun4));
Here, the type signature passed to fty::Command is different from the real type signature but it
is compatible, the conversion is handled automatically. Since the console library now sees the
argument and return value of type \c bool, the values will be parsed and formatted as boolean
values.

Special Console types

The %console library defines some special types to be used as arguments and/or return values.
Some of these are wrappers around basic types which provide custom formatting. Those are used
via argument type conversion (see previous section).

\see \ref senf_console_utilities

Extending the library to support additional types

To support or customize parsing/formatting of other types, they need to be registered. In it's
simplest case, this works, by just providing an appropriate overload for
senf_console_parse_argument() and senf_console_format_value():
struct Coordinate
{
Coordinate() : x(0), y(0) {}
Coordinate(int x_, int y_) : x(x_), y(y_) {}
int x, y;
}
Coordinate & out)
{
senf::console::parse( *(arg++), out.x );
senf::console::parse( *(arg++), out.y );
}
void senf_console_format_value(Coordinate const & value, std::ostream & os)
{
os << '(' << value.x << ' ' << value.y << ')';
}
The parser will accept an argument with two tokens which are each forwarded to the integer
parser. The senf::console::CheckedArgumentIteratorWrapper ensures two things: That all input
tokens are parsed and no extra trailing tokens are left unparsed and it checks, that all
referenced tokens really exist.

The formatter writes out the value as a parenthesized pair.
namespace fty = senf::console::factory;
Coordinate fun5(Coordinate const & p) { return Coordinate(2*p.x, 2*p.y) }
namespace kw = senf::console::kw;
.add("test10", fty::Command(&fun5))
.arg("x","coordinate to double",
kw::default_value = Coordinate())

We can now call test10 with a coordinate argument:

server:/$ test10 (2 7)
(4 14)
server:/$ help test10
Usage:
    test10 [x:Coordinate]

With:
    x         Coordinate to double
        default: (0 0)
server:/$

If you want to customize the formatting of default values differently from the formating of return-values or if you want to change the displayed name of a type, you will need to specialize the senf::console::ArgumentTraits class instead of implementing senf_console_parse_argument(). See senf::console::ArgumentTraits and senf::console::ReturnValueTraits for more.

Macro Definition Documentation

◆ SENF_CONSOLE_REGISTER_ENUM

#define SENF_CONSOLE_REGISTER_ENUM (   Type,
  Values 
)    SENF_CONSOLE_REGISTER_ENUM_(BOOST_PP_EMPTY(), BOOST_PP_EMPTY(), Type, Values)

Register enum type for argument parsing.

Enum types need to be registered explicitly to support parsing.

enum Foo { Foo1, Foo2 };
SENF_CONSOLE_REGISTER_ENUM( Foo, (Foo1)(Foo2) );

This macro will register an enum type and it's enumerators defined at namespace scope. See SENF_CONSOLE_REGISTER_ENUM_MEMBER to register a member enum type.

By default, the keys used to represent the enumerator values in the console are identical to the enumerator names in C++ (In the example above Foo1 and Foo2). You may however override this default key using the 'key("key", enumerator)' modifier:

enum Foo { Foo1, Foo2 };
SENF_CONSOLE_REGISTER_ENUM( Foo, (key("1", Foo1), Foo2) );

This will register the first enumerator Foo1 under the name '1'.

Note
All enumerator keys must be unique ignoring case.

The enum parser will accept any unique initial substring ignoring case as valid enum value.

Definition at line 179 of file Traits.hh.

◆ SENF_CONSOLE_REGISTER_ENUM_MEMBER

#define SENF_CONSOLE_REGISTER_ENUM_MEMBER (   Class,
  Type,
  Values 
)    SENF_CONSOLE_REGISTER_ENUM_(Class::, BOOST_PP_EMPTY(), Type, Values)

Register enum type for argument parsing.

Enum types need to be registered explicitly to support parsing.

class SomeClass
{
enum Foo { Foo1, Foo2 };
};
SENF_CONSOLE_REGISTER_ENUM_MEMBER( SomeClass, Foo, (Foo1)(Foo2) );

This macro will register an enum type and it's enumerators defined in a class. See SENF_CONSOLE_REGISTER_ENUM to register an enum type declared at namespace scope.

Definition at line 200 of file Traits.hh.