Added bindings for GNU readline.
authorRichard Jones <rjones@redhat.com>
Tue, 14 Apr 2009 14:52:04 +0000 (15:52 +0100)
committerRichard Jones <rjones@redhat.com>
Tue, 14 Apr 2009 14:52:04 +0000 (15:52 +0100)
README
configure.ac
fish/Makefile.am
fish/completion.c [new file with mode: 0644]
fish/fish.c
fish/fish.h
guestfish.pod
libguestfs.spec.in
src/generator.ml

diff --git a/README b/README
index d717a14..2db1725 100644 (file)
--- a/README
+++ b/README
@@ -30,7 +30,7 @@ Requirements
 
 - recent QEMU with vmchannel support
 
-- febootstrap >= 1.2
+- febootstrap >= 1.5
 
 - XDR, rpcgen
 
@@ -39,6 +39,8 @@ Requirements
 - perldoc (pod2man, pod2text) to generate the manual pages and
 other documentation.
 
+- (Optional) Readline to have nicer command-line editing in guestfish.
+
 - (Optional) OCaml if you want to rebuild the generated files, and
 also to build the OCaml bindings
 
index 7a32acd..da48491 100644 (file)
@@ -111,6 +111,26 @@ AC_ARG_WITH([mirror],
 MIRROR="$with_mirror"
 AC_SUBST(MIRROR)
 
+dnl Readline.
+AC_ARG_WITH([readline],
+    [AS_HELP_STRING([--with-readline],
+        [support fancy command line editing @<:@default=check@:>@])],
+    [],
+    [with_readline=check])
+
+LIBREADLINE=
+AS_IF([test "x$with_readline" != xno],
+    [AC_CHECK_LIB([readline], [main],
+        [AC_SUBST([LIBREADLINE], ["-lreadline -lncurses"])
+         AC_DEFINE([HAVE_LIBREADLINE], [1],
+                   [Define if you have libreadline])
+        ],
+        [if test "x$with_readline" != xcheck; then
+         AC_MSG_FAILURE(
+             [--with-readline was given, but test for readline failed])
+         fi
+        ], -lncurses)])
+
 dnl Check for OCaml (optional, for OCaml bindings).
 AC_PROG_OCAML
 AC_PROG_FINDLIB
index b9c51b8..11598b8 100644 (file)
@@ -19,11 +19,12 @@ bin_PROGRAMS = guestfish
 
 guestfish_SOURCES = \
        cmds.c \
+       completion.c \
        fish.c \
        fish.h
 guestfish_CFLAGS = \
        -I$(top_builddir)/src -Wall \
        -DGUESTFS_DEFAULT_PATH='"$(libdir)/guestfs"'
-guestfish_LDADD = $(top_builddir)/src/libguestfs.la
+guestfish_LDADD = $(top_builddir)/src/libguestfs.la $(LIBREADLINE)
 
 CLEANFILES = *~
diff --git a/fish/completion.c b/fish/completion.c
new file mode 100644 (file)
index 0000000..150b1aa
--- /dev/null
@@ -0,0 +1,142 @@
+/* libguestfs generated file
+ * WARNING: THIS FILE IS GENERATED BY 'src/generator.ml'.
+ * ANY CHANGES YOU MAKE TO THIS FILE WILL BE LOST.
+ *
+ * Copyright (C) 2009 Red Hat Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#ifdef HAVE_LIBREADLINE
+#include <readline/readline.h>
+#endif
+
+#include "fish.h"
+
+#ifdef HAVE_LIBREADLINE
+
+static const char *commands[] = {
+  "add",
+  "add-cdrom",
+  "add-drive",
+  "aug-close",
+  "aug-defnode",
+  "aug-defvar",
+  "aug-get",
+  "aug-init",
+  "aug-insert",
+  "aug-load",
+  "aug-ls",
+  "aug-match",
+  "aug-mv",
+  "aug-rm",
+  "aug-save",
+  "aug-set",
+  "autosync",
+  "cat",
+  "cdrom",
+  "chmod",
+  "chown",
+  "command",
+  "command-lines",
+  "config",
+  "exists",
+  "file",
+  "get-autosync",
+  "get-path",
+  "get-verbose",
+  "is-dir",
+  "is-file",
+  "kill-subprocess",
+  "launch",
+  "list-devices",
+  "list-partitions",
+  "ll",
+  "ls",
+  "lvcreate",
+  "lvm-remove-all",
+  "lvs",
+  "lvs-full",
+  "mkdir",
+  "mkdir-p",
+  "mkfs",
+  "mount",
+  "mounts",
+  "path",
+  "pvcreate",
+  "pvs",
+  "pvs-full",
+  "read-lines",
+  "rm",
+  "rm-rf",
+  "rmdir",
+  "run",
+  "set-autosync",
+  "set-path",
+  "set-verbose",
+  "sfdisk",
+  "sync",
+  "touch",
+  "umount",
+  "umount-all",
+  "unmount",
+  "unmount-all",
+  "verbose",
+  "vgcreate",
+  "vgs",
+  "vgs-full",
+  "write-file",
+  NULL
+};
+
+static char *
+generator (const char *text, int state)
+{
+  static int index, len;
+  const char *name;
+
+  if (!state) {
+    index = 0;
+    len = strlen (text);
+  }
+
+  while ((name = commands[index]) != NULL) {
+    index++;
+    if (strncasecmp (name, text, len) == 0)
+      return strdup (name);
+  }
+
+  return NULL;
+}
+
+#endif /* HAVE_LIBREADLINE */
+
+char **do_completion (const char *text, int start, int end)
+{
+  char **matches = NULL;
+
+#ifdef HAVE_LIBREADLINE
+  if (start == 0)
+    matches = rl_completion_matches (text, generator);
+#endif
+
+  return matches;
+}
index b0d91c7..51a3d50 100644 (file)
 #include <inttypes.h>
 #include <assert.h>
 
+#ifdef HAVE_LIBREADLINE
+#include <readline/readline.h>
+#include <readline/history.h>
+#endif
+
 #include <guestfs.h>
 
 #include "fish.h"
@@ -46,6 +51,9 @@ static void script (int prompt);
 static void cmdline (char *argv[], int optind, int argc);
 static int issue_command (const char *cmd, char *argv[]);
 static int parse_size (const char *str, off_t *size_rtn);
+static void initialize_readline (void);
+static void cleanup_readline (void);
+static void add_history_line (const char *);
 
 /* Currently open libguestfs handle. */
 guestfs_h *g;
@@ -114,6 +122,8 @@ main (int argc, char *argv[])
   char *p;
   int c;
 
+  initialize_readline ();
+
   /* guestfs_create is meant to be a lightweight operation, so
    * it's OK to do it early here.
    */
@@ -208,6 +218,8 @@ main (int argc, char *argv[])
   else
     cmdline (argv, optind, argc);
 
+  cleanup_readline ();
+
   exit (0);
 }
 
@@ -254,13 +266,50 @@ shell_script (void)
   script (0);
 }
 
+#define FISH "><fs> "
+
+static char *line_read = NULL;
+
+static char *
+rl_gets (int prompt)
+{
+#ifdef HAVE_LIBREADLINE
+
+  if (line_read) {
+    free (line_read);
+    line_read = NULL;
+  }
+
+  line_read = readline (prompt ? FISH : "");
+
+  if (prompt && line_read && *line_read)
+    add_history_line (line_read);
+
+#else /* !HAVE_LIBREADLINE */
+
+  static char buf[8192];
+  int len;
+
+  if (prompt) printf (FISH);
+  line_read = fgets (buf, sizeof buf, stdin);
+
+  if (line_read) {
+    len = strlen (line_read);
+    if (len > 0 && buf[len-1] == '\n') buf[len-1] = '\0';
+  }
+
+#endif /* !HAVE_LIBREADLINE */
+
+  return line_read;
+}
+
 static void
 script (int prompt)
 {
-  char buf[8192];
+  char *buf;
   char *cmd;
   char *argv[64];
-  int len, i;
+  int i;
 
   if (prompt)
     printf ("\n"
@@ -272,15 +321,12 @@ script (int prompt)
            "\n");
 
   while (!quit) {
-    if (prompt) printf ("><fs> ");
-    if (fgets (buf, sizeof buf, stdin) == NULL) {
+    buf = rl_gets (prompt);
+    if (!buf) {
       quit = 1;
       break;
     }
 
-    len = strlen (buf);
-    if (len > 0 && buf[len-1] == '\n') buf[len-1] = '\0';
-
     /* Split the buffer up at whitespace. */
     cmd = strtok (buf, " \t");
     if (cmd == NULL)
@@ -544,3 +590,54 @@ parse_string_list (const char *str)
 
   return argv;
 }
+
+#ifdef HAVE_LIBREADLINE
+static char histfile[1024];
+static int nr_history_lines = 0;
+#endif
+
+static void
+initialize_readline (void)
+{
+#ifdef HAVE_LIBREADLINE
+  const char *home;
+
+  home = getenv ("HOME");
+  if (home) {
+    snprintf (histfile, sizeof histfile, "%s/.guestfish", home);
+    using_history ();
+    (void) read_history (histfile);
+  }
+
+  rl_readline_name = "guestfish";
+  rl_attempted_completion_function = do_completion;
+#endif
+}
+
+static void
+cleanup_readline (void)
+{
+#ifdef HAVE_LIBREADLINE
+  int fd;
+
+  if (histfile[0] != '\0') {
+    fd = open (histfile, O_WRONLY|O_CREAT, 0644);
+    if (fd == -1) {
+      perror (histfile);
+      return;
+    }
+    close (fd);
+
+    (void) append_history (nr_history_lines, histfile);
+  }
+#endif
+}
+
+static void
+add_history_line (const char *line)
+{
+#ifdef HAVE_LIBREADLINE
+  add_history (line);
+  nr_history_lines++;
+#endif
+}
index 3997d6d..86e5e35 100644 (file)
@@ -40,4 +40,7 @@ extern void list_commands (void);
 extern void display_command (const char *cmd);
 extern int run_action (const char *cmd, int argc, char *argv[]);
 
+/* in completion.c (auto-generated) */
+extern char **do_completion (const char *text, int start, int end);
+
 #endif /* FISH_H */
index 9d988bf..8691dfe 100644 (file)
@@ -217,6 +217,11 @@ same effect as using the B<-v> option.
 Set the path that guestfish uses to search for kernel and initrd.img.
 See the discussion of paths in L<guestfs(3)>.
 
+=item HOME
+
+If compiled with GNU readline support, then the command history
+is saved in C<$HOME/.guestfish>
+
 =back
 
 =head1 SEE ALSO
index cfa1ec7..0aaa10b 100644 (file)
@@ -17,9 +17,11 @@ BuildRequires: /usr/bin/pod2man
 BuildRequires: /usr/bin/pod2text
 BuildRequires: febootstrap >= 1.5
 BuildRequires: augeas-devel >= 0.5.0
+BuildRequires: readline-devel
 BuildRequires: qemu
 
-# If you want to build the bindings for different languages:
+# These are only required if you want to build the bindings for
+# different languages:
 BuildRequires: ocaml
 BuildRequires: ocaml-findlib-devel
 BuildRequires: perl-devel
index 83098e7..6ef8e1b 100755 (executable)
@@ -2829,6 +2829,87 @@ and generate_fish_cmds () =
   pr "}\n";
   pr "\n"
 
+(* Readline completion for guestfish. *)
+and generate_fish_completion () =
+  generate_header CStyle GPLv2;
+
+  let all_functions =
+    List.filter (
+      fun (_, _, _, flags, _, _, _) -> not (List.mem NotInFish flags)
+    ) all_functions in
+
+  pr "\
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#ifdef HAVE_LIBREADLINE
+#include <readline/readline.h>
+#endif
+
+#include \"fish.h\"
+
+#ifdef HAVE_LIBREADLINE
+
+static const char *commands[] = {
+";
+
+  (* Get the commands and sort them, including the aliases. *)
+  let commands =
+    List.map (
+      fun (name, _, _, flags, _, _, _) ->
+       let name2 = replace_char name '_' '-' in
+       let alias =
+         try find_map (function FishAlias n -> Some n | _ -> None) flags
+         with Not_found -> name in
+
+       if name <> alias then [name2; alias] else [name2]
+    ) all_functions in
+  let commands = List.flatten commands in
+  let commands = List.sort compare commands in
+
+  List.iter (pr "  \"%s\",\n") commands;
+
+  pr "  NULL
+};
+
+static char *
+generator (const char *text, int state)
+{
+  static int index, len;
+  const char *name;
+
+  if (!state) {
+    index = 0;
+    len = strlen (text);
+  }
+
+  while ((name = commands[index]) != NULL) {
+    index++;
+    if (strncasecmp (name, text, len) == 0)
+      return strdup (name);
+  }
+
+  return NULL;
+}
+
+#endif /* HAVE_LIBREADLINE */
+
+char **do_completion (const char *text, int start, int end)
+{
+  char **matches = NULL;
+
+#ifdef HAVE_LIBREADLINE
+  if (start == 0)
+    matches = rl_completion_matches (text, generator);
+#endif
+
+  return matches;
+}
+";
+
 (* Generate the POD documentation for guestfish. *)
 and generate_fish_actions_pod () =
   let all_functions_sorted =
@@ -4072,6 +4153,10 @@ Run it from the top source directory using the command
   generate_fish_cmds ();
   close ();
 
+  let close = output_to "fish/completion.c" in
+  generate_fish_completion ();
+  close ();
+
   let close = output_to "guestfs-structs.pod" in
   generate_structs_pod ();
   close ();