[PATCH] Implement a simple read-eval-print loop.

servilio servilio at gmail.com
Sat Nov 20 06:20:14 PST 2010


This implementation uses GNU readline for the prompt and command
history, with the default file completion enabled. GLib is used to
split the read line into an arguments list.
---
 Makefile.local |    2 +-
 configure      |   40 ++++++++++++++++++-
 notmuch.c      |  115 ++++++++++++++++++++++++++++++++++++++++++++++++++------
 3 files changed, 141 insertions(+), 16 deletions(-)

diff --git a/Makefile.local b/Makefile.local
index f9b5a9b..3aff873 100644
--- a/Makefile.local
+++ b/Makefile.local
@@ -31,7 +31,7 @@ GPG_FILE=$(SHA1_FILE).asc
 # Smash together user's values with our extra values
 FINAL_CFLAGS = -DNOTMUCH_VERSION=$(VERSION) $(CFLAGS) $(WARN_CFLAGS)
$(CONFIGURE_CFLAGS) $(extra_cflags)
 FINAL_CXXFLAGS = $(CXXFLAGS) $(WARN_CXXFLAGS) $(CONFIGURE_CXXFLAGS)
$(extra_cflags) $(extra_cxxflags)
-FINAL_NOTMUCH_LDFLAGS = $(LDFLAGS) -Llib -lnotmuch
$(AS_NEEDED_LDFLAGS) $(GMIME_LDFLAGS) $(TALLOC_LDFLAGS)
+FINAL_NOTMUCH_LDFLAGS = $(LDFLAGS) -Llib -lnotmuch
$(AS_NEEDED_LDFLAGS) $(GMIME_LDFLAGS) $(TALLOC_LDFLAGS)
$(READLINE_LDFLAGS)
 FINAL_NOTMUCH_LINKER = CC
 ifneq ($(LINKER_RESOLVES_LIBRARY_DEPENDENCIES),1)
 FINAL_NOTMUCH_LDFLAGS += $(CONFIGURE_LDFLAGS)
diff --git a/configure b/configure
index c58dd0f..c9ea920 100755
--- a/configure
+++ b/configure
@@ -259,6 +259,32 @@ else
     errors=$((errors + 1))
 fi

+printf "Checking for readline... "
+
+echo "#include <stdio.h>
+#include <readline/readline.h>
+#include <readline/history.h>
+
+int main(void)
+{
+    static char *line = (char *)NULL;
+    line = readline(\"\");
+    add_history(line);
+    return 0;
+}" > have_readline.c
+
+if ${CC} -lreadline -o have_readline have_readline.c > /dev/null 2>&1
+then
+    printf "Yes.\n"
+    have_readline=1
+    readline_ldflags="-lreadline"
+else
+    printf "No.\n"
+    have_readline=0
+    errors=$((errors + 1))
+fi
+rm -f have_readline have_readline.c
+
 printf "Checking for valgrind development files... "
 if pkg-config --exists valgrind; then
     printf "Yes.\n"
@@ -341,6 +367,10 @@ EOF
 	echo "	The talloc library (including development files such as headers)"
 	echo "	http://talloc.samba.org/"
     fi
+    if [ $have_readline -eq 0 ]; then
+	echo "	The readline library (including development files such as headers)"
+	echo "	http://tiswww.case.edu/php/chet/readline/rltop.html"
+    fi
     cat <<EOF

 With any luck, you're using a modern, package-based operating system
@@ -349,11 +379,11 @@ case a simple command will install everything
you need. For example:

 On Debian and similar systems:

-	sudo apt-get install libxapian-dev libgmime-2.4-dev libtalloc-dev
+	sudo apt-get install libxapian-dev libgmime-2.4-dev libtalloc-dev
libreadline-dev

 Or on Fedora and similar systems:

-	sudo yum install xapian-core-devel gmime-devel libtalloc-devel
+	sudo yum install xapian-core-devel gmime-devel libtalloc-devel readline-devel

 On other systems, similar commands can be used, but the details of the
 package names may be different.
@@ -560,6 +590,9 @@ GMIME_LDFLAGS = ${gmime_ldflags}
 TALLOC_CFLAGS = ${talloc_cflags}
 TALLOC_LDFLAGS = ${talloc_ldflags}

+# Flags needed to compile and link against readline
+READLINE_LDFLAGS = ${readline_ldflags}
+
 # Flags needed to have linker set rpath attribute
 RPATH_LDFLAGS = ${rpath_ldflags}

@@ -580,5 +613,6 @@ CONFIGURE_CXXFLAGS =
-DHAVE_GETLINE=\$(HAVE_GETLINE) \$(GMIME_CFLAGS)    \\
 		     \$(TALLOC_CFLAGS) -DHAVE_VALGRIND=\$(HAVE_VALGRIND) \\
 		     \$(VALGRIND_CFLAGS) \$(XAPIAN_CXXFLAGS)             \\
                      -DHAVE_STRCASESTR=\$(HAVE_STRCASESTR)
-CONFIGURE_LDFLAGS =  \$(GMIME_LDFLAGS) \$(TALLOC_LDFLAGS) \$(XAPIAN_LDFLAGS)
+CONFIGURE_LDFLAGS =  \$(GMIME_LDFLAGS) \$(TALLOC_LDFLAGS) \\
+                     \$(READLINE_LDFLAGS) \$(XAPIAN_LDFLAGS) \\
 EOF
diff --git a/notmuch.c b/notmuch.c
index 9ba0ec0..630e272 100644
--- a/notmuch.c
+++ b/notmuch.c
@@ -18,8 +18,17 @@
  *
  * Authors: Carl Worth <cworth at cworth.org>
  *	    Keith Packard <keithp at keithp.com>
+ *          Servilio Afre Puentes <servilio at gmail.com>
  */

+#include <stdio.h>
+
+#include <string.h>
+
+#include <glib.h>
+#include <readline/readline.h>
+#include <readline/history.h>
+
 #include "notmuch-client.h"

 typedef int (*command_function_t) (void *ctx, int argc, char *argv[]);
@@ -35,6 +44,9 @@ typedef struct command {
 static int
 notmuch_help_command (void *ctx, int argc, char *argv[]);

+static int
+notmuch_repl_command (void *ctx, int argc, char *argv[]);
+
 static const char search_terms_help[] =
     "\tSeveral notmuch commands accept a comman syntax for search\n"
     "\tterms.\n"
@@ -361,6 +373,10 @@ command_t commands[] = {
       "\tby the \"--format=json\" option of \"notmuch show\". If the\n"
       "\tmessage specified by the search terms does not include a\n"
       "\tpart with the specified \"id\" there will be no output." },
+    { "repl", notmuch_repl_command,
+      NULL,
+      "Execute an interactive interpreter of notmuch commands.",
+      "\tAlso known as a read-eval-print loop.\n" },
     { "config", notmuch_config_command,
       "[get|set] <section>.<item> [value ...]",
       "Get or set settings in the notmuch configuration file.",
@@ -471,6 +487,90 @@ notmuch_help_command (unused (void *ctx), int
argc, char *argv[])
     return 1;
 }

+static int
+notmuch_command_dispatch (void *ctx, int argc, char *argv[])
+{
+    command_t *command;
+    unsigned int i;
+
+    for (i = 0; i < ARRAY_SIZE (commands); i++) {
+	command = &commands[i];
+
+	if (strcmp (argv[0], command->name) == 0)
+	    return (command->function) (ctx, argc - 1, &argv[1]);
+    }
+
+    fprintf (stderr, "Error: Unknown command '%s' (see \"notmuch help\")\n",
+	     argv[0]);
+
+    return 1;
+}
+
+/*
+ * A notmuch REPL (Read-eval-print loop) with readline support.
+ */
+static int
+notmuch_repl_command (void *ctx, unused (int argc), unused (char *argv[]))
+{
+    const char *prompt = "notmuch> ";
+    static char *line = (char *)NULL;
+    int ret = 0;
+    gint read_argc = 0;
+    gchar **read_argv;
+    GError *parse_error;
+
+    /* Initialize readline. */
+    /* Allow conditional parsing of the ~/.inputrc file. */
+    rl_readline_name = "notmuch";
+
+    do
+    {
+        read_argv = NULL;
+        parse_error = NULL;
+        line = readline(prompt);
+	if (line && *line)
+	{
+	    g_shell_parse_argv((gchar *)line,
+			       &read_argc,
+			       &read_argv,
+			       &parse_error);
+
+	    if (parse_error == NULL)
+	    {
+	        add_history(line);
+	    }
+	    free (line);
+	    line = (char *)NULL;
+
+	    if (parse_error != NULL)
+	    {
+	        fprintf (stderr, "%s\n", parse_error->message);
+		g_error_free (parse_error);
+		continue;
+	    }
+
+	    if (STRNCMP_LITERAL (read_argv[0], "repl") == 0)
+	    {
+	        fprintf (stderr, "No nasty nestings, please!\n");
+	        continue;
+	    }
+
+	    if (STRNCMP_LITERAL (read_argv[0], "quit") == 0)
+	    {
+	        fprintf (stdout, "Bye!\n");
+	        break;
+	    }
+
+	    ret = notmuch_command_dispatch(ctx,
+					   read_argc,
+					   read_argv);
+	    g_strfreev(read_argv);
+	}
+    } while (1);
+
+    return 0;
+}
+
 /* Handle the case of "notmuch" being invoked with no command
  * argument. For now we just call notmuch_setup_command, but we plan
  * to be more clever about this in the future.
@@ -539,8 +639,7 @@ int
 main (int argc, char *argv[])
 {
     void *local;
-    command_t *command;
-    unsigned int i;
+    int res;

     local = talloc_new (NULL);

@@ -557,17 +656,9 @@ main (int argc, char *argv[])
 	return 0;
     }

-    for (i = 0; i < ARRAY_SIZE (commands); i++) {
-	command = &commands[i];
-
-	if (strcmp (argv[1], command->name) == 0)
-	    return (command->function) (local, argc - 2, &argv[2]);
-    }
-
-    fprintf (stderr, "Error: Unknown command '%s' (see \"notmuch help\")\n",
-	     argv[1]);
+    res = notmuch_command_dispatch (local, argc - 1, &argv[1]);

     talloc_free (local);

-    return 1;
+    return res;
 }
-- 
1.7.2.3


More information about the notmuch mailing list