Skip to content
Snippets Groups Projects
Commit 72d5f6e8 authored by g0dil's avatar g0dil
Browse files

Console: Add 'Variable' command 'onChange' implementation

Console: 'Variable' command documentation
parent c83592da
No related branches found
No related tags found
No related merge requests found
......@@ -358,6 +358,21 @@
server:/$
</pre>
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:
\code
void over(int);
void over(int,int);
senf::console::root()
.add("over", static_cast<void (*)(int)>(&over));
senf::console::root()
.add("over", static_cast<void (*)(int,int)>(&over));
\endcode
\subsection console_attributes Attributes
As have seen so far, some documentation is automatically provided. We can add more info, by
......@@ -583,21 +598,19 @@
Member functions are supported like non-member functions. They must however be added through a
senf::console::ScopedDirectory instance to bind them to their instance.
\code
class Test
class Test1
{
public:
senf::console::ScopedDirectory<Test> dir;
Test(std::string label) : dir(this), label_ (label)
{
dir.add("test4", &Test::test2);
dir.add("test4", &Test::test3);
}
senf::console::ScopedDirectory<Test1> dir;
std::string test2(std::string const & text)
Test1(std::string label) : dir(this), label_ (label)
{ dir.add("test", &Test::test1);
dir.add("test", &Test::test2); }
std::string test1(std::string const & text)
{ return label_ + ": " + text; }
void test3(std::ostream & os, unsigned n, std::string const & text)
void test2(std::ostream & os, unsigned n, std::string const & text)
{ while (n-- > 0) os << label << ": " << text << std::endl; }
private:
......@@ -606,12 +619,117 @@
// ...
Test testOb ("test");
senf::console::root().add("testobj", testOb.dir);
Test1 test1ob ("test");
senf::console::root().add("test1ob", test1ob.dir);
\endcode
Binding via senf::console::ScopedDirectory ensures, that the commands are automatically removed
from the tree when the object is destroyed.
\section console_variables Variables
\subsection console_varadd 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.
\code
class Test2
{
public:
senf::console::ScopedDirectory<Test2> dir;
Test2() : dir(this), var_(0)
{ dir.add("var", var_); }
private:
int var_;
};
Test2 test2ob;
senf::console::root().add("test2ob", test2ob.dir);
\endcode
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:
\htmlonly
<pre>
server:/$ test2ob/var
0
server:/$ test2ob/var 10
server:/$ test2ob/var
10
server:/$ help test2ob
Usage:
1- var new_value:int
2- var
server:/$
</pre>
\endhtmlonly
\subsection console_varro 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)
\code
int var (0);
senf::console::root().add("var1", boost::cref(var));
\endcode
A read-only variable only has a single overload:
\htmlonly
<pre>
server:/$ var1
0
server:/$ help var1
Usage:
var1
server:/$
</pre>
\endhtmlonly
\subsection console_varattr 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
\subsection console_varchange 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
\code
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",var)
.onChange(&varChanged);
\endcode
After this setup, \c varChanged will be called, whenever the value has changed.
\see senf::console::VariableAttributor for the complete attribute interface
*/
......
......@@ -310,6 +310,8 @@ namespace senf {
namespace console {
namespace detail {
#ifndef DOXYGEN
struct ParsedCommandAddNodeAccess
{
template <class Attributor, class Node>
......@@ -369,6 +371,8 @@ namespace detail {
cmdNode.add( CreateParsedCommandOverload<CmdTraits>::create(fn) ) );
}
#endif
}}}
template <class Function>
......
......@@ -95,6 +95,8 @@ public:
template <unsigned n>
detail::ArgumentInfo<typename boost::mpl::at_c<arg_types, n>::type> & arg() const;
void function(Function fn);
protected:
private:
......@@ -131,6 +133,8 @@ public:
template <unsigned n>
detail::ArgumentInfo<typename boost::mpl::at_c<arg_types, n>::type> & arg() const;
void function(Function fn);
protected:
private:
......@@ -177,6 +181,14 @@ arg() const
typename boost::mpl::at_c<arg_types, n>::type > & >(arg(n));
}
template <class FunctionTraits, class ReturnValue>
void
senf::console::ParsedCommandOverload<FunctionTraits, ReturnValue, BOOST_PP_ITERATION() >::
function(Function fn)
{
function_ = fn;
}
template <class FunctionTraits, class ReturnValue>
prefix_
senf::console::ParsedCommandOverload<FunctionTraits, ReturnValue, BOOST_PP_ITERATION()>::
......@@ -210,6 +222,14 @@ arg() const
typename boost::mpl::at_c<arg_types, n>::type > & >(arg(n));
}
template <class FunctionTraits>
void
senf::console::ParsedCommandOverload<FunctionTraits, void, BOOST_PP_ITERATION() >::
function(Function fn)
{
function_ = fn;
}
template <class FunctionTraits>
prefix_
senf::console::ParsedCommandOverload<FunctionTraits, void, BOOST_PP_ITERATION() >::
......
......@@ -49,8 +49,9 @@ prefix_ Variable const & senf::console::detail::QueryVariable<Variable>::operato
// senf::console::detail::SetVariable<Variable>
template <class Variable>
prefix_ senf::console::detail::SetVariable<Variable>::SetVariable(Variable & var)
: var_ (var)
prefix_ senf::console::detail::SetVariable<Variable>::SetVariable(Variable & var,
OnChangeHandler handler)
: var_ (var), handler_ (handler)
{}
template <class Variable>
......@@ -66,12 +67,6 @@ prefix_ void senf::console::detail::SetVariable<Variable>::operator()(Variable c
var_ = value;
}
template <class Variable>
prefix_ void senf::console::detail::SetVariable<Variable>::onChange(OnChangeHandler handler)
{
handler_ = handler;
}
///////////////////////////////////////////////////////////////////////////
// senf::console::ConstVariableAttributor<Variable>
......@@ -116,6 +111,15 @@ senf::console::VariableAttributor<Variable>::typeName(std::string const & name)
return *this;
}
template <class Variable>
prefix_ typename senf::console::VariableAttributor<Variable>
senf::console::VariableAttributor<Variable>::onChange(OnChangeHandler handler)
{
setOverload_.function(
boost::bind(detail::SetVariable<Variable>(var_, handler),_2));
return *this;
}
template <class Variable>
prefix_ typename senf::console::VariableAttributor<Variable>
senf::console::VariableAttributor<Variable>::doc(std::string const & doc)
......@@ -135,8 +139,9 @@ senf::console::VariableAttributor<Variable>::formatter(Formatter formatter)
template <class Variable>
prefix_
senf::console::VariableAttributor<Variable>::VariableAttributor(QueryOverload & queryOverload,
SetOverload & setOverload)
: ConstVariableAttributor<Variable> (queryOverload), setOverload_ (setOverload)
SetOverload & setOverload,
Variable & var)
: ConstVariableAttributor<Variable> (queryOverload), setOverload_ (setOverload), var_ (var)
{}
///////////////////////////////////////////////////////////////////////////
......@@ -156,7 +161,7 @@ senf::console::detail::VariableNodeCreator<Variable,isConst>::add(DirectoryNode
node.add(name, typename detail::QueryVariable<Variable>::Function(
detail::QueryVariable<Variable>(var))).overload() );
return VariableAttributor<Variable>(queryOverload, setOverload);
return VariableAttributor<Variable>(queryOverload, setOverload, var);
}
template <class Variable>
......
......@@ -52,6 +52,10 @@ namespace console {
senf_console_add_node(DirectoryNode & node, std::string const & name,
boost::reference_wrapper<Variable> var, int);
/** \brief Variable command attributes (const)
\see VariableAttributor
*/
template <class Variable>
class ConstVariableAttributor
{
......@@ -72,32 +76,92 @@ namespace console {
friend class detail::VariableNodeCreator<Variable const>;
};
template <class Variable>
/** \brief Variable command attributes
Variable commands allow to register any arbitrary variable as a command node. The variable
will be registered as two command overloads: One which takes a single argument of the
variables type to set the variable and another one taking no arguments and just querying the
current variable value.
\code
int var;
ScopedDirectory<> dir;
dir.add("var", var);
\endcode
Variables should be registered only with a ScopedDirectory declared in the same scope
(e.g. as a class member for member variables). This ensures, that the variable node is
removed from the tree when the scope is destroyed.
Since a variable command is added as a combination of two ordinary overloads, it is possible
to register additional overloads with the same name before or after registering the
variable.
It is also possible, to register a variable read-only. To achieve this, just wrap it with \c
boost::cref(). Such a variable cannot be changed only queried. Therefore, it does not have
the parser() and typeName() attributes.
\code
dir.add("const_var", boost::cref(var))
\endcode
\ingroup console_commands
*/
template <class Variable>
class VariableAttributor
: public ConstVariableAttributor<Variable>
{
public:
typedef typename detail::SetVariable<Variable>::Traits::Overload SetOverload;
typedef typename detail::ArgumentInfo<typename SetOverload::arg1_type>::Parser Parser;
typedef typename detail::SetVariable<Variable>::OnChangeHandler OnChangeHandler;
typedef OverloadedCommandNode node_type;
typedef VariableAttributor return_type;
typedef typename ConstVariableAttributor<Variable>::Formatter Formatter;
typedef typename ConstVariableAttributor<Variable>::QueryOverload QueryOverload;
VariableAttributor parser(Parser parser);
VariableAttributor typeName(std::string const & name);
VariableAttributor doc(std::string const & doc);
VariableAttributor formatter(Formatter formatter);
VariableAttributor doc(std::string const & doc); ///< Set documentation of the variable
VariableAttributor formatter(Formatter formatter); ///< Set formatter
/**< The \a formatter must be a callable with a signature
compatible with
\code
void formatter(Variable const & value, std::ostream & os);
\endcode
The \a formatter takes the return value of the call \a
value and writes it properly formated to \a os. */
VariableAttributor parser(Parser parser); ///< Set argument parser
/**< The parser is an arbitrary callable object with
the signature
\code
void parser(senf::console::ParseCommandInfo::TokensRange const & tokens, value_type & out);
\endcode
where \c value_type is the type of the overload
parameter. The parser must read and parse the complete
\a tokens range and return the parsed value in \a
out. If the parser fails, it must raise a
senf::console::SyntaxErrorException. */
VariableAttributor typeName(std::string const & name); ///< Set name of the variable type
VariableAttributor onChange(OnChangeHandler handler); ///< Set change callback
/**< The \a handler callback is called, whenever the value
of the variable is changed. The new value has already
been set, when the callback is called, the old value is
passed to the callback. The callback must have a
signature compatible to
\code
void handler(Variable const & oldValue);
\endcode */
protected:
private:
VariableAttributor(QueryOverload & queryOverload, SetOverload & setOverload);
VariableAttributor(QueryOverload & queryOverload, SetOverload & setOverload,
Variable & var);
SetOverload & setOverload_;
Variable & var_;
friend class detail::VariableNodeCreator<Variable>;
};
......
......@@ -40,12 +40,15 @@ namespace console {
namespace detail {
#ifndef DOXYGEN
template <class Variable>
struct QueryVariable
{
typedef Variable const & Signature ();
typedef boost::function<Signature> Function;
typedef detail::ParsedCommandTraits<Signature> Traits;
typedef Variable const & result_type;
QueryVariable(Variable const & var);
......@@ -61,11 +64,11 @@ namespace detail {
typedef boost::function<Signature> Function;
typedef detail::ParsedCommandTraits<Signature> Traits;
typedef boost::function<void (Variable const &)> OnChangeHandler;
typedef void result_type;
SetVariable(Variable & var);
SetVariable(Variable & var, OnChangeHandler handler = OnChangeHandler());
void operator()(Variable const & value) const;
void onChange(OnChangeHandler handler);
Variable & var_;
OnChangeHandler handler_;
......@@ -87,6 +90,8 @@ namespace detail {
Variable & var);
};
#endif
}}}
///////////////////////////////ih.e////////////////////////////////////////
......
......@@ -46,6 +46,10 @@ namespace {
void testFormatter(int value, std::ostream & os)
{ os << '[' << value << ']'; }
static bool changed_ (false);
void testCallback(int oldValue)
{ changed_ = true; }
}
BOOST_AUTO_UNIT_TEST(variables)
......@@ -62,10 +66,12 @@ BOOST_AUTO_UNIT_TEST(variables)
.doc("Current blorg limit")
.formatter(&testFormatter)
.parser(&testParser)
.typeName("number");
.typeName("number")
.onChange(&testCallback);
parser.parse("test/var; test/var 10; test/var",
boost::bind<void>( boost::ref(executor), boost::ref(ss), _1 ));
BOOST_CHECK_EQUAL( ss.str(), "[5]\n[0]\n" );
BOOST_CHECK( changed_ );
ss.str("");
dir("var").help(ss);
......
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