enhancements to the MENU program
Orest Jarosiewicz
orest at pyuxd.UUCP
Wed Oct 5 01:00:18 AEST 1983
# This is an enhaced version of Richard Conn's MENU program.
# Rather than using system(3) to execute the command lines this version
# opens a pipe to shell at the start of the session, and sends the command
# lines over the pipe to the shell.
# This allows things like the ability to set shell variables and having the
# the shell remember the current working directory.
#
# This changes were made on a 3B20S running BTL UNIX 5.0.
# To install the MENU program and its new documentation, just execute this file.
# Two files will be created, "menu.1" and "menu.c".
# On BTL UNIX 5.0 compile the program with:
# cc menu.c -DSYS3 -o menu
#
# Orest Jarosiewicz
awk 'BEGIN {file=""}
/^#### / {file=$2; print "Building " $2
print "" > file
next
}
{if( file == "" ) next
print substr( $0, 2 ) >> file
}' $0
#### menu.1
#.TH MENU 1
#.SH NAME
#MENU -- A Menu Preprocessor for UNIX
#.sp 2
#MENU Version 2.0 by Richard Conn
#.SH SYNOPSIS
#
# MENU is invoked by a command line of the following form:
#
#.ce
#menu [-n] menufile
#
#where the "-n" is optional and is the number of a menu to be invoked in
#a menu file which contains more than one menu.
#
# Hence, MENU command lines may take the following forms:
#
#.nf
# menu mymenu <-- invoke on file 'mymenu'
# menu -2 mymenu <-- invoke menu 2 of file 'mymenu'
#
#.fi
#.SH DESCRIPTION
# MENU (written in C) is a very simple menu preprocessor for use
#under the UNIX operating system. A user can compose files containing
#screen displays and command lines which are invoked in response to
#single-character
#commands, and MENU will present those screen displays to the user and
#accept both single-character and text string (terminated by a RETURN)
#input from the user.
#
#.SH WRITING MENU FILES
#
# A file which can be processed by MENU can take on the following
#general format:
#
#.nf
#-o <-- set global options (optional line)
##o <-- define screen display and set local options
#<text> <-- text of screen display
## <-- end of screen display
#<c><text> <-- command letter followed by text of command line
# ... <-- additional menus, all of the format
# #o
# <text>
# #
# <c><text>
##o <-- define screen display and set local options for
# next menu
#<text>
##
#<c><text> <-- command letter followed by text of command line
## <-- marks end of last menu
#
#.fi
# There are three options recognized by the MENU processor. These
#options are:
#
#.nf
# d - Display text of menu (screen display)
# p - If 'd' is on, page (fill screen with blank lines)
# when end of menu text (screen display)
# is reached
# x - Allow the user to exit to UNIX
#
#.fi
# When MENU is first invoked, all three options are OFF. The text
#of the screen displays will not be shown and the user will not be allowed
#to exit to UNIX.
#
# The first line (which is optional) of the menu file
#toggles the global options to ON from their initial state of OFF. The
#dash (-) can be followed by any combination of the menu options in any
#order (-dpx = -xpd = -pxd). This global option line sets the options
#for all of the menus in the file except for those menus whose option
#line toggles selected options.
#
# Each menu begins with its own option line. This is the hash (#)
#followed by zero or more of the option characters. For each option character
#specified, the state of the global options FOR THIS MENU ONLY is reversed.
#Note the following example:
#
#.ne 20
#.nf
# -dpx <-- turn on all options
# #x <-- do not allow the user to exit
# to UNIX for this menu
# <text>
# #
# <c><text>
# #px <-- do not allow the user to exit
# to UNIX and do not page the
# screen display for this menu
# <text>
# #
# <c><text>
# # <-- all options are on for this menu
# <text>
# #
# <c><text>
# #
#
#.fi
# The text of the screen display (that text which follows the
#local options line for each menu) can be any text desired with the
#one exception that the first character of any line of this text may
#not be a hash (#). The hash, of course, signals the end of the
#screen display text and the beginning of the command text.
#Example:
#
#.nf
# #dpx <-- this menu has no globals set
# Any <-- text of menu
# Text
# I Want
# #
# <c><text>
# # <-- only one menu in this file
#
#.fi
# Finally, the last part of a menu entry is the command
#definition itself. The definition of a menu command is of the
#following general form:
#
#.ce
#<char><text>
#
#where <char> is one character which will invoke the text of a
#command line.
#When you first invoke the MENU program it creates a pipe to the shell.
#The shell is selected by looking at the SHELL environment variable.
#If this variable is not set then
#.I "sh(1)"
#is used.
#For example, valid menu command lines include:
#
#.nf
# dls <-- run ls if char 'd' is typed
# Dls | pr -4t <-- run 'ls | pr -4t' if char
# 'D' is typed
#
#.fi
# There are three special characters which may be embedded
#into the text of the command line to cause MENU to perform some
#additional processing. These characters are:
#
#.nf
# : <-- goto a menu
# ! <-- cause MENU to wait when
# command line completes
# ~ <-- submit the command line by spawning off a
# subshell, this is useful for invoking commands
# which must be attached to a physical
# device, such as vi.
# = <-- change the current working directory
# ' <-- prompt user for string input
#
#.fi
# The first special character is used to transfer control
#from one menu in a menu file to another. If the colon (:) is the
#first character of text (right after the single-character command),
#then the rest of the line is assumed to be the text of a number,
#and control is transferred to that menu (first menu is 1, second is 2, etc)
#in the file. ":2" will transfer to menu 2, ":20" will transfer
#to menu 20, etc. Examples:
#
#.nf
# 2:2 <-- goto menu 2 if char '2' is typed
# h:1 <-- goto menu 1 if char 'h' is typed
# ?:100 <-- goto menu 100 if char '?' is typed
#
#.fi
# The second character is used to cause MENU to wait for user
#input after the text of the command line is executed (before displaying
#the screen of the menu the user is returning to). If the exclamation
#mark (!) is the first character of text (right after the single-character
#command), then MENU will wait after the text of the command line
#is executed. Examples:
#
#.nf
# d!ls <-- run 'ls' and then wait
# D!ls | pr -4t <-- run 'ls | pr -4t' and then wait
#
#.fi
# The third character is used to invoke a command line by using a
#subshell. This option is useful when submitting commands which
#put the terminal into raw mode, such as screen editors.
#
# The fourth character is used to change MENU's current working directory.
#Upon seeing this option MENU will send a change directory command to the
#shell so that it too will change to the new working directory. Examples:
#
#.nf
# U=.. <-- go up to the parent directory
# c=bin <-- go into the bin directory
#.fi
#
# Finally, the fifth character is followed by text to prompt the
#user for input, and the user is allowed to enter any text he desires.
#This text will be inserted at the point of the prompt. Unlike the
#other two commands, the text input command (') may appear anywhere
#in the text of the command line. This text may be bracketed by the
#quote character (like 'Enter Source File'), in which case the user's
#input is inserted into the command line at this point and command line
#processing from the menu file resumes after the second quote. Alternatively,
#this text may be preceeded by the quote character (like 'Enter File? ),
#in which case the end of the line indicates the end of the prompt. In
#both cases, the last character of the prompt is followed by a trailing
#blank to improve readability.
#Examples:
#
#.ne 22
#.nf
# u'Enter Command? <-- user is prompted with
# 'Enter Command? '
# and his text becomes
# the command line
# u!'Command? <-- like above, but MENU
# waits when the command
# is completed
# d!ls 'File Spec?' | wc <-- the command line
# 'ls <user input> | wc' is
# built
# ccp 'Source?' 'Destination?'
# <-- the user is prompted once
# with 'Source? ', allowed
# to enter his input, is
# prompted on the next line
# with 'Destintion? ',
# allowed to enter his input,
# and the command line
# 'cp <user 1> <user 2>'
# is built
#.fi
#.SH EXAMPLE
#
# The following is a sample menu file.
#Note that it contains commands which are available
#on my UNIX SYSTEM III but may not be available on yours.
#
#.nf
#
#-dpx
##p
#MENU 1 - Basic Operations
#
# ---- Directory ---- ---- File Transfer ----
# d display directory 1 compress file
# D disp dir with arg 2 uncompress file
# / change directory u uc - transfer file
#
# ------ Filer ------ -------- Other --------
# c copy file C C Programming Menu
# e edit file w who is on?
# f format file z enter any command
# r remove file
# t type file
#
##
#C:2
#ccp 'Source File?' 'Destination File or Dir?
#d!ls | pr -4t
#D!ls 'Enter File Spec?
#/='Enter Directory Name?
#1xsq 'Enter File Name?
#2xusq 'Enter File Name?
#uuc z
#e~vi 'Enter File Name?
#fnroff >out.roff 'Output is on out.roff -- Enter File Name?
#rrm 'Enter File Name?
#t!type 'Enter File Name?
#w!who
#z!'Enter Command Line?
##
#MENU 2 -- C Programming
#
# -------- Filer -------- ---- Compiler ----
# d display directory c compile prog
# e edit file
# r remove file ----- Other ------
# t type file z run prog
# M Main menu
##
#M:1
#d!ls | pr -4t
#evi 'Enter File Name?
#rrm 'Enter File Name?
#t!type 'Enter File Name?
#c!cc 'Enter File Name?
#z!'Enter Command Line?
##
#.fi
#.SH FILES
#.nf
#menu.c -- source code
#mcheck.c -- menu file syntax checker
#.fi
#.SH DIAGNOSTICS
# MENU issues very simple error messages when syntax errors are
#encountered in the menu file. I did this to keep MENU as simple as possible,
#and it is a good idea to run any new menu file through MCHECK before running
#it under MENU. MCHECK gives complete, self-explanatory error messages on
#syntax errors contained in a menu file.
#
#.SH BUGS
#
# No known problems exist at this time. This design is vastly improved
#over the original MENU 1.0, and nesting of shell procedures as well as
#multiple prompted entries on the same line are now supported.
#### menu.c
#/*
# * MENU -- a menu preprocessor for UNIX
# * by Richard Conn
# *
# * MENU allows the user to create menu files, containing screen displays
# * and command lines which are to be issued in response to single-character
# * commands presented by the user. Once a user issues a single-character
# * command, MENU executes his command line (as a shell procedure) and
# * then resumes control (in most cases).
# *
# ** enhacements involving piping to a shell, and changing the current
# ** working directory where done by Orest Jarosiewicz
# *
# ** compile with
# ** cc -DSYS3 menu.c -o menu
# **
# ** [odj]
# */
#
##define versmaj 2 /* major version */
##define versmin 0 /* minor version */
#
#/* Version History
# * Version 1.0 - Richard Conn
# * 2.0 - Richard Conn
# * Rather than creating a shell procedure file and then
# * executing it via an execl, the program was
# * redesigned to use the system() library function,
# * which I feel is a much better, cleaner design.
# * No intermediate command files are created now.
# * NOTE: This redesign caused many problems because
# * of what I believe to be an undocumented bug with
# * system() -- if an global variables of type int
# * are declared, calling system() results in an
# * error message about an invalid instruction exec
# * and then a core dump. The program redesign removed
# * all global variables.
# */
#
##define FALSE 0
##define TRUE ~FALSE
##define HASH '#' /* menu separator */
##define GOTO ':' /* goto command in menu text */
##define WAIT '!' /* Wait command in menu text */
##define QUOTE '\'' /* quote for user input */
##define SUBSHELL '~' /** invoke command in subshell **/
##define CHGDIR '=' /** change working directory **/
##define llimit 23 /* number of lines in screen display */
##define EOB 0x7f /* end of buffer marker */
##define ESC 0x1b /* user abort (if allowed) */
##define BEL '\7'
##define LF '\n'
##define SCRSIZ 4000 /* size of buffer for screen display */
##define CMDSIZ 8000 /* size of buffer for command lines */
#
##include <stdio.h>
##include <ctype.h>
#
##ifdef SYS3
##include <sgtty.h>
##endif
#
##ifdef JHU
##include </usr/includeJHU/stty.h> /* at BRL */
##endif
#
#/* options structure */
##include <sys/signal.h>
#
#struct opt {
# int disp; /* display on/off */
# int page; /* page on/off */
# int exit; /* exit on/off */
#};
#
#catchit()
#{
# return;
# }
#
#main(argc,argv)
#int argc;
#char *argv[];
#{
# char file[50]; /* menu file name */
# char inline[400]; /* menu command line */
# int cmd, ccode; /* user command, ret code */
# int Wait, menunum; /* Wait flag, menu number */
# struct opt globopts, locopts; /* global and local options */
# char screen[SCRSIZ], text[CMDSIZ]; /* screen and text buffers */
# extern char *getenv();
# int pid; /** process id **/
# FILE *Shell;
# char *Shelle;
# char Cmd[ 420 ]; /** Shell points to a pipe
# ** to the shell, cmd holds
# ** the commands sent to it
# ** Shelle points to the
# ** environment var SHELL.
# ** [odj]
# **/
#
# if (argc==1) { /* give user help info if no args */
# help();
# exit(-1);
# }
#
# /* process options if any */
# menunum = 1; /* set to first menu */
# if (*argv[1]=='-') menunum = atoi(argv[1]+1); /* extract options */
# if (!menunum) menunum = 1; /* set to 1 if error in arg */
#
# /* extract menu file name */
# file[0] = '\0'; /* set no file name */
# if (argc==2 && *argv[1]!='-') strcat(file, argv[1]);
# else if (argc>2) strcat(file, argv[2]);
# else {
# help();
# exit(-1);
# }
#
# /** open pipe to shell [odj] **/
# Shelle = getenv( "SHELL" );
# if( Shelle != NULL && Shelle[ 0 ] != '\0' ) strcpy( Cmd, Shelle );
# else strcpy( Cmd, "sh" );
# Shell = popen( Cmd, "w" );
# if( Shell == NULL ){
# fprintf( stderr,"%s: can't open pipe to %s\n", argv[ 0 ], Cmd);
# exit( 5 );
# }
# pid = getpid();
#
# /* get menu from file */
# getmenu(file,menunum,&globopts,&locopts,screen,text);
#
# /* process menu */
# while (TRUE) { /* exit only on ESC from user (if allowed) */
# if (locopts.disp) prdisp(screen,&locopts); /* print display */
# Wait = FALSE; /* set no Wait */
# printf("MENU %d.%d Command%s", versmaj, versmin,
# locopts.exit ? " (ESC to exit)? " : "? "); /* prompt */
# cmd = getcmd(); /* get command from user */
# if (locopts.exit && cmd==ESC) exit(0); /* exit */
# ccode = prcmd(cmd,text,inline,&Wait); /* get cmnd */
# switch (ccode) {
# case 0 : /* command line */
# /** execute command, the signal will notify
# ** the process when the shell is done processing
# ** this line
# **/
# sprintf( Cmd, "%s;kill -16 %d", inline, pid );
# signal( SIGUSR1, catchit );
# fprintf( Shell, "%s\n", Cmd );
# fflush( Shell );
# wait((int *) 0); /** wait for shell to process the line
# **/
# if (Wait) {
# printf("Strike any key -- ");
# setraw();
# getchar();
# clrraw();
# printf("\n");
# }
# break;
# case 2 : /** do a plain "system", for things like calling vi **/
# system( inline );
# if (Wait) {
# printf("Strike any key -- ");
# setraw();
# getchar();
# clrraw();
# printf("\n");
# }
# break;
# case 1 : /* goto new menu */
# menunum = atoi (inline); /* get menu number */
# if (!menunum) menunum = 1;
# getmenu(file,menunum,&globopts,&locopts,
# screen,text); /* get menu */
# break;
# case 3 : /** change current directory, this has to
# ** be done within menu, and within the shell
# ** [odj]
# **/
# if( chdir( inline ) != 0 ){
# fprintf(stderr,"Can't change to directory %s\n",
# inline);
# break;
# }
# fprintf( Shell, "cd %s\n", inline ); fflush( Shell );
# break;
# default : /* invalid command */
# putchar(BEL);
# break;
# }
# }
#}
#
#/* print display if desired and page if desired */
#prdisp(screen,locops)
#char *screen;
#struct opt *locops;
#{
# int linecnt;
#
# linecnt = 0; /* init line counter */
# while (*screen != EOB) {
# putchar(*screen); /* send char */
# if (*screen++ == LF) linecnt++; /* increment line count */
# }
# if (locops->page) /* page display */
# while (linecnt++ < llimit) printf("\n");
#}
#
#/* get raw command from user */
#getcmd()
#{
# int c;
#
# setraw(); /* set no echo and immed input */
# c = getchar() & 0x7f; /* get command and mask out MSB */
# clrraw(); /* restore normal I/O */
# if (c > ' ') putchar(c); /* echo if printing char */
# printf("\n"); /* new line */
# return(c);
#}
#
#/* get and process command from user */
#prcmd(c,text,line,Wait)
#int c, *Wait;
#char *text, *line;
#{
# char *scmd(), *cline;
# register Subshell = 0;
#
# cline = scmd(c,text); /* cline pts to command from text */
# *line = '\0'; /* init command line */
# if (!(*cline)) return(99); /* command not found */
# if (*cline==GOTO) { /* GOTO command */
# ++cline; /* pt to first digit */
# while (isdigit(*cline)) *line++ = *cline++; /* copy */
# *line = '\0'; /* set end of string */
# return(1); /* goto return code */
# }
# if( *cline == CHGDIR ){ /** check if this a directory change**/
# ++cline;
# docmnd( cline, line ); /** directive **/
# return( 3 );
# }
# if( *cline == SUBSHELL ){ /** is this a subshell invokation?**/
# Subshell = 1; /** (useful for invoking things like vi)**/
# ++cline;
# }
# if (*cline==WAIT) { /* Wait in command line */
# *Wait=TRUE; /* set flag */
# ++cline; /* pt to first valid cmd line char */
# }
# docmnd(cline,line); /* extract and process command line */
# return(Subshell ? 2 : 0); /* command line return code */
#}
#
#/* scan commands in command buffer for command passed */
#char *scmd(cmd,text)
#int cmd;
#char *text;
#{
# while (*text != EOB) { /* scan thru buffer */
# if (*text==cmd) return(++text); /* found command */
# while (*text++ != LF); /* skip current line */
# }
# return(""); /* null command if not found */
#}
#
#/* build command line */
#docmnd(cline,line)
#char *cline, *line;
#{
# char userin[200]; /* buffer for build and user input */
#
# while (*cline != LF) { /* advance over line */
# switch (*cline) {
# case QUOTE :
# ++cline; /* pt to after quote */
# while (*cline != LF && *cline != QUOTE)
# putchar(*cline++); /* print prompt */
# putchar(' '); /* print extra space */
# gets(userin); /* get input from user */
# if (*cline == QUOTE) cline++; /* skip ending quote */
# break;
# default :
# userin[0] = *cline++; /* set up string */
# userin[1] = '\0';
# break;
# }
# strcat (line, userin); /* append user input or char */
# }
#}
#
#/* extract menu from menu file */
#getmenu(file,menunum,globopts,locopts,screen,text)
#int menunum;
#struct opt *globopts, *locopts;
#char *file, *screen, *text;
#{
# FILE *fopen(), *fd;
# int c;
#
# /* open menu file */
# fd=fopen(file,"r");
# if (fd==NULL) {
# printf("Menu %s NOT Found\n", file);
# exit(0);
# }
#
# /* set global options */
# globopts->disp = FALSE;
# globopts->page = FALSE;
# globopts->exit = FALSE;
# if ((c=getc(fd)) == '-') setopts(fd,globopts);
# else ungetc(fd,c);
#
# /* skip to desired menu */
# skip(fd,menunum);
#
# /* set local options */
# locopts->disp = globopts->disp;
# locopts->page = globopts->page;
# locopts->exit = globopts->exit;
# setopts(fd,locopts);
#
# /* build buffers */
# build(fd,screen,SCRSIZ); /* build screen display */
# build(fd,text,CMDSIZ); /* build command buffer */
#
# /* close menu file */
# fclose(fd);
#}
#
#/* set options */
#setopts(fd,opts)
#FILE *fd;
#struct opt *opts;
#{
# int c;
#
# while ((c=getc(fd)) != LF)
# switch (c) {
# case 'D' :
# case 'd' :
# opts->disp = ~opts->disp;
# break;
# case 'P' :
# case 'p' :
# opts->page = ~opts->page;
# break;
# case 'X' :
# case 'x' :
# opts->exit = ~opts->exit;
# break;
# case EOF :
# premature();
# break;
# default :
# break; /* skip invalid option */
# }
#}
#
#/* Skip to desired menu and load buffers */
#skip(fd,menunum)
#FILE *fd;
#int menunum;
#{
# int mctr, c;
#
# /* skip required number of menus */
# if (getc(fd) != HASH) strerror(); /* first char must be a hash */
# for (mctr=menunum-1; mctr; mctr--) skipmenu(fd); /* skip menus */
#}
#
#/* Skip over menu */
#skipmenu(fd)
#FILE *fd;
#{
# int c;
#
# /* advance over screen display */
# while ((c=getc(fd)) != HASH)
# while (c != LF) {
# if (c == EOF) strerror();
# c=getc(fd);
# }
#
# /* advance over commands */
# while ((c=getc(fd)) != HASH)
# while (c != LF) {
# if (c == EOF) strerror();
# c=getc(fd);
# }
#}
#
#/* Build buffers */
#build(fd,buff,size)
#FILE *fd;
#char *buff;
#int size;
#{
# int c;
#
# while ((c=getc(fd)) != HASH) { /* loop to end of section */
# while (c != LF) { /* loop to end of line */
# if (c == EOF) premature();
# *buff++ = c & 0x7f; /* store char */
# if (!(--size)) overflow();
# c = getc(fd); /* get next char */
# }
# *buff++ = c; /* store LF */
# if (!(--size)) overflow();
# }
# *buff = EOB; /* set end of buffer marker */
#}
#
#/* premature EOF exit */
#premature()
#{
# printf("Premature End of File Encountered\n");
# exit(-1);
#}
#
#/* menu structure error */
#strerror()
#{
# printf("Menu Structure Error\n");
# exit(-1);
#}
#
#/* Buffer overflow */
#overflow()
#{
# printf("Buffer Overflow\n");
# exit(-1);
#}
#
#/* Print Help Message */
#help()
#{
# printf("Usage: menu [-n] menufile");
# printf("\n\twhere 'n' is the menu number to start at\n");
#}
#
#/* set byte-oriented I/O */
#setraw()
#{
##ifdef SYS3
# struct sgttyb ttys;
#
# if (gtty(0,&ttys) < 0) { /* get term characteristics */
# printf("Can't set Byte-Oriented I/O\n");
# exit(-1);
# }
# ttys.sg_flags |= RAW; /* set RAW */
# ttys.sg_flags &= ~ECHO; /* set no echo */
# if (stty(0,&ttys) < 0) { /* set term characteristics */
# printf("Can't set Byte-Oriented I/O\n");
# exit(-1);
# }
##endif
##ifdef JHU
# struct sttybuf ttys;
#
# if (gtty(0,&ttys) < 0) { /* get term characteristics */
# printf("Can't set Byte-Oriented I/O\n");
# exit(-1);
# }
# ttys.sg_mode |= RAW; /* set RAW */
# ttys.sg_mode &= ~ECHO; /* set no echo */
# if (stty(0,&ttys) < 0) { /* set term characteristics */
# printf("Can't set Byte-Oriented I/O\n");
# exit(-1);
# }
##endif
#}
#
#/* restore normal operation for I/O */
#clrraw()
#{
##ifdef SYS3
# struct sgttyb ttys;
#
# if (gtty(0,&ttys) <0) { /* get term characteristics */
# printf("Can't restore normal I/O\n");
# exit(-1);
# }
# ttys.sg_flags &= ~RAW; /* clear RAW */
# ttys.sg_flags |= ECHO; /* enable ECHO */
# if (stty(0,&ttys) < 0) { /* set term characteristics */
# printf("Can't restore normal I/O\n");
# exit(-1);
# }
##endif
##ifdef JHU
# struct sttybuf ttys;
#
# if (gtty(0,&ttys) <0) { /* get term characteristics */
# printf("Can't restore normal I/O\n");
# exit(-1);
# }
# ttys.sg_mode &= ~RAW; /* clear RAW */
# ttys.sg_mode |= ECHO; /* enable ECHO */
# if (stty(0,&ttys) < 0) { /* set term characteristics */
# printf("Can't restore normal I/O\n");
# exit(-1);
# }
##endif
#}
More information about the Comp.sources.unix
mailing list