To simplify the usage, there will always be three interfaces to a specific config source:
Constructor | senf::console::FileConfig() |
Class | senf::console::ConfigFile |
Helper | senf::console::parseFile() |
In it's simplest form, parsing a configuration file consists of calling senf::console::parseFile() with the name of the respective config file as argument.
senf::console::parseFile("some.conf");
To get more flexible, instantiate a senf::console::ConfigFile instance at use that to parse the file
senf::console::ConfigFile cf ("some.conf"); // The following line is optional: Call to ignore mussing files cf.ignoreMissing(); cf.parse();
If the application supports other configuration sources besides a single configuration file (like command line options) or if it supports multiple configuration files (e.g. a system-wide and a user specific configuration file) see Multiple sources and add one (or more) senf::console::FileConfig() source to a senf::console::ConfigBundle.
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.
/server/port 1234; /logger/targets/console { accept senf::log::Debug IMPORTANT; accept server::ServerLog CRITICAL; }
Constructor | senf::console::OptionsConfig() |
Class | senf::console::ProgramOptions |
Helper | senf::console::parseOptions() |
Command line options can either be parsed by calling the senf::console::parseOptions() helper
senf::console::parseOptions(argc, argv)
or more flexibly by instantiating a senf::console::ProgramOptions class
std::vector<std::string> args; senf::console::ProgramOptions opts (argc, argv); opts .nonOptions(args) .alias('c', "--mycommand",true) .alias('C', "--mycommand=2 3"); opts.parse();
This registeres two short options and accumulates all non-option arguments in args
.
If the application supports other configuration sources besides the command line options (like configuration files) see Multiple sources and add a senf::console::OptionsConfig() source to a senf::console::ConfigBundle.
See senf::console::ProgramOptions for the source specific additional parameters. These apply to senf::console::ProgramOptions and to the senf::console::OptionsConfig() source.
Options can be abbreviated at each directory boundary: A command /foo/bar/do
can be called as --f-b-d
as long as this name is unique.
Everything after the first '=' character is passed as arguments to the command. The exact interpretation depends on the command:
Command | File syntax | Option syntax |
---|---|---|
void doo() |
/path/to/doo; |
--path-to-doo |
void doo(std::string const &) |
/path/to/doo john.doe@everywhere.org; |
--path-to-doo="john.doe@everywhere.org" |
void doo(std::string const &) |
/path/to/doo "some text"; |
--path-to-doo="some text" |
void doo(std::string const &, int) |
/path/to/doo take 1; |
--path-to-doo="take 1" |
void doo(std::string const &, int) |
/path/to/doo "take two" 1; |
--path-to-doo='"take two" 1' |
Short options are registered as aliases for long options. They can be registered with or without an implied parameter and can optionally take a parameter. so after
opts .alias('c', "--mycommand",true) .alias('C', "--mycommand=2 3");
we can call
$ program -C -c "4 5" $ program -Cc"4 5"
which is the same as
$ program --mycommand="2 3" --mycommand="4 5"
(Beware, that the second argument to alias()
must not be shell quoted).
The first possibility to control this is to change the root node. This is done by
senf::console::parseFile("/etc/myserver.conf", senf::console::root()['config']);
This functionality is even more powerful by combining it with link
nodes: This allows to selectively choose commands from the node tree which are to be made accessible for configuration. See The node tree.
// Create a console/config aware object and place it (that is it's directory node) into the node // tree FooObject foo; senf::console::root().add("foo", foo.dir); // Open configuration file senf::console::ConfigFile cf ("/etc/myserver.conf"); // Parse only commands in the configuration file which are in the foo.dir directory cf.parse(foo.dir); ... // Anywhere later, parse the rest of the configuration file cf.parse();
This feature allows to parse parts of one or more configuration sources before the console/config tree has been fully established. Partial parsing can be applied any number of times to arbitrary nodes. Any command already parsed will be skipped automatically.
When combining partial parsing with chroot()
and link's
, it is important to realize, that partial parsing always applies to the real target and ignores links. This is very important: It allows a subsystem to parse it's configuration parameters irrespective of any links pointing to nodes of that subsystem.
When parsing configuration commands, especially using partial / incremental parsing, all parse commands should be applied to each configuration source in turn. This is the responsibility of senf::console::ConfigBundle.
senf::console::ScopedDirectory<> config; senf::console::root().add("config", config); // Let's enable all logger commands for configuration config.link("logger", senf::console::root()["logger"]); // Create bundle and add sources std::vector<std::string> args; senf::console::ConfigBundle conf (senf::console::root()["config"]); conf.add( senf::console::FileConfig("/etc/myserver.conf") ); conf.add( senf::console::FileConfig(".myserver.conf")->ignoreMissing() ); conf.add( senf::console::OptionsConfig(senf::Daemon::instance().argc(), senf::Daemon::instance().argv()) ) .nonOptions(args) .alias('c', "--mycommand",true) .alias('C', "--mycommand=2 3"); // Parse the logger subsystem commands in '/logger' conf.parse(senf::console::root()['logger']); ... // Parse all other configuration commands. All necessary commands and links in '/config' must by // now have been created. conf.parse();
This example parses three configuration sources: Two configuration files and additional parameters specified on the command line. All the configuration commands are placed into the /config
directory (directly or via links). The configuration sources are parsed in the order they are specified, so in this case, the command line options will override any options specified in one of the configuration files.
#include <senf/Console.hh> int main(int argc, char * argv []) { // Configure console nodes, add commands ... // Start console server senf::console::start(senf::INet4SocketAddress(12345u)) .name("myserver"); // You need to enter the scheduler main-loop for the server to work senf::scheduler::process(); // Alternatively enter the main-loop via the PPI // senf::ppi::run(); }
This will start the server on IPv4 port 12345. The servers name (as displayed in the interactive console prompt) is set to 'myserver'.
After launching the application, the server can be accessed at the given port:
bash$ telnet localhost 12345 Trying 127.0.0.1... Connected to localhost. Escape character is '^]'. myserver:/$ exit Connection closed by foreign host. bash$
It is possible to start multiple server consoles by calling start()
multiple times with different ports/addresses. Each server can be configured separately (e.g. root node, mode ...).q
int main(int, char**) { senf::console::Server & server ( senf::console::start( ... ) ); // Do something ... server.stop() }
The client instance can be accessed via the std::ostream
arg of any command callback
void someCallback(std::ostream & os, ... ) { senf::console::Client & client (senf::console::Client::get(os)); // Use the client's log target client.route<senf::log::Debug, senf::Log::IMPORTANT>(); }
The shell supports auto-cd and auto-completion: If you enter the name of a directory at the prompt, the console will change to that directory. With auto-completion, any unique beginning of a path component will be completed automatically and transparently to the corresponding full name.
By sending data immediately after opening the connection, the console is switched into non-interactive mode. In this mode, no prompt is displayed. In this mode, commands are not terminated automatically by end-of-line (CR). This allows, to easily cat an arbitrary configuration file into the network console using netcat:
$ nc -q1 localhost 23232 < some.conf
The argument -q1
makes netcat close the sending end of the connection on EOF and wait up to 1 second for the console to terminate. Even better, use netcat6
, which has full TCP half-close support.
$ echo "ls" | nc6 --half-close localhost 23232 2>/dev/null console/ server/ test/ $
Commands are executed as soon as the terminating character (';', '{' or '}') is received or when the sending end of the connection is closed.
To start a UDP server, just create an instance of the senf::console::UDPServer class
senf::console::UDPServer server (senf::INet4SocketAddress("127.0.0.1:23232"));
Commands may then be sent to this UDP console e.g. using netcat
$ echo "cd sys; ls" | nc -uq0 localhost 23232 2>/dev/null
4. Classes |
|
class | senf::console::ConfigBundle |
Combine multiple configuration sources. More... |
|
class | senf::console::ConfigFile |
Console node tree based config file parser. More... |
|
class | senf::console::ProgramOptions |
Console node tree based command line option parser. More... |
|
class | senf::console::Server |
Interactive console server. More... |
|
class | senf::console::Client |
Server client instance. More... |
|
class | senf::console::UDPServer |
UDP Console server. More... |