Skip to content
Snippets Groups Projects
Commit 7b2864f6 authored by g0dil's avatar g0dil
Browse files

Console: Documentation of the configuration support

parent 128529c2
No related branches found
No related tags found
No related merge requests found
......@@ -38,34 +38,44 @@
namespace senf {
namespace console {
/** \brief
/** \brief Combine multiple configuration sources
A ConfigBundle combines several sources and parses them together, in the order they were
added. Parse restrictions are applied uniformly to all sources.
\code
// Add three configuration sources: A system configuration file, a user configuration file
// and command line options
senf::console::ConfigBundle conf;
conf.add( senf::console::FileConfig("/etc/some.conf") );
conf.add( senf::console::FileConfig("local.conf") );
conf.add( senf::console::OptionsConfig(senf::Daemon::instance().argc(),
senf::Daemon::instance().argv()) );
conf.parse();
\endcode
This bundle may also be passed to other code which may use restricted parsing to parse
partial information from all configuration sources.
\ingroup console_access
*/
class ConfigBundle
{
public:
///////////////////////////////////////////////////////////////////////////
// Types
///////////////////////////////////////////////////////////////////////////
///\name Structors and default members
///@{
ConfigBundle();
ConfigBundle(DirectoryNode & root);
// default default constructor
// default copy constructor
// default copy assignment
// default destructor
// no conversion constructors
ConfigBundle(DirectoryNode & root); ///< Set custom root node
///@}
///////////////////////////////////////////////////////////////////////////
template <class Source>
Source & add(boost::intrusive_ptr<Source> source);
///< Add configuration source
void parse(); ///< Parse config file
/**< All nodes already parsed are skipped */
void parse(DirectoryNode & restrict); ///< Parse config file under \a restrict
......@@ -93,6 +103,12 @@ namespace console {
namespace detail {
// hrmpf ... Can't place this into Config.ih ...
/** \brief Internal: Provide ConfigBundle facade for a single-source config.
The BundleMixin is used to define supplementary configuration objects for one specific
configuration source. A BundleMixin is \e not a ConfigBundle since it has no public \c add()
member.
*/
class BundleMixin
{
public:
......
......@@ -38,6 +38,11 @@ namespace senf {
namespace console {
namespace detail {
/** \brief Internal: Executor wrapper implementing restricted execution
A RestrictedExecutor will only process commands which a re children of a given node. It does
\e not follow any links.
*/
class RestrictedExecutor
: boost::noncopyable
{
......@@ -89,6 +94,11 @@ namespace detail {
friend class RestrictGuard;
};
/** \brief Internal: Set restricted node of a RestrictedExecutor
A RestrictGuard will set the node to which to restrict. It will automatically reset the node
in it's destructor.
*/
class RestrictedExecutor::RestrictGuard
{
public:
......@@ -110,7 +120,10 @@ namespace detail {
};
/** \brief
/** \brief Internal: ConfigSource base class
All configuration sources derive from ConfigSource. A ConigSource somehow reads
configuration commands and passes them to a RestrictedExecutor.
*/
class ConfigSource
: public senf::intrusive_refcount
......
......@@ -45,7 +45,7 @@ prefix_ void senf::console::detail::ConfigFileSource::v_parse(RestrictedExecutor
///////////////////////////////////////////////////////////////////////////
prefix_ void senf::console::readConfig(std::string const & filename, DirectoryNode & root)
prefix_ void senf::console::parseFile(std::string const & filename, DirectoryNode & root)
{
ConfigFile cfg (filename, root);
cfg.parse();
......
......@@ -52,6 +52,10 @@ namespace console {
// Parse rest of the config file
cf.parse();
\endcode
If your application uses multiple configuration sources, use a ConfigBundle and FileConfig
\ingroup console_access
*/
class ConfigFile
: public detail::BundleMixin
......@@ -80,8 +84,15 @@ namespace console {
\related ConfigFile
*/
void readConfig(std::string const & filename, DirectoryNode & root = root());
void parseFile(std::string const & filename, DirectoryNode & root = root());
/** \brief ConfigBundle source reading a configuration file
This constructor is used to create a config source parsing the given configuration file to
add to a ConfigBundle.
\related ConfigFile
*/
detail::ConfigFileSource::ptr FileConfig(std::string const & filename);
}}
......
......@@ -34,6 +34,8 @@ namespace senf {
namespace console {
namespace detail {
#ifndef DOXYGEN
class ConfigFileSource : public ConfigSource
{
public:
......@@ -50,6 +52,8 @@ namespace detail {
CommandParser parser_;
};
#endif
}}}
///////////////////////////////ih.e////////////////////////////////////////
......
......@@ -33,13 +33,15 @@
There are three parts to the Config/console library:
\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
The Config/Console library is built around several components
\li The \link node_tree Node tree\endlink represents all configuration options and commands
organized in a filesystem like structure.
\li \link console_commands Actions\endlink are added to the node tree in the form of command
nodes.
\li There exist several interfaces to \link console_access access\endlink entries in the node
tree: interactive console, reading configuration files etc.
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
......@@ -62,7 +64,7 @@
namespace kw = senf::console::kw;
int main(int, char**)
int main(int argc, char** argv)
{
// Provide global documentation
senf::console::root()
......@@ -75,13 +77,25 @@
.arg("foo")
.arg(kw::name = "bar", kw::default_value = 0);
// Parse command line parameters
senf::console::parseOptions(argc,argv);
// Start the interactive console server
senf::console::Server::start(senf::INet4SocketAddress(senf::INet4Address::None, 23232u))
.name("someServer");
// Run the scheduler
senf::Scheduler::instance().process();
}
\endcode
after this registration, the console can be accessed easily via telnet:
after this registration, we can call the command from the command-line using
<pre>
$ someServer --mycommand="1 2"
</pre>
the console can be accessed easily via telnet:
<pre>
$ telnet localhost 23232
......@@ -101,44 +115,14 @@
\see \ref console_testserver for a complete example application
\section intro_init Initialization
To make the console accessible, it must be initialized when the program is started:
\code
#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::instance().process();
// Alternatively enter the main-loop via the PPI
// senf::ppi::run();
}
\endcode
\section intro_usage Access
This will start the server on IPv4 port 12345. The servers name (as displayed in the interactive
console prompt) is set to 'myserver'.
There are several ways to access the node tree:
\li By parsing configuration files
\li By parsing command line parameters
\li By providing interactive console access
After launching the application, the server can be accessed at the given port:
\htmlonly
<pre>
bash$ telnet localhost 12345
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
myserver:/$ exit
Connection closed by foreign host.
bash$
</pre>
\endhtmlonly
\see console_access
\section intro_nodes The node tree
......@@ -164,13 +148,66 @@
command.
\see \ref console_commands
*/
/** \defgroup console_access Accessing the Console/Config tree
The Console/Config library provides several ways to use the node tree to configure and control
an application.
\autotoc
\section console_access_config 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.
\subsection console_access_file 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.
\code
senf::console::parseFile("some.conf");
\endcode
To get more flexible, instantiate a senf::console::ConfigFile instance at use that to parse the
file
\code
senf::console::ConfigFile cf ("some.conf");
cf.parse();
\endcode
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.
\section intro_language The console/config language
\subsubsection console_access_file_syntax Configuration file syntax
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.
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
......@@ -186,8 +223,242 @@
\see \ref console_parser
\subsection console_access_options 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
\code
senf::console::parseOptions(argc, argv)
\endcode
or more flexibly by instantiating a senf::console::ProgramOptions class
\code
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();
\endcode
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.
\subsubsection console_access_options_syntax 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 parsed into argument tokens using the normal
config/console parser. If the option has no '=' character, the list of argument tokens will be
empty.
<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 test";</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>
</table>
The last column is additionally quoted using standard \c sh quoting: quotes in arguments need to
be additionally quoted for the shell.
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
\code
opts
.alias('c', "--mycommand",true)
.alias('C', "--mycommand=2 3");
\endcode
we can call
<pre>
$ program -C -c "4 5"
$ program -Cc"4 5"
</pre>
which is the same as
<pre>
$ program --mycommand="2 3" --mycommand="4 5"
</pre>
(Beware, that the second argument to \c alias() is \e not shell quoted).
\subsection console_access_root 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:
\section console_misc Further features
\code
senf::console::parseFile("/etc/myserver.conf", senf::console::root()['config']);
\endcode
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.
\subsection console_access_partial Partial / incremental configuration
Another feature provided by senf::console::ConfigBundle and all helper classes is partial
parsing.
\code
// Create a console/config aware object and place it into the node tree
FooObject foo;
senf::console::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();
\endcode
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.
\subsection console_access_multiple 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.
\code
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") );
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();
\endcode
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.
\section console_access_console The interactive console
To make the console accessible, it must be initialized when the program is started:
\code
#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::instance().process();
// Alternatively enter the main-loop via the PPI
// senf::ppi::run();
}
\endcode
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:
\htmlonly
<pre>
bash$ telnet localhost 12345
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
myserver:/$ exit
Connection closed by foreign host.
bash$
</pre>
\endhtmlonly
\subsection console_serverclient Server and Client objects
......@@ -232,6 +503,7 @@
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 th corresponding full
name.
*/
/** \defgroup console_commands Supported command types
......
......@@ -251,8 +251,9 @@ namespace console {
| HexString
};
Token();
Token(TokenType type, std::string token);
Token(); ///< Create empty token
Token(TokenType type, std::string token); ///< Create token with given type and value
std::string const & value() const; ///< String value of token
/**< This value is properly unquoted */
......@@ -274,16 +275,48 @@ namespace console {
std::ostream & operator<<(std::ostream & os, Token const & token);
/** \brief Create a \c None token
\related Token */
Token NoneToken();
/** \brief Create a \c PathSeparator ['/'] token
\related Token */
Token PathSeparatorToken();
/** \brief Create an \c ArgumentGroupOpen ['('] token
\related Token */
Token ArgumentGroupOpenToken();
/** \brief Create a \c ArgumentGroupClose [')'] token
\related Token */
Token ArgumentGroupCloseToken();
/** \brief Create a \c DirectoryGroupOpen ['{'] token
\related Token */
Token DirectoryGroupOpenToken();
/** \brief Create a \c DirectoryGroupClose ['}'] token
\related Token */
Token DirectoryGroupCloseToken();
/** \brief Create a \c CommandTerminator [';'] token
\related Token */
Token CommandTerminatorToken();
/** \brief Create a \c OtherPunctuation ['=', ','] token with the given \a value
\related Token */
Token OtherPunctuationToken(std::string const & value);
/** \brief Create a \c BasicString token with the given \a value
\related Token */
Token BasicStringToken(std::string const & value);
/** \brief Create a \c HexString token with the given \a value
\related Token */
Token HexStringToken(std::string const & value);
/** \brief Create a \c Word token with the given \a value
\related Token */
Token WordToken(std::string const & value);
/** \brief Single parsed console command
......@@ -343,12 +376,16 @@ namespace console {
/**< The returned range contains \e all argument tokens in a
single range not divided into separate arguments. */
void clear();
void clear(); ///< Clear all data members
void builtin(BuiltinCommand builtin);
void command(std::vector<Token> & commandPath);
void builtin(BuiltinCommand builtin); ///< Assign builtin command
void command(std::vector<Token> & commandPath); ///< Assign non-builtin command
void addToken(Token const & token);
void addToken(Token const & token); ///< Add argument token
/**< You \e must ensure, that the resulting argument tokens
are properly nested regarding '()' groups, otherwise
interpreting arguments using the arguments() call will
crash the program. */
protected:
......@@ -564,6 +601,11 @@ namespace console {
read. */
bool parseArguments(std::string arguments, ParseCommandInfo & info);
///< Parse \a argumtns
/**< parseArguments() parses the string \a arguments which
contains arbitrary command arguments (without the name
of the command). The argument tokens are written into
\a info. */
private:
struct Impl;
......
......@@ -37,6 +37,33 @@
namespace senf {
namespace console {
/** \brief Console node tree based command line option parser
A ProgramOptions instance allows flexible parsing of command line options against the
console node tree. If you just want to parse all options, the senf::console::parseOptions()
function will do that. ProgramOptions however allows to incrementally parse only a
subset of the given command line options.
\code
std::vector<std::string> args;
senf::console::ProgramOptions cf (argc, argv);
cf
.nonOptions(args)
.alias('n', "--foo-bar=x")
.alias('x', "--xxx", true);
// Parse only options under the directory of some object. The object 'ob'
// must have been registered somewhere in the node tree
cf.parse(ob.dir);
// Parse rest of the config file
cf.parse();
\endcode
If your application uses multiple configuration sources, use a ConfigBundle and
OptionsConfig.
\ingroup console_access
*/
class ProgramOptions
: public detail::BundleMixin
{
......@@ -46,20 +73,55 @@ namespace console {
///@{
ProgramOptions(int argc, char ** argv, DirectoryNode & root = root());
///< Create ProgramOptions parser for given options
/**< The given argc/argv values are those passed to main by
the operating system. Especially argv[0] is \e not an
option and is ignored. */
///@}
///////////////////////////////////////////////////////////////////////////
template <class Container>
ProgramOptions & nonOptions(Container & container);
///< Set container to add non-option arguments to
/**< \a container must have a \c clear() and a \c
push_back(std::string) member. All non-options are
added to \a container. Before parsing the command-line,
\a clear() is called. */
ProgramOptions & alias(char letter, std::string const & longOpt, bool withArg=false);
///< Set short option alias
/**< A short option is always an alias for a long option
with or without argument. if \a withArg is \c true, the
short option will expect an argument on the command
line. This argument will be appended (with an
additional '=') to \a longOpt. If \a withArg is \c
false (the default), \a longOpt may optional contain an
argument.
\param[in] letter option letter
\param[in] longOpt long option alias
\param[in] withArg \c true, if the option should take
an argument. */
private:
detail::ProgramOptionsSource & config_;
};
/** \brief Parse command line options
The command line otpions in \a argc / \a argv will be parsed, interpreting all node's
relative to \a root as root node.
\related ProgramOptions
*/
void parseOptions(int argc, char ** argv, DirectoryNode & root = root());
/** \brief ConfigBundle source reading command line options
This cosntructor is used to create aconfig source parsing the given command line options to
add to a ConfigBundle.
\related ProgramOptions
*/
detail::ProgramOptionsSource::ptr OptionsConfig(int argc, char ** argv);
}}
......
......@@ -36,6 +36,8 @@ namespace senf {
namespace console {
namespace detail {
#ifndef DOXYGEN
class ProgramOptionsSource : public ConfigSource
{
public:
......@@ -90,6 +92,8 @@ namespace detail {
boost::scoped_ptr<NonOptionContainer> nonOptions_;
};
#endif
}}}
///////////////////////////////ih.e////////////////////////////////////////
......
......@@ -40,6 +40,8 @@ namespace senf {
namespace console {
namespace detail {
#ifndef DOXYGEN
/** \brief Internal: GNU readline based ClientReader implementation
This implementation of the ClientReader interface uses GNU readline library to provide a
......@@ -107,6 +109,8 @@ namespace detail {
boost::scoped_ptr<ClientReader> reader_;
};
#endif
}}}
///////////////////////////////hh.e////////////////////////////////////////
......
......@@ -66,6 +66,8 @@ namespace console {
\implementation We do \e not provide an \c instance() member so we can easily later extend
the server to allow registering more than one instance, e.g. with each instance on a
differently firewalled port and with different security restrictions.
\ingroup console_access
*/
class Server
: boost::noncopyable
......@@ -115,6 +117,8 @@ namespace console {
Whenever a new client connects, a new instance of this class is created. This class shows a
command prompt, receives the commands, parses them and then passes (using a CommandParser)
and passes the commands to an Executor instance.
\ingroup console_access
*/
class Client
: public senf::intrusive_refcount,
......@@ -166,10 +170,13 @@ namespace console {
};
/** \brief Output Console Client instance as it's string representation
*/
\related Client
*/
std::ostream & operator<<(std::ostream & os, Client const & client);
/** \brief Output Console Client instance as it's string representation
*/
\related Client
*/
std::ostream & operator<<(std::ostream & os, Client * client);
}}
......
......@@ -548,6 +548,11 @@ p.commalist {
margin-left: 3em;
}
h4 {
font-size: 100%;
margin-bottom: .4em;
}
table.memname {
width: 100%;
font-weight: normal;
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment