add a simple interactive readline console
authorTero Marttila <terom@fixme.fi>
Tue, 31 Mar 2009 15:41:24 +0300
changeset 92 99661e5aac91
parent 91 bca23cbe1dce
child 93 42ade8285570
add a simple interactive readline console
TODO
src/CMakeLists.txt
src/console.c
src/console.h
src/nexus.c
src/nexus.h
--- a/TODO	Mon Mar 30 16:44:00 2009 +0300
+++ b/TODO	Tue Mar 31 15:41:24 2009 +0300
@@ -3,8 +3,9 @@
  * sock_openssl, or improve sock_gnutls
  * tests...
 
-irc_conn:
- * ratelimit queue for outgoing messages
+irc_queue:
+ * fix use of line_proto_send
+ * more expansive tests
 
 irc_net:
  * reconnect, maybe cycling servers?
--- a/src/CMakeLists.txt	Mon Mar 30 16:44:00 2009 +0300
+++ b/src/CMakeLists.txt	Tue Mar 31 15:41:24 2009 +0300
@@ -12,13 +12,13 @@
 set (SOCK_SOURCES sock.c sock_tcp.c sock_gnutls.c sock_test.c line_proto.c)
 set (IRC_SOURCES irc_line.c irc_conn.c irc_net.c irc_chan.c chain.c irc_cmd.c irc_proto.c irc_client.c irc_user.c irc_queue.c)
 
-set (NEXUS_SOURCES nexus.c ${CORE_SOURCES} ${SOCK_SOURCES} ${IRC_SOURCES} signals.c module.c config.c)
+set (NEXUS_SOURCES nexus.c ${CORE_SOURCES} ${SOCK_SOURCES} ${IRC_SOURCES} signals.c module.c config.c console.c)
 set (TEST_SOURCES test.c ${CORE_SOURCES} ${SOCK_SOURCES} ${IRC_SOURCES})
 set (IRC_LOG_SOURCES irc_log.c)
 
 # define our libraries
 set (MODULE_LIBRARIES "dl")
-set (NEXUS_LIBRARIES ${LibEvent_LIBRARIES} ${GnuTLS_LIBRARIES} ${MODULE_LIBRARIES})
+set (NEXUS_LIBRARIES ${LibEvent_LIBRARIES} ${GnuTLS_LIBRARIES} ${MODULE_LIBRARIES} "readline")
 
 # compiler flags
 set (CFLAGS "-Wall -Wextra -std=gnu99")
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/console.c	Tue Mar 31 15:41:24 2009 +0300
@@ -0,0 +1,91 @@
+#include "console.h"
+
+#include <unistd.h>
+#include <stdio.h>
+#include <readline/readline.h>
+#include <assert.h>
+
+/** The global console state */
+static struct console _console;
+
+/**
+ * Our stdin input handler
+ */
+static void console_input (int fd, short what, void *arg)
+{
+    struct console *console = arg;
+
+    (void) console;
+    (void) fd;
+    (void) what;
+
+    // tell readline to process it
+    rl_callback_read_char();
+}
+
+/**
+ * Our readline line handler
+ */
+static void console_line (char *line)
+{
+    struct console *console = &_console;
+
+    // invoke the console callback
+    console->callbacks->on_line(line, console->cb_arg);
+}
+
+err_t console_init (struct console **console_ptr, struct event_base *ev_base, const struct console_config *config,
+        const struct console_callbacks *callbacks, void *cb_arg, struct error_info *err)
+{
+    struct console *console = &_console;
+
+    // check it's not already initialized
+    assert(!console->initialized);
+
+    // store
+    console->callbacks = callbacks;
+    console->cb_arg = cb_arg;
+
+    // setup the input event
+    if ((console->ev = event_new(ev_base, STDIN_FILENO, EV_READ | EV_PERSIST, &console_input, console)) == NULL)
+        JUMP_SET_ERROR(err, ERR_EVENT_NEW);
+
+    // setup readline
+    rl_callback_handler_install(config->prompt, &console_line);
+    
+    // mark it as initialized
+    console->initialized = true;
+
+    // enable input
+    if (event_add(console->ev, NULL))
+        JUMP_SET_ERROR(err, ERR_EVENT_ADD);
+
+    // ok
+    *console_ptr = console;
+
+    return SUCCESS;
+
+error:
+    console_destroy(console);
+
+    return ERROR_CODE(err);
+}
+
+void console_destroy (struct console *console)
+{
+    // remove the input event
+    if (console->ev)
+        event_free(console->ev); 
+    
+    console->ev = NULL;
+
+    // de-init rl?
+    if (console->initialized)
+        rl_callback_handler_remove();
+    
+    console->initialized = false;
+
+    // remove stored stuff
+    console->callbacks = console->cb_arg = NULL;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/console.h	Tue Mar 31 15:41:24 2009 +0300
@@ -0,0 +1,62 @@
+#ifndef CONSOLE_H
+#define CONSOLE_H
+
+/**
+ * @file
+ *
+ * An interactive line-based console interface for runtime configuration.
+ *
+ * This uses the GNU readline library to implement the console, and hence, the console uses global state, and there
+ * can only be one console per process - not that it should matter, since the console requires a tty.
+ *
+ * XXX: the log module interferes with this
+ */
+#include "error.h"
+#include <event2/event.h>
+#include <stdbool.h>
+
+/**
+ * Callbacks for event-based actions
+ */
+struct console_callbacks {
+    /** A line was read from the console */
+    void (*on_line) (char *line, void *arg);
+};
+
+/**
+ * Configuration info for console operation
+ */
+struct console_config {
+    /** The prompt to use when displaying lines */
+    const char *prompt;
+};
+
+/**
+ * The console state...
+ */
+struct console {
+    /** The input event */
+    struct event *ev;
+
+    /** The callback functions */
+    const struct console_callbacks *callbacks;
+
+    /** The callback context argument */
+    void *cb_arg;
+
+    /** Already initialized? */
+    bool initialized;
+};
+
+/**
+ * Initialize the console, setting up the TTY and input handler
+ */
+err_t console_init (struct console **console_ptr, struct event_base *ev_base, const struct console_config *config,
+        const struct console_callbacks *callbacks, void *cb_arg, struct error_info *err);
+
+/**
+ * Deinitialize the console, restoring the TTY and releasing resources
+ */
+void console_destroy (struct console *console);
+
+#endif /* CONSOLE_H */
--- a/src/nexus.c	Mon Mar 30 16:44:00 2009 +0300
+++ b/src/nexus.c	Tue Mar 31 15:41:24 2009 +0300
@@ -26,7 +26,8 @@
     
     /** Options without short names */
     _OPT_EXT_BEGIN      = 0x00ff,
-
+    
+    OPT_CONSOLE,
 };
 
 /**
@@ -39,6 +40,7 @@
     {"module",          1,  NULL,   OPT_MODULE      },
     {"config",          1,  NULL,   OPT_CONFIG      },
     {"debug",           0,  NULL,   OPT_DEBUG       },
+    {"console",         0,  NULL,   OPT_CONSOLE     },
     {0,                 0,  0,      0               },
 };
 
@@ -57,7 +59,9 @@
     printf(" --module / -m          add a module using '<name>:<path>' format\n");
     printf(" --config / -C          add a module configuration option using '<mod_name>:<name>[:<value>]' format\n");
     printf(" --debug / -d           set logging level to DEBUG\n");
-
+    printf(" --console              use the interactive console\n");
+    
+    // dump loaded module configs
     if (nexus && !TAILQ_EMPTY(&nexus->modules->list)) {
         struct module *module;
 
@@ -232,6 +236,35 @@
     return SUCCESS;
 }
 
+static void on_line (char *line, void *arg)
+{
+    struct nexus *nexus = arg;
+
+    (void) nexus;
+
+    // just dump it
+    log_info("read line from console: '%s'", line);
+}
+
+static struct console_callbacks nexus_console_callbacks = {
+    .on_line        = &on_line,
+};
+
+/**
+ * Open the console
+ */
+static err_t apply_console (struct nexus *nexus, struct error_info *err)
+{
+    struct console_config config = {
+        .prompt     = " > ",
+    };
+
+    log_info("initializing the console");
+    
+    // just init it
+    return console_init(&nexus->console, nexus->ev_base, &config, &nexus_console_callbacks, nexus, err);
+}
+
 /**
  * Parse arguments and apply them to the given nexus
  */
@@ -275,6 +308,12 @@
             case OPT_DEBUG:
                 set_log_level(LOG_DEBUG);
                 break;
+            
+            case OPT_CONSOLE:
+                if (apply_console(nexus, err))
+                    return ERROR_CODE(err);
+                
+                break;
 
             case '?':
                 usage(nexus, argv[0]);
@@ -295,6 +334,10 @@
     
     log_info("Quitting...");
 
+    // destroy the console
+    if (ctx->console)
+        console_destroy(ctx->console);
+
     // unload the modules
     modules_unload(ctx->modules);
 
@@ -319,17 +362,20 @@
     if ((nexus->ev_base = event_base_new()) == NULL)
         FATAL("event_base_new");
     
+
     // initialize signal handlers
     if ((ERROR_CODE(&err) = signals_create(&nexus->signals, nexus->ev_base)))
         FATAL("signals_create");
-
+ 
     // add our signal handlers
     if (signal_ignore(SIGPIPE, &err))
         FATAL_ERROR(&err, "signals_ignore(SIGPIPE)");
-
+    
+    // XXX: add our SIGINT handler after console_init()?
     if ((ERROR_CODE(&err) = signals_add(nexus->signals, SIGINT, &on_sigint, nexus)))
         FATAL_ERROR(&err, "signals_add(SIGINT)");
  
+
     // initialize sock module
     if (sock_init(nexus->ev_base, &err))
         FATAL_ERROR(&err, "sock_init");
@@ -342,10 +388,12 @@
     if (irc_client_create(&nexus->client, &err))
         FATAL_ERROR(&err, "irc_client_create");
 
+
     // parse args
     if (parse_args(nexus, argc, argv, &err))
         FATAL_ERROR(&err, "parse_args");
-    
+
+  
     // run event loop
     log_info("entering event loop");
 
--- a/src/nexus.h	Mon Mar 30 16:44:00 2009 +0300
+++ b/src/nexus.h	Tue Mar 31 15:41:24 2009 +0300
@@ -10,6 +10,7 @@
 
 #include <event2/event.h>
 #include "signals.h"
+#include "console.h"
 #include "module.h"
 #include "irc_client.h"
 
@@ -23,6 +24,9 @@
     /** Our signal handlers */
     struct signals *signals;
 
+    /** Our console */
+    struct console *console;
+
     /** Our loaded modules */
     struct modules *modules;