From: g0dil Date: Tue, 29 Apr 2008 23:54:00 +0000 (+0000) Subject: Console: Add console logging documentation X-Git-Url: http://g0dil.de/git?a=commitdiff_plain;h=1b1d76302a5d61e918ef71f1c8e11f80ac1262e2;p=senf.git Console: Add console logging documentation Console: Implement (and document) Server::stop() Console: Implement working readline C-d and minnimal C-c support Utils/Logger: Make Stream optional in routing commands git-svn-id: https://svn.berlios.de/svnroot/repos/senf/trunk@828 270642c3-0616-0410-b53a-bc976706d245 --- diff --git a/Console/Example.dox b/Console/Example.dox index 9481834..bb39190 100644 --- a/Console/Example.dox +++ b/Console/Example.dox @@ -64,18 +64,66 @@ 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 \c exit built-in command at the console. + + The next callback accesses the client instance directly to manipulate the logging: - \until Example + \until } + + 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 \c main() which initializes the node tree and starts the console server + + \until route + + Here we just setup more verbose logging and set \c SIGPIPE signals to be ignored. \c SIGPIPE's + are a pain and really should be disabled. + + \until settings - The \c main routine enables more verbose console logging and adds some directories and callbacks - to the tree so we have some stuff to play around with. + This shows, how to set the top-level documentation and create a new subdirectory directly - The following few lines of code instantiate a \c TestObject instance and add this object's - directory node to the tree + \until mkdir + Here we create another new directory but 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). + + This way of stroing a refernce is not bad, but even better is to use a \c + senf::console::ScopedDirectory<> for this + + \until functions + + This will automatically remove the node from the tree when the senf::console::ScopedDiretory + instance is destroyed and keeps the node alive even when unlinked from the tree (a plain + reference becomes invalid when anyone later unlinks the node from the tree). This is much safer + and is the preferred way to keep a hold on a directory. + + The next statements add commands to the various directories declared so far + \until Example + + We now continue by creating an instance of our test class \c TestObject - Now we are ready to start the server and enter the Scheduler main-loop + \until Example + + We add that objects directory to the \c 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. \until } */ diff --git a/Console/Mainpage.dox b/Console/Mainpage.dox index 1b7da14..e6cf404 100644 --- a/Console/Mainpage.dox +++ b/Console/Mainpage.dox @@ -185,6 +185,53 @@ \endcode \see \ref console_parser + + + \section console_misc Further features + + \subsection console_serverclient 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: + \code + int main(int, char**) + { + senf::console::Server & server ( senf::console::start( ... ) ); + + // Do something ... + + server.stop() + } + \endcode + + The client instance can be accessed via the \c std::ostream arg of any command callback + \code + void someCallback(std::ostream & os, ... ) + { + senf::console::Client & client (senf::console::Client::get(os)); + + // Use the client's log target + client.route(); + } + \endcode + + \see + senf::console::Server for the Server API \n + senf::console::Client / List of all + members for the Client API + + + \subsection console_shell Features of the interactive console shell + + The interactive shell will use the GNU readline library for the first connected + instance. Further users will not have access to this functionality since GNU readline is + completely non-reentrant. + + 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 th corresponding full + name. */ /** \defgroup console_commands Supported command types diff --git a/Console/Readline.cc b/Console/Readline.cc index 8d796e6..16bcfa0 100644 --- a/Console/Readline.cc +++ b/Console/Readline.cc @@ -72,16 +72,20 @@ namespace { int readline_getc_function(FILE *) { if (senf::console::detail::ReadlineClientReader::active()) - return senf::console::detail::ReadlineClientReader::instance().getc(); + return senf::console::detail::ReadlineClientReader::instance().getc() else return -1; } void readline_callback(char * input) { - if (senf::console::detail::ReadlineClientReader::active() && input) - return senf::console::detail::ReadlineClientReader::instance().callback( - std::string(input) ); + if (senf::console::detail::ReadlineClientReader::active()) { + if (input) + return senf::console::detail::ReadlineClientReader::instance().callback( + std::string(input) ); + else // input == 0 -> EOF (or Ctrl-D) + senf::console::detail::ReadlineClientReader::instance().eof(); + } } ssize_t readline_cookie_write_function(void * cookie, char const * buffer, size_t size) @@ -100,6 +104,14 @@ namespace { void readline_deprep_term() {} + int restart_line(int count, int key) + { + rl_kill_full_line(count, key); + rl_crlf(); + rl_forced_update_display(); + return 0; + } + } prefix_ senf::console::detail::ReadlineClientReader::ReadlineClientReader(Client & client) @@ -129,6 +141,7 @@ prefix_ senf::console::detail::ReadlineClientReader::ReadlineClientReader(Client rl_deprep_term_function = &readline_deprep_term; rl_getc_function = &readline_getc_function; rl_bind_key('\t', &rl_insert); + rl_bind_key('\x03', &restart_line); using_history(); // Don't ask me, where I found this ... @@ -191,11 +204,11 @@ prefix_ void senf::console::detail::ReadlineClientReader::charEvent(Scheduler::E stopClient(); return; } - ch_ = ch; + ch_ = static_cast(ch); if (skipChars_ > 0) --skipChars_; - else if (ch_ == static_cast(0xff)) + else if (ch_ == 0xff) skipChars_ = 2; else if (ch_ != 0) rl_callback_read_char(); diff --git a/Console/Readline.cci b/Console/Readline.cci index 8d12264..da2842c 100644 --- a/Console/Readline.cci +++ b/Console/Readline.cci @@ -62,6 +62,12 @@ prefix_ void senf::console::detail::ReadlineClientReader::terminate() terminate_ = true; } +prefix_ void senf::console::detail::ReadlineClientReader::eof() +{ + stream() << '\n' << std::flush; + stopClient(); +} + /////////////////////////////////////////////////////////////////////////// // senf::console::detail::SafeReadlineClientReader diff --git a/Console/Readline.hh b/Console/Readline.hh index 7a348ef..48cfb71 100644 --- a/Console/Readline.hh +++ b/Console/Readline.hh @@ -63,6 +63,7 @@ namespace detail { void callback(std::string line); void write(std::string text); void terminate(); + void eof(); private: virtual void v_disablePrompt(); diff --git a/Console/Server.hh b/Console/Server.hh index e33be2c..dc7630d 100644 --- a/Console/Server.hh +++ b/Console/Server.hh @@ -54,7 +54,6 @@ namespace console { This class provides an interactive console TCP server. - \todo Add readline support \todo Add interactivity detection using timeout \idea To support blocking commands, we could give the Client 'suspend()' and 'resume()' members. suspend() would probably throw some kind of exception to transfer control back diff --git a/Console/testServer.cc b/Console/testServer.cc index bf41384..429d959 100644 --- a/Console/testServer.cc +++ b/Console/testServer.cc @@ -71,37 +71,45 @@ void shutdownServer() void enableLogging(std::ostream & os) { - senf::console::Client::get(os).route(); + senf::console::Client::get(os).route(); } int main(int, char **) { ::signal(SIGPIPE, SIG_IGN); - senf::log::ConsoleTarget::instance().route< senf::SenfLog, senf::log::NOTICE >(); + senf::log::ConsoleTarget::instance().route< senf::log::VERBOSE >(); senf::console::root() .doc("This is the console test application"); + senf::console::root() .mkdir("console") .doc("Console settings"); + + senf::console::DirectoryNode & serverDir ( + senf::console::root() + .mkdir("server") + .doc("server commands") ); + + senf::console::ScopedDirectory<> testDir; senf::console::root() - .mkdir("test") + .add("test", testDir) .doc("Test functions"); - senf::console::root() - .mkdir("server"); senf::console::root()["console"] .add("showlog", &enableLogging) .doc("Enable display of log messages on the current console"); - senf::console::root()["server"] + + serverDir .add("shutdown", &shutdownServer) .doc("Terminate server application"); - senf::console::root()["test"] + + testDir .add("echo", &echo) .doc("Example of a function utilizing manual argument parsing"); TestObject test; - senf::console::root()["test"] + testDir .add("testob", test.dir) .doc("Example of an instance directory"); diff --git a/Doxyfile b/Doxyfile index c671589..b556d23 100644 --- a/Doxyfile +++ b/Doxyfile @@ -13,4 +13,5 @@ TAGFILES = \ "$(TOPDIR)/Packets/doc/Packets.tag" \ "$(TOPDIR)/Packets/DefaultBundle/doc/DefaultBundle.tag" \ "$(TOPDIR)/Socket/doc/Socket.tag" \ - "$(TOPDIR)/Utils/doc/Utils.tag" + "$(TOPDIR)/Utils/doc/Utils.tag" \ + "$(TOPDIR)/senfscons/doc/senfscons.tag" diff --git a/Mainpage.dox b/Mainpage.dox index 036a1e6..f7ff985 100644 --- a/Mainpage.dox +++ b/Mainpage.dox @@ -171,7 +171,9 @@ The most simple way to build using SENF is to use a very simple SCons helper which automatically supports debug and final builds, uses SENF either centrally installed or locally built and has - some other nice features. See \ref senfutil for more info and an example for this utility. + some other nice features. See Building Projects using SENF + for more info and an example for this utility. \see \ref senf_components \n \ref senf_overview diff --git a/Utils/Logger/Target.cti b/Utils/Logger/Target.cti index 8718906..1608c9f 100644 --- a/Utils/Logger/Target.cti +++ b/Utils/Logger/Target.cti @@ -38,98 +38,48 @@ #ifndef DOXYGEN -template +template prefix_ void senf::log::Target::route(action_t action, int index) { - route(&Stream::instance(), 0, NONE::value, action, index); + route(action, index); } -template -prefix_ void senf::log::Target:: -route(action_t action, int index, - typename boost::enable_if< boost::is_convertible >::type *) -{ - route(&Stream::instance(), 0, Level::value, action, index); -} - -template -prefix_ void senf::log::Target:: -route(action_t action, int index, - typename boost::enable_if< boost::is_convertible >::type *) -{ - route(&Stream::instance(), &Area::instance(), NONE::value, action, index); -} - -template -prefix_ void senf::log::Target:: -route(action_t action, int index, - typename boost::enable_if< boost::is_convertible >::type *) -{ - route(&Stream::instance(), &AreaClass::SENFLogArea::instance(), NONE::value, action, index); -} - -template -prefix_ void senf::log::Target:: -route(action_t action, int index, - typename boost::enable_if< boost::is_convertible >::type *) +template +prefix_ void senf::log::Target::route(action_t action, int index) { - route(&Stream::instance(), &Area::instance(), Level::value, action, index); + route(action, index); } -template -prefix_ void senf::log::Target:: -route(action_t action, int index, - typename boost::enable_if< boost::is_convertible >::type *) +template +prefix_ void senf::log::Target::route(action_t action, int index) { - route(&Stream::instance(), &AreaClass::SENFLogArea::instance(), Level::value, action, index); + typedef detail::RouteParameters Params; + route( detail::InstanceP::value(), + detail::InstanceP::value(), + Params::Level::value, + action, index); } -// senf::log::target::ununroute - -template +template prefix_ void senf::log::Target::unroute(action_t action) { - unroute(&Stream::instance(), 0, NONE::value, action); + unroute(action); } -template -prefix_ void senf::log::Target:: -unroute(action_t action, - typename boost::enable_if< boost::is_convertible >::type *) -{ - unroute(&Stream::instance(), 0, Level::value, action); -} - -template -prefix_ void senf::log::Target:: -unroute(action_t action, - typename boost::enable_if< boost::is_convertible >::type *) -{ - unroute(&Stream::instance(), &Area::instance(), NONE::value, action); -} - -template -prefix_ void senf::log::Target:: -unroute(action_t action, - typename boost::enable_if< boost::is_convertible >::type *) -{ - unroute(&Stream::instance(), &AreaClass::SENFLogArea::instance(), NONE::value, action); -} - -template -prefix_ void senf::log::Target:: -unroute(action_t action, - typename boost::enable_if< boost::is_convertible >::type *) +template +prefix_ void senf::log::Target::unroute(action_t action) { - unroute(&Stream::instance(), &Area::instance(), Level::value, action); + unroute(action); } -template -prefix_ void senf::log::Target:: -unroute(action_t action, - typename boost::enable_if< boost::is_convertible >::type *) +template +prefix_ void senf::log::Target::unroute(action_t action) { - unroute(&Stream::instance(), &AreaClass::SENFLogArea::instance(), Level::value, action); + typedef detail::RouteParameters Params; + unroute( detail::InstanceP::value(), + detail::InstanceP::value(), + Params::Level::value, + action); } #endif diff --git a/Utils/Logger/Target.hh b/Utils/Logger/Target.hh index 7c999b8..664f5b2 100644 --- a/Utils/Logger/Target.hh +++ b/Utils/Logger/Target.hh @@ -71,7 +71,7 @@ namespace log { implementation is more efficient and utilizes a routing cache). Each routing entry consists of the following parameters - \li (mandatory) \e stream. The entry will match only messages directed at that stream + \li (optional) \e stream. The entry will match only messages directed at that stream \li (optional) \e area. If the area is specified, only messages directed at that area are matched, otherwise any area will be allowed \li (optional) \e level. If the log level is specified, messages will be accepted if their @@ -193,10 +193,11 @@ namespace log { template void route( action_t action = ACCEPT, int index = -1); ///< Add route (static) /**< Add a route for the given combination of \a Stream, \a - Area and \a Level. The \a Stream parameter is mandatory - while either \a Area or \a Level are optional (the - template signature is shown simplified here): + Area and \a Level. All parameters (\a Stream, \a Area + and \a Level) are optional (the template signature is + shown simplified here). Examples: \code + target.route(); target.route(); target.route(); target.route(); @@ -206,9 +207,9 @@ namespace log { See the class description for information on the \a action and \a index parameters - \tparam Stream mandatory stream to match - \tparam Area optional area to match - \tparam Level optional level, matches messages with + \tparam Stream stream to match + \tparam Area area to match + \tparam Level level, matches messages with at least the given level. \param[in] action routing action to take \param[in] index position of new route in the routing @@ -220,9 +221,9 @@ namespace log { unsigned level = NONE::value, action_t action = ACCEPT, int index = -1); ///< Add route (dynamic) /**< Add a route for the given combination of \a stream, \a - area and \a level. The \a stream parameter is mandatory - while either \a area or \a level may be left - unspecified by setting them to the empty string or + area and \a level. All parameters (\a Stream, \a Area + and \a Level) are optional and may be omitted by + setting them to the empty string or the senf::log::NONE::value respectively. See the class description for information on the \a @@ -233,10 +234,10 @@ namespace log { \throws InvalidAreaException if the given \a area is not found in the AreaRegistry - \param[in] stream mandatory stream to match - \param[in] area optional area to match - \param[in] level optional level, matches messages with - at least the given level. + \param[in] stream stream to match + \param[in] area area to match + \param[in] level level, matches messages with at least + the given level. \param[in] action routing action to take \param[in] index position of new route in the routing table */ @@ -255,9 +256,9 @@ namespace log { found, it will be removed, otherwise the call will be ignored - \tparam Stream mandatory stream to match - \tparam Area optional area to match - \tparam Level optional level, matches messages with + \tparam Stream stream to match + \tparam Area area to match + \tparam Level level, matches messages with at least the given level. \param[in] action routing action to take */ @@ -276,10 +277,10 @@ namespace log { found, it will be removed, otherwise the call will be ignored - \param[in] stream mandatory stream to match - \param[in] area optional area to match - \param[in] level optional level, matches messages with - at least the given level. + \param[in] stream stream to match + \param[in] area area to match + \param[in] level level, matches messages with at least + the given level. \param[in] action routing action to take */ void unroute(int index=-1); ///< Remove route (indexed) /**< This call will remove the route with the given index. @@ -291,51 +292,19 @@ namespace log { # ifndef DOXYGEN - template void route( - action_t action = ACCEPT, int index = -1); - template void route( - action_t action = ACCEPT, int index = -1, - typename boost::enable_if< boost::is_convertible >::type * = 0); - template void route( - action_t action = ACCEPT, int index = -1, - typename boost::enable_if< boost::is_convertible >::type * = 0); - template void route( - action_t action = ACCEPT, int index = -1, - typename boost::enable_if< boost::is_convertible >::type * = 0); - template void route( - action_t action = ACCEPT, int index = -1, - typename boost::enable_if< boost::is_convertible >::type * = 0); - template void route( - action_t action = ACCEPT, int index = -1, - typename boost::enable_if< boost::is_convertible >::type * = 0); - - template void unroute( - action_t action = ACCEPT); - template void unroute( - action_t action = ACCEPT, - typename boost::enable_if< boost::is_convertible >::type * = 0); - template void unroute( - action_t action = ACCEPT, - typename boost::enable_if< boost::is_convertible >::type * = 0); - template void unroute( - action_t action = ACCEPT, - typename boost::enable_if< boost::is_convertible >::type * = 0); - template void unroute( - action_t action = ACCEPT, - typename boost::enable_if< boost::is_convertible >::type * = 0); - template void unroute( - action_t action = ACCEPT, - typename boost::enable_if< boost::is_convertible >::type * = 0); + template + void route(action_t action = ACCEPT, int index = -1); + template + void route(action_t action = ACCEPT, int index = -1); + template + void route(action_t action = ACCEPT, int index = -1); + + template + void unroute(action_t action = ACCEPT); + template + void unroute(action_t action = ACCEPT); + template + void unroute(action_t action = ACCEPT); # endif diff --git a/Utils/Logger/Target.ih b/Utils/Logger/Target.ih index b0b61d9..d11daf0 100644 --- a/Utils/Logger/Target.ih +++ b/Utils/Logger/Target.ih @@ -71,6 +71,74 @@ namespace detail { template void write(std::string msg); + senf::mpl::rv<0u> RouteParameterCheck_(...); + senf::mpl::rv<1u> RouteParameterCheck_(StreamBase *); + senf::mpl::rv<2u> RouteParameterCheck_(AreaBase *); + template senf::mpl::rv<3u> RouteParameterCheck_(T*, typename T::SENFLogArea * = 0); + senf::mpl::rv<4u> RouteParameterCheck_(LevelBase *); + + template < class T, class A2, class A1, + unsigned type = SENF_MPL_RV( RouteParameterCheck_(static_cast(0)) ) > + struct RouteParameters + {}; + + template + struct RouteParameters + : public RouteParameters + {}; + + struct NilLevel { + static unsigned const value = NONE::value; + }; + + template <> + struct RouteParameters + { + typedef mpl::nil Stream; + typedef mpl::nil Area; + typedef NilLevel Level; + }; + + template + struct RouteParameters + : public RouteParameters + { + typedef T Stream; + }; + + template + struct RouteParameters + : public RouteParameters + { + typedef T Area; + }; + + template + struct RouteParameters + : public RouteParameters + { + typedef typename T::SENFLogArea Area; + }; + + template + struct RouteParameters + : public RouteParameters + { + typedef T Level; + }; + + template + struct InstanceP + { + static RV * value() { return & T::instance(); } + }; + + template + struct InstanceP + { + static RV * value() { return 0; } + }; + }}} ///////////////////////////////ih.e//////////////////////////////////////// diff --git a/senfscons/Mainpage.dox b/senfscons/Mainpage.dox index 5c81984..85ea790 100644 --- a/senfscons/Mainpage.dox +++ b/senfscons/Mainpage.dox @@ -33,8 +33,10 @@ namespace senfscons { href="http://www.stack.nl/~dimitri/doxygen/">Doxygen. This infrastructure uses quite a bit of pre- and postprocessing (which is integrated with the provided Doxygen builder) to fix some doxygen problems and generate a (IMHO) more readable layout. + + \autotoc - \section senfutil Building Projects using SENF + \section senfutil_overview Building Projects using SENF When building projects using senf, SENFSCons has a very simple helper module \ref senfutil to make the building of libraries utilizing senf simpler: @@ -89,11 +91,16 @@ namespace senfscons { LOGLEVELS='senf::log::Debug||IMPORTANT myapp::Transactions|mytrans::Area|VERBOSE' - \section layout The Project Layout + \section senfscons_intro Introduction to the SENFSCons build system - A Project using the SENFSCons infrastructure will always use a consistent directory layout. The - top-level directory will contain one subdirectory for every module. The main target will often - be considered to be just another module using the facilities provided by library modules. + Here we give an overview on how SENF itself is built. The SENFSCons system aims to be quite + flexible at separates SENF specific tasks from generic tasks to facilitate reuse. + + \subsection senfscons_layout The Project Layout + + The SENFSCons infrastructure will always use a consistent directory layout. The top-level + directory will contain one subdirectory for every module. The main target will often be + considered to be just another module using the facilities provided by library modules. The top-level project directory must contain the SENFSCons module in 'senfscons'. @@ -105,7 +112,7 @@ namespace senfscons { cross-link the different module documentations. The unit-tests as well are run on a per-module basis. - \section Standard Build Configuration + \subsection senfscons_buildconf Standard Build Configuration When the \c SConsctruct and \c SConscript files are build using the default SENFSCons helpers, by default all libraries and binaries are built. Some additional targets are diff --git a/senfscons/senfutil.py b/senfscons/senfutil.py index a0954a8..2e537d8 100644 --- a/senfscons/senfutil.py +++ b/senfscons/senfutil.py @@ -35,8 +35,6 @@ def SetupForSENF(env): opts.Add( BoolOption('final', 'Build final (optimized) build', False) ) opts.Update(env) - print env.subst('$LOGLEVELS') - if env.subst('$LOGLEVELS'): env.Append( expandLogOption=expandLogOption ) env.Append( CPPDEFINES = { 'SENF_LOG_CONF': '$expandLogOption' } )