From: g0dil Date: Wed, 26 Mar 2008 23:31:13 +0000 (+0000) Subject: Console: Add lots of documentation X-Git-Url: http://g0dil.de/git?a=commitdiff_plain;h=c70f7413515b513656f850f51a3cc2ea9d776a37;p=senf.git Console: Add lots of documentation Console: Code cleanup Console: Add automatic node naming git-svn-id: https://svn.berlios.de/svnroot/repos/senf/trunk@767 270642c3-0616-0410-b53a-bc976706d245 --- diff --git a/Console/Executor.cc b/Console/Executor.cc index b808a09..517c46c 100644 --- a/Console/Executor.cc +++ b/Console/Executor.cc @@ -46,7 +46,7 @@ namespace { /////////////////////////////////////////////////////////////////////////// // senf::console::Executor -prefix_ bool senf::console::Executor::operator()(ParseCommandInfo const & command, +prefix_ void senf::console::Executor::operator()(ParseCommandInfo const & command, std::ostream & output) { SENF_LOG(( "Executing: " << command )); @@ -57,7 +57,7 @@ prefix_ bool senf::console::Executor::operator()(ParseCommandInfo const & comman try { switch(command.builtin()) { case ParseCommandInfo::NoBuiltin : - traverseToCommand(command.commandPath())(output, command.arguments()); + traverseCommand(command.commandPath())(output, command.arguments()); break; case ParseCommandInfo::BuiltinCD : @@ -72,14 +72,15 @@ prefix_ bool senf::console::Executor::operator()(ParseCommandInfo const & comman } else { oldCwd_ = cwd_; - cwd_ = traverseTo(command.arguments().begin()[0]).thisptr(); + cwd_ = traverseDirectory(command.arguments().begin()[0]).thisptr(); } } break; case ParseCommandInfo::BuiltinLS : { - DirectoryNode const & dir ( - command.arguments().empty() ? cwd() : traverseTo(command.arguments().begin()[0])); + DirectoryNode const & dir ( command.arguments() + ? traverseDirectory(command.arguments().begin()[0]) + : cwd() ); for (DirectoryNode::child_iterator i (dir.children().begin()); i != dir.children().end(); ++i) { output << i->first; @@ -93,7 +94,7 @@ prefix_ bool senf::console::Executor::operator()(ParseCommandInfo const & comman case ParseCommandInfo::BuiltinPUSHD : dirstack_.push_back(cwd_); if ( command.arguments() ) - cwd_ = traverseTo(command.arguments().begin()[0]).thisptr(); + cwd_ = traverseDirectory(command.arguments().begin()[0]).thisptr(); break; case ParseCommandInfo::BuiltinPOPD : @@ -107,52 +108,60 @@ prefix_ bool senf::console::Executor::operator()(ParseCommandInfo const & comman throw ExitException(); case ParseCommandInfo::BuiltinHELP : - try { - GenericNode & node ( - command.arguments() - ? cwd().traverse( - boost::make_iterator_range( - boost::make_transform_iterator(command.arguments().begin()[0].begin(), TraverseTokens()), - boost::make_transform_iterator(command.arguments().begin()[0].end(), TraverseTokens()))) - : cwd() ); - node.help(output); - output << std::flush; - } - catch (UnknownNodeNameException &) { - output << "invalid path" << std::endl; - } + GenericNode const & node (command.arguments() + ? traverseNode(command.arguments().begin()[0]) + : cwd()); + output << prettyName(typeid(node)) << " at " << node.path() << "\n\n"; + node.help(output); + output << std::flush; break; + } } + catch (InvalidPathException &) { + output << "invalid path" << std::endl; + } catch (InvalidDirectoryException &) { output << "invalid directory" << std::endl; } catch (InvalidCommandException &) { output << "invalid command" << std::endl; } - return true; +} + +prefix_ senf::console::GenericNode & +senf::console::Executor::traverseNode(ParseCommandInfo::argument_value_type const & path) +{ + try { + return cwd().traverse( + boost::make_iterator_range( + boost::make_transform_iterator(path.begin(), TraverseTokens()), + boost::make_transform_iterator(path.end(), TraverseTokens()))); + } + catch (std::bad_cast &) { + throw InvalidPathException(); + } + catch (UnknownNodeNameException &) { + throw InvalidPathException(); + } } prefix_ senf::console::DirectoryNode & -senf::console::Executor::traverseTo (ParseCommandInfo::argument_value_type const & path) +senf::console::Executor::traverseDirectory(ParseCommandInfo::argument_value_type const & path) { try { - return dynamic_cast( - cwd().traverse( - boost::make_iterator_range( - boost::make_transform_iterator(path.begin(), TraverseTokens()), - boost::make_transform_iterator(path.end(), TraverseTokens())))); + return dynamic_cast( traverseNode(path) ); } catch (std::bad_cast &) { throw InvalidDirectoryException(); } - catch (UnknownNodeNameException &) { + catch (InvalidPathException &) { throw InvalidDirectoryException(); } } prefix_ senf::console::CommandNode & -senf::console::Executor::traverseToCommand(ParseCommandInfo::CommandPathRange const & path) +senf::console::Executor::traverseCommand(ParseCommandInfo::CommandPathRange const & path) { try { return dynamic_cast( cwd().traverse(path) ); diff --git a/Console/Executor.hh b/Console/Executor.hh index 189eece..93071ac 100644 --- a/Console/Executor.hh +++ b/Console/Executor.hh @@ -38,7 +38,21 @@ namespace senf { namespace console { - /** \brief + /** \brief Execute config/console commands + + The Executor interprets parsed config/console commands and executes them. It manages the + current execution context (current directory, directory stack and so on). + + The executor is normally called directly by the parser callback for each command. + + Executing the built-in 'exit' command will throw Executor::ExitException. This exception + (which is not derived from std::exception since it's not a real exception) must be handled + by the caller. + + All directories are managed using weak pointers. If any of the directories expires (current + directory, directory stack, last directory) it will be replaced with the root + directory. Directories expire when they are destructed or when they are detached from the + config tree root node. */ class Executor : boost::noncopyable @@ -51,7 +65,7 @@ namespace console { typedef boost::iterator_range< ParseCommandInfo::argument_iterator> Arguments; - struct ExitException {}; // NOT derived from std::exception ! + struct ExitException {}; ///< Thrown by built-in 'exit' command /////////////////////////////////////////////////////////////////////////// //\/name Structors and default members @@ -62,15 +76,19 @@ namespace console { ///\} /////////////////////////////////////////////////////////////////////////// - bool operator()(ParseCommandInfo const & command, std::ostream & output); - DirectoryNode & cwd() const; + void operator()(ParseCommandInfo const & command, std::ostream & output); + ///< Execute command + /**< Output will be written to \a output. */ + DirectoryNode & cwd() const; ///< Current working directory protected: private: - DirectoryNode & traverseTo(ParseCommandInfo::argument_value_type const & path); - CommandNode & traverseToCommand(ParseCommandInfo::CommandPathRange const & path); + GenericNode & traverseNode(ParseCommandInfo::argument_value_type const & path); + DirectoryNode & traverseDirectory(ParseCommandInfo::argument_value_type const & path); + CommandNode & traverseCommand(ParseCommandInfo::CommandPathRange const & path); + struct InvalidPathException {}; struct InvalidDirectoryException {}; struct InvalidCommandException {}; diff --git a/Console/Node.cc b/Console/Node.cc index f21ab66..f74a37b 100644 --- a/Console/Node.cc +++ b/Console/Node.cc @@ -73,20 +73,27 @@ senf::console::DirectoryNode::remove(std::string const & name) throw UnknownNodeNameException() << ": '" << name << "'"; GenericNode::ptr node (i->second); children_.erase(i); + node->parent_ = 0; return node; } prefix_ void senf::console::DirectoryNode::add(GenericNode::ptr node) { BOOST_ASSERT( ! node->parent() ); + if (node->name().empty()) { + node->name("unnamed"); + SENF_LOG((senf::log::MESSAGE)("Adding 'unnamed' node")); + } if (children_.find(node->name()) != children_.end()) { unsigned suffix (0); std::string newName; do { ++suffix; - newName = node->name() + boost::lexical_cast(suffix); + newName = node->name() + "-" + boost::lexical_cast(suffix); } while (children_.find(newName) != children_.end()); - name(*node, newName); + SENF_LOG((senf::log::MESSAGE)("Uniquifying node '" << node->name() << "' to '" + << newName << "'")); + node->name(newName); } children_.insert(std::make_pair(node->name(),node)); node->parent_ = this; diff --git a/Console/Node.cci b/Console/Node.cci index 915d954..43954f3 100644 --- a/Console/Node.cci +++ b/Console/Node.cci @@ -52,11 +52,6 @@ prefix_ void senf::console::GenericNode::name(std::string const & name) name_ = name; } -prefix_ void senf::console::GenericNode::name(GenericNode & node, std::string const & name) -{ - node.name_ = name; -} - prefix_ boost::shared_ptr senf::console::GenericNode::parent() const { @@ -66,8 +61,10 @@ prefix_ boost::shared_ptr senf::console::GenericNo prefix_ senf::console::GenericNode::ptr senf::console::GenericNode::unlink() { - SENF_ASSERT( parent() ); - return parent()->remove(name()); + if (parent_) + return parent()->remove(name()); + else + return thisptr(); } prefix_ void senf::console::GenericNode::help(std::ostream & output) @@ -90,10 +87,9 @@ prefix_ senf::console::GenericNode::cptr senf::console::GenericNode::thisptr() /////////////////////////////////////////////////////////////////////////// // senf::console::DirectoryNode -prefix_ std::auto_ptr -senf::console::DirectoryNode::create() +prefix_ senf::console::DirectoryNode::ptr senf::console::DirectoryNode::create() { - return std::auto_ptr(new DirectoryNode()); + return ptr(new DirectoryNode()); } prefix_ senf::console::DirectoryNode & @@ -113,8 +109,7 @@ senf::console::DirectoryNode::operator()(std::string const & name) prefix_ senf::console::DirectoryNode & senf::console::DirectoryNode::mkdir(std::string const & name) { - std::auto_ptr node (create()); - return add(name, node); + return add(name, create()); } prefix_ senf::console::DirectoryNode::ChildrenRange senf::console::DirectoryNode::children() @@ -174,10 +169,10 @@ prefix_ senf::console::SimpleCommandNode::SimpleCommandNode(Function const & fn) : fn_ (fn) {} -prefix_ std::auto_ptr +prefix_ senf::console::SimpleCommandNode::ptr senf::console::SimpleCommandNode::create(Function const & fn) { - return std::auto_ptr(new SimpleCommandNode(fn)); + return ptr(new SimpleCommandNode(fn)); } prefix_ senf::console::SimpleCommandNode & diff --git a/Console/Node.cti b/Console/Node.cti index c56f014..ef80237 100644 --- a/Console/Node.cti +++ b/Console/Node.cti @@ -47,16 +47,6 @@ senf::console::NodeCreateTraits::Creator::create(DirectoryNode & node, template prefix_ NodeType & senf::console::DirectoryNode::add(std::string const & name, - std::auto_ptr node) -{ - GenericNode::ptr p (node); - p->name(name); - add(p); - return static_cast(*p); -} - -template -prefix_ NodeType & senf::console::DirectoryNode::add(std::string const & name, boost::shared_ptr node) { SENF_ASSERT( ! node->parent() ); diff --git a/Console/Node.hh b/Console/Node.hh index 10ac02e..cf48403 100644 --- a/Console/Node.hh +++ b/Console/Node.hh @@ -37,6 +37,7 @@ #include #include "../Utils/Exception.hh" #include "../Utils/mpl.hh" +#include "../Utils/Logger/SenfLog.hh" #include "Parse.hh" //#include "Node.mpp" @@ -50,11 +51,25 @@ namespace console { class DirectoryNode; class CommandNode; - /** \brief + /** \brief Config/console node tree base-class + + GenericNode is the base class of all node objects. There are two basic node types derived + from GenericNode: DirectoryNode and CommandNode. + + All nodes are dynamically allocated and automatically managed using reference counting. + + All nodes are normally linked into a single tree which root node is + senf::console::root(). Nodes may however be orphaned (not linked to the tree) either + directly (the node has no parent) or indirectly (the node has a parent but is part of an + orphaned subtree which is not linked to the root node). + + Every active (non-orphaned) node (except the root() node) has a non-empty node name. This + name is assigned to the node when adding the node to the tree. */ class GenericNode : public boost::enable_shared_from_this { + SENF_LOG_CLASS_AREA(); public: /////////////////////////////////////////////////////////////////////////// // Types @@ -67,39 +82,56 @@ namespace console { virtual ~GenericNode(); - std::string const & name() const; - boost::shared_ptr parent() const; + std::string const & name() const; ///< Node name + boost::shared_ptr parent() const; ///< Parent node + /**< May be null, if the node is the root node or if it is + not linked to the tree */ - std::string path() const; + std::string path() const; ///< Node path + /**< The node path is built by joining the names of all + parent nodes with '/' chars. */ - ptr unlink(); + ptr unlink(); ///< Remove node from it's parent directory + /**< You may either discard the return value and thereby + dispose the node or may re-attach the node at some + other place using DirectoryNode::add(). */ - bool active() const; + bool active() const; ///< \c true, if node is attached to the root() node - void help(std::ostream & output) const; + void help(std::ostream & output) const; /// Write help info to \a output - ptr thisptr(); - cptr thisptr() const; + ptr thisptr(); ///< Get smart pointer to node + cptr thisptr() const; ///< Get smart pointer to node (const) protected: GenericNode(); void name(std::string const & name); - static void name(GenericNode & node, std::string const & name); - void parent(DirectoryNode * parent); +#ifndef DOXYGEN private: +#else + public: +#endif virtual void v_help(std::ostream & output) const = 0; + ///< Provide help information + /**< This member must be implemented in derived classes + to provide node specific help information. */ + private: std::string name_; DirectoryNode * parent_; - friend class intrusive_refcount_base; friend class DirectoryNode; }; class SimpleCommandNode; + /** \brief Internal: Node creation helper traits + + This class is used internally to find out the type of node to create for a specific argument + type. + */ template struct NodeCreateTraits { @@ -117,13 +149,29 @@ namespace console { }; }; - /** \brief - ///\fixme Provide a default name for added nodes if 'name' is empty ? + /** \brief Config/console tree directory node + + This node type provides the internal and root nodes of the tree. It allows to add arbitrary + children and supports directory traversal. + + Nodes are normally not instantiated manually but are created by the DirectoryNode via + mkdir() or add(). Special add() members however allow externally allocated node objects. + + Nodes may be added to the tree only once, otherwise chaos will ensue. Since nodes are always + managed dynamically, there is a special ObjectDirectory proxy template which provides a + DirectoryNode facade. ObjectDirectory is used if a class wants to manage it's own directory + as a data member. + + Every node is assigned a (new) name when it is added to a directory. If the directory + already has an entry of that name, the name is made unique by appending a suffix of the form + '-n' where n is a number starting at 1. If the name is empty, int is set to 'unnamed' and + then uniquified as above. Automatically providing unique names simplifies adding + configuration/console support to generic components. */ class DirectoryNode : public GenericNode { + SENF_LOG_CLASS_AREA(); typedef std::map ChildMap; - public: /////////////////////////////////////////////////////////////////////////// // Types @@ -139,7 +187,9 @@ namespace console { ///\name Structors and default members ///\{ - static std::auto_ptr create(); + static ptr create(); ///< Create node object. + /**< You should normally use either mkdir() or + ObjectDirectory instead of create() */ ///\} /////////////////////////////////////////////////////////////////////////// @@ -147,32 +197,91 @@ namespace console { ///\{ template - NodeType & add(std::string const & name, std::auto_ptr node); - - template NodeType & add(std::string const & name, boost::shared_ptr node); + ///< Add node to tree + /**< Adds the \a node to the tree as a child of \a this + node. The node is given the name \a name. If a node of + that name already exists, a numeric suffix of the form + '-n' is added to the name until the name is unique. If + \a name is empty, it is set to 'unnamed'. */ template typename NodeCreateTraits::NodeType & add (std::string const & name, Object const & ob); + ///< Generic child node factory + /**< This member is used to create a new child node of the + current directory. The type of node created depends on + the type of argument passed. + + The node type is selected by the NodeCreateTraits + class. To allow adding a specific node type, you need + to provide an overload for + senf_console_add_node which must be visible at + when you register the new node. + \code + MyNodeType & senf_console_add_node( + DirectoryNode & dir, + std::string const & name, + MySpecialObject const & ob, + int) + { + return dir.add(name, MyNodeType::create(ob)); + } + \endcode + (Do not forget the last unnamed 'int' parameter which + is not used but serves to disambiguate the + overloads). */ GenericNode::ptr remove(std::string const & name); + ///< Remove node \a name from the tree + /**< The returned pointer may either be discarded, which + will automatically dispose the removed node, or it may + be saved and/or re-attached at some other place in the + tree. */ DirectoryNode & operator[](std::string const & name) const; + ///< Get directory child node + /**< \throws UnknownNodeNameException if a child \a name + does not exist. + \throws std::bad_cast if the child \a name is not a + directory node. */ + CommandNode & operator()(std::string const & name) const; + ///< Get command child node + /**< \throws UnknownNodeNameException if a child \a name + does not exist + \throws std::bad_cast if the child \a name is not a + command node. */ + GenericNode & get(std::string const & name) const; + ///< Get child node + /**< \throws UnknownNodeNameException if a child \a name + does not exist */ DirectoryNode & mkdir(std::string const & name); + ///< Create sub-directory node ChildrenRange children() const; + ///< Return iterator range over all children. + /**< The returned range is sorted by child name. */ ///\} /////////////////////////////////////////////////////////////////////////// template GenericNode & traverse(ForwardRange const & range); + ///< Traverse node path starting at this node + /**< The FordwareRange::value_type must be + (convertible to) std::string. Each range element + constitutes a step along the node traversal. + + If the range starts with an empty element, the + traversal is started at the root() node, otherwise it + is started at \a this node. The traversal supports '.', + '..' and ignores further empty elements. */ DirectoryNode & doc(std::string const & doc); + ///< Set node documentation ptr thisptr(); cptr thisptr() const; @@ -192,22 +301,27 @@ namespace console { BOOST_TYPEOF_REGISTER_TYPE(DirectoryNode); + /// Exception: Unknown node name struct UnknownNodeNameException : public senf::Exception { UnknownNodeNameException() : senf::Exception("Unknown node name") {}}; - // We need this specialization since we cannot passe auto_ptr via const & !! - template - struct NodeCreateTraits< std::auto_ptr > - {}; - +#ifndef DOXYGEN template struct NodeCreateTraits< boost::shared_ptr > {}; +#endif + + /** \brief Config/console tree command node + + The CommandNode is the base-class for the tree leaf nodes. Concrete command node + implementations are derived from this class. - /** \brief + To execute a command, CommandNode::operator()() is called. This abstract virtual function + must be implemented in a derived class. */ class CommandNode : public GenericNode { + SENF_LOG_CLASS_AREA(); public: /////////////////////////////////////////////////////////////////////////// // Types @@ -221,6 +335,11 @@ namespace console { /////////////////////////////////////////////////////////////////////////// virtual void operator()(std::ostream & output, Arguments const & arguments) = 0; + ///< Called to execute the command + /**< \param[in] output stream where result messages may be + written to + \param[in] arguments command arguments. This is a + range of ranges of ArgumentToken instances. */ ptr thisptr(); cptr thisptr() const; @@ -231,14 +350,22 @@ namespace console { private: }; - /** \brief + /** \brief Most simple CommandNode implementation + + This CommandNode implementation simply forwards the \a output and \a arguments arguments to + an arbitrary callback. */ class SimpleCommandNode : public CommandNode { + SENF_LOG_CLASS_AREA(); public: /////////////////////////////////////////////////////////////////////////// // Types + typedef boost::shared_ptr ptr; + typedef boost::shared_ptr cptr; + typedef boost::weak_ptr weak_ptr; + typedef boost::function Function; /////////////////////////////////////////////////////////////////////////// @@ -248,7 +375,7 @@ namespace console { ptr thisptr(); cptr thisptr() const; - static std::auto_ptr create(Function const & fn); + static ptr create(Function const & fn); SimpleCommandNode & doc(std::string const & doc); @@ -262,9 +389,11 @@ namespace console { std::string doc_; }; +#ifndef DOXYGEN template SimpleCommandNode & senf_console_add_node(DirectoryNode & node, std::string const & name, Function const & fn, ...); +#endif BOOST_TYPEOF_REGISTER_TYPE(SimpleCommandNode); diff --git a/Console/ObjectDirectory.cti b/Console/ObjectDirectory.cti index 3fb3a62..75b9c9f 100644 --- a/Console/ObjectDirectory.cti +++ b/Console/ObjectDirectory.cti @@ -49,7 +49,7 @@ senf::console::OwnerNodeCreateTraits::Creator::create(DirectoryNod template prefix_ senf::console::ObjectDirectory::ObjectDirectory(Owner * owner) - : node_ (DirectoryNode::create().release()), owner_ (owner) + : node_ (DirectoryNode::create()), owner_ (owner) {} template diff --git a/Console/ObjectDirectory.hh b/Console/ObjectDirectory.hh index ccecf8d..1f26bef 100644 --- a/Console/ObjectDirectory.hh +++ b/Console/ObjectDirectory.hh @@ -37,6 +37,11 @@ namespace senf { namespace console { + /** \brief Internal: Node creation helper traits (ObjectDirectory proxy) + + This class is used like NodeCreateTraits to customize the child node creation. This trait + class is used however by the ObjectDirectory proxy. + */ template struct OwnerNodeCreateTraits { @@ -54,9 +59,41 @@ namespace console { }; }; + /** \brief Internal: Marker base class for all ObjectDirectory proxies + */ struct ObjectDirectoryBase {}; - /** \brief + /** \brief DirectoryNode member proxy + + ObjectDirectory is used whenever a class wants to manage it's own directory. The class + allows to declare the directory as a public member object. This allows the user of the class + to register the directory in the command tree. By using the proxy, the node is automatically + detached from the tree (and thereby destroyed) when the object (and thereby the proxy) is + destroyed. + + \code + class MyClass + { + public: + ObjectDirectory configDir; + + MyClass() : configDir(this) + { + configDIr.add(...); + } + }; + \endcode + + The ObjectDirectory proxy implements 'add()' to add new children to the proxied + DirectoryNode. All add() variants supported by DirectoryNode are supported by + ObjectDirectory. + + \idea This proxy could be made obsolete by allowing to allocate node objects + statically. This could be achieved by moving back to an intrusive_ptr implementation for + normal pointing needs with an added twist: Give each node a smart_ptr member pointing to + itself with a null deleter. This allows to create weak_ptr's to the nodes which will + automatically expire when the node is deleted (either statically or by the + intrusive_ptr). */ template class ObjectDirectory : public ObjectDirectoryBase @@ -80,20 +117,23 @@ namespace console { template typename OwnerNodeCreateTraits::NodeType & add(std::string const & name, Object const & ob); + ///< Create new child node + /**< Adds a new child node to the (proxied) + DirectoryNode. How the node is added is configured + using the OwnerNodeCreateTraits template. The default + implementation just forwards the call to the proxied + directory node. */ - DirectoryNode & node() const; + DirectoryNode & node() const; ///< Access the proxied DirectoryNode protected: private: - static SimpleCommandNode & create(DirectoryNode & node, Owner * owner, - std::string const & name, - SimpleCommandNode::Function const & fn); - DirectoryNode::ptr node_; Owner * owner_; }; +#ifndef DOXYGEN template SimpleCommandNode & senf_console_add_node( DirectoryNode & node, Owner & owner, std::string const & name, Function const & fn); @@ -107,6 +147,8 @@ namespace console { DirectoryNode & senf_console_add_node( DirectoryNode & dir, std::string const & name, Node const & node, int, typename boost::enable_if< boost::is_convertible >::type * = 0); +#endif + }} ///////////////////////////////hh.e//////////////////////////////////////// diff --git a/Console/Parse.hh b/Console/Parse.hh index 36cde7c..8684f80 100644 --- a/Console/Parse.hh +++ b/Console/Parse.hh @@ -42,12 +42,16 @@ namespace console { namespace detail { struct ParserAccess; } - /** \brief + /** \brief Single argument token + + All command arguments are split into tokens by the parser. Each token is returned as an + ArgumentToken instance. */ class ArgumentToken { public: - std::string const & value() const; + std::string const & value() const; ///< String value of token + /**< This value is properly unquoted */ protected: @@ -59,8 +63,16 @@ namespace console { friend class detail::ParserAccess; }; + /** \brief Console command - /** \brief + Every command parsed is returned in a ParseCommandInfo instance. This information is purely + taken from the parser, no semantic information is attached at this point, the config/console + is not involved in any why. ParseCommandInfo consist of + + \li the type of command: builtin or normal command represented by a possibly relative path + into the command tree. + \li the command + \li the arguments. Every argument consists of a range of ArgumentToken instances. */ class ParseCommandInfo { @@ -92,11 +104,22 @@ namespace console { BuiltinEXIT, BuiltinHELP }; - BuiltinCommand builtin() const; - CommandPathRange commandPath() const; - ArgumentsRange arguments() const; - TokensRange tokens() const; - + BuiltinCommand builtin() const; ///< Command type + /**< \returns \c NoBuiltin, if the command is an ordinary + command, otherwise the id of the builtin command */ + CommandPathRange commandPath() const; ///< Command path + /**< This is the path to the command if it is not a builtin + command. Every element of the returned range + constitutes one path element. If the first element is + empty, the path is an absolute path, otherwise it is + relative. If the last element is an empty string, the + path ends in a '/' char. */ + ArgumentsRange arguments() const; ///< Command arguments + /**< The returned range contains one token range for each + agument. */ + TokensRange tokens() const; ///< All argument tokens + /**< The returned range contains \e all argument tokens in a + single range not divided into separate arguments. */ protected: private: @@ -123,9 +146,25 @@ namespace console { friend class detail::ParserAccess; }; + /**< \brief Output ParseCommandInfo instance + \related ParseCommandInfo + */ std::ostream & operator<<(std::ostream & stream, ParseCommandInfo const & info); - /** \brief + /** \brief Parse commands + + This class implements a parser for the console/config language. It supports parsing strings + as well as files. For every parsed command, a callback function is called. + + \implementation The implementation is based on Boost.Spirit. See the file \ref Parse.ih for + the formal language grammar. + + \implementation Parsing an arbitrary iostream is not supported since arbitrary streams are + not seekable. If this is needed, it can however be provided using stream iterators and + some special iterator adaptors from Boost.Spirit. However, the amount of backtracking + needs to be analyzed before this is viable. + + \todo Implement more detailed error reporting and error recovery. */ class CommandParser : boost::noncopyable @@ -146,8 +185,10 @@ namespace console { ///@} /////////////////////////////////////////////////////////////////////////// - bool parse(std::string command, Callback cb); - bool parseFile(std::string filename, Callback cb); + bool parse(std::string command, Callback cb); ///< Parse string + bool parseFile(std::string filename, Callback cb); ///< Parse file + /**< \throws SystemException if the file cannot be + read. */ private: struct Impl; diff --git a/Console/Parse.ih b/Console/Parse.ih index 02930e6..f676f10 100644 --- a/Console/Parse.ih +++ b/Console/Parse.ih @@ -42,6 +42,8 @@ namespace senf { namespace console { namespace detail { +#ifndef DOXYGEN + struct append_action { template @@ -349,6 +351,8 @@ namespace detail { }; }; +#endif + }}} ///////////////////////////////ih.e//////////////////////////////////////// diff --git a/Console/Server.hh b/Console/Server.hh index c8d2c7f..685eae2 100644 --- a/Console/Server.hh +++ b/Console/Server.hh @@ -50,8 +50,12 @@ namespace console { class Client; - /** \brief - ///\fixme Use special non-blocking streambuf + /** \brief Interactive console server + + This class provides an interactive console TCP server. + + \todo Add readline support + \todo Add interactivity detection using timeout */ class Server : boost::noncopyable @@ -69,9 +73,12 @@ namespace console { ~Server(); static Server & start(senf::INet4SocketAddress const & address); + ///< Start server on given IPv4 address/port static Server & start(senf::INet6SocketAddress const & address); + ///< Start server on given IPv6 address/port - void name(std::string const & name); + void name(std::string const & name); ///< Set server name + /**< This information is used in the prompt string. */ protected: @@ -94,12 +101,18 @@ namespace console { friend class Client; }; - /** \brief + /** \brief Server client instance + + 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. \fixme Fix Client::clientData implementation - Remove the 'dup' needed here so we don't close the same fd twice (see Client constructor) - Make output non-blocking - Don't register a new ReadHelper every round + \fixme Remove the 'dup' needed here so we don't close the same fd twice (see Client + constructor) + \fixme Make output non-blocking (use a non-blocking/discarding streambuf) and possibly set + socket send buffer size + \fixme Don't register a new ReadHelper every round */ class Client : public senf::intrusive_refcount @@ -111,7 +124,8 @@ namespace console { ~Client(); - void stopClient(); + void stopClient(); ///< Stop the client + /**< This will close the client socket. */ protected: diff --git a/Console/testServer.cc b/Console/testServer.cc index f0d500e..8b3f3da 100644 --- a/Console/testServer.cc +++ b/Console/testServer.cc @@ -67,23 +67,39 @@ namespace { } }; + void shutdownServer(std::ostream &, + senf::console::CommandNode::Arguments const &) + { + senf::Scheduler::instance().terminate(); + throw senf::console::Executor::ExitException(); + } + } int main(int, char const **) { senf::log::ConsoleTarget::instance().route< senf::SenfLog, senf::log::NOTICE >(); - senf::console::root().doc("This is the console test application"); - senf::console::root().mkdir("network") + senf::console::root() + .doc("This is the console test application"); + senf::console::root() + .mkdir("network") .doc("Network related settings"); - senf::console::root()["network"].mkdir("eth0") + senf::console::root()["network"] + .mkdir("eth0") .doc("Ethernet device eth0"); - senf::console::root().mkdir("server"); - senf::console::root()["network"].add("route", &fn) + senf::console::root() + .mkdir("server"); + senf::console::root()["server"] + .add("shutdown", &shutdownServer) + .doc("Terminate server application"); + senf::console::root()["network"] + .add("route", &fn) .doc("Example of a directly registered function"); TestObject test; - senf::console::root().add("testob", test.dir) + senf::console::root() + .add("testob", test.dir) .doc("Example of an instance directory"); senf::console::Server::start( senf::INet4SocketAddress("127.0.0.1:23232") )