Test Server

You can find this code in the testServer.cc source file.

#include <iostream>
#include <senf/Utils/Console.hh>
#include <senf/Scheduler/Scheduler.hh>

namespace kw = senf::console::kw;

Here we see the necessary include files. For console support only senf/Console.hh needs to be included. This will pull in all necessary definitions.

We declare an alias for the senf::console::kw namespace which we will use further down for the keyword arguments.

namespace fty = senf::console::factory;

void echo(std::ostream & output, senf::console::ParseCommandInfo const & command)
{
    typedef senf::console::ParseCommandInfo::TokensRange::iterator iterator;
    iterator i (command.tokens().begin());
    iterator i_end (command.tokens().end());
    for (; i != i_end; ++i) {
        output << i->value() << ' ';
    }
    output << "\n";
}

The first callback echo utilizes manual argument parsing. You should always refer to the iterator types as shown here since this will be safe from future changes.

The following struct TestObject is used to show how member functions and objects are used in the console.

Important is the definition of the public senf::console::ScopedDirectory member called dir. This member can be used later to add the class to the node tree. Here we just register a single function vat (with automatic argument parsing) and set some command attributes.

    TestObject()
        : dir(this)
        {
            dir.add("vat", fty::Command(&TestObject::vat, this)
                    .arg("vat", "VAT in %", kw::default_value = 19)
                    .arg("amount", "Amount including VAT")
                    .doc("Returns the amount of {vat}-% VAT included in {amount}") );
        }

    double vat (int vat, double amount)
        {
            return amount * vat/(100.0+vat);
        }
};

The shutdownServer callback terminates the server.

This happens in two steps: First the terminate() call tells the scheduler to leave it's main-loop after shutdownServer returns (which is ultimately called via the console server from the scheduler). Throwing a senf::console::Executor::ExitException is like entering the exit built-in command at the console.

The next callback accesses the client instance directly to manipulate the logging:

void enableLogging(std::ostream & os)
{
    senf::console::Client::get(os).route<senf::log::NOTICE>();
}

The senf::console::Client instance can be accessed using the senf::console::Client::get() helper via the output stream. Since every Client is a senf::log::Target, we can route arbitrary log messages to the console instance.

We now define main() which initializes the node tree and starts the console server

int main(int, char **)
{
    ::signal(SIGPIPE, SIG_IGN);
    senf::log::ConsoleTarget::instance().route< senf::log::VERBOSE >();

Here we just setup more verbose logging and set SIGPIPE signals to be ignored. SIGPIPE's are a pain and really should be disabled.

    senf::console::root()
        .doc("This is the console test application");

    senf::console::root()
        .add("console",fty::Directory()
             .doc("Console settings"));

This shows, how to set the top-level documentation and create a new subdirectory.

    senf::console::DirectoryNode & serverDir (
        senf::console::root()
        .add("server",fty::Directory()
             .doc("server commands")));

Here we create another new directory and save a reference so we can later access the node directly. All the add commands return such a node reference of the correct type (this is a lie, but it works like this anyways and it's an implementation detail that must not concern you here).

Instead of creating a new directory directly and later sotring a reference, it is better to use senf::console::ScopedDirectory<> like this:

    senf::console::ScopedDirectory<> testDir;
    senf::console::root()
        .add("test", testDir)
        .doc("Test functions");

This will automatically remove the node from the tree when the senf::console::ScopedDirectory instance is destroyed. It also protects against the problem of dangling references: When using a plain reference, removing the directory from the tree will destroy the node. The reference however will still reference the (now nonexistent) directory and any access via the reference will crash the program.

The next statements add commands to the various directories declared so far

    senf::console::root()["console"]
        .add("showlog", fty::Command(&enableLogging)
             .doc("Enable display of log messages on the current console"));

    senf::console::root().add("sl", fty::Link(senf::console::root()["console"]("showlog")));

    serverDir
        .add("shutdown", fty::Command(&shutdownServer)
             .doc("Terminate server application"));

    testDir
        .add("echo", fty::Command(&echo)
             .doc("Example of a function utilizing manual argument parsing"));

We now continue by creating an instance of our test class TestObject

    TestObject test;
    testDir
        .add("extra", test.dir)
        .doc("Example of an instance directory");

We add that objects directory to the test dir. We now have created a directory structure like tis:

/
  console/
    showlog
  server/
    shutdown
  test/
    echo
    testob/
      vat

We now start the server (giving it a nice descriptive name) and run the scheduler.

    senf::console::root().add("ex", fty::Link(test.dir));

    senf::console::Server::start( senf::INet4SocketAddress(23232u) )
        .name("testServer");

    senf::scheduler::process();
    return 0;
}