rsalz@bbn.com (Rich Salz) (04/15/88)
Submitted-by: island!argv@sun.com (Dan Heller) Posting-number: Volume 14, Issue 41 Archive-name: mush6.0/part09 #! /bin/sh # This is a shell archive. Remove anything before this line, then unpack # it by saving it into a file and typing "sh file". To overwrite existing # files, type "sh file -c". You can also feed this as standard input via # unshar, or by typing "sh <file", e.g.. If this archive is complete, you # will see the following message at the end: # "End of archive 9 (of 14)." # Contents: loop.c tool_help # Wrapped by rsalz@fig.bbn.com on Wed Apr 13 20:04:51 1988 PATH=/bin:/usr/bin:/usr/ucb ; export PATH if test -f 'loop.c' -a "${1}" != "-c" ; then echo shar: Will not clobber existing file \"'loop.c'\" else echo shar: Extracting \"'loop.c'\" \(24591 characters\) sed "s/^X//" >'loop.c' <<'END_OF_FILE' X/* loop.c (c) copyright 1986 (Dan Heller) */ X X/* X * Here is where the main loop for text mode exists. Also, all the X * history is kept here and all the command parsing and execution X * and alias expansion in or out of text/graphics mode is done here. X */ X X#include "mush.h" X X#ifdef BSD X#include <sys/wait.h> X#else X#ifndef SYSV X#include <wait.h> X#endif /* SYSV */ X#endif /* BSD */ X X#define ever (;;) X#define MAXARGS 100 X#define isdelimeter(c) (index(" \t;|", c)) X char *alias_expand(), *hist_expand(), *reference_hist(), *hist_from_str(); char **calloc(); X struct history { X int histno; X char **argv; X struct history *prev; X struct history *next; X}; static struct history *hist_head, *hist_tail; struct history *malloc(); X#define NULL_HIST (struct history *)0 X static char *last_aliased; static int hist_size, print_only; X do_loop() X{ X register char *p, **argv; X char **last_argv = DUBL_NULL, line[256]; X int argc, c = (iscurses - 1); X struct history *new; X#ifdef CURSES X int save_echo_flg = FALSE; X#endif /* CURSES */ X X /* catch the right signals -- see main.c for other signal catching */ X (void) signal(SIGINT, catch); X (void) signal(SIGQUIT, catch); X (void) signal(SIGHUP, catch); X (void) signal(SIGTERM, catch); X (void) signal(SIGCHLD, sigchldcatcher); X (void) signal(SIGPIPE, SIG_IGN); /* if pager is terminated before end */ X X turnoff(glob_flags, IGN_SIGS); X if (hist_size == 0) /* if user didn't set history in .rc file */ X hist_size = 1; X X for ever { X if (setjmp(jmpbuf)) { X Debug("jumped back to main loop (%s: %d)\n", __FILE__,__LINE__); X#ifdef CURSES X if (c > 0) { /* don't pass last command back to curses_command() */ X iscurses = TRUE; X c = hit_return(); X } X#endif /* CURSES */ X } X#ifdef CURSES X if (iscurses || c > -1) { X /* if !iscurses, we know that we returned from a curses-based X * call and we really ARE still in curses. Reset tty modes! X */ X if (ison(glob_flags, ECHO_FLAG)) { X turnoff(glob_flags, ECHO_FLAG); X echo_off(); X save_echo_flg = TRUE; X } X if (!iscurses) { X iscurses = TRUE; X c = hit_return(); X } X if ((c = curses_command(c)) == -1 && save_echo_flg) { X echo_on(); X turnon(glob_flags, ECHO_FLAG); X save_echo_flg = FALSE; X } X continue; X } X#endif /* CURSES */ X clear_msg_list(msg_list); X (void) check_new_mail(); X X /* print a prompt according to printf like format: X * (current message, deleted, unread, etc) are found in mail_status. X */ X mail_status(1); X if (Getstr(line, 256, 0) > -1) X p = line; X else { X if (p = do_set(set_options, "ignoreeof")) { X if (!*p) X continue; X else X p = strcpy(line, p); /* so processing won't destroy var */ X } else { X (void) quit(0, DUBL_NULL); X continue; /* quit may return if new mail arrives */ X } X } X X skipspaces(0); X if (!*p && !(p = do_set(set_options, "newline"))) { X (void) readmsg(0, DUBL_NULL, msg_list); X continue; X } X if (!*p) /* if newline is set, but no value, then continue */ X continue; X X /* upon error, argc = -1 -- still save in history so user can X * modify syntax error. if !argv, error is too severe. We pass X * the last command typed in last_argv for history reference, and X * get back the current command _as typed_ (unexpanded by aliases X * or history) in last_argv. X */ X if (!(argv = make_command(p, &last_argv, &argc))) X continue; X /* now save the new argv in the newly created history structure */ X if (!(new = malloc(sizeof (struct history)))) X error("can't increment history"); X else { X new->histno = ++hist_no; X new->argv = last_argv; /* this is the command _as typed_ */ X new->next = NULL_HIST; X new->prev = hist_head; X /* if first command, the tail of the list is "new" because X * nothing is in the list. If not the first command, the X * head of the list's "next" pointer points to the new command. X */ X if (hist_head) X hist_head->next = new; X else X hist_tail = new; X hist_head = new; X } X /* X * truncate the history list to the size of the history. X * Free the outdated command (argv) and move the tail closer to front. X * use a while loop in case the last command reset histsize to "small" X */ X while (hist_head->histno - hist_tail->histno >= hist_size) { X hist_tail = hist_tail->next; X free_vec(hist_tail->prev->argv); X xfree(hist_tail->prev); X hist_tail->prev = NULL_HIST; X } X X if (print_only) { X print_only = 0; X free_vec(argv); X } else if (argc > -1) X (void) do_command(argc, argv, msg_list); X } X} X X/* make a command from "buf". X * first, expand history references. make an argv from that and save X * in last_argv (to be passed back and stored in history). After that, X * THEN expand aliases. return that argv to be executed as a command. X */ char ** make_command(start, last_argv, argc) register char *start, ***last_argv; int *argc; X{ X register char *p, **tmp; X char buf[BUFSIZ]; X X if (!last_argv) X tmp = DUBL_NULL; X else X tmp = *last_argv; X /* first expand history -- (here's where argc gets set) X * pass the buffer, the history list to reference if \!* (or whatever) X * result in static buffer (pointed to by p) -- even if history parsing is X * ignored, do this to remove \'s behind !'s and verifying matching quotes X */ X if (!(p = hist_expand(start, tmp, argc)) || Strcpy(buf, p) > BUFSIZ) X return DUBL_NULL; X /* if history was referenced in the command, echo new command */ X if (*argc) X puts(buf); X X /* argc may == -1; ignore this error for now but catch it later */ X if (!(tmp = mk_argv(buf, argc, 0))) X return DUBL_NULL; X X /* save this as the command typed */ X if (last_argv) X *last_argv = tmp; X X /* expand all aliases (recursively) X * pass _this_ command (as typed and without aliases) to let aliases X * with "!*" be able to reference the command line just typed. X */ X if (alias_stuff(buf, *argc, tmp) == -1) X return DUBL_NULL; X X /* now, expand variable references and make another argv */ X if (!variable_expand(buf)) X return DUBL_NULL; X X if (!last_argv) X free_vec(tmp); X X /* with everything expanded, build final argv from new buffer X * Note that backslashes and quotes still exist. Those are removed X * because argument final is 1. X */ X tmp = mk_argv(buf, argc, 1); X return tmp; X} X X/* X * do the command specified by the argument vector, argv. X * First check to see if argc < 0. If so, someone called this X * command and they should not have! make_command() will return X * an argv but it will set argc to -1 if there's a sytanx error. X */ do_command(argc, argv, list) char **argv, list[]; X{ X register char *p; X char **tmp = argv; X int i, status; X long do_pipe = ison(glob_flags, DO_PIPE); X X turnoff(glob_flags, IS_PIPE); X X if (argc <= 0) { X turnoff(glob_flags, DO_PIPE); X return -1; X } X X clear_msg_list(list); X X for (i = 0; do_pipe >= 0 && argc; argc--) { X p = argv[i]; X if (!strcmp(p, "|") || !strcmp(p, ";")) { X if (do_pipe = (*p == '|')) X turnon(glob_flags, DO_PIPE); X argv[i] = NULL; X /* if piping, then don't call next command if this one fails. */ X if ((status = exec_argv(i, argv, list)) <= -1 && do_pipe) { X print("Broken pipe.\n"); X do_pipe = -1, turnoff(glob_flags, DO_PIPE); X } X /* if command failed and piping, or command worked and not piping */ X if (do_pipe <= 0) X status = 0, clear_msg_list(list); X /* else command worked and piping: set is_pipe */ X else if (!status) X turnon(glob_flags, IS_PIPE), turnoff(glob_flags, DO_PIPE); X argv[i] = p; X argv += (i+1); X i = 0; X } else X i++; X } X if (do_pipe >= 0) X status = exec_argv(i, argv, list); X Debug("freeing: "), print_argv(tmp); X free_vec(tmp); X turnoff(glob_flags, DO_PIPE); X return status; X} X exec_argv(argc, argv, list) register char **argv, list[]; X{ X register int n; X X if (!argv || !*argv || **argv == '\\' && !*++*argv) { X if (ison(glob_flags, IS_PIPE) || ison(glob_flags, DO_PIPE)) X print("Invalid null command.\n"); X return -1; X } X Debug("executing: "), print_argv(argv); X X /* if interrupted during execution of a command, return -1 */ X if (isoff(glob_flags, IGN_SIGS) && setjmp(jmpbuf)) { X Debug("jumped back to exec_argv (%s: %d)\n", __FILE__, __LINE__); X return -1; X } X X /* standard commands */ X for (n = 0; cmds[n].command; n++) X if (!strcmp(argv[0], cmds[n].command)) X return (*cmds[n].func)(argc, argv, list); X X /* ucb-Mail compatible commands */ X for (n = 0; ucb_cmds[n].command; n++) X if (!strcmp(argv[0], ucb_cmds[n].command)) X return (*ucb_cmds[n].func)(argc, argv, list); X X /* for hidden, undocumented commands */ X for (n = 0; hidden_cmds[n].command; n++) X if (!strcmp(argv[0], hidden_cmds[n].command)) X return (*hidden_cmds[n].func)(argc, argv, list); X X#ifdef SUNTOOL X /* check tool-only commands */ X if (istool) X for (n = 0; fkey_cmds[n].command; n++) X if (!strcmp(argv[0], fkey_cmds[n].command)) X return (*fkey_cmds[n].func)(argc, argv); X#endif /* SUNTOOL */ X X if ((isdigit(**argv) || index("^.*$-`{}", **argv)) X && (n = get_msg_list(argv, list)) != 0) { X if (n > 0 && isoff(glob_flags, DO_PIPE)) X for (n = 0; n < msg_cnt; n++) X if (msg_bit(list, n)) { X display_msg((current_msg = n), (long)0); X unset_msg_bit(list, n); X } X return 0; X } else if (strlen(*argv) == 1 && index("$^.", **argv)) { X if (!msg_cnt) X print("No messages."); X else { X if (**argv != '.') X current_msg = (**argv == '$') ? msg_cnt-1 : 0; X set_msg_bit(list, current_msg); X display_msg(current_msg, (long)0); X } X return 0; X } X /* get_msg_list will set the current message bit if nothing parsed */ X unset_msg_bit(list, current_msg); X X if (!istool && do_set(set_options, "unix")) { X if (ison(glob_flags, IS_PIPE) || ison(glob_flags, DO_PIPE)) X print("There is no piping to or from UNIX commands.\n"); X else X execute(argv); /* try to execute a unix command */ X return -1; /* doesn't affect messages! */ X } X X print("%s: command not found.\n", *argv); X if (!istool) X print("type '?' for valid commands, or type `help'\n"); X return -1; X} X X/* recursively look for aliases on a command line. aliases may X * reference other aliases. X */ alias_stuff(b, argc, Argv) register char *b, **Argv; X{ X register char *p, **argv = DUBL_NULL; X register int n = 0, i = 0, Argc; X static int loops; X int dummy; X X if (++loops == 20) { X print("Alias loop.\n"); X return -1; X } X for (Argc = 0; Argc < argc; Argc++) { X register char *h = Argv[n + ++i]; X register char *p2 = ""; X X /* we've hit a command separator or the end of the line */ X if (h && strcmp(h, ";") && strcmp(h, "|")) X continue; X X /* create a new argv containing this (possible subset) of argv */ X if (!(argv = calloc((unsigned)(i+1), sizeof (char *)))) X continue; X while (i--) X strdup(argv[i], Argv[n+i]); X X if ((!last_aliased || strcmp(last_aliased, argv[0])) X && (p = alias_expand(argv[0]))) { X /* if history was referenced, ignore the rest of argv X * else copy all of argv onto the end of the buffer. X */ X if (!(p2 = hist_expand(p, argv, &dummy))) X break; X if (!dummy) X (void) argv_to_string(p2+strlen(p2), argv+1); X if (Strcpy(b, p2) > BUFSIZ) { X print("Not enough buffer space.\n"); X break; X } X /* release old argv and build a new one based on new string */ X free_vec(argv); X if (!(argv = mk_argv(b, &dummy, 0))) X break; X if (alias_stuff(b, dummy, argv) == -1) X break; X } else X b = argv_to_string(b, argv); X xfree(last_aliased), last_aliased = NULL; X free_vec(argv); X b += strlen(b); X if (h) { X p2 = h; X while (++Argc < argc && (h = Argv[Argc])) X if (strcmp(h, ";") && strcmp(h, "|")) X break; X b += strlen(sprintf(b, " %s ", p2)); X n = Argc--; X } X i = 0; X } X xfree(last_aliased), last_aliased = NULL; X --loops; X if (Argc < argc) { X free_vec(argv); X return -1; X } X return 0; X} X char * alias_expand(cmd) register char *cmd; X{ X register char *p; X register int x; X X if (!(p = do_set(functions, cmd))) X return NULL; X last_aliased = savestr(cmd); /* to be freed elsewhere; don't strdup! */ X if (isoff(glob_flags, WARNING)) X return p; X for (x = 0; cmds[x].command; x++) X if (!strcmp(cmd, cmds[x].command)) { X wprint("(real command: \"%s\" aliased to \"%s\")\n", cmd, p); X return p; X } X for (x = 0; ucb_cmds[x].command; x++) X if (!strcmp(cmd, ucb_cmds[x].command)) { X wprint("(ucb-command: \"%s\" aliased to \"%s\")\n", cmd, p); X return p; X } X return p; X} X X/* expand history references and separate message lists from other tokens */ char * hist_expand(str, argv, hist_was_referenced) register char *str, **argv; register int *hist_was_referenced; X{ X static char buf[BUFSIZ]; X register int b = 0, inquotes = 0; X int first_space = 0, ignore_bang; X X ignore_bang = (ison(glob_flags, IGN_BANG) || X do_set(set_options, "ignore_bang")); X X if (hist_was_referenced) X *hist_was_referenced = 0; X while (*str) { X while (!inquotes && isspace(*str)) X str++; X do { X if (!*str) X break; X if (b >= BUFSIZ-1) { X print("argument list too long.\n"); X return NULL; X } X if ((buf[b] = *str++) == '\'') { X /* make sure there's a match! */ X inquotes = !inquotes; X } X if (!first_space && !inquotes && index("0123456789{}*$", buf[b]) X && b && !index("0123456789{}- \t", buf[b-1])) { X buf[b+1] = buf[b]; X buf[b++] = ' '; X while ((buf[++b] = *str++) && index("0123456789-,${}", buf[b])) X ; X if (!buf[b]) X str--; X first_space++; X } X /* check for (;) (|) or any other delimeter and separate it from X * other tokens. X */ X if (!inquotes && buf[b] != '\0' && isdelimeter(buf[b]) && X (b < 0 || buf[b-1] != '\\')) { X if (b && !isspace(buf[b-1])) X buf[b+1] = buf[b], buf[b++] = ' '; X b++; X break; X } X /* if double-quotes, just copy byte by byte, char by char ... */ X if (buf[b] == '"') { X int B = b; X while ((buf[++B] = *str++) && buf[B] != '"') X ; X if (buf[B]) X b = B; X else X str--; X b++; X continue; X } X if (buf[b] == '\\') { X if ((buf[++b] = *str) == '!') X buf[--b] = '!'; X ++str; X } else if (buf[b] == '!' && *str && *str != '\\' && !isspace(*str) X && !ignore_bang) { X char word[BUFSIZ]; X if (!(str = reference_hist(str, word, argv))) X return NULL; X if (hist_was_referenced) X *hist_was_referenced = 1; X if (strlen(word) + b >= BUFSIZ) { X print("argument list too long.\n"); X return NULL; X } X b += Strcpy(&buf[b], word) - 1; X } X b++; X } while (*str && (!isdelimeter(*str) || str[-1] == '\\')); X if (!inquotes) X first_space++, buf[b++] = ' '; X } X buf[b] = 0; X return buf; X} X X/* X * find mush variable references and expand them to their values. X * variables are preceded by a '$' and cannot be within single X * quotes. Only if expansion has been made do we copy buf back into str. X * RETURN 0 on failure, 1 on success. X */ variable_expand(str) register char *str; X{ X register int b = 0; X char buf[BUFSIZ], *start = str; X int expanded = 0; X X while (*str) { X if (*str == '~' && (str == start || isspace(*(str-1)))) { X register char *p = any(str, " \t"), *tmp; X int x = 1; X if (p) X *p = 0; X tmp = getpath(str, &x); X /* if error, print message and return 0 */ X if (x == -1) { X wprint("%s: %s\n", str, tmp); X return 0; X } X b += Strcpy(buf+b, tmp); X if (p) X *p = ' ', str = p; X else X str += strlen(str); X expanded = 1; X } X /* if single-quotes, just copy byte by byte, char by char ... */ X if ((buf[b] = *str++) == '\'') { X while ((buf[++b] = *str++) && buf[b] != '\'') X ; X if (!buf[b]) X str--; X } X /* If $ is eoln, continue. Variables must start with a `$' X * and continue with {, _, a-z, A-Z or it is not a variable. } X */ X if (buf[b] == '$' && X (isalpha(*str) || *str == '{' || *str == '_')) /* } */ { X register char c, *p, *var, *end; X X if (*(end = var = str) == '{') /* } */ { X if (!isalpha(*++str) && *str != '_') { X print("Illegal variable name.\n"); X return 0; X } X if (!(end = index(var, '}'))) /* { */ { X print("Unmatched '{'.\n"); /* } */ X return 0; X } X *end++ = 0; X } else X while (isalnum(*++end) || *end == '_') X ; X /* advance "str" to the next parse-point, replace the end X * of "var" (end) with a null, and save char in `c' X */ X c = *(str = end), *end = 0; X X /* get the value of the variable. */ X if (p = do_set(set_options, var)) X b += Strcpy(buf+b, p); X else { X print("%s: undefined variable\n", var); X return 0; X } X expanded = 1; X *str = c; /* replace the null with the old character */ X } else X b++; X } X buf[b] = 0; X if (expanded) /* if any expansions were done, copy back into orig buf */ X (void) strcpy(start, buf); X return 1; X} X X/* make an vector of space delimeted character strings out of string "str". X * place in "argc" the number of args made. If final is true, then remove X * quotes and backslants according to standard. X */ char ** mk_argv(str, argc, final) register char *str; register int *argc; X{ X register char *s, *p; X register int tmp, err = 0; X char *newargv[MAXARGS], **argv, *p2, c; X X *argc = 0; X while (*str) { X while (isspace(*str)) X ++str; X if (*str) { /* found beginning of a word */ X s = p = str; X do { X if (p - s >= BUFSIZ-1) { X print("argument list too long.\n"); X return DUBL_NULL; X } X if ((*p = *str++) == '\\') { X if (final && (*str == ';' || *str == '|')) X /* make ";" look like " ;" */ X *p = ' '; X if (*++p = *str) /* assign and compare to NULL */ X str++; X continue; X } X if (p2 = index("\"'", *p)) { X register char c2 = *p2; X /* you can't escape quotes inside quotes of the same type */ X if (!(p2 = index(str, c2))) { X if (final) X print("Unmatched %c.\n", c2); X err++; X p2 = str; X } X tmp = (int)(p2 - str) + 1; /* take upto & include quote */ X (void) strncpy(p + !final, str, tmp); X p += tmp - 2 * final; /* change final to a boolean */ X if (*(str = p2)) X str++; X } X } while (++p, *str && (!isdelimeter(*str) || str[-1] == '\\')); X if (c = *str) /* set c = *str, check for null */ X str++; X *p = 0; X if (*s) { X newargv[*argc] = savestr(s); X (*argc)++; X } X *p = c; X } X } X if (!*argc) X return DUBL_NULL; X /* newargv[*argc] = NULL; */ X if (!(argv = calloc((unsigned)((*argc)+1), sizeof(char *)))) { X perror("mk_argv: calloc"); X return DUBL_NULL; X } X for (tmp = 0; tmp < *argc; tmp++) X argv[tmp] = newargv[tmp]; X if (err) X *argc = -1; X return argv; X} X X/* X * reference previous history from syntax of str and place result into buf X * We know we've got a history reference -- we're passed the string starting X * the first char AFTER the '!' (which indicates history reference) X */ char * reference_hist(str, buf, hist_ref) register char *str, *buf, **hist_ref; X{ X int relative = *str == '-'; /* relative from current hist_no */ X int old_hist, argstart = 0, lastarg, argend = 0, n = 0; X register char *p, **argv = hist_ref; X struct history *hist; X X buf[0] = 0; X if (index("!:$*", *str)) { X old_hist = hist_no; X if (*str == '!') X str++; X } else if (isdigit(*(str + relative))) X str = my_atoi(str + relative, &old_hist); X else if (!(p = hist_from_str(str, &old_hist))) X return NULL; X else X str = p; X if (relative) X old_hist = (hist_no-old_hist) + 1; X if (old_hist == hist_no) { X if (!(argv = hist_ref)) X print("You haven't done anything yet!\n"); X } else { X if (old_hist <= hist_no-hist_size || old_hist > hist_no || X old_hist <= 0) { X if (old_hist <= 0) X print("You haven't done that many commands, yet.\n"); X else X print("Event %d %s.\n", old_hist, X (old_hist > hist_no)? "hasn't happened yet": "expired"); X return NULL; X } X hist = hist_head; X while (hist && hist->histno != old_hist) X hist = hist->prev; X if (hist) X argv = hist->argv; X } X if (!argv) X return NULL; X while (argv[argend+1]) X argend++; X lastarg = argend; X if (*str && index(":$*-", *str)) { X int isrange; X if (*str == ':' && isdigit(*++str)) X str = my_atoi(str, &argstart); X if (isrange = (*str == '-')) X str++; X if (!isdigit(*str)) { X if (*str == 'p') X str++, print_only = 1; X else if (!*str || isdelimeter(*str)) X if (isrange) X argend--; /* unspecified end of range implies last-1 arg */ X else X argend = argstart; /* no range specified; use arg given */ X else { X if (*str == '*') X if (argv[0]) X argstart = 1, argend = ++lastarg; X else X argstart = 0; X else if (*str == '$' && !isrange) X argstart = argend; X else if (*str != '$') X print("%c: unknown arguement selector.\n", *str); X str++; X } X } else X str = my_atoi(str, &argend); X } X if (argstart > lastarg || argend > lastarg || argstart > argend) { X print("Bad argument selector.\n"); X return NULL; X } X while (argstart <= argend) { X n += Strcpy(&buf[n], argv[argstart++]); X buf[n++] = ' '; X } X buf[--n] = 0; X return str; X} X X/* find a history command that contains the string "str" X * place that history number in "hist" and return the end of the string X * parsed: !?foo (find command with "foo" in it) !?foo?bar (same, but add "bar") X * in the second example, return the pointer to "bar" X */ char * hist_from_str(str, hist_number) register char *str; register int *hist_number; X{ X register char *p = NULL, c = 0; X int full_search = 0, len, found; X char buf[BUFSIZ]; X struct history *hist; X#ifndef REGCMP X extern char *re_comp(); X#else X extern char *regcmp(); X#endif /* REGCMP */ X X if (*str == '?') { X if (p = index(++str, '?')) X *p++ = 0; X else X p = str + strlen(str); X full_search = 1; X } else if (*str == '{') X if (!(p = index(str, '}'))) { /* { */ X print("Unmatched '}'"); X return NULL; X } else X *p++ = 0, ++str; X else X p = str; X while (*p && *p != ':' && !isspace(*p)) X p++; X c = *p, *p = 0; X if (*str) { X#ifndef REGCMP X if (re_comp(str)) X#else X if (!regcmp(str, NULL)) X#endif /* REGCMP */ X return NULL; X } else { X *hist_number = hist_no; X return p; X } X len = strlen(str); X /* move thru the history in reverse searching for a string match. */ X for (hist = hist_head; hist; hist = hist->prev) { X if (full_search) { X (void) argv_to_string(buf, hist->argv); X Debug("Checking for (%s) in (#%d: %s)\n", str, hist->histno, buf); X } X if (!full_search) { X (void) strcpy(buf, hist->argv[0]); X Debug("Checking for (%s) in (#%d: %*s)\n", X str, hist->histno, len, buf); X found = !strncmp(buf, str, len); X } else X found = X#ifndef REGCMP X re_exec(buf) X#else X !!regex(str, buf, NULL) /* convert to boolean value */ X#endif /* REGCMP */ X == 1; X if (found) { X *hist_number = hist->histno; X Debug("Found it in history #%d\n", *hist_number); X *p = c; X return p; X } X } X print("%s: event not found\n", str); X return NULL; X} X disp_hist(n, argv) /* argc not used -- use space for the variable, "n" */ register int n; char **argv; X{ X register int list_num = TRUE, num_of_hists = hist_size; X register int reverse = FALSE; X struct history *hist = hist_tail; X X while (*++argv && *argv[0] == '-') { X n = 1; X do switch(argv[0][n]) { X case 'h': list_num = FALSE; X when 'r': reverse = TRUE; hist = hist_head; X otherwise: print("usage: history [-h] [-r] [#histories]\n"); X return -1; X } X while (argv[0][++n]); X } X if (*argv) X if (!isdigit(**argv)) { X print("history: badly formed number\n"); X return -1; X } else X num_of_hists = atoi(*argv); X X if (num_of_hists > hist_size || num_of_hists > hist_no) X num_of_hists = min(hist_size, hist_no); X X if (!reverse) X while (hist_no - hist->histno > num_of_hists) { X printf("skipping %d\n", hist->histno); X hist = hist->next; X } X X do_pager(NULL, TRUE); X for (n = 0; n < num_of_hists && hist; n++) { X char buf[256]; X if (list_num) X do_pager(sprintf(buf, "%4.d ", hist->histno), FALSE); X (void) argv_to_string(buf, hist->argv); X (void) do_pager(buf, FALSE); X if (do_pager("\n", FALSE) == -1) X break; X hist = (reverse)? hist->prev : hist->next; X } X do_pager(NULL, FALSE); X return -1; X} X init_history(newsize) X{ X if ((hist_size = newsize) < 1) X hist_size = 1; X} END_OF_FILE if test 24591 -ne `wc -c <'loop.c'`; then echo shar: \"'loop.c'\" unpacked with wrong size! fi # end of 'loop.c' fi if test -f 'tool_help' -a "${1}" != "-c" ; then echo shar: Will not clobber existing file \"'tool_help'\" else echo shar: Extracting \"'tool_help'\" \(23733 characters\) sed "s/^X//" >'tool_help' <<'END_OF_FILE' X@(#)tool_help (c) copyright 10/18/86 (Dan Heller) X X%general% X X IF ALL ELSE FAILS, READ THE DIRECTIONS! X This famous quote applies here more than ever. If you are unfamiliar with this mailtool, get yourself acquainted with it by choosing HELP options in all menu items. If you get frustrated or confused about how to use or run a command, or if you want to know how something works or get to know quick shortcuts in achieving tasks, it is advisable to look at the Help option available with the item. X Since there are many different options to some of the commands in mailtool, explanation of options for the commands can be found by choosing the RIGHT mouse button over an item. This will display a menu of options for the command. One of the menu options will almost always be a Help option. X Give yourself a head start, try selecting this same item with the RIGHT mouse button. When you do, you will given some more help topics to choose from. X%% X X%help% Help was designed for users to get help from anywhere on the mailtool window. The RIGHT mouse button may be selected on virtually every - X on any of the windows on the entire tool and a menu will appear. The last item in almost every menu is a "help" item. You will get an appropriate help message describing what you can do at the position you are in on the mailtool window. X If a help message isn't much help, it may be more helpful to reference a different help item which describes in more detail what you want to know. For example, reading the help for "folder" will help you better understand the method in which mail messages are stored than it would if you had read the help message for "save" first. X%% X X%mouse% The mouse is an image (cursor) which moves across the screen. Its position indicates which window is to receive input when you type or click a mouse button. X The mouse may take upon different images which indicate various things. When the image looks like a "coffee cup", Mushtool is in the process of doing something, like sending mail, or reading in new mail. In this event, you should wait till the cursor returns to its normal state before attempting to do anything else. Go get some coffee. X When the mouse looks like a pair of glasses, you are reading mail and when it looks like a pencil, you are editing a letter. When in the Header Window, the cursor will look like the mouse device that you hold with the buttons flashing on and off. This is to remind you that you can use each button to do different tasks. X In one window, the Main Panel Window, the cursor image looks like an envelope. Placing the cursor over "Panel Items" and selecting the LEFT button will do that command. Selecting the RIGHT mouse button will give a menu of options to choose from. In most cases, Help is available and the end of each menu list to help you with the proper use of Panel Items. X When you are asked a yes or no question, choosing either the LEFT or RIGHT mouse buttons is the same as typing "y" or "n". X%% X X%respond% This item responds to mail in 4 ways. In all cases, at least one recipient of your message will be the sender of the message you are responding to. If a subject was in the author's letter, then it will be used as your subject. X The first and most used method of response is to the author of the message only. Selecting this item with the LEFT mouse button will use this method for responding to mail. X If you want to include a copy of the author's message, then choose the menu item which says to include the message. If you wish for all the recipients of the message to receive a copy of your reply, then choosing the third item will include them. X The fourth menu item will mail to the author and everyone listed on the To and Cc lines of the message, and include the message you are responding to in your text. X In such cases where you include the message you are responding to, the included message will be indented by "> " to identify it from your message. If you would like to have a string otherby selecting the left mouse button on the "folder" item and TYPING the exact name of the folder you wish to access. The "pathname" to the folder may start with a tilde (~) indicating your home directory. Or, it may contain a plus sign before the name indicating your Mail directory (+reports, for example). Alternatively, you can type '%' to access your system Mailbox, the place where all your mail is first delivered. And finally, you can type '#' to indicate the previously accessed folder. See the help for "chdir" for more information. X%% X X%chdir% This is used to just change working directories. Your working directory contains files and other directories. Files can be "mail folders" which contain mail messages. You can change to other directories using some of the following methods: X You can select from the menu, HOME or Mail, which are your home and default mail directories. Or, select the left mouse button and TYPE in the name of the directory you would like to change to. X Typed names may have the following syntax: X X~[/subpath] will change to your home directory and X a path below that, if specified. Also, X you can specify other users: ~username X+[subpath] This is your default Mail directory. X%% X X%save% You may save messages in two ways. The most commonly used method is to save messages to your mailbox folder ("mbox") in your home directory. If you use mail very frequently and save large amounts of mail, you can save messages to other folders for better organization. X Usually, when messages are saved, mushtool marks them for deletion for the next update. If you don't want to have saved messages deleted, you must undelete them or set the variable "keepsave" in the options screen. X There is a text item in the Main Panel Window which allows you to type the name of the file to save a message. Select the LEFT mouse button over the "Save" item, and type the filename and hit return. If there is no filename specified, then messages are saved to your mbox file. X You can specify a range, or group of messages to save by typing a range in the Header Panel Window. If there is a message list in that panel item, then the range of messages specified there will be saved. If not, the current message will be saved. X For additional information, see the help option for Folders. X%% X X%quit% There are various ways in which you may be finished with mailtool. The most commonly used method is to simply "close" the tool to an iconic form. This means that you haven't really quit, but you have merely put it on "hold" till later. It will become an icon on the side or corner of the screen and appear to sit and do nothing. To close the tool to icon form, there are two methods which have will have two different effects. X The first method is to select this panel item with the left mouse button. This will update your current folder (deleting messages marked for deletion) and close the tool. The second method is to use the tool manager around the perimeter of the window and select X"close". This will close the tool without updating your mailfile. X Whenever the mailtool is in the "closed" state, it periodically checks your mail and updates your folder with the new mail. While mailtool is in iconic form, it will display the number of messages you have in the current folder. X There are two equally similar methods of exiting mailtool, rather than just closing to an icon: you may select the second menu item in the menu given by this panel item or you may use the tool mana- X "quit" item. X Using the tool manager's quit will exit the tool without updating your folder whereas the panel item's menu selection will have the mailtool prompt you whether to update the current folder or not. X%% X X%help_menu_help_msg% Selecting an item within this menu will give you help on that item. If you want to execute the action, choose the other menu by placing the mouse over the menu BEHIND this menu, continue to keep your RIGHT mouse button depressed and select the LEFT mouse button over the menu on the bottom and select that action. X%% X X%msg_menu% When given menu in the Header Window, you will have a choice of actions to take. The message may not be the current one, it may be any message that appears in the Headers Window. The "title" of the menu will indicate which message you are referring to. X At this point, you can select actions to take. You can Read, Delete, Undelete, Save, Reply to, or Print messages. Most of these are self explanatory, but if you need help with one of these, place the mouse over the menu BEHIND the given menu, continue to have the RIGHT mouse button depressed and select the LEFT mouse button over the Help Menu. X This action toggles the menus such that you can change back and forth between these menus. The menu you are on will tell which action to take on that message. In either case, you place the mouse over the action to take, and, if you are on the help menu, help will be given regarding that particular action. If not in the help menu, then that specific action will actually be taken. X%% X X%edit% Choosing this item with the LEFT mouse button in the Main Panel Window or in the Menu item will allow you to access a full-screen editor. The editor which you will use is indicated when you select the "opts" item in the Main Panel Window. X While you are typing a letter, you can specify explicitly which editor to use by typing (on a line by itself) "~v editor". Type "~?" on a line by itself while typing to see a list of valid X"~commands". X Upon exiting the editor, you can continue typing and even reenter the editor if you like in the same manner. X%% X X%update% This item will update the current folder you are using. Changes are updated to the folder; that is, deleted mail is removed and all other mail is copied back to the folder unless otherwise specified. See the help in "folder" for more information on folders. X If new mail has arrived, it will incorporate it. Otherwise, new mail is incorporated every two minutes or so, if some comes in. X%% X X%headers% The message headers are displayed in their own separate window. The "current" message is usually displayed in either BOLD or REVERSE text. This "highlighted" message is the one which is displayed at the bottom, larger window. In the message window, each message is displayed in the following format: the message number is displayed first; if it is the "current" message, then there is a '>' sign. The next character is the 'status' character: X 'N' -- New (and unread) X 'U' -- not new, but still Unread X '*' -- delete messages (set show_deleted) X 'P' -- preserve in spoolfile. X 'O' -- Old message which has also been Read. If there is just a space (no character), the message is new, but you've already read it. You should explicitly save or delete these. X Following that is the Author of the message and/or all or part of his network address and login name. Following that is the number of lines the message is. In quotes is all or part of the "Subject" X(if one was specified). X To read a message, select either the READ item in the main panel subwindow or move the mouse over the message header you want to read and press the LEFT mouse button. Or, the MIDDLE mouse button will delete that message. Choosing the RIGHT mouse button will give you a menu of things to do then. Included in the menu, is a help item which describes the selections in the menu. X%% X X%preserve% Usually, after you read mail and you "update" or quit mailtool, unread messages are copied back into your system mailbox, deleted messages are removed, and messages which have been read but not deleted are saved in your "mbox" file. Specifying "hold" prevents this from ever happening, but you can mark specific messages to be held in your system mailbox by preserving them. X%% X X%compose% When you start to compose a letter for mailing, you will be prompted for the login name(s), of whom you want to mail, the (optional) subject of the message, and an optional list of carbon copy recipients. This is an additional list of login names who will be mailed copies of your message. X After that, anything you type will be added to your message. If you select the RIGHT mouse button in the window in which you are type to get a menu of things to do. You may enter an editor if your message needs to be modified in more detail. X When you're through with your message, you can send it by typing (on a line by itself) "." or X^D. Or, you can select the Send item in the Main Panel Window and your mail will be sent. You cannot send mail while still in an editor; you must exit the editor first. X If you have the option "autoedit" set, you are automatically put into an editor when you want to compose or whenever you reply to a letter. In this case, whenever you're through editing the letter, you will be put back into the main editing mode where you terminate and send the letter using any of the above methods. X%% X X%next% You can page through all your messages by selecting "Next" after reading each message. The same effect is gotten when you select the "Delete" item when the option, "autoprint" is set to be true (see "opts") except that the current message is deleted before the next one is displayed. Deleting mail which is not important helps the efficiency of mailtool and reduces unnecessary use of system resources. X In the Header Window, you will notice the cursor looks like the mouse you use. The blinking buttons on the mouse image remind you that you can use any of the three buttons at any time. When you move the mouse over a message and choose a button, the message under the mouse is going to be the one affected. Choosing left button will read the message, the middle button will delete it, and the right button will give you a menu. X%% X X%aliases% Aliases are used as a method of mailing to users with long addresses using short names. For example, if you wanted to mail to X argv@spam.istc.sri.com but didn't want to type that all the time, then you could make an alias by selecting the alias menu item that specifies "adding alias" and then TYPE: X Dan argv@spam.istc.sri.com If you want to mail to a list of people and do so frequently enough to want an alias name for the whole list, then you would type something like this: X project-group fred mary bob@foo-bar herb sly@baz.bitnet X To mail to an "alias" you would compose a letter and address the letter: X To: Dan Subject: Alias example Cc: project-group X(rest of letter) X%% X X%alts% X"Alternates" are alternate names for YOU. In messages you receive, your account will appear on the "To" or "Cc" list. When you REPLY to those messages, mailtool will construct a message header for your letter which will contain the To and Cc lists of recipients from the original message. You would probably want your name taken off the list so you do not mail yourself a copy of your own message. If you have other account names or accounts on other machines, you can let mailtool know what those mail addresses are so they can be removed from the lists as well. X Note, that if YOU add your name MANUALLY (type it yourself) to either of the lists, it will not be removed. X You can set such a list in your .mailrc file in your home directory by adding the line: X alts hostname1 hostname2 ... X If you prefer to not have your name removed from lists when responding to mail, set the option "metoo" and this prevents the need for alternates and your name will never be removed. X%% X X%opts% To set or unset options and their values, move the mouse over the option of your choice and select the LEFT button to toggle true/false values. If an option requires a string value, you must type the value, so select the LEFT button to reference the option, and then type away. Use a Carriage Return to enter the final value for the option. X You may select the RIGHT mouse button anywhere in the window to give a menu which consists of saving options permanently, reading in previous settings (from ~/.mailrc), and other things. X%% X X%ignore% When readset a key for multiple commands, separate the commands with semicolons: X L9: update ; close X This example would update your mailbox (committing changes) and close the tool to an icon. X%% X X%message range% You can specify a large group of messages using a combination of special symbols in addition to numbers. For example, if you wish to save all of the messages, then you can use `*' to represent them all. If you were to type the "star" and select the Save menu option for "save range", then you would save ALL the messages you have (including deleted ones). X If you would like to save messages 4 through 9, then you would specify: X4-9 If you want to specify the messages between 2 and 32 except for messages X6, 8 and message 12-14, you would type: X2-32 {6,8,12-14} Commas or spaces can be used to separate numbers. X Note that you cannot specify negated messages without first specifying normal messages; e.g. {2-5} 1-11 doesn't make sense. X%% X X%sort% Sorting messages can be accomplished by selecting one of the menu items in this panel item. By default (using the LEFT mouse button), sorting is done by message status. New messages are first, followed by unread messages, old/read messages, replied to messages, and finally deleted messages. You may also sort messages by author, date, or subject by selecting the menu item. X%% END_OF_FILE if test 23733 -ne `wc -c <'tool_help'`; then echo shar: \"'tool_help'\" unpacked with wrong size! fi # end of 'tool_help' fi echo shar: End of archive 9 \(of 14\). cp /dev/null ark9isdone MISSING="" for I in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 ; do if test ! -f ark${I}isdone ; then MISSING="${MISSING} ${I}" fi done if test "${MISSING}" = "" ; then echo You have unpacked all 14 archives. rm -f ark[1-9]isdone ark[1-9][0-9]isdone else echo You still need to unpack the following archives: echo " " ${MISSING} fi ## End of shell archive. exit 0 -- Please send comp.sources.unix-related mail to rsalz@uunet.uu.net.