summaryrefslogtreecommitdiffabout
Side-by-side diff
Diffstat (more/less context) (ignore whitespace changes)
-rw-r--r--NEWS7
-rw-r--r--README5
-rw-r--r--configure.ac49
-rw-r--r--doc/gdbm.texi22
-rw-r--r--doc/gdbmtool.115
-rw-r--r--src/Makefile.am12
-rw-r--r--src/gdbmtool.c102
-rw-r--r--src/gdbmtool.h31
-rw-r--r--src/gram.y7
-rw-r--r--src/input-rl.c235
-rw-r--r--src/input-std.c43
-rw-r--r--src/lex.l134
12 files changed, 578 insertions, 84 deletions
diff --git a/NEWS b/NEWS
index a564b2e..c9ab654 100644
--- a/NEWS
+++ b/NEWS
@@ -1,13 +1,13 @@
-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)
* gdbm_fetch, gdbm_firstkey, and gdbm_nextkey behavior
If the requested key was not found, these functions return datum with
dptr pointing to NULL and set gdbm_errno to GDBM_ITEM_NOT_FOUND (in
prior releases, gdbm_errno was set to GDBM_NO_ERROR),
@@ -22,24 +22,29 @@ The gdbm_fetch function no longer aborts on memory allocation errors.
Instead it sets gdbm_errno to GDBM_MALLOC_ERROR and returns NULL datum.
* Per-database error codes
* New functions
** 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
directory where the headers file dbm.h and ndbm.h will be installed.
Use this variable to avoid conflicts with already installed headers.
E.g.:
./configure --enable-libgdbm-compat COMPATINCLUDEDIR=/usr/include/gdbm
diff --git a/README b/README
index 99e680a..369460a 100644
--- a/README
+++ b/README
@@ -50,24 +50,29 @@ Build and install gdbmexport with the specified gdbm 1.8 library.
Build gdbmexport with specified (static) library. LIB must be
a loader argument valid for the use with cc(1), e.g.: -lgdbm-1.8.
** --gdbm183-libdir=DIR
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
Please report bugs to <bug-gdbm@gnu.org>
* Documentation, updates etc.
For the latest updates, visit <http://www.gnu.org/software/gdbm>,
diff --git a/configure.ac b/configure.ac
index 15270b9..336e05c 100644
--- a/configure.ac
+++ b/configure.ac
@@ -115,24 +115,73 @@ then
AC_CHECK_FUNCS([msync])
fi
AC_TYPE_OFF_T
AC_CHECK_SIZEOF(off_t)
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
src/Makefile
src/gdbm.h
doc/Makefile
compat/Makefile
export/Makefile])
AC_OUTPUT
diff --git a/doc/gdbm.texi b/doc/gdbm.texi
index 4c7c5c0..d172ad7 100644
--- a/doc/gdbm.texi
+++ b/doc/gdbm.texi
@@ -1819,24 +1819,30 @@ supply the necessary data, as shown in example below:
@example
gdbmtool> @kbd{store}
key? @kbd{three}
data? @kbd{3}
@end example
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
@node variables
@subsection Shell Variables
@cindex variables, gdbmtool
A number of @command{gdbmtool} parameters is kept in its internal
variables.
@@ -2104,24 +2110,40 @@ with its parameters and a short description of what it does. Optional
arguments are enclosed in square brackets.
@end deffn
@anchor{gdbmtool import}
@deffn {command verb} import @var{file-name} [replace] [nometa]
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
given, the record following the one with this key will be fetched.
See also @code{first}, above.
@xref{Sequential}, for more information on sequential access.
@end deffn
diff --git a/doc/gdbmtool.1 b/doc/gdbmtool.1
index dae881c..c479822 100644
--- a/doc/gdbmtool.1
+++ b/doc/gdbmtool.1
@@ -4,25 +4,25 @@
.\" 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/>. */
-.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]\
[\fB\-\-newdb\fR] [\fB\-\-no\-lock\fR]
[\fB\-\-no\-mmap\fR] [\fB\-\-norc\fR]
[\fB\-\-quiet\fR] [\fB\-\-read\-only\fR] [\fB\-\-synchronize\fR]\
[\fIDBFILE\fR]
.sp
\fBgdbmtool\fR [\fB\-Vh\fR] ][\fB\-\-help\fR] [\fB\-\-usage\fR] [\fB\-\-version\fR]
@@ -149,25 +149,25 @@ Close the currently open database.
.BR count
Print the number of entries in the database.
.TP
.BR current
Print the current bucket.
.TP
\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
the dump (*note Flat files::). By default, ASCII dump will be created.
.TP
\fBfetch\fR \fIKEY\fR
Fetch and display the record with the given \fIKEY\fR.
.TP
.BR first
@@ -178,24 +178,35 @@ command (see below).
.TP
\fBhash\fR \fIKEY\fR
Compute and display the hash value for the given \fIKEY\fR.
.TP
.BR header
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
.B nometa
argument turns off restoring meta-information from the dump file.
.TP
\fBlist\fR
List the contents of the database.
.TP
diff --git a/src/Makefile.am b/src/Makefile.am
index 6a05634..b55deb6 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -70,27 +70,37 @@ libgdbm_la_LDFLAGS = -version-info $(VI_CURRENT):$(VI_REVISION):$(VI_AGE)
noinst_LIBRARIES = libgdbmapp.a
libgdbmapp_a_SOURCES =\
err.c\
mem.c\
gdbmapp.h\
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
@@ -314,38 +314,24 @@ get_screen_lines ()
if (lines)
ws.ws_row = strtol (lines, NULL, 10);
}
return ws.ws_row;
}
#else
const char *lines = getenv ("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)
{
free (file_name);
file_name = estrdup (param->argv[0]->v.string);
}
}
@@ -742,25 +728,25 @@ list_handler (struct handler_param *param)
}
free (key.dptr);
key = nextkey;
}
}
/* 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)
{
int format = GDBM_DUMP_FMT_ASCII;
int flags = GDBM_WRCREAT;
int i;
int filemode;
@@ -882,36 +868,37 @@ source_handler (struct handler_param *param)
void help_handler (struct handler_param *param);
int help_begin (struct handler_param *param, size_t *exp_count);
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,
checkdb, delete_handler, NULL,
{ { N_("KEY"), GDBM_ARG_DATUM, DS_KEY }, { NULL } }, N_("delete a record") },
{ S(export), T_CMD,
checkdb, export_handler, NULL,
{ { N_("FILE"), GDBM_ARG_STRING },
@@ -998,43 +985,83 @@ struct command command_tab[] = {
{ NULL } }, N_("define datum structure") },
{ S(source), T_CMD,
NULL, source_handler, NULL,
{ { "FILE", GDBM_ARG_STRING },
{ NULL } }, N_("source command script") },
{ S(close), T_CMD,
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)
{
struct command const *ac = a;
struct command const *bc = b;
return strcmp (ac->name, bc->name);
}
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)
{
if (exp_count)
*exp_count = sizeof (command_tab) / sizeof (command_tab[0]) + 1;
return 0;
}
@@ -1165,67 +1192,96 @@ gdbmarg_kvpair (struct kvpair *kvp, struct locus *loc)
{
struct gdbmarg *arg = ecalloc (1, sizeof (*arg));
arg->next = NULL;
arg->type = GDBM_ARG_KVPAIR;
arg->ref = 1;
if (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)
p->loc = *loc;
p->val.s = val;
return p;
}
struct kvpair *
kvpair_list (struct locus *loc, struct slist *s)
{
struct kvpair *p = ecalloc (1, sizeof (*p));
p->type = KV_LIST;
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;
free (kvp->key);
switch (kvp->type)
{
case KV_STRING:
free (kvp->val.s);
break;
@@ -1525,44 +1581,48 @@ source_rcfile ()
}
free (fname);
}
}
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, "");
#endif
bindtextdomain (PACKAGE, LOCALEDIR);
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':
source = optarg;
intr = 0;
break;
case 'l':
bv = 0;
@@ -1631,14 +1691,16 @@ main (int argc, char *argv[])
memset (&param, 0, sizeof (param));
argmax = 0;
if (!norc)
source_rcfile ();
/* 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
@@ -15,24 +15,26 @@
You should have received a copy of the GNU General Public License
along with GDBM. If not, see <http://www.gnu.org/licenses/>. */
#include "autoconf.h"
#include "gdbmdefs.h"
#include "gdbm.h"
#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 */
};
/* Location in input file */
struct locus
{
struct point beg, end;
@@ -85,44 +87,58 @@ typedef struct locus gdbm_yyltype_t;
fprintf (File, "%s:%u.%u", \
(Loc).beg.file, \
(Loc).beg.line, \
(Loc).beg.col); \
} \
while (0)
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
{
struct kvpair *next;
int type;
struct locus loc;
char *key;
union
{
@@ -152,24 +168,32 @@ struct gdbmarg
char *string;
datum dat;
struct kvpair *kvpair;
} v;
};
/* 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 *);
struct gdbmarg *gdbmarg_kvpair (struct kvpair *kvl, struct locus *);
int gdbmarg_free (struct gdbmarg *arg);
void gdbmarg_destroy (struct gdbmarg **parg);
struct command;
@@ -253,12 +277,15 @@ void end_def (void);
int yylex (void);
int yyerror (char const *s);
int yyparse (void);
void datum_format (FILE *fp, datum const *dat, struct dsegm *ds);
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);
diff --git a/src/gram.y b/src/gram.y
index 9a6e3c2..2f6d7c0 100644
--- a/src/gram.y
+++ b/src/gram.y
@@ -159,31 +159,30 @@ kvpair : value
value : string
{
$$ = kvpair_string (&@1, $1);
}
| '{' slist '}'
{
$$ = 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
;
defn : T_DEF defid { begin_def (); } defbody
{
end_def ();
dsegm_free_list (dsdef[$2]);
diff --git a/src/input-rl.c b/src/input-rl.c
new file mode 100644
index 0000000..f435ed4
--- a/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
--- a/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 */
+}
+
diff --git a/src/lex.l b/src/lex.l
index 9c2c5f8..236474d 100644
--- a/src/lex.l
+++ b/src/lex.l
@@ -37,36 +37,34 @@ advance_line ()
yylloc.beg = point; \
yylloc.beg.col++; \
} \
point.col += yyleng; \
yylloc.end = point; \
} \
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;
ino_t ino; /* Inode number */
dev_t dev; /* Device number */
FILE *file; /* Input file */
YY_BUFFER_STATE buf; /* Buffer */
};
@@ -306,36 +304,34 @@ yywrap ()
void
begin_def (void)
{
BEGIN (DEF);
}
void
end_def (void)
{
BEGIN (INITIAL);
}
-static ssize_t
-read_input (char *buf, size_t size)
+void
+print_prompt_at_bol (void)
{
- if (interactive)
+ if (YY_AT_BOL ())
{
- 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;
int len;
char ptr[1];
};
static struct strseg *strseg_head, *strseg_tail;
@@ -444,127 +440,157 @@ vlerror (struct locus *loc, const char *fmt, va_list ap)
void
lerror (struct locus *loc, const char *fmt, ...)
{
va_list ap;
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)
+ {
+ slist_insert (&tail, slist_new_l (prompt, s - prompt));
+ if (!head)
+ head = tail;
+ }
+ if (expand_char (s[1], &tail) == 0)
{
- putchar ('%');
- break;
+ if (!head)
+ head = tail;
+ prompt = s + 2;
}
- expand_char (*s);
+ else
+ prompt = s;
+ s += 2;
}
else
- putchar (*s);
+ ++s;
+ }
+
+ if (s > prompt)
+ {
+ slist_insert (&tail, slist_new_l (prompt, s - prompt));
+ if (!head)
+ head = tail;
}
- fflush (stdout);
+ len = 0;
+ for (p = head; p; p = p->next)
+ len += strlen (p->str);
+
+ ret = emalloc (len + 1);
+ end = ret;
+ for (p = head; p; p = p->next)
+ {
+ s = p->str;
+ while (*s)
+ *end++ = *s++;
+ }
+ *end = 0;
+
+ slist_free (head);
+
+ return ret;
}

Return to:

Send suggestions and report system problems to the System administrator.