Accessing the Console/Config tree

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...
 

Detailed Description

The Console/Config library provides several ways to use the node tree to configure and control an application.

Configuration support

The configuration support of the Console/Config library revolves around the ConfigSource
concept. Each ConfigSource will somehow provide commands which will then be executed against the
node tree.

To simplify the usage, there will always be three interfaces to a specific config source:
\li A constructor to build a bare config source which is then added to a
    senf::console::ConfigBundle (see \ref console_access_multiple)
\li A class parsing and executing a single config source. The visible interface of this class is
    a combination of the constructor and the senf::console::ConfigBundle interfaces.
\li A helper function which will do the complete parsing of a single source with default
    parameters.

When parsing these configuration sources, it is always possible to optionally change the root
node used during parsing and it is also possible to restrict parsing to a command subset. See
\ref console_access_partial.

Configuration files

<table class="senf fixedwidth">
<tr><td><b>Constructor</b></td> <td>senf::console::FileConfig()</td></tr>
<tr><td><b>Class</b></td>       <td>senf::console::ConfigFile</td></tr>
<tr><td><b>Helper</b></td>      <td>senf::console::parseFile()</td></tr>
</table>

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 \ref console_access_multiple and add one (or more)
senf::console::FileConfig() source to a senf::console::ConfigBundle.

Configuration file syntax

Configuration files are written in a simple configuration language. This 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.
/server/port 1234;
/logger/targets/console {
accept senf::log::Debug IMPORTANT;
accept server::ServerLog CRITICAL;
}
\see \ref console_parser

Command line options

<table class="senf fixedwidth">
<tr><td><b>Constructor</b></td> <td>senf::console::OptionsConfig()</td></tr>
<tr><td><b>Class</b></td>       <td>senf::console::ProgramOptions</td></tr>
<tr><td><b>Helper</b></td>      <td>senf::console::parseOptions()</td></tr>
</table>

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;
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 \c args.

If the application supports other configuration sources besides the command line options (like
configuration files) see \ref console_access_multiple and add a senf::console::OptionsConfig()
source to a senf::console::ConfigBundle.

See \ref senf::console::ProgramOptions for the source specific additional parameters. These
apply to senf::console::ProgramOptions and to the senf::console::OptionsConfig() source.

Options syntax

Command line options are primarily parsed as long-options. Long options start with '--'. Further
'-' characters serve as directory separators if required (that is, they are \e only interpreted
as directory separator is there is no entry in the current (sub-) directory matching more than a
single name component). This still allows using hyphens in node names.

Options can be abbreviated at each directory boundary: A command <tt>/foo/bar/do</tt> can be
called as <tt>--f-b-d</tt> 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:
\li If the command only takes a single token as argument (e.g. a single string or numeric
    value), everything after the '=' sign is parsed into a single token (e.g. see rows 2 and 3
    of the following table).
\li In all other cases, the string after the '=' sign is parsed into argument tokens using the
    config/console parser. In this case, quoted strings need to be quoted twice, once for the
    shell and once for the config/console parser (e.g. see rows 4 and 5 of the following table).
\li If the option has no '=' character, the list of argument tokens will be empty (e.g. see row
    1 of the following table)

Without these rules, multi-word string arguments would \e always have to be quoted twice (for
the shell and the config/console parser).

<table style="font-size:80%" class="senf">
<tr><th>Command</th><th>File syntax</th><th>Option syntax</th></tr>

<tr>
  <td><tt>void doo()</tt></td>
  <td><tt>/path/to/doo;</tt></td>
  <td><tt>--path-to-doo</tt></td>
</tr>

<tr>
  <td><tt>void doo(std::string const &)</tt></td>
  <td><tt>/path/to/doo john.doe@everywhere.org;</tt></td>
  <td><tt>--path-to-doo="john.doe@everywhere.org"</tt></td>
</tr>

<tr>
  <td><tt>void doo(std::string const &)</tt></td>
  <td><tt>/path/to/doo "some text";</tt></td>
  <td><tt>--path-to-doo="some text"</tt></td>
</tr>

<tr>
  <td><tt>void doo(std::string const &, int)</tt></td>
  <td><tt>/path/to/doo take 1;</tt></td>
  <td><tt>--path-to-doo="take 1"</tt></td>
</tr>

<tr>
  <td><tt>void doo(std::string const &, int)</tt></td>
  <td><tt>/path/to/doo "take two" 1;</tt></td>
  <td><tt>--path-to-doo='"take two" 1'</tt></td>
</tr>
</table>

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 \c alias() must \e not be shell quoted).

Changing the root node

When used in it's default state, parsing will always interpret all commands relative to the
senf::console::root() node and will parse a file completely.

The first possibility to control this is to change the root node. This is done by
\li passing that root node to the helper class or to the parse helper as an additional argument
    (see the respective documentation).
\li passing it to the senf::console::ConfigBundle constructor when parsing multiple sources.

for example:
senf::console::parseFile("/etc/myserver.conf", senf::console::root()['config']);
This functionality is even more powerful by combining it with \c link nodes: This allows to
selectively choose commands from the node tree which are to be made accessible for
configuration. See \ref node_tree.

Partial / incremental configuration

Another feature provided by senf::console::ConfigBundle and all helper classes is partial
parsing.
// 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 \c chroot() and \c link's, it is important to realize, that
<em>partial parsing always applies to the \e real target and ignores links</em>. This is very
important: It allows a subsystem to parse it's configuration parameters irrespective of any
links pointing to nodes of that subsystem.

Multiple sources

Most of the time, an application will utilize multiple configuration sources: A global
configuration file, maybe a user specific local configuration file, command line options ...

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::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;
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(),
.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
<tt>/config</tt> 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.

The network console

To make the network console accessible, it must be initialized when the program is started:
#include <senf/Console.hh>
int main(int argc, char * argv [])
{
// Configure console nodes, add commands ...
// Start console server
.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

Server and Client objects

The senf::console::Server and senf::console::Client objects offer further API calls. To access
the server instance you need to store away the senf::console::Server reference returned when
starting the server so you can later refer to it:
int main(int, char**)
{
// Do something ...
server.stop()
}
The client instance can be accessed via the \c std::ostream arg of any command callback
void someCallback(std::ostream & os, ... )
{
// Use the client's log target
client.route<senf::log::Debug, senf::Log::IMPORTANT>();
}
\see
    senf::console::Server for the Server API \n
    <a href="classsenf_1_1console_1_1Client-members.html">senf::console::Client / List of all
    members</a> for the Client API

The interactive console shell

The interactive shell implements a fully function line editor on capable terminals. This support
is available when using a full featured telnet client on a fully supported terminal (like vt100
or xterm).

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.

Non-interactive network console

After a new connection is established, the console server waits a short time for data to arrive.
Only if nothing happens in the first 500ms, an interactive session is initialized.

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 \e 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 <tt>-q1</tt> 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 \c 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.

Non-interactive UDP console

The UDP console allows to script the console tree via UDP packets. Every UDP packet must be a
complete command (or sequence of commands). The combined reply of all these commands will be
returned in a single UDP packet. This reply can be disabled or directed to a different address.

To start a UDP server, just create an instance of the senf::console::UDPServer class

(Remember to enter the scheduler main-loop for processing)

Commands may then be sent to this UDP console e.g. using netcat

$ echo "cd sys; ls" | nc -uq0 localhost 23232 2>/dev/null
\see senf::console::UDPServer