namespace fty = senf::console::factory; dir.add("name", fty::Command(callback));
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 not guaranteed, you can call these members on the node reference returned by the add()
call.
namespace fty = senf::console::factory; dir.add("name", fty::Command(callback) .doc("The documentation") );
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. senf::console::CheckedArgumentIteratorWrapper args (command.arguments()); // Extract the first argument. This is again a token range. senf::console::ParseCommandInfo::TokensRange arg1Tokens ( *(args++) ); 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.
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));
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:/$
std::ostream &
. 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:/$
namespace fty = senf::console::factory; senf::console::root().add("test4", fty::Command(&fun3)); senf::console::root().add("test4", fty::Command(&fun2));
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:/$
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 { senf::console::ScopedDirectory<SomeModule> dir; 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)))); } }
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:/$
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 name of an argument, just it's type.) Every callback argument corresponds with a call of the arg()
attribute. Argument attributes are set using keywords from the 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).
You don't need to specify any information for an argument: To skip an argument, just call 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.
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 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:/$
namespace fty = senf::console::factory; senf::console::root() .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:/$
.doc ( doc ) | Set documentation for all overloads |
.overloadDoc ( doc ) | Set documentation for a specific overload |
.arg ( argument attributes ) | Set argument attributes (see below) |
The most important argument attributes (all defined in the senf::console::kw namespace) are:
kw::name | Parameter name |
kw::description | One-line description of the argument |
kw::default_value | Arguments default value |
namespace fty = senf::console::factory; class Test1 { public: senf::console::ScopedDirectory<Test1> dir; 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.
namespace fty = senf::console::factory; class Test2 { public: senf::console::ScopedDirectory<Test2> dir; Test2() : dir(this), var_(0) { dir.add("var", fty::Variable(var_) ); } private: int var_; }; Test2 test2ob; senf::console::root().add("test2ob", test2ob.dir);
server:/$ test2ob/var 0 server:/$ test2ob/var 10 server:/$ test2ob/var 10 server:/$ help test2ob Usage: 1- var new_value:int 2- var server:/$
boost::cref()
(where cref
stands for const
reference)
namespace fty = senf::console::factory; int var (0); senf::console::root().add("var1", fty::Variable(boost::cref(var)));
server:/$ var1 0 server:/$ help var1 Usage: var1 server:/$
.doc ( doc ) | Set variable documentation |
.onChange ( handler ) | Set change handler |
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, varChanged
will be called, whenever the value has changed.
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 char
types: These types are by default parsed as numeric values not single-character data. To interpret char
values as single-char strings, use senf::console::CharAsString.
std::vector
, std::list
, std::set
, std::multiset
, std::map
and std::multimap
.
All container types are parsed as parenthesized list of elements. Each element is parsed as defined for the element type:
vector
, list
or set
of integers:
(1 2 3)
vector
, list
or set
of strings:
("String 1" "String 2" "String 3")
vector
, list
or set
of pair<int,string>
:
((1 "String 1") (2 "String 2") (3 "String 3"))
Empty collection:
()
Collection's with only one element may skip the parenthesis if and only if the element type does not need additional parenthesis
A vector
, list
or set
of integer with one element may be written with or without parenthesis:
(1) 1
but a single element vector
, list
or set
of pair<int,string>
may only be written:
((1 "String 1"))
In mapping containers, the key and value are separated by =
:
(foo=1 bar=2 "foo bar"=3)
true
and false
for their representation. When parsing a boolean value, most sensible representations will be accepted:
true |
false |
senf::console::formatTrueFalse |
on |
off |
senf::console::formatOnOff |
enabled |
disabled |
senf::console::formatEnabledDisabled |
yes |
no |
senf::console::formatYesNo |
non-zero integer | 0 |
senf::console::formatOneZero |
The boolean parser will accept these values in any (mixed) case and accepts any unique initial substring (e.g. Y
/ N
).
The last column lists explicit formatters which can be set to customize the return value formatting of a registered overload accordingly.
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 }; senf::console::ScopedDirectory<Test3> dir; 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
namespace fty = senf::console::factory; int fun4(int value) { return value; } senf::console::root() .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 bool
, the values will be parsed and formatted as boolean values.
struct Coordinate { Coordinate() : x(0), y(0) {} Coordinate(int x_, int y_) : x(x_), y(y_) {} int x, y; } void senf_console_parse_argument(senf::console::ParseCommandInfo::TokensRange const & tokens, Coordinate & out) { senf::console::CheckedArgumentIteratorWrapper arg (tokens); 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; senf::console::root() .add("test10", fty::Command(&fun5)) .arg("x","coordinate to double", kw::default_value = Coordinate())
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.
7. 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... |
|
8. Namespaces |
|
namespace | senf::console::factory |
Console node factories. |
|
namespace | senf::console::kw |
Keyword argument tags. |
|
9. Defines |
|
#define | SENF_CONSOLE_REGISTER_ENUM(Type, Values) SENF_CONSOLE_REGISTER_ENUM_(BOOST_PP_EMPTY(), Type, Values) |
Register enum type for argument parsing. |
|
#define | SENF_CONSOLE_REGISTER_ENUM_MEMBER(Class, Type, Values) SENF_CONSOLE_REGISTER_ENUM_(Class::, Type, Values) |
Register enum type for argument parsing. |
#define | ||||
SENF_CONSOLE_REGISTER_ENUM | ( | 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) );
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) );
Foo1
under the name '1'
.
#define | ||||
SENF_CONSOLE_REGISTER_ENUM_MEMBER | ( | Class, | ||
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) );