Commit 820568fb 820568fbf17c86fd4e68fff5e056c1c6deb1a319 by Sergey Poznyakoff

mh: implement prompter

* configure.ac (MU_COND_READLINE): New cond.
* mh/prompter.c: New file.
* mh/prompter-rl.c: New file.
* mh/prompter-tty.c: New file.
* mh/prompter.h: New file.
* mh/TODO: Update.
* mh/Makefile.am: Build prompter.

* doc/texinfo/mu-mh.texi: Document prompter
* po/POTFILES.in: Update.
1 parent 110d13ec
......@@ -909,7 +909,7 @@ AC_SUBST(CURSES_LIBS)
dnl Check for GNU Readline
AC_SUBST(READLINE_LIBS)
if test x"$usereadline" = x"yes"; then
if test "$usereadline" = "yes"; then
dnl FIXME This should only link in the curses libraries if it's
dnl really needed!
......@@ -919,7 +919,7 @@ if test x"$usereadline" = x"yes"; then
AC_CHECK_LIB(readline, readline, mu_have_readline=yes)
LIBS=$saved_LIBS
if test x"$mu_have_readline" = x"yes"; then
if test "$mu_have_readline" = "yes"; then
AC_CHECK_HEADERS(readline/readline.h,
AC_DEFINE(WITH_READLINE,1,[Enable use of readline]))
READLINE_LIBS="-lreadline $CURSES_LIBS"
......@@ -933,6 +933,8 @@ if test x"$usereadline" = x"yes"; then
fi
AM_CONDITIONAL([MU_COND_READLINE], [test "$usereadline" = "yes"])
AH_BOTTOM([
/* Newer versions of readline have rl_completion_matches */
#ifndef HAVE_RL_COMPLETION_MATCHES
......
......@@ -311,25 +311,6 @@ behaviour is disabled when @option{--quiet} option was given.
The @option{--all} mode does not display commented out entries.
@item repl
Understands @option{--use} option. Disposition shell provides
@code{use} command.
@item rmm
@enumerate 1
@item
Different behaviour if one of the messages in the list does not exist:
Mailutils @command{rmm} does not delete any messages. Standard
@command{rmm} in this case deletes all messages preceding the
non-existent one.
@item
The @code{rmmproc} profile component is not used.
@end enumerate
@item pick
The command line syntax @option{--@var{component} @var{string}}) is
......@@ -388,6 +369,31 @@ pick --before '1 year ago'
etc...
@end smallexample
@item prompter
@enumerate
@item
Prompter attempts to use GNU Readline library, if it is installed.
Consequently, arguments to @option{-erase} and @option{-kill} option
must follow GNU style key sequence notation (@pxref{Readline Init File
Syntax, keyseq,,readline,GNU Readline Library}).
If @command{prompter} is built without @command{readline}, it accepts
the following character notations:
@table @asis
@item \@var{nnnn}
Here, @var{n} stands for a single octal digit.
@item ^@var{chr}
This notation is translated to the ASCII code @samp{@var{chr} + 0100}.
@end table
@item
Component continuation lines are not required to begin with a
whitespace. If leading whitespace is not present, @command{prompter}
will add it automatically.
@end enumerate
@item refile
@enumerate
......@@ -405,6 +411,25 @@ compatibility only.
Message specs and folder names may be interspersed.
@end enumerate
@item repl
Understands @option{--use} option. Disposition shell provides
@code{use} command.
@item rmm
@enumerate 1
@item
Different behaviour if one of the messages in the list does not exist:
Mailutils @command{rmm} does not delete any messages. Standard
@command{rmm} in this case deletes all messages preceding the
non-existent one.
@item
The @code{rmmproc} profile component is not used.
@end enumerate
@item sortm
New option @option{--numfield} specifies numeric comparison for the
......
......@@ -34,6 +34,7 @@ bin_PROGRAMS = \
mhparam\
mhpath\
pick\
prompter\
refile\
repl\
rmf\
......@@ -44,6 +45,21 @@ bin_PROGRAMS = \
whatnow\
whom
prompter_LDADD = $(mh_LIBS)
if MU_COND_READLINE
PROMPTER_FUN=prompter-rl.c
prompter_LDADD += @READLINE_LIBS@
else
PROMPTER_FUN=prompter-tty.c
prompter_LDADD += @CURSES_LIBS@
endif
prompter_SOURCES = \
prompter.c\
prompter.h\
$(PROMPTER_FUN)
noinst_LIBRARIES = libmh.a
libmh_a_SOURCES= \
......
......@@ -40,6 +40,7 @@ State Nice Utility Comments
+ 10 mhpath
+ 10 whatnow
+ 20 sortm
+ 20 prompter
Utilities In Alphabetical Order
===============================
......@@ -55,6 +56,7 @@ mhl
mhn
mhpath
pick
prompter
refile
repl
rmf
......
/* GNU Mailutils -- a suite of utilities for electronic mail
Copyright (C) 2010 Free Software Foundation, Inc.
GNU Mailutils 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.
GNU Mailutils 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 GNU Mailutils. If not, see <http://www.gnu.org/licenses/>. */
/* MH prompter - readline support */
#include <mh.h>
#include "prompter.h"
#include <readline/readline.h>
void
prompter_init ()
{
}
void
prompter_done ()
{
}
void
prompter_set_erase (const char *keyseq)
{
rl_bind_keyseq (keyseq, rl_delete);
}
void
prompter_set_kill (const char *keyseq)
{
rl_bind_keyseq (keyseq, rl_kill_full_line);
}
char *
prompter_get_value (const char *name)
{
char *prompt = NULL;
char *val;
if (name)
{
prompt = xmalloc (strlen (name) + 3);
strcpy (prompt, name);
strcat (prompt, ": ");
}
val = readline (prompt);
free (prompt);
return val;
}
char *
prompter_get_line ()
{
return doteof_filter (readline (NULL));
}
/* GNU Mailutils -- a suite of utilities for electronic mail
Copyright (C) 2010 Free Software Foundation, Inc.
GNU Mailutils 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.
GNU Mailutils 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 GNU Mailutils. If not, see <http://www.gnu.org/licenses/>. */
/* MH prompter - traditional tty/termios support */
#include <mh.h>
#include "prompter.h"
#include "muaux.h"
#if HAVE_TERMIOS_H
# include <termios.h>
#endif
mu_stream_t strin;
#if HAVE_TCGETATTR
struct termios tios;
#endif
static int
decode_key (const char *keyseq)
{
if (keyseq[0] == '^' && keyseq[1])
{
if (keyseq[2])
{
mu_error (_("invalid keysequence (%s near %d)"),
keyseq, 2);
exit (1);
}
return keyseq[1] + 0100;
}
else if (keyseq[0] == '\\')
{
int i;
int c = 0;
for (i = 1; i <= 3; i++)
{
int n;
switch (keyseq[i])
{
case '0':
n = 0;
break;
case '1':
n = 1;
break;
case '2':
n = 2;
break;
case '3':
n = 3;
break;
case '4':
n = 4;
break;
case '5':
n = 5;
break;
case '6':
n = 6;
break;
case '7':
n = 7;
break;
default:
mu_error (_("invalid keysequence (%s near %d)"),
keyseq, i);
exit (1);
}
c = (c << 3) + n;
}
if (keyseq[i])
{
mu_error (_("invalid keysequence (%s near %d)"),
keyseq, i);
exit (1);
}
return c;
}
else if (keyseq[1])
{
mu_error (_("invalid keysequence (%s near %d)"),
keyseq, 2);
exit (1);
}
return keyseq[0];
}
static RETSIGTYPE
sighan (int signo)
{
prompter_done ();
exit (0);
}
void
prompter_init ()
{
int rc;
if ((rc = mu_stdio_stream_create (&strin, MU_STDIN_FD, MU_STREAM_READ)))
{
mu_error (_("cannot open stdout: %s"), mu_strerror (rc));
exit (1);
}
#if HAVE_TCGETATTR
rc = tcgetattr (MU_STDOUT_FD, &tios);
if (rc)
{
mu_error (_("tcgetattr failed: %s"), mu_strerror (rc));
exit (1);
}
else
{
static int sigtab[] = { SIGPIPE, SIGABRT, SIGINT, SIGQUIT, SIGTERM };
mu_set_signals (sighan, sigtab, MU_ARRAY_SIZE (sigtab));
}
#endif
}
void
prompter_done ()
{
#if HAVE_TCGETATTR
int rc = tcsetattr (MU_STDOUT_FD, TCSANOW, &tios);
if (rc)
mu_error (_("tcsetattr failed: %s"), mu_strerror (rc));
#endif
}
void
prompter_set_erase (const char *keyseq)
{
#if HAVE_TCGETATTR
int rc;
struct termios t = tios;
t.c_lflag |= ICANON;
t.c_cc[VERASE] = decode_key (keyseq);
rc = tcsetattr (MU_STDOUT_FD, TCSANOW, &t);
if (rc)
mu_error (_("tcsetattr failed: %s"), mu_strerror (rc));
#endif
}
void
prompter_set_kill (const char *keyseq)
{
#if HAVE_TCGETATTR
int rc;
struct termios t = tios;
t.c_lflag |= ICANON;
t.c_cc[VKILL] = decode_key (keyseq);
rc = tcsetattr (MU_STDOUT_FD, TCSANOW, &t);
if (rc)
mu_error (_("tcsetattr failed: %s"), mu_strerror (rc));
#endif
}
char *
prompter_get_value (const char *name)
{
size_t size = 0, n;
char *buf = NULL;
mu_stream_printf (strout, "%s: ", name);
mu_stream_flush (strout);
if (mu_stream_getline (strin, &buf, &size, &n) || n == 0)
return NULL;
mu_rtrim_cset (buf, "\n");
return buf;
}
char *
prompter_get_line ()
{
size_t size = 0, n;
char *buf = NULL;
if (mu_stream_getline (strin, &buf, &size, &n) || n == 0)
return NULL;
mu_rtrim_cset (buf, "\n");
return doteof_filter (buf);
}
/* GNU Mailutils -- a suite of utilities for electronic mail
Copyright (C) 2010 Free Software Foundation, Inc.
GNU Mailutils 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.
GNU Mailutils 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 GNU Mailutils. If not, see <http://www.gnu.org/licenses/>. */
/* MH prompter command */
#include <mh.h>
#include "prompter.h"
static char doc[] = N_("GNU MH prompter")"\v"
N_("Use -help to obtain the list of traditional MH options.");
static char args_doc[] = N_("FILE");
enum {
ARG_ERASE=256,
ARG_KILL,
ARG_PREPEND,
ARG_NOPREPEND,
ARG_RAPID,
ARG_NORAPID,
ARG_DOTEOF,
ARG_NODOTEOF
};
static struct argp_option options[] = {
{ "erase", ARG_ERASE, N_("CHAR"), 0,
N_("set erase character") },
{ "kill", ARG_KILL, N_("CHAR"), 0,
N_("set kill character") },
{ "prepend", ARG_PREPEND, N_("BOOL"), 0,
N_("prepend user input to the message body") },
{ "noprepend", ARG_NOPREPEND, NULL, OPTION_HIDDEN,
NULL },
{ "rapid", ARG_RAPID, N_("BOOL"), 0,
N_("do not display message body") },
{ "norapid", ARG_NORAPID, NULL, OPTION_HIDDEN,
NULL },
{ "doteof", ARG_DOTEOF, N_("BOOL"), 0,
N_("a period on a line marks end-of-file") },
{ "nodoteof", ARG_NODOTEOF, NULL, OPTION_HIDDEN,
NULL },
{ NULL }
};
struct mh_option mh_option[] = {
{ "erase", MH_OPT_ARG, "chr" },
{ "kill", MH_OPT_ARG, "chr" },
{ "prepend", MH_OPT_BOOL },
{ "rapid", MH_OPT_BOOL },
{ "doteof", MH_OPT_BOOL },
{ NULL }
};
char *erase_seq;
char *kill_seq;
int prepend_option;
int rapid_option;
int doteof_option;
static error_t
opt_handler (int key, char *arg, struct argp_state *state)
{
switch (key)
{
case ARG_ERASE:
erase_seq = arg;
break;
case ARG_KILL:
kill_seq = arg;
break;
case ARG_PREPEND:
prepend_option = is_true (arg);
break;
case ARG_NOPREPEND:
prepend_option = 0;
break;
case ARG_RAPID:
rapid_option = is_true (arg);
break;
case ARG_NORAPID:
rapid_option = 0;
break;
case ARG_DOTEOF:
doteof_option = is_true (arg);
break;
case ARG_NODOTEOF:
doteof_option = 0;
break;
default:
return ARGP_ERR_UNKNOWN;
}
return 0;
}
static int
is_empty_string (const char *str)
{
if (!str)
return 1;
return mu_str_skip_class (str, MU_CTYPE_BLANK)[0] == 0;
}
char *
doteof_filter (char *val)
{
if (!val)
return NULL;
if (doteof_option && val[0] == '.')
{
if (val[1] == 0)
{
free (val);
return NULL;
}
memmove (val, val + 1, strlen (val + 1) + 1);
}
return val;
}
mu_stream_t strout;
int
main (int argc, char **argv)
{
int index;
int rc;
mu_stream_t in, tmp;
mu_message_t msg;
mu_header_t hdr;
mu_iterator_t itr;
const char *file;
char *newval;
mu_off_t size;
mu_body_t body;
mu_stream_t bstr;
MU_APP_INIT_NLS ();
mh_argp_init ();
mh_argp_parse (&argc, &argv, 0, options, mh_option, args_doc, doc,
opt_handler, NULL, &index);
if (index == argc)
{
mu_error (_("file name not given"));
exit (1);
}
file = argv[index];
prompter_init ();
if (erase_seq)
prompter_set_erase (erase_seq);
if (kill_seq)
prompter_set_erase (kill_seq);
if ((rc = mu_stdio_stream_create (&strout, MU_STDOUT_FD, MU_STREAM_WRITE)))
{
mu_error (_("cannot open stdout: %s"), mu_strerror (rc));
return 1;
}
if ((rc = mu_file_stream_create (&in, file, MU_STREAM_RDWR)))
{
mu_error (_("cannot open input file `%s': %s"),
file, mu_strerror (rc));
return 1;
}
rc = mu_stream_to_message (in, &msg);
if (rc)
{
mu_error (_("input stream %s is not a message (%s)"),
file, mu_strerror (rc));
return 1;
}
if ((rc = mu_temp_file_stream_create (&tmp, NULL)))
{
mu_error (_("Cannot open temporary file: %s"),
mu_strerror (rc));
return 1;
}
/* Copy headers */
mu_message_get_header (msg, &hdr);
mu_header_get_iterator (hdr, &itr);
for (mu_iterator_first (itr); !mu_iterator_is_done (itr);
mu_iterator_next (itr))
{
const char *name, *val;
mu_iterator_current_kv (itr, (const void **)&name, (void**)&val);
if (!is_empty_string (val))
{
mu_stream_printf (tmp, "%s: %s\n", name, val);
mu_stream_printf (strout, "%s: %s\n", name, val);
}
else
{
int cont = 0;
mu_opool_t opool;
const char *prompt = name;
mu_opool_create (&opool, 1);
do
{
size_t len;
char *p;
p = prompter_get_value (prompt);
if (!p)
return 1;
prompt = NULL;
if (cont)
{
mu_opool_append_char (opool, '\n');
if (!mu_isspace (p[0]))
mu_opool_append_char (opool, '\t');
}
len = strlen (p);
if (len > 0 && p[len-1] == '\\')
{
len--;
cont = 1;
}
else
cont = 0;
mu_opool_append (opool, p, len);
free (p);
}
while (cont);
newval = mu_opool_finish (opool, NULL);
if (!is_empty_string (newval))
mu_stream_printf (tmp, "%s: %s\n", name, newval);
mu_opool_destroy (&opool);
}
}
mu_stream_printf (strout, "--------\n");
mu_stream_write (tmp, "\n", 1, NULL);
/* Copy body */
if (prepend_option)
{
mu_stream_printf (strout, "\n--------%s\n\n", _("Enter initial text"));
while (newval = prompter_get_line ())
{
mu_stream_write (tmp, newval, strlen (newval), NULL);
free (newval);
mu_stream_write (tmp, "\n", 1, NULL);
}
}
mu_message_get_body (msg, &body);
mu_body_get_streamref (body, &bstr);
if (!prepend_option && !rapid_option)
{
mu_stream_copy (strout, bstr, 0, NULL);
mu_stream_seek (bstr, 0, MU_SEEK_SET, NULL);
}
mu_stream_copy (tmp, bstr, 0, NULL);
mu_stream_unref (bstr);
if (!prepend_option && !rapid_option)
{
printf ("\n--------%s\n\n", _("Enter additional text"));
while (newval = prompter_get_line ())
{
mu_stream_write (tmp, newval, strlen (newval), NULL);
free (newval);
mu_stream_write (tmp, "\n", 1, NULL);
}
}
/* Destroy the message */
mu_message_destroy (&msg, mu_message_get_owner (msg));
/* Rewind the streams and copy data back to in. */
mu_stream_seek (in, 0, MU_SEEK_SET, NULL);
mu_stream_seek (tmp, 0, MU_SEEK_SET, NULL);
mu_stream_copy (in, tmp, 0, &size);
mu_stream_truncate (in, size);
mu_stream_destroy (&in);
mu_stream_destroy (&tmp);
mu_stream_destroy (&strout);
prompter_done ();
return 0;
}
/* GNU Mailutils -- a suite of utilities for electronic mail
Copyright (C) 2010 Free Software Foundation, Inc.
GNU Mailutils 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.
GNU Mailutils 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 GNU Mailutils. If not, see <http://www.gnu.org/licenses/>. */
void prompter_init (void);
void prompter_done (void);
void prompter_set_erase (const char *keyseq);
void prompter_set_kill (const char *keyseq);
char *prompter_get_value (const char *name);
char *prompter_get_line (void);
char *doteof_filter (char *val);
extern mu_stream_t strout;
......@@ -179,6 +179,8 @@ mh/mhl.c
mh/mhn.c
mh/mhpath.c
mh/pick.c
mh/prompter.c
mh/prompter-tty.c
mh/refile.c
mh/repl.c
mh/rmf.c
......