[comp.sources.misc] v05i073: grabchars 1.3, get/filter keystrokes directly from user

daniel@island.UUCP (Dan Smith, Release 28.0) (12/07/88)

Posting-number: Volume 5, Issue 73
Submitted-by: "Dan Smith, Release 28.0" <daniel@island.UUCP>
Archive-name: grabchars1.3

	"grabchars" gets one or more keystrokes from the user, without
requiring them to hit return.  It was written to make shell scripts
(doesn't matter what type) more interactive.

	I know that it works fine on Suns running SUN OS 3.2-3.5,
and a Vax 11/750 running Mt. Xinu 4.3 BSD.  I don't have support
for SYS V or VMS or anything else like that yet; send me patches...
Installation should be pretty straighforward, read the README and
run Config.

	You'll find uses for this in all sorts of places.  The prime
candidate is in your .login file, it's easy to use this to select different
options.  I've provided a "demo" csh script which runs through many of the
options.  For the most part, grabchars can replace the use of "$<" in
csh scripts and "read" in sh scripts, and offer filtering, timeouts, and
default answers - the exception at the moment is in trying to read control
characters.

	Enjoy, and send me your comments/suggestions.  2.0 will provide
SYS V support.

				dan

DanSmith IslandGraphics 4000CivicCenterDr SanRafael MarinCo CA 94903 4154911000
415 332 FAST(h) 491 0402(Fax)|d: Nobodys' fault but mine| UnixFeastsMusicFilm
daniel@island.uu.net   unicom!daniel@pacbell.com  {lll-crg,apple}!well!dansmith

------------------------ snip -------- cut ------- chop ---------
#! /bin/sh
# This is a shell archive, meaning:
# 1. Remove everything above the #! /bin/sh line.
# 2. Save the resulting text in a file.
# 3. Execute the file with /bin/sh (not csh) to create:
#	README
#	Config
#	TODO
#	Makefile.dist
#	demo
#	grabchars.1
#	globals.c
#	grabchars.c
#	sys.c
#	grabchars.h
# This archive created: Mon Dec  5 19:52:34 1988
# By:	Dan "Bucko" Smith ()
export PATH; PATH=/bin:/usr/bin:$PATH
echo shar: "extracting 'README'" '(3940 characters)'
if test -f 'README'
then
       echo shar: "will not over-write existing file 'README'"
else
cat << \SHAR_EOF > 'README'
                       Grabchars 1.3

		Copyright (c) 1988, Dan Smith

What this is:

	"grabchars" gets one or more keystrokes from the user, without
requiring them to hit return.  It was written to make shell scripts
(doesn't matter what type) more interactive.

	I know that it works fine on Suns running SUN OS 3.2-3.5,
and a Vax 11/750 running Mt. Xinu 4.3 BSD.  I don't have support
for SYS V or VMS or anything else like that yet; send me patches...

	You'll find uses for this in all sorts of places.  The prime
candidate is in your .login file, it's easy to use this to select different
options.  I've provided a "demo" csh script which runs through many of the
options.  For the most part, grabchars can replace the use of "$<" in
csh scripts and "read" in sh scripts, and offer filtering, timeouts, and
default answers - the exception at the moment is in trying to read control
characters.

Putting this together:

	Run Config; it will figure out where you might want to
put the executable and the man page.  It will also put you into an
editor with the Makefile, and then it will run a "make clean",
"make depend", and a "make release".  You will need getopt (3)
to compile this program.  If you're not sure if you have this, try
"nm /lib/libc.a | grep getopt".  Get a Public Domain version if you
don't (write me if you get really stuck, I have it).

Usage: (see the man page for more...)

	grabchars			gets one keystroke
	grabchars -b			output to stdout and stderr
	grabchars -c<valid characters>  only <valid chars> are returned
	grabchars -d<char(s)>		default char or string to return
	grabchars -e			output to stderr instead of stdout
	grabchars -f			flush any previous input
	grabchars -h			help screen
	grabchars -n<number>		number of characters to read
	grabchars -p<prompt>		prompt to help user
	grabchars -q<prompt>		prompt to help user (through stderr)
	grabchars -r			RETURN exits (use with -n)
	grabchars -s			silent, just return status
	grabchars -t<seconds>		timeout after <seconds>
	grabchars -E			erase/kill character processing

	examples: (values to arguments can be in the same word or the next one)

	grabchars -caeiou	 or
	grabchars -c aeiou		get one of the vowels
	grabchars -c i			get the letter 'i'
	grabchars '-penter a letter '	print the prompt "enter a letter "
	grabchars '-qenter a letter '	print the prompt ('q' for question)
					"enter a letter " through stderr...
	grabchars -n4			get four characters
	grabchars -t2			timeout after two seconds

	print a prompt and grab three characters...
	grabchars -p 'enter three characters >> ' -n 3

	get two numbers with a ten second timeout...
	grabchars -c 0123456789 -n2 -t10

	note that arguments like "-n4" or "-n 4" are handled the same way

	grabchars -h			will give a usage screen...

Legal paragraph:

	Grabchars may be freely copied, changed, and used, in whole or
in part.  Don't try to make any money off of it.  Don't remove my headers.
Don't say you wrote it.  This kit should be kept freely available, disk
space permitting.  Not responsible for mishaps arising out of the use
of this program, on the other hand, I haven't run into any problems with
this. Lastly, Do not use any part of grabchars in any commercial product.
Let's change the subject :-)

Patches and things like that:

	"Help! Save The World!" (LW)...if you make any modifications
to grabchars, send me a diff suitable for use with the patch program.
I don't want ten different versions of this running around.  I need to
know what changes need to be made to get this to run on Sys V and other
Unix (or other) variants.  I don't use them, so at this point I don't
know!

Where to reach me:

	Dan Smith
	Island Graphics
	4000 Civic Center Drive
	San Rafael, Ca 94903
	+1 (415) 491 1000 (w), 491 0402 (fax), 332 3278 (h)

	daniel@island.uu.net or	
	{pixar,grenada,ucbvax!ucbcad,unicom,well,sun,uunet}!island!daniel

	unicom!daniel@pacbell.com

	{apple,lll-crg}!well!dansmith

SHAR_EOF
fi
echo shar: "extracting 'Config'" '(2396 characters)'
if test -f 'Config'
then
       echo shar: "will not over-write existing file 'Config'"
else
cat << \SHAR_EOF > 'Config'
#!/bin/csh -f
#
#	$Header: Config,v 1.3 88/12/05 19:45:39 daniel grabchars_1_3 $
#
#	Config - set up Makefile for grabchars
#
#	Dan Smith (daniel@island.uu.net), November 1988
#
#	Config file for grabchars... must be csh, no attempt
#	made to be eunice (i.e. echo " ") compatible...
#
clear
cat << GUMBY

	Config for grabchars

	This csh script will figure out a few things about your
system, and then will run a "make clean", a "make depend", and
a "make release".  You should then try out grabchars, and, once assured
that it's behaving itself, you should run a "make install"

GUMBY
echo -n 'press return to start...'
set ignore=$<

#	figure out where to put this when the user types
#	"make install"...we'll try in three likely places...

foreach try_bin (/usr/local{/bin,} /usr/public/bin)
	echo -n looking at $try_bin...
	if (-w $try_bin) then
		set bin_dir=$try_bin && echo yep... && break
	endif
	echo ""
end

#	figure out where to put the man pages when the user
#	"make install"...a few places come to mind...

foreach try_man (/usr/{,local/,public/}man/man1)
	echo -n looking at $try_man...
	if (-w $try_man) then
		set man_dir=$try_man && echo yep... && break
	endif
	echo ""
end

if ($?bin_dir) then
	echo BIN_DIR looks like it will be $bin_dir
else
	echo BIN_DIR is unknown...check the Makefile when it comes up...
	set bin_dir=""
endif

if ($?man_dir) then
	echo MAN_DIR looks like it will be $man_dir
else
	echo MAN_DIR is unknown...check the Makefile when it comes up...
	set man_dir=""
endif

set myedit=vi
if ($?EDITOR) then
	set myedit=$EDITOR
endif

oh_really:
	if (! -e $myedit) then
		echo -n "I don't see a $myedit...where is your editor? "
		set myedit=$<
		echo ok...
	endif

echo editor of choice seems to be \"$myedit\"...

if  (-e Makefile) mv Makefile{,.old} >& /dev/null

echo making makefile...
sed	-e "s+^BIN_DIR.*+BIN_DIR = $bin_dir+" \
	-e "s+^MAN_DIR.*+MAN_DIR = $man_dir+" \
	-e "s+^EDITOR.*+EDITOR = $myedit+" \
	< Makefile.dist > Makefile

echo going to $myedit with Makefile for you to check out..
echo -n press return...
set ignore=$<
$myedit Makefile || \
	echo where is your editor\?\!\!  && goto oh_really

echo " "
echo clean up any residue from before, doing a \"make clean\"
sleep 2
make clean

echo " "
echo going to run a \"make depend\"...
sleep 2
make depend

echo " "
echo ok, let\'s try to make this with \"make release\"
sleep 2
make release
SHAR_EOF
chmod +x 'Config'
fi
echo shar: "extracting 'TODO'" '(403 characters)'
if test -f 'TODO'
then
       echo shar: "will not over-write existing file 'TODO'"
else
cat << \SHAR_EOF > 'TODO'
TODO file...

	add SYS V code...

	add support for arrow keys handle control characters better...

	I have the code for this in my "phonemail" program (to be
	posted soon).  I'm not sure what would constitute reasonable
	return values (have negative values? values greater than 128?)
	Is there a canonical return value list for keyboard events?

	stronger erase/kill processing...

	Thatzit for now...

SHAR_EOF
fi
echo shar: "extracting 'Makefile.dist'" '(2088 characters)'
if test -f 'Makefile.dist'
then
       echo shar: "will not over-write existing file 'Makefile.dist'"
else
cat << \SHAR_EOF > 'Makefile.dist'
#	$Header: Makefile.dist,v 1.3 88/12/05 19:45:47 daniel grabchars_1_3 $
#
#       Makefile for grabchars
#
#	Dan Smith (daniel@island.uu.net), November 1988
#
SRCS =	globals.c grabchars.c sys.c
OBJS =	globals.o grabchars.o sys.o
HDRS =	grabchars.h
MAN_PAGE = grabchars.1
OTHERS = README Config TODO Makefile.dist demo
ALL_TEXT = $(OTHERS) $(MAN_PAGE) $(SRCS) $(HDRS)

#	where to put things...
BIN_DIR =
MAN_DIR =

#	for rcs...
STATE = grabchars_1_3
VERSION = 1.3

#	change to suit...
PRINTER = -Pislandlw
EDITOR = /usr/ucb/vi

#	if you have this, check the path.  If you don't, leave it alone...
MKID = mkid
SHAR = shar -v

CC = cc
CFLAGS = -g

#	defines...
#
#	BSD is mandatory for now...if you patch for SYS V please
#	#ifdef for "SYS_V" in init_term (), handle_erase (),
#	and lets_go ()...
#
#	DV_ERASE is my friends stab at putting erase/kill processing
#	in this.  I haven't thoroughly tested it (-E option)
DEFS = -DBSD -DDV_ERASE

#	force this, leave csh out of it...
SHELL=/bin/sh

.c.o:
	$(CC) -c $(CFLAGS) $(DEFS) $*.c

all: grabchars

release:
	@make CFLAGS=-O all

grabchars: $(OBJS)
	- mv grabchars grabchars.old 2>/dev/null
	@echo loading...
	$(CC) $(CFLAGS) $(OBJS)  -o grabchars
	@echo done...

install: release
	- cp grabchars $(BIN_DIR)
	- cp grabchars.1 $(MAN_DIR) && echo "preparing man page" \
	&& man grabchars
 
id: $(SRCS) $(HDRS)
	- $(MKID) $(SRCS) $(HDRS) || echo $(MKID) not available...

e: $(SRCS) $(HDRS)
	$(EDITOR) $(SRCS) $(HDRS) Makefile

pgrind:
	@echo pgrinding out sources...
	lpq $(PRINTER)
	pgrind $(SRCS) $(HDRS) Makefile

rcs:
	@echo checking grabchars in to RCS...
	ci -s$(STATE) -r$(VERSION) -f -u $(ALL_TEXT)

shar:
	@echo bundling up grabchars for transit...
	$(SHAR) $(ALL_TEXT) > grabchars.shar

clean: id tags
	- touch $(OBJS) grabchars
	- /bin/rm $(OBJS) grabchars

depend: 
	@echo making dependencies...
	sed -n '1,/^#	lines after this point/p' Makefile >.depends &&\
	cc -M $(SRCS) | tee /dev/tty >> .depends && mv .depends Makefile

tags: $(SRCS)
	ctags $(SRCS)

#       lines after this point produced with cc -M, leave this line here
SHAR_EOF
fi
echo shar: "extracting 'demo'" '(1881 characters)'
if test -f 'demo'
then
       echo shar: "will not over-write existing file 'demo'"
else
cat << \SHAR_EOF > 'demo'
#!/bin/csh -f
clear
cat << GUMBY

	Grabchars demo...

	get one character with "grabchars"
GUMBY
grabchars
echo " status returned was $status"

cat << POKEY

	grab a vowel with "grabchars -caeiou"

	Type something that isn't a vowel at first...

POKEY
grabchars -caeiou
echo " status returned was $status"

cat << WILMA

	prompt the user  with "grabchars -p 'give me any character >> '"
WILMA
grabchars -p 'give me any character >> '
echo " status returned was $status"

cat << FRED

	prompt through stderr  with "grabchars -q 'give me any character >> '",
so that we can set the variable "user_char"...
FRED
set user_char=`grabchars -q 'give me any character >> '`
echo " status returned was $status"
echo '$user_char = '$user_char

cat << BETTY

	enter three characters...  "grabchars -n3"

BETTY
grabchars -n3
echo " status returned was $status"

cat <<  BARNEY

	enter 10 characters within 3 seconds... "grabchars -n10 -t3"


BARNEY
grabchars -n10 -t3
set really_typed=$status
if ($really_typed == 10) then
	echo 'hey\! you got 10\!?'
else
	echo "    $really_typed returned...means that grabchars timed out..."
endif

def_test:
cat << RADRED

	let the user pick a default...
	using "grabchars -d yes -p 'just hit return '...

RADRED
#	the '-f' here is used to flush any previous input...
grabchars -f -d yes -p 'just hit return '
if ($status != 3) then
	echo whoops\!  Let\'s try that again...
	goto def_test
endif

cat << MUSKRAT

	same idea, but let this timeout in four seconds...
	using "grabchars -f -d always -t 4"

	(don't type anything...)

MUSKRAT
grabchars -f -d always -t 4

cat << PEBBLES


	The last one...  get two numbers with a ten second timeout...
	trying "grabchars -c 0123456789 -n2 -t10 -p 'give me 2 numbers >> '

PEBBLES
grabchars -c 0123456789 -n2 -t10 -p 'give me 2 numbers >> '
echo " status returned was $status"
echo ""
echo test/demo done...enjoy\!
SHAR_EOF
chmod +x 'demo'
fi
echo shar: "extracting 'grabchars.1'" '(4786 characters)'
if test -f 'grabchars.1'
then
       echo shar: "will not over-write existing file 'grabchars.1'"
else
cat << \SHAR_EOF > 'grabchars.1'
''' Man page for grabchars, uses Larry Wall's "patch" man page as
''' a template.
.de Sh
.br
.ne 5
.PP
\fB\\$1\fR
.PP
..
.de Sp
.if t .sp .5v
.if n .sp
..
'''
'''     Set up \*(-- to give an unbreakable dash;
'''     string Tr holds user defined translation string.
'''     Bell System Logo is used as a dummy character.
'''
.ie n \{\
.tr \(bs-\*(Tr
.ds -- \(bs-
.if (\n(.H=4u)&(1m=24u) .ds -- \(bs\h'-12u'\(bs\h'-12u'-\" diablo 10 pitch
.if (\n(.H=4u)&(1m=20u) .ds -- \(bs\h'-12u'\(bs\h'-8u'-\" diablo 12 pitch
.ds L" ""
.ds R" ""
.ds L' '
.ds R' '
'br\}
.el\{\
.ds -- \(em\|
.tr \*(Tr
.ds L" ``
.ds R" ''
.ds L' `
.ds R' '
'br\}
.TH GRABCHARS 1 LOCAL
.SH NAME
grabchars - get keystrokes directly from user
.SH SYNOPSIS
.B grabchars
[options]
.SH DESCRIPTION
\fBGrabchars\fP gets characters from the user as they are
typed in, without having to wait for the return key to
be pressed.  Among other things, this allows shell scripts
to be written with highly interactive menus.
.PP
By default,
.I grabchars
will obtain one character from stdin, echo that character to stdout,
and return with a status of one; meaning one character read.
.TP 5
.B \-b
Both
.I stdout
and
.I stderr
are used for output.  This is useful for setting a variable in
a shell script and echoing a keystroke to the screen at the
same time.
.TP 5
.B \-c<valid characters>
Only characters in
.I <valid characters>
are accepted.  Regular expressions such as [a-z]
may be used to specify ranges.  All other characters are ignored.
.TP 5
.B \-d<char(s)>
Default char or string to output if the user hits
.B RETURN
or lets
.B grabchars
timeout.  The status that is returned is the same as if the user had
typed in the character or string, so this option may be used with
the
.B \-s
(silent) flag.
.TP 5
.B \-e
Output goes to
.I stderr
rather than
.I stdout.
.TP 5
.B \-f
Flush any previous input.  By default,
.I grabchars
will see any characters present in
.I stdin,
which allows for some typeahead in shell scripts.
.TP 5
.B \-h
Help/usage screen.
.TP 5
.B \-p<prompt>
Sets up a prompt for the user.  See
.I EXAMPLES.
.TP 5
.B \-q<prompt>
Sets up a prompt for the user, except it is printed to
.I stderr
rather than
.I stdout.
.TP 5
.B \-r
The
.B RETURN
key exits.  Use this with the -n option to allow for variable
numbers of characters to be typed in.
.TP 5
.B \-n<number>
Number of characters to read.  By default,
.I grabchars
looks for one character.
.TP 5
.B \-s
Silent.  Do not output anything. Just return a status.
.TP 5
.B \-t<seconds>
Time to allow the user to respond.  By default, the user
can take as long as he or she wants to.  The timeout option allows
you to write shell scripts where you can offer some assistance
if it's obvious that the user might be stuck.
.TP 5
.B \-E
Erase/kill processing is done.  You have use of the keys (usually
DELETE and ^U or ^X) that you would normally have from the
shell for deleting characters.  This is useful with the
.B \-n
option, where many characters are being typed in.  This code hasn't
been thoroughly tested.
.SH EXAMPLES
.TP 5
.B grabchars
gets one keystroke
.TP 5
.B grabchars \-caeiou
get one of the vowels
.TP 5
.B grabchars -c i
get the letter 'i'
.TP 5
.B grabchars '\-penter a letter '
print the prompt "enter a letter "
.TP 5
.B grabchars '\-qenter a letter '
print the prompt ('q' for question) "enter a letter " through
.I stderr.
.TP 5
.B grabchars \-n4
get four characters.
.TP 5
.B grabchars \-d a
If the first character typed is a
.B RETURN,
pretend it was an 'a'.
.TP 5
.B grabchars \-d gumby
If the first character typed is a
.B RETURN,
pretend that the user typed in "gumby".
.TP 5
.B grabchars \-r
The
.B RETURN
key will exit
.I grabchars.  You would use this with the
.B -n
option, so that variable numbers of characters may be entered.
.TP
.B grabchars \-n 4 \-r \-t 10
Accept up to four characters, or exit when
.B RETURN
is hit, or exit when 10 seconds have elapsed.
.TP 5
.B grabchars \-t2
timeout after two seconds.
.TP 5
.B grabchars \-d gumby \-t2
If the first character typed is a
.B RETURN,
or if two seconds have gone by,
pretend that the user typed in "gumby".
.TP 5
.B grabchars \-n3 \-p 'initials: '
print a prompt and grab three characters.
.TP 5
.B grabchars \-c 0123456789 \-n2 \-t10
get two numbers with a ten second timeout.
.PP
note that arguments like "-n4" or "-n 4" are handled the same way
.SH SEE ALSO
csh(1) and sh(1)
for syntax of
.I csh
and
.I sh
scripts, respectively.
See "The Unix Csh Field Guide", by Gail and Paul Anderson (Prentice Hall),
for an excellent tour of csh and good examples of writing csh scripts.
.SH DIAGNOSTICS
.I
Grabchars
returns
.B \-2
if it times out, or
.B \-1
if it gives a usage statement.  Otherwise, it
returns the number of characters successfully read.
.SH AUTHOR
.nf
Dan Smith (daniel@island.uu.net or {ucbvax!ucbcad,well,sun}!island!daniel)
SHAR_EOF
fi
echo shar: "extracting 'globals.c'" '(1778 characters)'
if test -f 'globals.c'
then
       echo shar: "will not over-write existing file 'globals.c'"
else
cat << \SHAR_EOF > 'globals.c'
/*
**	$Header: globals.c,v 1.3 88/12/05 19:46:01 daniel grabchars_1_3 $
**
**	globals.c - declare global variables for grabchars
**
**	Dan Smith (daniel@island.uu.net), November 29, 1988
*/
#include <stdio.h>
#include "grabchars.h"	/* where we typedef FLAGS */

FILE *outfile, *otherout;
FLAG *flags;

int exit_stat;
char valid_chars[128], default_string[128];

#ifdef DV_ERASE
char *erase_buf;	/* DV: Malloc'ed later */
#endif

/*
**	this gets in the way sitting in grabchars.c, so I moved it here.
**	If you add anything, keep in mind that you're trying to squeeze
**	everything onto a 24 line window/terminal... The "-h" doc
**	is just a convenience, any option that grabchars doesn't already
**	use will give the help screen...
*/
char *usage_statement[] = {
"usage: grabchars		gets one keystroke",
"	-b			output to stdout and stderr",
"	-c<valid characters>	only <valid chars> are returned",
"	-d<char(s)>		default char or string to return",
"	-e			output to stderr instead of stdout",
"	-f			flush any previous input before reading",
"	-h			help screen",
"	-n<number>		number of characters to read",
"	-p<prompt>		prompt to help user",
"	-q<prompt>		prompt to help user (through stderr)",
"	-r			RETURN key exits (use with -n)",
"	-s			silent, just return status",
"	-t<seconds>		timeout after <seconds>",
"	-E			honor erase/kill characters", /* DV */
"examples: (values to arguments can be in the same word or the next one)",
"grabchars -c aeiou		get one of the vowels",
"grabchars -c i			get the letter 'i'",
"grabchars '-penter a letter '	print the prompt \"enter a letter \"",
"grabchars -n4			get four characters",
"grabchars -t2			timeout after two seconds",
" ",
"print a prompt and grab three characters...",
"grabchars -p 'enter three characters >> ' -n 3",
0
};
SHAR_EOF
fi
echo shar: "extracting 'grabchars.c'" '(8500 characters)'
if test -f 'grabchars.c'
then
       echo shar: "will not over-write existing file 'grabchars.c'"
else
cat << \SHAR_EOF > 'grabchars.c'
/*
**	$Header: grabchars.c,v 1.3 88/12/05 19:46:06 daniel grabchars_1_3 $
**
**	grabchars.c	- get characters directly from the user
**
**	Dan Smith (daniel@island.uu.net), October 23, 1988
**
**	This program grabs characters from the user as they are
**	typed in, without having to wait for the return key to
**	be pressed.  Among other things, this allows shell scripts
**	to be written with highly interactive menus...
**
**	[to jump right to the code, search for "start of grabchars"]
**
**	Usage rundown:
**
**	grabchars			gets one keystroke
**	grabchars -b			output to stdout and stderr
**	grabchars -c<valid characters>  only <valid chars> are returned
**	grabchars -d<char(s)>		default char or string to return
**	grabchars -e			output to stderr instead of stdout
**	grabchars -f			flush any previous input
**	grabchars -h			help screen
**	grabchars -n<number>		number of characters to read
**	grabchars -p<prompt>		prompt to help user
**	grabchars -q<prompt>		prompt to help user (through stderr)
**	grabchars -r			RETURN key exits (use with -n)
**	grabchars -s			silent, just return status
**	grabchars -t<seconds>		timeout after <seconds>
**	grabchars -E			erase/kill character processing
**
**	examples: (values to arguments can be in the same word or the next one)
**
**	grabchars -caeiou	 or
**	grabchars -c aeiou		get one of the vowels
**	grabchars -c i			get the letter 'i'
**	grabchars '-penter a letter '	print the prompt "enter a letter "
**	grabchars '-qenter a letter '	print the prompt ('q' for question)
**					"enter a letter " through stderr...
**	grabchars -n4			get four characters
**	grabchars -t2			timeout after two seconds
**
**	print a prompt and grab three characters...
**	grabchars -p 'enter three characters >> ' -n 3
**
**	get two numbers with a ten second timeout...
**	grabchars -c 0123456789 -n2 -t10
**
**	note that arguments like "-n4" or "-n 4" are handled the same way
**
**	History:
**	
**	Oct 1988: versions 1.0 - 1.1
**
**	November 6, 1988 (1.15)
**	added -f flag to flush input, default is to use	TIOCSETN instead
**	of TIOCSETP
**
**	November 22, 1988 (1.16)
**	added -d flag for a default character or string to use if the
**	user hits return first thing or times out.  handle_default ()
**	was added at this time
**
**	November 23, 1988 (1.19)
**	added -r flag to exit when RETURN is hit.  This was suggested by
**	David Vezie.
**
**	November 29, 1988 (1.2)
**	Disaster strikes...I was updating Makefile.dist, and copied SRCS
**	to OBJS, and forgot to change grabchars.c to grabchars.o, and
**	then (after Config) did a "make clean"!  I realized that I did not
**	have a backup, but fortunately had David Vezie's hack of grabchars.c
**	from a few days ago (he had added erase/line kill character processing)
**	...moral: use RCS, check your Makefiles!  Used this as an opportunity
**	to split things up into grabchars.h, globals.c,	and sys.c
**	Got -c to handle ranges (via re_comp() and re_exec())
*/

/*	start of grabchars... */
#include <stdio.h>
#include <signal.h>
#include "grabchars.h"

/*	see globals.c */
extern FILE *outfile, *otherout;
extern FLAG *flags;
extern int exit_stat;
extern char *usage_statement[];

/*
**	David Vezie (island!r2d2!dv) took a great shot at putting in
**	erase/kill processing.  I need to test this some more, and I'll
**	most likely change it a bit.  I put DV_ERASE in the Makefile
**	as a default; take it out if it misbehaves :-)
*/
#ifdef DV_ERASE
extern char *erase_buf;	/* DV: Malloc'ed later */
#endif

main (argc, argv)
int argc;
register char **argv;
{
	/* two signal/wrapup handling routines in sys.c */
	int lets_go (), overtime ();

	/* for -d option */
	void handle_default ();

	/* for getopt () */
	extern int optind, opterr;
	extern char *optarg;
	char comarg;

	int how_many = 1;	/* how many chars to read */
	int num_read;		/* how many we have read... */
	int timeout;		/* and an optional time to do it in... */
	int i;			/* for usage_statement if we need it.. */

	/*
	**	re_comp (), re_exec () let us do things
	**	like "grabchars -c '[a-d]'" or "grabchars -c '[^a-z]'"...
	*/
	char *re_comp (), *re_error;
	extern char valid_chars[128], default_string[128];
	char ch, check_str[2];

	alarm (0);
	opterr = 0;
	exit_stat = -1;	/* if we're interrupted, exit with this status */

	outfile = stdout;
	otherout = stderr;
	if ((flags = (FLAG *) malloc (sizeof (FLAG))) == (FLAG *) NULL) {
		fprintf (stderr, "can't find enough memory...bye!\n");
		exit (exit_stat);	/* we don't need lets_go () for this */
	}

	init_flags ();
	init_signal ();

	while ((comarg = getopt (argc, argv,  "befrsEc:d:n:p:q:t:")) != EOF) {
		switch (comarg) {
			case 'b':
				flags->both = 1;
				break;
			case 'c':
				flags->check = 1;
				strcpy (valid_chars, optarg);
				if (strlen (optarg) == 0) {
					fprintf (stderr, "-c option: must have at least one valid character\n");
					exit (-1);
				}
				/*
				** most of the time, grabchars can be
				** called safely with things like
				** "a-z", because we can check to
				** see if we need to add brackets...
				*/
				if (valid_chars[0] != '[' && 
				valid_chars[strlen (valid_chars) - 1] != ']')
					sprintf (valid_chars, "%c%s%c",
						'[', optarg, ']');

				if ((re_error = re_comp (valid_chars))
								!= NULL) {
					fprintf (stderr,
						"-c option: %s\n", re_error);
					exit (-1);
				}
				break;
			case 'd':
				flags->dflt = 1;
				strcpy (default_string, optarg);
				if (strlen (optarg) == 0) {
				fprintf (stderr, "-d option: must have at least one character for default\n");
					exit (-1);
				}
				break;
			case 'e':
				outfile = stderr;
				otherout = stdout;
				break;
			case 'f':
				flags->flush = 1;
				break;
			case 'n':
				how_many = atoi (optarg);
				if (how_many <= 0) {
					fprintf (stderr, "-n option: number of characters to read must be greater than zero\n");
					exit (-1);
				}
				break;
			case 'p':
				fprintf (stdout, "%s", optarg);
				break;
			case 'q':
				fprintf (stderr, "%s", optarg);
				break;
			case 'r':
				flags->ret_key = 1;
				break;
			case 's':
				flags->silent = 1;
				break;
			case 't':
				timeout = atoi (optarg);
				if (timeout <= 0) {
					fprintf (stderr, "-t option: number of seconds to timeout must be greater than zero\n");
					exit (-1);
				}

				/*
				** we must have some valid time >0 seconds to
				** get here, so we'll set an alarm...
				*/
				signal (SIGALRM, overtime);
				alarm ((unsigned int) timeout);
				break;
			case 'E':	/* DV: honor erase/kill flag */
#ifdef DV_ERASE
				flags->erase = 1;
#else
				fprintf (stderr, "-E is disabled\n");
				exit (-1);
#endif
				break;

				/*
				** I bet I could leave out "default", but
				** I also bet that all getopt () routines
				** are not created equal, so in it stays!
				*/
			case '?':
			default:
				i = 0;
				while (usage_statement[i])
					puts (usage_statement[i++]);
				exit (-1);
		}
	}

	/* we're still here, really running...now change the tty... */
	init_term ();

#ifdef DV_ERASE
	/* DV: malloc (okay, well calloc) space for the erase buffer */
	if (flags->erase) {
		/* We can't do it up in the switch, because we don't know
		** how many is how_many
		*/
		erase_buf = (char *)calloc (1, how_many);
		if (erase_buf == NULL) {
			fprintf (stderr,
			"Error:  Couldn't malloc space for erase buffer\n");
			exit_stat = -1;
			lets_go();
		}
	}
#endif

	for (num_read = 0; num_read < how_many; num_read++) {
		ch = getchar ();

		/* use default_string, this does *not* return */
		if (ch == '\n' && flags->dflt && num_read == 0)
			handle_default ();

		/*
		** set by -r, a RETURN key gets us out (use with -n)
		** suggested by David Vezie
		*/
		if (ch == '\n' && flags->ret_key)
			break;

		/*	filter chars... */
		if (flags->check) {
			sprintf (check_str, "%c", ch);
			if (re_exec (check_str) != 1) {
				num_read--;
				continue;
			}
		}

		/*
		** if we're just looking for a return status
		** then have flags->silent set (-s)
		**
		** DV: Also, we don't want to output yet,
		** if we're processing erase/kill charfacters.
		*/
#ifdef DV_ERASE
		if (! flags->silent && ! flags->erase) {
#else
		if (! flags->silent) { 
#endif
			putc (ch, outfile);
			if (flags->both)
				putc (ch, otherout);
		}

#ifdef DV_ERASE
		if (flags->erase)
			handle_erase (ch, &num_read);
#endif
	}

#ifdef DV_ERASE
	if (flags->erase) {
		fprintf (outfile, "%s", erase_buf);
		if (flags->both)
			fprintf (otherout, "%s", erase_buf);
	}
#endif

	exit_stat = num_read;
	lets_go ();
}
SHAR_EOF
fi
echo shar: "extracting 'sys.c'" '(4386 characters)'
if test -f 'sys.c'
then
       echo shar: "will not over-write existing file 'sys.c'"
else
cat << \SHAR_EOF > 'sys.c'
/*
**	$Header: sys.c,v 1.3 88/12/05 19:46:14 daniel grabchars_1_3 $
**
**	sys.c - terminal routines for grabchars
**
**	Dan Smith (daniel@island.uu.net), November 29, 1988
**
**	History:
**
**	December 2, 1988
**	made #ifdefs for DV_ERASE and BSD, wrote notes in handle_erase ()
**	for changes and improvements
*/

#include <stdio.h>
#include <sgtty.h>
#include <signal.h>
#include "grabchars.h"

struct sgttyb orig, new;

/* all declared in globals.c */
extern FILE *outfile, *otherout;
extern FLAG *flags;
extern int exit_stat;
extern char default_string[128];

#ifdef DV_ERASE
extern char *erase_buf;
#endif

/* initialize global flags */
init_flags ()
{
	flags->both = 0;
	flags->check = 0;
	flags->dflt = 0;
	flags->flush = 0;
	flags->ret_key = 0;
	flags->silent = 0;
	flags->erase = 0;
}

/*
**	initialize tty
**
**	this really needs SYS V code...any patchers out there?
*/
init_term ()
{
	/*	play havoc with the terminal :-) */

#ifdef BSD
	ioctl (0, TIOCGETP, &orig);
	new = orig;
	new.sg_flags &= ~ECHO;
	new.sg_flags |= CBREAK;

	(flags->flush) ? ioctl (0, TIOCSETP, &new) :	/* to flush... */
		ioctl (0, TIOCSETN, &new);		/* ...or not to flush */
#endif
}

/* handle the outside world */
init_signal ()
{
	int lets_go ();

	signal (SIGINT, lets_go);
	signal (SIGTSTP, lets_go);
	signal (SIGQUIT, lets_go);
}

/*
**	something's up with the user...give a useful exit status so
**	we can ask things like "do you need help?"
*/
int overtime ()
{
	int lets_go ();
	void handle_default ();

	/* does not return */
	if (exit_stat == -1 && flags->dflt)
		handle_default ();

	exit_stat = -2;
	lets_go ();
}

/*
**	the default_flag is set, and the user either typed a return
**	or timed out.  This routine does not return.
*/
void handle_default ()
{
	int lets_go ();

	if (! flags->silent) {
		fputs (default_string, outfile);
		if (flags->both)
			fputs (default_string, otherout);
	}
	exit_stat = strlen (default_string);
	lets_go ();
}

/*	clean up and get out of here... */
int lets_go ()
{
#ifdef BSD
	ioctl (0, TIOCSETP, &orig);
#endif
	exit (exit_stat);
}

#ifdef DV_ERASE

/*
**	December 2, 1988
**	Bucko's (daniel) in progress notes for changing this...
**
**	first time through processing should be called as its' own
**	function from the -E case in the main (getopt ()) switch...
**
**	stdout and stderr should never be affected by any erasures...
**	(they probably are not now, I haven't thoroughly tested this...)
**
**	I need to drag in my word erase routine; never can tell how
**	long some people are going to want their lines with -n! :-)
**
**	If someone wants a control char (via literal (^V)), we should
**	give it to them...this would be more compatible with $< (csh)
**	and read (sh)... grabchars almost completely replaces these
**	now.
**
**	we can also be sensitive to pipe/no pipe via isatty ()...
*/

/*	DV: handle erase characters, kill characters, etc. */
handle_erase (ch, cnt)
char ch;
int *cnt;
{
	static char first = 1;
	static char erasec, killc, werasec, lnextc, rprntc;
	static char lnextflg = 0;
	static char *cp;
	static FILE *tty;
	int i;

	if (first) {
		/* initialize static things */
		struct sgttyb sb;
		struct ltchars ltc;

		first = 0;
		cp = erase_buf;
		tty = fopen ("/dev/tty", "w");

		/* . this isn't going to do... what if -e is set?...dan */
		if (tty == NULL)
			tty = stderr;
		ioctl (0, TIOCGETP, &sb);
		ioctl (0, TIOCGLTC, &ltc);
		erasec = sb.sg_erase;
		killc = sb.sg_kill;
		werasec = ltc.t_werasc;
		lnextc = ltc.t_lnextc;
		rprntc = ltc.t_rprntc;
	}

	if (lnextflg) {
		ch |= 0x80;
		lnextflg = 0;
	}
	(*cnt) --;
	if (ch == erasec) {
		if (*cnt < 0)
			return;
		fprintf (tty, "\b \b");
		(*cnt) --;
		*--cp = 0;
	} else if (ch == killc) {
		while (*cnt >= 0) {
			fprintf (tty, "\b \b");
			(*cnt) --;
			*--cp = 0;
		}
	} else if (ch == werasec) {
		if (*cnt < 0)
			return;
		while ((cp[-1] == ' ' || cp[-1] == '\t') && (*cnt) >= 0) {
			fprintf (tty, "\b");
			(*cnt) --;
			*--cp = 0;
		}
		while (cp[-1] != ' ' && cp[-1] != '\t' && (*cnt) >= 0) {
			fprintf (tty, "\b \b");
			(*cnt) --;
			*--cp = 0;
		}
	} else if (ch == lnextc) {
		lnextflg = 1;
		fprintf (tty, "^\b");
	} else if (ch == rprntc) {
		for (i = strlen (erase_buf); i > 0; i--)
			putc ('\b', tty);
		fprintf (tty, "%s", erase_buf);
	} else {
		ch &= 0x7f;
		fprintf (tty, "%c", ch);
		*cp++ = ch;
		(*cnt) ++;
	}
	fflush (tty);
	return;
}
#endif
SHAR_EOF
fi
echo shar: "extracting 'grabchars.h'" '(553 characters)'
if test -f 'grabchars.h'
then
       echo shar: "will not over-write existing file 'grabchars.h'"
else
cat << \SHAR_EOF > 'grabchars.h'
/*
**	$Header: grabchars.h,v 1.3 88/12/05 19:46:20 daniel grabchars_1_3 $
**
**	grabchars.h - setup for grabchars
**
**	Dan Smith (daniel@island.uu.net), November 29, 1988
*/

struct flag_type {
	int both;	/* output to stdout and stderr */
	int check;	/* filter input */
	int dflt;	/* use following char or string as default */
	int flush;	/* if set flush input buffer */
	int ret_key;	/* RETURN key exits when set */
	int silent;	/* be quiet, just return a status */
	int erase;	/* DV: honor erase/kill characters */
};

typedef struct flag_type FLAG;
SHAR_EOF
fi
exit 0
#	End of shell archive