Hi,
I, as a frequent MapiClient user, have added some convenience functions
to the readline interface over time:
- commandline-selectable "save/load history" option (default=off).
- basic 'tab' command completion (for SQL only, as of now). Completes on
table names and SQL commands. Could be improved, but this already saves
a lot of typing.
I have added the .mx code and patch for MapiClient.mx and Makefile.ag
If someone could look over this code; it would be nice to have it in cvs.
--
Arjan Scherpenisse
Centrum voor Wiskunde en Informatica,
Amsterdam, the Netherlands
Index: MapiClient.mx
===================================================================
RCS file: /cvsroot/monetdb/MonetDB/src/mapi/clients/C/MapiClient.mx,v
retrieving revision 1.57
diff -u -r1.57 MapiClient.mx
--- MapiClient.mx 11 May 2004 11:03:04 -0000 1.57
+++ MapiClient.mx 2 Jun 2004 14:56:16 -0000
@@ -56,6 +56,7 @@
-t & --trace & trace Monet interaction \\
-T & --time & time commands \\
-u user & --user=user & user id \\
+ -H & --history & load/save cmdline history (default off) \\
-? & --help & show this usage message \\
\end{tabular}
@@ -83,6 +84,7 @@
#ifdef HAVE_LIBREADLINE
#include
#include
+#include "ReadlineTools.h"
#endif
#ifndef S_ISCHR
@@ -805,6 +807,7 @@
fprintf(stderr, " -t | --trace /* trace Monet interaction */\n");
fprintf(stderr, " -T | --time /* time commands */\n");
fprintf(stderr, " -u user | --user=user /* user id */\n" );
+ fprintf(stderr, " -H | --history /* load/save cmdline history (default off) */\n" );
fprintf(stderr, " -? | --help /* show this usage message */\n");
exit(-1);
}
@@ -832,8 +835,9 @@
int guest = 0;
int linemode = 1;
int c;
- int quiet = 0;
Mapi mid;
+ int quiet = 0;
+ int save_history = 0;
static struct option long_options[] = {
{"blocked", 1, 0, 'b'},
{"config", 1, 0, 'c'},
@@ -848,6 +852,7 @@
{"time", 0, 0, 'T'},
{"trace", 0, 0, 't'},
{"user", 1, 0, 'u'},
+ {"history", 0, 0, 'H'},
{"quiet", 0, 0, 'q'},
{"help", 0, 0, '?'},
{0, 0, 0, 0}
@@ -860,7 +865,7 @@
if ((setlen = mo_builtin_settings(&set)) == 0)
usage(argv[0]);
- while ((c = getopt_long(argc, argv, "b:c:l:u:p:P:qh:s:DtTd::?", long_options, NULL)) != -1) {
+ while ((c = getopt_long(argc, argv, "b:c:l:u:p:P:qHh:s:DtTd::?", long_options, NULL)) != -1) {
switch (c) {
case 'b':
if (*optarg == 't' || *optarg == 'T' || *optarg == '1')
@@ -924,6 +929,9 @@
*eq = '=';
break;
}
+ case 'H':
+ save_history = 1;
+ break;
case '?':
usage(argv[0]);
default:
@@ -998,6 +1006,10 @@
optind++;
}
+#ifdef HAVE_LIBREADLINE
+ init_readline(mid, language, save_history);
+#endif
+
if (command) {
c = doRequest(mid, command);
} else {
@@ -1014,6 +1026,11 @@
}
c = doFileByLines(mid, stdin, prompt, linemode);
}
+
+#ifdef HAVE_LIBREADLINE
+ deinit_readline();
+#endif
+
mapi_disconnect(mid);
return c;
}
Index: Makefile.ag
===================================================================
RCS file: /cvsroot/monetdb/MonetDB/src/mapi/clients/C/Makefile.ag,v
retrieving revision 1.3
diff -u -r1.3 Makefile.ag
--- Makefile.ag 25 May 2004 21:02:46 -0000 1.3
+++ Makefile.ag 2 Jun 2004 14:56:16 -0000
@@ -27,7 +27,7 @@
MAPI_LIBS = $(SOCKET_LIBS)
lib_Mapi = {
- SOURCES = Mapi.mx
+ SOURCES = Mapi.mx
LIBS = $(MAPI_LIBS) ../../../common/libmutils ../../../common/libstream
HEADERS = h
}
@@ -37,9 +37,9 @@
SOURCES = Mapi.mx
}
-BINS = {
- SOURCES = MapiClient.mx
- MapiClient_LIBS = libMapi $(MAPI_LIBS) $(READLINE_LIBS) \
+bin_MapiClient = {
+ SOURCES = MapiClient.mx ReadlineTools.mx
+ LIBS = libMapi $(MAPI_LIBS) $(READLINE_LIBS) \
../../../common/libmutils ../../../common/libstream \
$(ICONV_LIBS)
}
@h
#ifndef READLINETOOLS_H_INCLUDED
#define READLINETOOLS_H_INCLUDED
#include
#include
#include
#include "Mapi.h"
void init_readline(Mapi mid, const char *language, int save_history);
void deinit_readline();
char **sql_completion (const char *text, int start, int end);
char *sql_tablename_generator (const char *text, int state);
char *sql_command_generator (const char *text, int state);
#endif
@c
/*
* Readline specific stuff
*/
#include "ReadlineTools.h"
#define PATHLENGTH 256 /* maximum file pathname length. */
const char *SQL_COMMANDS[] = { "SELECT", "INSERT", "UPDATE", "SET", "DELETE", "COMMIT", "ROLLBACK",
"DROP TABLE", "CREATE", "ALTER", "RELEASE SAVEPOINT", "START TRANSACTION", 0};
Mapi _mid;
char _history_file[PATHLENGTH];
int _save_history = 0;
void
init_readline(Mapi mid, const char *language, int save_history)
{
_mid = mid;
/* Allow conditional parsing of the ~/.inputrc file. */
rl_readline_name = "MapiClient";
/* Tell the completer that we want to try our own completion before std completion (filename) kicks in. */
if (strcmp(language, "sql")==0) {
rl_attempted_completion_function = sql_completion;
} else {
// FIXME: mil completion
}
if (save_history) {
#ifndef NATIVE_WIN32
if (getenv("HOME")!=NULL) {
snprintf(_history_file, PATHLENGTH, "%s/.mapiclient_history_%s", getenv("HOME"), language);
_save_history = 1;
}
#else
snprintf(_history_file, PATHLENGTH, "%s%c_mapiclient_history_%s", mo_find_option(&set, setlen, "prefix"), DIR_SEP, language);
_save_history = 1;
#endif
read_history(_history_file);
}
}
void
deinit_readline()
{
if (_save_history) {
write_history(_history_file);
}
}
char **
sql_completion (const char *text, int start, int end)
{
char **matches;
matches = (char **)NULL;
if (start==0 || end == 0) {
}
if (start == 0) {
matches = rl_completion_matches (text, sql_command_generator);
} else {
matches = rl_completion_matches (text, sql_tablename_generator);
}
return (matches);
}
char *sql_tablename_generator (const char *text, int state) {
static int seekpos, len, rowcount;
static MapiHdl table_hdl;
char *name;
if (!state) {
seekpos = 0;
len = strlen (text);
if ((table_hdl = mapi_query(_mid, "SELECT name FROM tables"))==NULL || mapi_error(_mid)) {
if (table_hdl) {
mapi_explain_query(table_hdl, stderr);
mapi_close_handle(table_hdl);
} else
mapi_explain(_mid, stderr);
return NULL;
}
rowcount = mapi_get_row_count(table_hdl);
}
while (seekpos < rowcount) {
mapi_seek_row(table_hdl, seekpos++, MAPI_SEEK_SET);
mapi_fetch_row(table_hdl);
name = mapi_fetch_field(table_hdl, 0);
if (strncmp(name, text, len) == 0)
return strdup(name);
}
return NULL;
}
// SQL commands (at start of line)
char *sql_command_generator (const char *text, int state) {
static int index, len;
const char *name;
if (!state) {
index = 0;
len = strlen(text);
}
while ((name = SQL_COMMANDS[index++])) {
#ifdef HAVE_STRNCASECMP
if (strncasecmp(name, text, len)==0)
#else
if (strncmp(name, text, len)==0)
#endif
return strdup(name);
}
return NULL;
}