A Form Letter Facility for E-Mail
Robert Ward and Scott Graham
Whether electronic or paper-bound, forms contribute
a useful regularity
and structure to office communications. Forms are especially
useful
to those who must supply detailed information about
an unfamiliar
subject or event. Thus, in a traditional paper-based
office, when
the bookkeeper is impressed into phone duty because
the phone staff
is out with the flu, a well-designed order form can
save the day.
By itemizing and structuring all the details that must
be captured
to complete an order, the form improves the bookkeeper's
chance of
getting the order right. Similarly, when users are faced
with reporting
a technical problem or requesting a technical service,
a well-designed
electronic form can help them capture all the details
that the system
administrator needs.
This article presents a general purpose form-letter
program. With
this system (built on top of the standard mail program),
administrators
can create easy-to-use electronic forms to facilitate
common user
messages -- such as bug reports, resource requests,
and account
changes.
While the frmlet package is useful to administrators,
it is
entirely general and user-maintained. Users create and
maintain their
own form templates. Each new or modified template is
automatically
checked for minimal correctness, and, if it passes,
is automatically
added to the list of forms available for use.
To send a form, the user selects a form from a list
of available forms
(templates), fills in the blanks (using his or her standard
editor),
and exits. The frmlet application then mails the form
to the
"owner," whose name is embedded in the form
template.
While useful in its own right, frmlet is also a nice
illustration
of directory and menuing conventions and techniques
that are widely
used in various UNIX applications.
The Application
The forms package is two top-level scripts designed
to be embedded
in a menuing system. The frmlet script (Listing 1) manages
a session in which a user fills in and mails an existing
form. The
maintfrm script (Listing 2) manages a maintenance session,
during which a user could create, edit, or delete a
form template.
The frmlet script takes a directory as an argument.
This directory
should contain a set of related form templates. Passing
the directory
as a parameter allows the same script to be used for
several different
categories of forms. The marketing department, the shipping
department,
and the customer support department can each have their
own "private"
set of forms. Widely used forms can be in a "enterprise-wide"
directory. Thus the script begins by testing for this
parameter and
making the selected directory the current directory.
(The LOCALCMDS
and MAILCMD variables simply isolate certain system-dependent
details.)
The frmlet script then calls a utility script, dirmnu
(Listing 3),
which does all the real work. The dirmnu script
dynamically
builds a menu, based on what it finds in the current
directory, and
returns the file name corresponding to the user's selection
on stdout.
Both top-level scripts invoke dirmnu, which we cover
in more
detail later.
The next line in frmlet
receiver = `fgrep "Send to:" \
$form_file | ....
exemplifies a common UNIX "trick" -- using
grep to extract data marked by some user-created convention.
In this instance, we decided that every template would
contain the
name of the user to whom it would be sent on a line
that begins with
the words "Send to:" (much like the mail header
conventions).
Some of our lp administration scripts assume that lines
containing
an exclamation mark are description lines for use in
a menu. In other
applications, we've used a leading hash mark (#) to
mark description
lines. Whatever the convention, the technique is the
same: keep the
data in the user-maintained file (where it's more apt
to be updated
properly), and extract it based on some simple pattern.
Note that we include head -1 in the pipe to insure that
we
get only the first occurrence of "Send to:"
and then use sed
to remove the mark, leaving just the recipient's name.
The script next performs some integrity checks, and
then invokes the
mailer. The options in this MAILCMD line work for our
system,
but might need to be modified to work on yours. The
code assumes that
the mailer will invoke the editor and can "include"
a file
in the mailer's buffer without modifying the original
file. The options
are:
-s <subject>
-i <include file>
The dirmnu Script
In many ways, dirmnu is the most interesting of the
three scripts.
This utility script builds a picklist from the file
names in a selected
directory, and then returns the filename corresponding
to the user's
selection on stdout. The dirmnu script is invoked with
three
arguments: a title (which will appear at the top of
the picklist),
a prompt (which will appear at the bottom of the picklist),
and a
pattern (which is used to filter out unwanted files
from the picklist).
Normally, scripts write user-directed output to stdout.
This utility,
however, is designed to be "backquoted" in
other scripts,
and thus must return only its result on stdout (which
is implicitly
redirected by the backquoting mechanism). Accordingly,
all user-oriented
output is directed to stderr ( >&2 ). It might
be more technically
correct to direct the output to /dev/tty. Either stream
will
show on the terminal.
After echoing the title to the screen and checking for
an empty directory
(an error condition), dirmnu uses awk and pr to
format a list of files in columns. Our first draft used
a very simple
command in which pr actually did most of the work:
ls $pattern | awk '(print " " $0)' | \
pr -t -n\)2 -i20 -4 >&2h
The -t suppresses leading and trailing linefeeds;
the -n\)2 puts a two-digit number and a parenthesis
in front
of each filename; the -i20 option sets tabstops; and
the -4
option organizes the output as four columns. We wouldn't
have needed
awk at all, except that we wanted a space between the
number
and the filename. The tiny awk script inserts a space
in front
of each filename.
The more complicated version we ultimately used in Listing
3 computes
the number of columns dynamically, based on the length
of the longest
filename. The script pipes ls's output into an awk script
that prints the length of the longest filename. It then
uses expr
to compute the number of columns (all of our terminals
are 80-column
devices). The result is a parameter to the display formatting
pipeline.
After displaying the numbered picklist, dirmnu uses
another
little utility script, getnum (Listing 4), to get the
user's
response. The getnum script takes two numbers and a
prompt.
The numbers represent the range of valid responses.
Like dirmnu,
getnum is designed to be backquoted, and sends all interactive
output to stderr. This utility will loop, continuously
redisplaying
the prompt, until the user enters a valid response.
If the user makes
too many mistakes, the menu will scroll off the top
of the display.
A stronger design would use curser positioning to always
draw the
prompt in the same location.
By using an awk script to check the input range, we
get string-to-integer
conversion, range checking, and type validation all
for the price
of one.
Finally, dirmnu uses the getnum result in a head/tail
pipe to select the line corresponding to the selected
file. This usage
of head and tail is almost a shell script idiom.
This version of dirmnu uses the filename for the corresponding
menu entry. For some applications (and users), filenames
are just
too cryptic. If you expand your file convention to include
a pattern
that indicates an embedded file description, you can
make dirmnu
generate menus consisting of arbitrary descriptions.
For example
grep "DESC:" $pattern | sed "s/DESC:/ /" | \
pr -t -n\)2 -i" "1
should produce a one-column menu where each item is
a
description line extracted from the corresponding file.
The code also
depends on another small utility script, askyn (Listing
6),
which prompts the user for a response and repeats until
the user supplies
one.
The Maintenance Script
Though long, maintfrm (Listing 2) is quite straightforward.
The option to this script selects one of three modes:
create, edit,
or delete. When a template is to be edited, it is first
copied to
a temporary file, and when the modifications are complete
and correct,
the temporary file is copied over the original template
and then removed.
We used a little C utility, tmpname.c (Listing 5), to
generate
a unique name for the temporary file. You could get
a similar result
with
TEMPFILE=FORM$$.
The $$ method, however, isn't guaranteed to produce
a unique file -- tmpname is. For example, if invoked
by
process number 101, this script would unintentionally
overwrite any
template named FORM101. (See Leor Zolman's article in
Sys Admin,
1.4 [Nov/Dec 1992] for more information on tmpname.)
tmpnam
makes use of the standard library function tempnam,
so you
might wish to examine the man page for that.
Like frmlet, this script uses dirmnu to generate a picklist
of existing forms.
Both the create and edit options "syntax check"
the new form
template before installing it. Each option uses grep
to look
for the pattern "Send To:". If this pattern
is missing, the
frmlet script won't know how to address a filled-in
form. It
notifies the user that a "Send To:" is required
and calls
askyn (Listing 6) to see if it should continue.
To ensure that the temporary file gets properly deleted,
this script
traps all significant interrupts.
Summary
While this forms system offers significant benefits
to system administrators,
it is also general enough to be interesting to users.
That utility
is in itself an advantage to the administrator -- if
your users
are already familiar with the forms system, they are
more likely to
use it when a system-related need arises.
In many ways, this version is still a prototype. We've
really only
addressed the skeleton of the application -- the user
interface
issues remain unsolved. How do you make it easy to fill
in the blanks?
How do you partition the forms into related sets? Where
does it fit
into the existing menu system?
Even so, we've installed it as is. In classic testimony
to the effectiveness
of the UNIX utility set, frmlet yields a surprising
amount
of functionality from a tiny amount of code.
About the Authors
Robert Ward is president of R&D Publications and senior
editor of Sys Admin. He is the author of Debugging C, an
introduction to
scientific debugging, and co-author, with William Smith,
of Windows
Custom Controls. He has done consulting work in software
engineering
and data communications and holds a Master's degree
in Computer Science
from the University of Kansas.
Scott Graham received a BS degree in computer science,
computer
engineering, and mathematics from Graceland College
in Lamoni, IA.
He is a graduate student in Computer Science at the
University of Kansas
and is a Programmer/Analyst for R&D Publications.
|