diff options
-rw-r--r-- | NEWS | 7 | ||||
-rw-r--r-- | README | 5 | ||||
-rw-r--r-- | configure.ac | 49 | ||||
-rw-r--r-- | doc/gdbm.texi | 22 | ||||
-rw-r--r-- | doc/gdbmtool.1 | 15 | ||||
-rw-r--r-- | src/Makefile.am | 12 | ||||
-rw-r--r-- | src/gdbmtool.c | 100 | ||||
-rw-r--r-- | src/gdbmtool.h | 31 | ||||
-rw-r--r-- | src/gram.y | 7 | ||||
-rw-r--r-- | src/input-rl.c | 235 | ||||
-rw-r--r-- | src/input-std.c | 43 | ||||
-rw-r--r-- | src/lex.l | 130 |
12 files changed, 575 insertions, 81 deletions
@@ -1,7 +1,7 @@ -GNU dbm NEWS -- history of user-visible changes. 2016-07-09 +GNU dbm NEWS -- history of user-visible changes. 2016-07-12 Copyright (C) 1990-2016 Free Software Foundation, Inc. See the end of file for copying conditions. Please send gdbm bug reports to <bug-gdbm@gnu.org>. Version 1.12.90 (Git) @@ -28,12 +28,17 @@ Instead it sets gdbm_errno to GDBM_MALLOC_ERROR and returns NULL datum. ** gdbm_last_errno ** gdbm_clear_error ** gdbm_needs_recovery +* Line editing in gdbmtool + +The gdbmtool utility now offers the usual line-editing facilities (if +the package has been compiled with GNU Readline. + Version 1.12, 2016-05-16 * New configuration variable COMPATINCLUDEDIR When used with --enable-libgdbm-compat, this variable points to the @@ -56,12 +56,17 @@ a loader argument valid for the use with cc(1), e.g.: -lgdbm-1.8. Build gdbmexport with the gdbm library from the specified directory. ** --gdbm183-includedir=DIR Build gdbmexport with gdbm.h from the specified directory. +** --without-readline + +Don't compile GNU Readline support. By default, configure enables +line-editing support if GNU Readline is available on the system. + * Installation make install * Bug reporting diff --git a/configure.ac b/configure.ac index 15270b9..336e05c 100644 --- a/configure.ac +++ b/configure.ac @@ -121,12 +121,61 @@ AC_CHECK_MEMBERS([struct stat.st_blksize]) AC_SUBST(GDBM183_LIBRARY) AC_SUBST(GDBM183_LIBDIR) AC_SUBST(GDBM183_INCLUDEDIR) AM_CONDITIONAL([COMPAT_OPT], [test "$want_compat" = yes]) AM_CONDITIONAL([ENABLE_EXPORT], [test "$want_export" = yes]) +# Check for Curses libs. +for lib in ncurses curses termcap +do + AC_CHECK_LIB($lib, tputs, [CURSES_LIBS="-l$lib"; break]) +done + +AC_SUBST(CURSES_LIBS) +# Readline +AC_ARG_WITH([readline], + AC_HELP_STRING([--without-readline], + [do not use readline]), + [ +case "${withval}" in + yes) status_readline=yes ;; + no) status_readline=no ;; + *) AC_MSG_ERROR(bad value ${withval} for --without-readline) ;; +esac],[status_readline=probe]) + +AC_SUBST(READLINE_LIBS) + +if test "$status_readline" != "no"; then + dnl FIXME This should only link in the curses libraries if it's + dnl really needed! + saved_LIBS=$LIBS + LIBS="$LIBS $CURSES_LIBS" + AC_CHECK_LIB(readline, readline, + [status_readline=yes], + [if test "$status_readline" = "yes"; then + AC_MSG_ERROR(readline requested but does not seem to be installed) + else + status_readline=no + fi]) + LIBS=$saved_LIBS + + if test "$status_readline" = "yes"; then + AC_CHECK_HEADERS(readline/readline.h, + AC_DEFINE(WITH_READLINE,1,[Enable use of readline])) + READLINE_LIBS="-lreadline $CURSES_LIBS" + saved_LIBS=$LIBS + LIBS="$LIBS $READLINE_LIBS" + AC_CHECK_FUNCS(rl_completion_matches) + LIBS=$saved_LIBS + fi +else + status_readline=no +fi + +AM_CONDITIONAL([GDBM_COND_READLINE], [test "$status_readline" = "yes"]) + # Initialize the test suite. AC_CONFIG_TESTDIR(tests) AC_CONFIG_FILES([tests/Makefile tests/atlocal po/Makefile.in]) AM_MISSING_PROG([AUTOM4TE], [autom4te]) AC_CONFIG_FILES([Makefile diff --git a/doc/gdbm.texi b/doc/gdbm.texi index 4c7c5c0..d172ad7 100644 --- a/doc/gdbm.texi +++ b/doc/gdbm.texi @@ -1825,12 +1825,18 @@ data? @kbd{3} However, such prompting is possible only in interactive mode. In non-interactive mode (e.g.@: when running a script), all arguments must be supplied with each command, otherwise @command{gdbmtool} will report an error and exit immediately. +@cindex readline +@cindex GNU Readline +If the package is compiled with GNU Readline, the input line can be +edited (@pxref{Command Line Editing, , +Command Line Editing, readline, GNU Readline Library}). + @menu * variables:: shell variables. * commands:: shell commands. * definitions:: how to define structured data. * startup files:: @end menu @@ -2110,12 +2116,28 @@ Import data from a flat dump file @var{file-name} (@pxref{Flat files}). If the word @samp{replace} is given as an argument, any records with the same keys as the already existing ones will replace them. The word @samp{nometa} turns off restoring meta-information from the dump file. @end deffn +@deffn {command verb} history +@deffnx {command verb} history @var{count} +@deffnx {command verb} history @var{n} @var{count} +Shows the command history list with line numbers. When used without +arguments, shows entire history. When used with one argument, +displays @var{count} last commands from the history. With two +arguments, displays @var{count} commands starting from @var{n}th +command. Command numbering starts with 1. + +This command is available only if GDBM was compiled with GNU Readline. +The history is saved in file @file{.gdbmtool_history} in the user's +home directory. If this file exists upon startup, it is read to +populate the history. Thus, command history is preserved between +@command{gdbmtool} invocations. +@end deffn + @deffn {command verb} list List the contents of the database. @end deffn @deffn {command verb} next [@var{key}] Sequential access: fetch and display the next record. If the @var{key} is diff --git a/doc/gdbmtool.1 b/doc/gdbmtool.1 index dae881c..c479822 100644 --- a/doc/gdbmtool.1 +++ b/doc/gdbmtool.1 @@ -10,13 +10,13 @@ .\" 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 GDBM. If not, see <http://www.gnu.org/licenses/>. */ -.TH GDBMTOOL 1 "May 18, 2016" "GDBM" "GDBM User Reference" +.TH GDBMTOOL 1 "July 12, 2016" "GDBM" "GDBM User Reference" .SH NAME gdbmtool \- examine and modify a GDBM database .SH SYNOPSIS \fBgdbmtool\fR [\fB\-lmNnqrs\fR] [\fB\-b\fR \fISIZE\fR] [\fB\-c\fR \fISIZE\fR]\ [\fB\-f\fR \fIFILE\fR] [\fB\-\-block\-size\fR=\fISIZE\fR] [\fB\-\-cache\-size\fR=\fISIZE\fR] [\fB\-\-file\fR \fIFILE\fR]\ @@ -155,13 +155,13 @@ Print the current bucket. \fBdelete\fR \fIKEY\fR Delete record with the given \fIKEY\fR. .TP .BR dir Print hash directory. .TP -\fBexport\fR, \fBe\fR \fIFILE\-NAME\fR [\fBtruncate\fR] [\fBbinary\fR|\fBascii\fR] +\fBexport\fR \fIFILE\-NAME\fR [\fBtruncate\fR] [\fBbinary\fR|\fBascii\fR] Export the database to the flat file \fIFILE\-NAME\fR. This is equivalent to .BR gdbm_dump (1). This command will not overwrite an existing file, unless the .B truncate parameter is also given. Another optional parameter determines the type of @@ -184,12 +184,23 @@ Print file header. .TP .BR help " or " ? Print a concise command summary, showing each command letter and verb with its parameters and a short description of what it does. Optional arguments are enclosed in square brackets. .TP +.B history +Shows the command history list with line numbers. This command is +available only if the program was compiled with GNU Readline. +.TP +\fBhistory\fR \fICOUNT\fR. +Shows \fICOUNT\fR latest commands from the command history. +.TP +\fBhistory\fR \fIN\fR \fICOUNT\fR. +Shows \fICOUNT\fR commands from the command history starting with \fIN\fRth +command. +.TP \fBimport\fR \fIFILE\-NAME\fR [\fBreplace\fR] [\fBnometa\fR] Import data from a flat dump file \fIFILE\-NAME\fR. If the .B replace argument is given, any records with the same keys as the already existing ones will replace them. The diff --git a/src/Makefile.am b/src/Makefile.am index 6a05634..b55deb6 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -76,21 +76,31 @@ libgdbmapp_a_SOURCES =\ parseopt.c\ progname.c # Programs bin_PROGRAMS = gdbmtool gdbm_load gdbm_dump -gdbmtool_LDADD = ./libgdbmapp.a ./libgdbm.la +gdbmtool_LDADD = \ + ./libgdbmapp.a\ + ./libgdbm.la\ + @READLINE_LIBS@ + gdbmtool_SOURCES = \ datconv.c\ gram.y\ lex.l\ gdbmtool.h\ gdbmtool.c\ var.c\ util.c +if GDBM_COND_READLINE + gdbmtool_SOURCES += input-rl.c +else + gdbmtool_SOURCES += input-std.c +endif + AM_YFLAGS = -dtv #AM_LFLAGS = -d gdbm_load_LDADD = ./libgdbmapp.a ./libgdbm.la gdbm_dump_LDADD = ./libgdbmapp.a ./libgdbm.la diff --git a/src/gdbmtool.c b/src/gdbmtool.c index 65b6740..2d57cbc 100644 --- a/src/gdbmtool.c +++ b/src/gdbmtool.c @@ -321,26 +321,12 @@ get_screen_lines () if (lines) return strtol (lines, NULL, 10); #endif return -1; } - -#define ARG_UNUSED __attribute__ ((__unused__)) - -#define NARGS 5 - -struct handler_param -{ - int argc; - struct gdbmarg **argv; - FILE *fp; - void *data; -}; - - /* Open database */ void open_handler (struct handler_param *param) { if (opendb (param->argv[0]->v.string) == 0) { @@ -748,13 +734,13 @@ list_handler (struct handler_param *param) /* quit - quit the program */ void quit_handler (struct handler_param *param ARG_UNUSED) { if (gdbm_file != NULL) gdbm_close (gdbm_file); - + input_done (); exit (EXIT_OK); } /* export FILE [truncate] - export to a flat file format */ void export_handler (struct handler_param *param) @@ -888,25 +874,26 @@ struct argdef { char *name; int type; int ds; }; +#define NARGS 5 + struct command { char *name; /* Command name */ size_t len; /* Name length */ int tok; int (*begin) (struct handler_param *param, size_t *); void (*handler) (struct handler_param *param); void (*end) (void *data); struct argdef args[NARGS]; char *doc; }; - struct command command_tab[] = { #define S(s) #s, sizeof (#s) - 1 { S(count), T_CMD, checkdb, count_handler, NULL, { { NULL } }, N_("count (number of entries)") }, { S(delete), T_CMD, @@ -1004,12 +991,19 @@ struct command command_tab[] = { NULL, close_handler, NULL, { { NULL } }, N_("close the database") }, { S(open), T_CMD, NULL, open_handler, NULL, { { "FILE", GDBM_ARG_STRING }, { NULL } }, N_("open new database") }, +#ifdef WITH_READLINE + { S(history), T_CMD, + input_history_begin, input_history_handler, NULL, + { { N_("[FROM]"), GDBM_ARG_STRING }, + { N_("[COUNT]"), GDBM_ARG_STRING }, + { NULL } }, N_("show input history") }, +#endif #undef S { 0 } }; static int cmdcmp (const void *a, const void *b) @@ -1023,12 +1017,45 @@ void sort_commands () { qsort (command_tab, sizeof (command_tab) / sizeof (command_tab[0]) - 1, sizeof (command_tab[0]), cmdcmp); } +/* Generator function for command completion. STATE lets us know whether + to start from scratch; without any state (i.e. STATE == 0), then we + start at the top of the list. */ +char * +command_generator (const char *text, int state) +{ + const char *name; + static int len; + static struct command *cmd; + + /* If this is a new word to complete, initialize now. This includes + saving the length of TEXT for efficiency, and initializing the index + variable to 0. */ + if (!state) + { + cmd = command_tab; + len = strlen (text); + } + + if (!cmd->name) + return NULL; + + /* Return the next name which partially matches from the command list. */ + while ((name = cmd->name)) + { + cmd++; + if (strncmp (name, text, len) == 0) + return strdup (name); + } + + /* If no names matched, then return NULL. */ + return NULL; +} /* ? - help handler */ #define CMDCOLS 30 int help_begin (struct handler_param *param ARG_UNUSED, size_t *exp_count) @@ -1171,32 +1198,62 @@ gdbmarg_kvpair (struct kvpair *kvp, struct locus *loc) arg->loc = *loc; arg->v.kvpair = kvp; return arg; } struct slist * -slist_new (char *s) +slist_new_s (char *s) { struct slist *lp = emalloc (sizeof (*lp)); lp->next = NULL; lp->str = s; return lp; } +struct slist * +slist_new (char const *s) +{ + return slist_new_s (estrdup (s)); +} + +struct slist * +slist_new_l (char const *s, size_t l) +{ + char *copy = emalloc (l + 1); + memcpy (copy, s, l); + copy[l] = 0; + return slist_new_s (copy); +} + void slist_free (struct slist *lp) { while (lp) { struct slist *next = lp->next; free (lp->str); free (lp); lp = next; } } +void +slist_insert (struct slist **where, struct slist *what) +{ + if (*where) + { + while (what->next) + what = what->next; + what->next = (*where)->next; + (*where)->next = what; + } + else + what->next = NULL; + *where = what; +} + struct kvpair * kvpair_string (struct locus *loc, char *val) { struct kvpair *p = ecalloc (1, sizeof (*p)); p->type = KV_STRING; if (loc) @@ -1213,13 +1270,12 @@ kvpair_list (struct locus *loc, struct slist *s) if (loc) p->loc = *loc; p->val.l = s; return p; } - static void kvlist_free (struct kvpair *kvp) { while (kvp) { struct kvpair *next = kvp->next; @@ -1531,12 +1587,13 @@ int main (int argc, char *argv[]) { int intr; int opt; int bv; int norc = 0; + int res; char *source = "-"; set_progname (argv[0]); #ifdef HAVE_SETLOCALE setlocale (LC_ALL, ""); @@ -1545,18 +1602,21 @@ main (int argc, char *argv[]) textdomain (PACKAGE); sort_commands (); /* Initialize variables. */ intr = isatty (0); + interactive = intr; /* Used early by input_init */ dsdef[DS_KEY] = dsegm_new_field (datadef_lookup ("string"), NULL, 1); dsdef[DS_CONTENT] = dsegm_new_field (datadef_lookup ("string"), NULL, 1); variable_set ("open", VART_STRING, "wrcreat"); variable_set ("pager", VART_STRING, getenv ("PAGER")); + input_init (); + for (opt = parseopt_first (argc, argv, optab); opt != EOF; opt = parseopt_next ()) switch (opt) { case 'f': @@ -1637,8 +1697,10 @@ main (int argc, char *argv[]) /* Welcome message. */ if (intr && !variable_is_true ("quiet")) printf (_("\nWelcome to the gdbm tool. Type ? for help.\n\n")); if (setsource (source, intr)) exit (EXIT_FATAL); - return yyparse (); + res = yyparse (); + input_done (); + return res; } diff --git a/src/gdbmtool.h b/src/gdbmtool.h index 84f1409..d0e9aa0 100644 --- a/src/gdbmtool.h +++ b/src/gdbmtool.h @@ -21,12 +21,14 @@ #include "gdbmapp.h" #include <stdlib.h> #include <stdio.h> #include <stdarg.h> #include <ctype.h> +#define ARG_UNUSED __attribute__ ((__unused__)) + /* Position in input file */ struct point { char *file; /* file name */ unsigned line; /* line number */ unsigned col; /* column number */ @@ -91,32 +93,46 @@ typedef struct locus gdbm_yyltype_t; void vlerror (struct locus *loc, const char *fmt, va_list ap); void lerror (struct locus *loc, const char *fmt, ...); void terror (const char *fmt, ...); -void print_prompt (void); +char *make_prompt (void); int setsource (const char *filename, int intr); extern char *file_name; extern int interactive; extern int open_mode; #define GDBMTOOLRC ".gdbmtoolrc" #define GDBMTOOL_DEFFILE "junk.gdbm" +ssize_t input_read (FILE *fp, char *buf, size_t size); +void input_init (void); +void input_done (void); + +struct handler_param; +void input_history_handler (struct handler_param *param); +int input_history_begin (struct handler_param *param, size_t *exp_count); + +void print_prompt_at_bol (void); +char *command_generator (const char *text, int state); + struct slist { struct slist *next; char *str; }; -struct slist *slist_new (char *s); +struct slist *slist_new (char const *s); +struct slist *slist_new_s (char *s); +struct slist *slist_new_l (char const *s, size_t l); void slist_free (struct slist *); +void slist_insert (struct slist **where, struct slist *what); #define KV_STRING 0 #define KV_LIST 1 struct kvpair { @@ -158,12 +174,20 @@ struct gdbmarg /* List of arguments */ struct gdbmarglist { struct gdbmarg *head, *tail; }; +struct handler_param +{ + int argc; + struct gdbmarg **argv; + FILE *fp; + void *data; +}; + void gdbmarglist_init (struct gdbmarglist *, struct gdbmarg *); void gdbmarglist_add (struct gdbmarglist *, struct gdbmarg *); void gdbmarglist_free (struct gdbmarglist *lst); struct gdbmarg *gdbmarg_string (char *, struct locus *); struct gdbmarg *gdbmarg_datum (datum *, struct locus *); @@ -259,6 +283,9 @@ int datum_scan (datum *dat, struct dsegm *ds, struct kvpair *kv); void dsprint (FILE *fp, int what, struct dsegm *ds); char *mkfilename (const char *dir, const char *file, const char *suf); char *tildexpand (char *s); int vgetyn (const char *prompt, va_list ap); int getyn (const char *prompt, ...); + +int getnum (int *pnum, char *arg, char **endp); +int get_screen_lines (void); @@ -165,19 +165,18 @@ value : string $$ = kvpair_list (&@1, $2.head); } ; slist : string { - $$.head = $$.tail = slist_new ($1); + $$.head = $$.tail = slist_new_s ($1); } | slist ',' string { - struct slist *s = slist_new ($3); - $1.tail->next = s; - $1.tail = s; + struct slist *s = slist_new_s ($3); + slist_insert (&$1.tail, s); $$ = $1; } ; string : T_IDENT | T_WORD diff --git a/src/input-rl.c b/src/input-rl.c new file mode 100644 index 0000000..f435ed4 --- /dev/null +++ b/src/input-rl.c @@ -0,0 +1,235 @@ +/* This file is part of GDBM, the GNU data base manager. + Copyright (C) 2016 Free Software Foundation, Inc. + + GDBM 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 3, or (at your option) + any later version. + + GDBM 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 GDBM. If not, see <http://www.gnu.org/licenses/>. */ + +#include "gdbmtool.h" +#include <readline/readline.h> +#include <readline/history.h> + +static char *pre_input_line; + +static int +pre_input (void) +{ + if (pre_input_line) + { + rl_insert_text (pre_input_line); + free (pre_input_line); + pre_input_line = NULL; + rl_redisplay (); + } + return 0; +} + +static int +retrieve_history (char *str) +{ + char *out; + int rc; + + rc = history_expand (str, &out); + switch (rc) + { + case -1: + yyerror (out); + free (out); + return 1; + + case 0: + break; + + case 1: + pre_input_line = out; + return 1; + + case 2: + printf ("%s\n", out); + free (out); + return 1; + } + return 0; +} + +ssize_t +input_read (FILE *fp, char *buf, size_t size) +{ + static char *input_line; + static size_t input_length; + static size_t input_off; +#define input_ptr() (input_line + input_off) +#define input_size() (input_length - input_off) + + if (interactive) + { + size_t len = input_size (); + if (!len) + { + if (input_line) + { + newline: + free (input_line); + input_line = NULL; + buf[0] = '\n'; + return 1; + } + else + { + char *prompt; + again: + prompt = make_prompt (); + input_line = readline (prompt); + free (prompt); + if (!input_line) + return 0; + input_length = strlen (input_line); + input_off = 0; + if (input_length) + { + if (retrieve_history (input_line)) + { + free (input_line); + goto again; + } + } + else + goto newline; + len = input_size (); + add_history (input_line); + } + } + + if (len > size) + len = size; + memcpy (buf, input_ptr (), len); + input_off += len; + + return len; + } + return fread (buf, 1, size, fp); +} + +struct history_param +{ + int from; + int count; +}; + +int +input_history_begin (struct handler_param *param, size_t *exp_count) +{ + struct history_param *p; + int from = 0, count = history_length; + + switch (param->argc) + { + case 1: + if (getnum (&count, param->argv[0]->v.string, NULL)) + return 1; + if (count > history_length) + count = history_length; + else + from = history_length - count; + break; + + case 2: + if (getnum (&from, param->argv[0]->v.string, NULL)) + return 1; + if (from) + --from; + if (getnum (&count, param->argv[1]->v.string, NULL)) + return 1; + + if (count > history_length) + count = history_length; + } + p = emalloc (sizeof *p); + p->from = from; + p->count = count; + param->data = p; + if (exp_count) + *exp_count = count; + return 0; +} + +void +input_history_handler (struct handler_param *param) +{ + struct history_param *p = param->data; + int i; + HIST_ENTRY **hlist; + FILE *fp = param->fp; + + hlist = history_list (); + for (i = 0; i < p->count; i++) + fprintf (fp, "%4d) %s\n", p->from + i + 1, hlist[p->from + i]->line); +} + +#define HISTFILE_PREFIX "~/." +#define HISTFILE_SUFFIX "_history" + +static char * +get_history_file_name () +{ + static char *filename = NULL; + + if (!filename) + { + char *hname; + + hname = emalloc (sizeof HISTFILE_PREFIX + strlen (rl_readline_name) + + sizeof HISTFILE_SUFFIX - 1); + strcpy (hname, HISTFILE_PREFIX); + strcat (hname, rl_readline_name); + strcat (hname, HISTFILE_SUFFIX); + filename = tildexpand (hname); + free (hname); + } + return filename; +} + +static char ** +shell_completion (const char *text, int start, int end) +{ + char **matches; + + matches = (char **) NULL; + + /* If this word is at the start of the line, then it is a command + to complete. Otherwise it is the name of a file in the current + directory. */ + if (start == 0) + matches = rl_completion_matches (text, command_generator); + + return (matches); +} + +void +input_init (void) +{ + /* Allow conditional parsing of the ~/.inputrc file. */ + rl_readline_name = (char *) progname; + rl_attempted_completion_function = shell_completion; + rl_pre_input_hook = pre_input; + if (interactive) + read_history (get_history_file_name ()); +} + +void +input_done (void) +{ + if (interactive) + write_history (get_history_file_name ()); +} + diff --git a/src/input-std.c b/src/input-std.c new file mode 100644 index 0000000..4508368 --- /dev/null +++ b/src/input-std.c @@ -0,0 +1,43 @@ +/* This file is part of GDBM, the GNU data base manager. + Copyright (C) 2016 Free Software Foundation, Inc. + + GDBM 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 3, or (at your option) + any later version. + + GDBM 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 GDBM. If not, see <http://www.gnu.org/licenses/>. */ + +#include "gdbmtool.h" + +ssize_t +input_read (FILE *fp, char *buf, size_t size) +{ + if (interactive) + { + print_prompt_at_bol (); + if (fgets (buf, size, fp) == NULL) + return 0; + return strlen (buf); + } + return fread (buf, 1, size, fp); +} + +void +input_init (void) +{ + /* nothing */ +} + +void +input_done (void) +{ + /* nothing */ +} + @@ -43,24 +43,22 @@ advance_line () while (0); #undef YY_INPUT #define YY_INPUT(buf,result,max_size) \ do \ { \ - result = read_input (buf, max_size); \ + result = input_read (yyin, buf, max_size); \ } \ while (0); void string_begin (void); void string_add (const char *s, int l); void string_addc (int c); char *string_end (void); int unescape (int c); -static ssize_t read_input (char *buf, size_t size); - struct context /* Input context */ { struct context *parent; /* Pointer to the parent context */ struct locus locus; /* Locus */ struct point point; int interactive; @@ -312,24 +310,22 @@ begin_def (void) void end_def (void) { BEGIN (INITIAL); } -static ssize_t -read_input (char *buf, size_t size) -{ - if (interactive) +void +print_prompt_at_bol (void) { if (YY_AT_BOL ()) - print_prompt (); - if (fgets (buf, size, yyin) == NULL) - return 0; - return strlen (buf); + { + char *s = make_prompt (); + fputs (s, stdout); + fflush (stdout); + free (s); } - return fread (buf, 1, size, yyin); } struct strseg { struct strseg *next; @@ -450,121 +446,151 @@ lerror (struct locus *loc, const char *fmt, ...) va_start (ap, fmt); vlerror (loc, fmt, ap); va_end (ap); } -struct prompt_exp; - -void -pe_file_name (struct prompt_exp *p) +static struct slist * +pe_file_name (void) { - if (file_name) - fwrite (file_name, strlen (file_name), 1, stdout); + return file_name ? slist_new (file_name) : NULL; } -void -pe_program_name (struct prompt_exp *p) +static struct slist * +pe_program_name (void) { - fwrite (progname, strlen (progname), 1, stdout); + return slist_new (progname); } -void -pe_package_name (struct prompt_exp *p) +static struct slist * +pe_package_name (void) { - fwrite (PACKAGE_NAME, sizeof (PACKAGE_NAME) - 1, 1, stdout); + return slist_new (PACKAGE_NAME); } -void -pe_program_version (struct prompt_exp *p) +static struct slist * +pe_program_version (void) { - fwrite (PACKAGE_VERSION, sizeof (PACKAGE_VERSION) - 1, 1, stdout); + return slist_new (PACKAGE_VERSION); } -void -pe_space (struct prompt_exp *p) +static struct slist * +pe_space (void) { - fwrite (" ", 1, 1, stdout); + return slist_new (" "); } struct prompt_exp { int ch; - void (*fun) (struct prompt_exp *); - char *cache; + struct slist *(*fun) (void); }; struct prompt_exp prompt_exp[] = { { 'f', pe_file_name }, { 'p', pe_program_name }, { 'P', pe_package_name }, { 'v', pe_program_version }, { '_', pe_space }, { 0 } }; -static void -expand_char (int c) +static int +expand_char (int c, struct slist **tailp) { struct prompt_exp *p; if (c && c != '%') { for (p = prompt_exp; p->ch; p++) { if (c == p->ch) { - if (p->cache) - free (p->cache); - p->fun (p); - return; + struct slist *s = p->fun (); + if (s) + slist_insert (tailp, s); + return 0; } } } - putchar ('%'); - putchar (c); + return 1; } char const * -psname () +psname (void) { if (YYSTATE == DEF || YYSTATE == MLSTR) return "ps2"; return "ps1"; } -void -print_prompt () +char * +make_prompt (void) { const char *s; const char *prompt; + struct slist *head = NULL, *tail = NULL, *p; + char *ret, *end; + size_t len; switch (variable_get (psname (), VART_STRING, (void *) &prompt)) { case VAR_OK: break; case VAR_ERR_NOTSET: - return; + return NULL; default: abort (); } - for (s = prompt; *s; s++) + for (s = prompt; *s; ) { - if (*s == '%') + if (*s == '%' && s[1]) { - if (!*++s) + if (s > prompt) { - putchar ('%'); - break; + slist_insert (&tail, slist_new_l (prompt, s - prompt)); + if (!head) + head = tail; + } + if (expand_char (s[1], &tail) == 0) + { + if (!head) + head = tail; + prompt = s + 2; } - expand_char (*s); + else + prompt = s; + s += 2; } else - putchar (*s); + ++s; } - fflush (stdout); + if (s > prompt) + { + slist_insert (&tail, slist_new_l (prompt, s - prompt)); + if (!head) + head = tail; + } + + len = 0; + for (p = head; p; p = |