summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--README4
-rw-r--r--configure.ac20
-rw-r--r--fish/Makefile.am3
-rw-r--r--fish/completion.c142
-rw-r--r--fish/fish.c111
-rw-r--r--fish/fish.h3
-rw-r--r--guestfish.pod5
-rw-r--r--libguestfs.spec.in4
-rwxr-xr-xsrc/generator.ml85
9 files changed, 367 insertions, 10 deletions
diff --git a/README b/README
index d717a14c..2db1725f 100644
--- 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
diff --git a/configure.ac b/configure.ac
index 7a32acda..da48491d 100644
--- a/configure.ac
+++ b/configure.ac
@@ -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
diff --git a/fish/Makefile.am b/fish/Makefile.am
index b9c51b88..11598b8a 100644
--- a/fish/Makefile.am
+++ b/fish/Makefile.am
@@ -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
index 00000000..150b1aaa
--- /dev/null
+++ b/fish/completion.c
@@ -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;
+}
diff --git a/fish/fish.c b/fish/fish.c
index b0d91c79..51a3d50d 100644
--- a/fish/fish.c
+++ b/fish/fish.c
@@ -29,6 +29,11 @@
#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
+}
diff --git a/fish/fish.h b/fish/fish.h
index 3997d6d4..86e5e357 100644
--- a/fish/fish.h
+++ b/fish/fish.h
@@ -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 */
diff --git a/guestfish.pod b/guestfish.pod
index 9d988bf9..8691dfe9 100644
--- a/guestfish.pod
+++ b/guestfish.pod
@@ -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
diff --git a/libguestfs.spec.in b/libguestfs.spec.in
index cfa1ec72..0aaa10b1 100644
--- a/libguestfs.spec.in
+++ b/libguestfs.spec.in
@@ -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
diff --git a/src/generator.ml b/src/generator.ml
index 83098e7b..6ef8e1b3 100755
--- a/src/generator.ml
+++ b/src/generator.ml
@@ -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 ();