Article 2119 of alt.sources:
Xref: feenix.metronet.com alt.sources:2119
Newsgroups: alt.sources
Path: feenix.metronet.com!news.utdallas.edu!hermes.chpc.utexas.edu!cs.utexas.edu!uunet!spool.mu.edu!agate!headwall.Stanford.EDU!unixhub!jupiter.SLAC.Stanford.EDU!jfm
From:
[email protected] (John F. McGowan)
Subject: junkmail - a Perl script for customized mass e-mailing
Message-ID: <
[email protected]>
Keywords: Perl e-mail custom mailer
Sender:
[email protected]
Organization: Stanford Linear Accelerator Center
Date: Fri, 22 Oct 1993 05:04:00 GMT
Lines: 1637
Below is a shell archive generated using shar containing junkmail, a
Perl script for generating custom mailings, some example files, some
documentation on the program, and a README file. junkmail contains
an embedded manpage following the convention in Programming Perl by
Larry Wall and Randal L. Schwartz.
Despite the title, the Internet does frown on unsolicited mass mailings
particularly for commercial purposes. Please exercise caution in
using junkmail.
Please send comments, bug reports and so forth to
[email protected].
- John McGowan
------------------------------->CUT HERE<-------------------------------
#! /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 shell archive."
# Contents: README junkmail entry template sample.resume sample.text
# manual
# Wrapped by jfm@jupiter on Thu Oct 21 21:54:12 1993
PATH=/bin:/usr/bin:/usr/ucb ; export PATH
if test -f 'README' -a "${1}" != "-c" ; then
echo shar: Will not clobber existing file \"'README'\"
else
echo shar: Extracting \"'README'\" \(4139 characters\)
sed "s/^X//" >'README' <<'END_OF_FILE'
X junkmail: An Automatic E-Mail Message Generator
X by John F. McGowan (
[email protected])
X
X This README note is copyright (C) 1993 by John F. McGowan
X
XIntroduction:
X
X Junkmail is a Perl script to automate sending customized letters
Xto multiple e-mail addresses, referred to as targets. The custom
Xinformation for each target such as name, e-mail address, and so forth
Xis represented as a record in a database. Each database record consists of
Xnamed database fields. Key fields (used to uniquely identify the record) are
Xtagged by a traling asterisk. The fields are ASCII strings. Database records
Xare entered using a data entry form.
X
X Junkmail works from a template file that contains special instructions
Xthat junkmail replaces with dates, the values of database fields, and
Xso forth.
X
X Junkmail can be used to send customized cover letters and resumes
Xto companies that have posted job listings in Internet jobs groups such as
Xmisc.jobs.offered, to send customized mailings to a group of friends or
Xassociates, to send customized mailings to co-workers, to send customized
Xmailings to a group of customers, and so forth.
X
XData Entry Form:
X
X A junkmail data entry form consists of a text file. Each line of
Xthe file is used for entering one database field. The form consists of
Xthe database field name (with trailing asterisk for key fields), a colon,
Xat least one space, followed by the value entered by the user. An editor
Xsuch as vi or emacs is used to edit the form. Junkmail uses the environment
Xvariable EDITOR to set the editor used to edit the form. The form is
Xspecified with the -f <file-spec> flag.
X
X There are other ways to enter data such as the -t <file-spec> text
Xdatabase entry method. However, the data entry form is the easiest.
X
XJunkmail Template File:
X
X A template is a template for a mailing, such as a cover letter
Xand resume. The template tells junkmail how to generate a custom
Xmailing for each target basd on the contents of the database.
XJunkmail interprets certain text strings specially however and
Xsubstitutes appropriate values in there place. $database-field-name
Xis replaced by the value of the database field for that record.
X#macro# indicates a built-in macro or command, such as #date# which is
Xreplaced by today's date. #particle# is replaced by a or an depending
Xon the next word's first letter (consonant or vowel).
X
X#if $field eq /something/
XHello there.
X#endif
X
Xis used to include the body (Hello there.) if the field matches something.
X
X#include "my.propaganda"
X
Xincludes another file in the mailing.
X
XDocumentation:
X
X This README file.
X
X junkmail -h outputs a short help listing the junkmail flags.
X
X junkmail contains documentation in the form of Perl comments and
Xan embedded manpage following the convention in Programming Perl by Larry
XWall and Randall Schwarz. To install the manual page,
X
X cp junkmail junkmail.1
X mv junkmail /your/man/man1 (and)
X setenv MANPATH MANPATH:/your/man (C shell).
X
X junkmail contains embedded nroff commands that cause the Perl
Xcode to be ignored by man.
X
XArchive Contents:
X
X README (this file)
X junkmail - the Perl script itself
X entry - a sample data entry form
X template - a sample template file
X sample.resume - a sample include file used by template
X sample.text - a sample ASCII text database
X manual - Programmer's Manual and Reference
X
XWarning Note:
X
XThe Internet community frowns on mass mailing campaigns. I developed
Xjunkmail to reply to postings in the Usenet jobs newsgroups. These
Xpostings SOLICIT a reply. In general, it is good etiquette to use
Xjunkmail to send letters to people who have solicited a message, to
Xfriends, to coworkers at your organization, and others who will not be
Xoffended by receiving a mailing.
X
XCopyright:
X
Xjunkmail is copyright (C) by John F. McGowan. This software may be
Xmodified and distributed but the copyright notice and the disclaimer
Xmust be retained.
X
XDisclaimer:
X
XThis software is distributed as is. There is no warranty, either
Xexpress or implied, that it will work correctly or as desired. In other
Xwords, use at your own risk.
X
X
X
X
X
X
END_OF_FILE
if test 4139 -ne `wc -c <'README'`; then
echo shar: \"'README'\" unpacked with wrong size!
fi
# end of 'README'
fi
if test -f 'junkmail' -a "${1}" != "-c" ; then
echo shar: Will not clobber existing file \"'junkmail'\"
else
echo shar: Extracting \"'junkmail'\" \(24949 characters\)
sed "s/^X//" >'junkmail' <<'END_OF_FILE'
X#!/usr/local/bin/perl
X'di';
X'ig00';
X#
X# Name: junkmail
X# Date: $Date: 1993/10/22 04:53:40 $
X# Version: $Revision: 1.20 $
X# Author: John F. McGowan ($Author: jfm $)
X#
X# Description: junkmail automates sending custom mailings to
X# multiple e-mail addresses (referred to as targets). The mailings
X# are customized using a database record for each target. The user
X# can specify database fields for information such as the target
X# name, organization, favorite color, political orientation, etc.
X# junkmail expects a database field named 'e-mail' to exist which
X# is the e-mail address of the target.
X#
X# junkmail supports database record entry using a simple user-defined
X# Data Entry Form. It also supports data entry from a text database
X# file using the ASCII quoted field format used by common word
X# processors such as Microsoft Word. junkmail expects key field
X# names to be terminated with an asterisk. The trailing asterisk
X# is treated specially by junkmail.
X#
X# junkmail can save the database entries in the ASCII text file format
X# if desired.
X#
X# junkmail can also save the database in a DBM format file.
X#
X# junkmail is written in Perl (Practical Extraction and Report
X# Language). Perl was written by Larry Wall. Good sources of
X# information on Perl are "Programming Perl" by Larry Wall and
X# Randal L. Schwartz and the comp.lang.perl newsgroup.
X#
X# junkmail comes in a shell archive (junkmail.shar) containing the junkmail
X# source code, a README file of information on junkmail, a sample
X# template file, a sample include file used with the sample
X# template, a sample data entry form, a short Programmer's Manual
X# and reference to assist customizing/upgrading junkmail for your
X# needs, and a sample ASCII text database file. junkmail contains
X# an embedded manpage following the convention in Programming Perl
X# by Larry Wall and Randal L. Schwartz.
X#
X# Revision Record:
X#
X# $Log: junkmail,v $
X# Revision 1.20 1993/10/22 04:53:40 jfm
X# 1. Improve -h help message.
X#
X# Revision 1.19 1993/10/22 04:37:38 jfm
X# 1. Junkmail now prompts for a data entry method is one was not
X# specified with the command line arguments.
X#
X# Revision 1.18 1993/10/22 03:45:25 jfm
X# 1. Remove the -r flag. This is obsolete.
X#
X# Revision 1.17 1993/10/22 03:32:03 jfm
X# *** empty log message ***
X#
X# Revision 1.16 1993/10/20 05:28:12 jfm
X# 1. cleaned up a bit, removed send_letter subroutine
X#
X# Revision 1.15 1993/09/21 19:30:28 jfm
X# #if now should handle key fields (trailing asterisk) correctly
X#
X# Revision 1.14 1993/09/21 18:51:24 jfm
X# 1. Added documentation on template file to manpage
X# 2. Added some protection against using binary file where text file
X# is expected.
X# 3. couple minor changes
X#
X# Revision 1.13 1993/09/16 23:44:54 jfm
X# 1. Add code to check that form file is a text file
X#
X# Revision 1.12 1993/09/16 23:36:31 jfm
X# 1. Added code to check if template file is a text file. Aborts if the
X# template file is binary.
X#
X# Revision 1.11 1993/07/23 20:53:28 jfm
X# 1. Added support for storing records in a DBM database file. Kind of
X# kludgy -- hope to improve.
X#
X# Revision 1.10 1993/06/10 02:28:14 jfm
X# 1. Added code to prompt for name of text database save file if not specified on command line.
X#
X# Revision 1.9 1993/06/03 22:02:25 jfm
X# 1. Added copyright notice to manpage
X#
X# Revision 1.8 1993/05/23 18:23:28 jfm
X# Slight improvement to the manpage at end of script.
X# Note that manpage is accessed by creating a link junkmail.1 in manpages
X# directory to the actual junkmail script file.
X#
X# Revision 1.7 1993/05/23 18:18:36 jfm
X# Added embedded manpage at end of script
X#
X# Revision 1.6 1993/05/22 21:35:24 jfm
X# Try to add a revision record using the $Log: junkmail,v $
X# Revision 1.20 1993/10/22 04:53:40 jfm
X# 1. Improve -h help message.
X#
X# Revision 1.19 1993/10/22 04:37:38 jfm
X# 1. Junkmail now prompts for a data entry method is one was not
X# specified with the command line arguments.
X#
X# Revision 1.18 1993/10/22 03:45:25 jfm
X# 1. Remove the -r flag. This is obsolete.
X#
X# Revision 1.17 1993/10/22 03:32:03 jfm
X# *** empty log message ***
X#
X# Revision 1.16 1993/10/20 05:28:12 jfm
X# 1. cleaned up a bit, removed send_letter subroutine
X#
X# Revision 1.15 1993/09/21 19:30:28 jfm
X# #if now should handle key fields (trailing asterisk) correctly
X#
X# Revision 1.14 1993/09/21 18:51:24 jfm
X# 1. Added documentation on template file to manpage
X# 2. Added some protection against using binary file where text file
X# is expected.
X# 3. couple minor changes
X#
X# Revision 1.13 1993/09/16 23:44:54 jfm
X# 1. Add code to check that form file is a text file
X#
X# Revision 1.12 1993/09/16 23:36:31 jfm
X# 1. Added code to check if template file is a text file. Aborts if the
X# template file is binary.
X#
X# Revision 1.11 1993/07/23 20:53:28 jfm
X# 1. Added support for storing records in a DBM database file. Kind of
X# kludgy -- hope to improve.
X#
X# Revision 1.10 1993/06/10 02:28:14 jfm
X# 1. Added code to prompt for name of text database save file if not specified on command line.
X#
X# Revision 1.9 1993/06/03 22:02:25 jfm
X# 1. Added copyright notice to manpage
X#
X# Revision 1.8 1993/05/23 18:23:28 jfm
X# Slight improvement to the manpage at end of script.
X# Note that manpage is accessed by creating a link junkmail.1 in manpages
X# directory to the actual junkmail script file.
X#
X# Revision 1.7 1993/05/23 18:18:36 jfm
X# Added embedded manpage at end of script
X# keyword
X#
X#
Xrequire "getopts.pl";
X
X$| = 1;
X
X&Getopts('f:s:d:b:t:c:mhc'); # -f -s -d -b -c take argument
X
X# set umask for process so only user can access files created
Xumask 077;
X
X# associative array of built in macros
X# key is macro name; value is perl subroutine to call
X%builtin = (
X 'date', 'GetDate',
X 'particle', 'Particle',
X '$1' , 'Evaluated Wrong', # test entry
X);
X# list of reserved words used by template facility
X%reserved =
X(
X 'include', 'INCLUDE',
X 'if' , 'IF',
X 'endif', 'ENDIF',
X);
X
Xif($ENV{'EDITOR'})
X{
X $myeditor = $ENV{'EDITOR'};
X}
Xelse
X{
X $myeditor = 'emacs';
X}
X
X# handle processing command line arguments below
Xif($opt_h)
X{
Xprint <<'EOF';
X junkmail
X
X Automatically generates a custom mailing and mails this
Xto a list of e-mail addresses, known as targets. Each target is
Xrepresented as a record in a user-defined database which can include
Xvarious information specific to the target.
X
X Command Line Flags:
X
X -h Help (this message)
X -m Automatically mail to targets that
X have a field 'e-mail' in record.
X -d Filename Read text database of addresses from
X Filename.
X -b <file-specification> Use DBM file with database of mailing
X targets.
X -t Filename Generate mailing from template
X contained in Filename.
X -f Filename Use form in Filename to enter data.
X -s Filename Save the database of targets to a File
X Filename in Text Database Format.
X -c User Send copy of each mailing to User.
X
XEOF
Xexit;
X} # end of processing help -h flag
X
Xif(!($opt_f || $opt_d))
X{
X print "You have not specified a data entry method!\nOptions are Data Entry Form data entry (-f flag) (or)\nText Database File Entry (-d flag)\n";
X do { print "Enter Form Entry, Text Database File Entry, or Quit (f/t/q): "; $c = <>; chop $c; } until $c =~ /[FfTtQq]/;
X
X if($c =~ /[Ff]/) # Data Entry Form data entry
X {
X print "Please enter Data Entry Form file specification: ";
X $opt_f = <>;
X chop $opt_f;
X while(!-e $opt_f)
X {
X print "File $opt_f does not exist!\nPlease enter another Data Entry Form file specification: ";
X $opt_f = <>;
X chop $opt_f;
X }
X }
X elsif($c =~ /[Tt]/) # Text Database File data entry
X {
X print "Please enter Text Database file specification: ";
X $opt_d = <>;
X chop $opt_d;
X while(!-e $opt_d)
X {
X print "File $opt_d does not exist!\nPlease enter another Data Entry Form file specification: ";
X $opt_d = <>;
X chop $opt_d;
X }
X }
X else
X {
X warn "Quitting junkmail! Bye!\n";
X exit;
X }
X}
X
Xif($opt_c)
X{
X $user = $opt_c;
X}
Xelse # do not want to send a copy to someone
X{
X $user = ''; # null string
X}
X
Xif($opt_t)
X{
X $tfile = $opt_t; #
X} # end processing template flag -t
Xelse #
X{
X print "You did not specify a template file!\nPlease enter file specification of your junkmail template file: ";
X $tfile = <>;
X chop $tfile;
X}
X
Xwhile(! -e $tfile)
X{
X print "Template file $tfile does not exist!\nDo you wish to enter another template file specification (y/n): ";
X $answer = <>;
X chop $answer;
X if($answer =~ /[Yy]/)
X {
X print "Enter new template file specification: ";
X $tfile = <>;
X chop $tfile;
X }
X else
X {
X exit;
X }
X}
X
X
Xif (-T $tfile) # template file should be text
X{
X open(TEMPLATE,"$tfile"); # open template file
X @template = <TEMPLATE>; # need unchanging master version of template
X close TEMPLATE; # close template file
X}
Xelse # binary file
X{
X die "Template file $tfile is not a text file!";
X}
X
X
Xif($opt_f) # form entry of data
X{
X if( -T $opt_f) # form should be a text file
X {
X $exit = 0; $n = 1;
X system("cp $opt_f /tmp/.form.1.$$");
X system("$myeditor /tmp/.form.1.$$");
X $form[1] = "/tmp/.form.1.$$";
X while($exit == 0)
X {
X do { print "Enter another address(y/n):"; $c = <>; chop $c;} until $c =~ /[YyNn]/;
X
X if($c =~ /[yY]/)
X {
X $n++;
X $form[$n] = "/tmp/.form.$n.$$";
X system("cp $opt_f /tmp/.form.$n.$$");
X system("$myeditor /tmp/.form.$n.$$");
X }
X else
X {
X $exit = 1;
X }
X } # end while loop
X# die "Finished processing forms ... quitting for now\n";
X
X for $i (1 .. $#form)
X {
X open(FORM,"$form[$i]") || die "ABORT: Could not open temporary file for data entry \n"; # open the form entry
X &parse_form(*fields, *mydata);
X &append_database(*fields, *mydata, *database); # add to internal database
X close FORM;
X system("rm $form[$i]"); # delete the entry
X } # close loop over entries
X }
X else # $opt_f is a binary file
X {
X die "Form $opt_f is not a text file!";
X }
X
X} # end if $opt_f
Xelse
X{
X# if form data entry not specified look for a text database file
X if($opt_d)
X {
X open(DBASE,$opt_d) || die "Aborting! Could not open database file ...\n";
X @database = <DBASE>; # read database into database array
X close DBASE;
X
X }
X else
X {
X die "You have not specified a method to enter the target information!\nOptions are either -f <Data Entry Form file-spec> or\n-d <ASCII Text Database File\n";
X
X } # end processing database file entry flag -d
X} # end processing form data entry flag -f
X
Xif($opt_b)
X{
X dbmopen(%theBase,"$opt_b",0666);
X}
Xelse
X{
X print "No DBM database specified! Do you wish to use a DBM database(y/n): ";
X $_ = <>;
X if(/^[Yy](es)?$/)
X {
X print "Enter DBM file-specification (mybase is default): ";
X $_ = <>;
X if(/^$/) # hit return to get default
X {
X dbmopen(%theBase,"mybase",0666);
X }
X else
X {
X chop;
X dbmopen(%theBase,"$_",0666);
X }
X }
X}
X
X#print @database; # output database
X
X# Generate and send letters from contents of database
Xif($_ = $database[0])
X{
X chop; # remove trailing new line
X @keyFields = (); %fieldType = ();
X @valFields = ();
X @fields = split(',');
X foreach $field (@fields) # extract the key fields
X {
X if($field =~ /\*/)
X {
X push(@keyFields,$field);
X $fieldType{$field} = "KEY";
X }
X else
X {
X push(@valFields,$field);
X $fieldType{$field} = "VAL";
X }
X }
X# put a line in DBM file to indicate the names of key and value fields in database and tag it with leading GS (ascii \036)
X $theBase{"\036" . join("\034",@keyFields)} = join("\034",@valFields);
X}
Xelse
X{
X die "database empty\n";
X}
X
X# loop over records in database; sending mailing to each record
Xfor $i (1 .. $#database)
X{
X $_ = $database[$i];
X &parse_data(*fields,*mydata); # parse a line of the text database file
X#generate a letter below
X# Update the DBM database
X @theKeys = ();
X @theValue = ();
X foreach $key (keys %mydata)
X {
X if($fieldType{$key} =~ "KEY")
X {
X push(@theKeys, $mydata{$key});
X }
X else # the value fields
X {
X push(@theValue, $mydata{$key});
X }
X }
X# update the database file
X $theBase{join("\034",@theKeys)} = join("\034",@theValue);
X# mail a mailing to each new entry in database if requested with -m flag
X if($opt_m || ($opt_m = &QueryMail) )
X {
X if($tfile)
X {
X @letter = @template;
X &parse_template(*fields, *mydata, *letter); # parse the template
X &mail_letter(*fields, *mydata, *letter, *user); # mail mailing specified by template file
X }
X else
X {
X die "No template file specified!";
X }
X } # end if opt_m (mail option)
X
X} # end loop over records in database
X
Xif($opt_s)
X{
X open(SAVE,">$opt_s") || die "Aborting! Could not open save file!\n";
X print SAVE @database; # save the database in text format
X close SAVE;
X}
Xelse
X{
X print "No save file specified! Do you wish to save the data to a text file(y/n): ";
X $_ = <>;
X if(/[yY]/)
X {
X print"Enter name of file: ";
X $saveFile = <>;
X open(SAVE,">$saveFile") || die "Aborting! Could not open save file!\n";
X print SAVE @database;
X close SAVE;
X }
X
X}
Xdbmclose(%theBase); # close the DBM database file
X#
X# Utility subroutines are defined below
X#
X
Xsub QueryMail
X{
X print "Do you wish to mail letters to targets (y/n}: ";
X $c = <>;
X if($c =~ /^[Yy](es)?$/)
X { return 1;}
X else
X { return 0;
X }
X}
X
X
Xsub parse_data
X{
X local(*fields, *mydata) = @_;
X local($remainder) = $_; # parse line from input
X local(@data); # make data array local
X local($dummy,$token);
X
X while($remainder)
X {
X local($char) = substr($remainder,0,1);
X if($char eq '"') # quoted field
X {
X ($dummy,$token,$remainder) = split(/^"|",|"\n/,$remainder,3);
X }
X else # not a quoted field
X {
X ($token,$remainder) = split(/,|\n/,$remainder,2);
X }
X push(@data,$token);
X }
X
X# loop over fields in a record in database
X local($i);
X for $i (0 .. $#fields)
X {
X $mydata{$fields[$i]} = $data[$i];
X }
X} # close subroutine parse_data definition
X
Xsub mail_letter
X{
X# declare arguments
X local(*fields, *mydata, *template, *user) = @_;
X# declare local variables
X local($pid);
X local($temp);
X local($cnt);
X local($email);
X# body of subroutine below
X# print "Doing mail_letter subroutine ... \n";
X
X $pid = $$; # $$ is special variable for process id
X $temp = "/tmp/.letter$pid";
X
X open(LETTER,"> $temp") || die "ABORT: Failed to open $temp file \n"; # scratch file for letter
X $cnt = chmod 0600, "$temp"; # protect the scratch file
X if($cnt != 1)
X {
X print "WARNING: failed to protect /tmp/letter$pid scratch file\n";
X }
X
X print LETTER @template;
X print LETTER "\f";
X close LETTER;
X
X $email = 'e-mail';
X
X if($mydata{$email} && $opt_m) # only mail if requested
X {
X local($target) = $mydata{$email};
X $| = 1; # enable command buffering
X system "mail $user $target \< $temp";
X }
X else
X {
X die "ABORT: e-mail database field does not exist.\n";
X }
X
X system("rm $temp") ; # remove the scratch file
X}
X
Xsub parse_template
X{
X local(*fields, *mydata, *template) = @_;
X local($i, $level, @output);
X local($cond);
X local($field, $op, $test_value, $comment);
X local($key);
X# Pass One: insert include files
X &Includes(*template);
X
X# print @template;
X &SubFields(*mydata,*template);
X# Pass 1.5: replace built-in macros with appropriate values
X &BuiltInMacros(*template);
X# Pass Two: replace $xxx with value of database field xxx
X# print "parse_template: after built in macros call ... \n";
X
X# print @template;
X# Pass Three: if ... endif constructs to include text depending on values of
X# database fields and so forth
X# print "Doing pass three of parsing the template file.\n";
X $level = 0; # start at no nesting
X $output[level] = 1; # output is true
X $i = 0;
X while($i < $#template)
X {
X if($template[$i] =~ /^#if\s+\$(\w+)\s+(\w\w)\s+\/(.*)\/(.*)$/)
X {
X local($field) = $1;
X local($op) = $2;
X local($test_value) = $3;
X local($comment) = $4;
X $level++; # increment the nesting level here
X
X if($op eq 'eq')
X {
X $cond = 0;
X $_ = $mydata{$field};
X if(/$test_value/) { $cond = 1; };
X $_ = $mydata{$field.'*'}; # key field
X if(/$test_value/) { $cond = 1; };
X }
X elsif($op eq 'ne')
X {
X $cond = 1;
X $_ = $mydata{$field};
X if(/$test_value/) { $cond = 0; };
X $_ = $mydata{$field.'*'}; # key field
X if(/$test_value/) { $cond = 0; };
X }
X else
X {
X die " Aborting! Syntax error at line $i of template! \n";
X }
X
X if($cond && $output[$level - 1])
X {
X $output[$level] = 1;
X }
X else
X {
X $output[$level] = 0;
X }
X
X splice(@template,$i,1); # remove the if line
X }
X elsif ($template[$i] =~ /^#endif.*$/)
X {
X $level--; # decrement level
X if(level < 0)
X {
X die "Aborting! too many #endifs in template\n";
X }
X splice(@template,$i,1); # remove the endif line
X }
X elsif ($output[$level] == 0) # output off for this line
X {
X splice(@template,$i,1); # remove the line
X }
X elsif($output[$level] == 1) # output this line (keep)
X {
X $i++;
X }
X else
X {
X die "Aborting in parse_template! Should never get here!\n";
X }
X# print "line: ",$i,"nesting level: ",$level," output: ",$output[$level],"\n";
X# print $template[$i];
X } # end if $template[$i]
X# print @template;
X# Pass Four?
X}
X
Xsub parse_form
X{
X local(*fields, *mydata) = @_;
X local($i) = 0;
X local($value,$label,$remainder); # declare local variables
X while(<FORM>) # loop over lines in form entry
X {
X chop; # remove trailing new line
X ($label,$value) = split(/: *| +/,$_,2);
X $label =~ y/A-Z/a-z/; # translate to lowercase
X $fields[$i] = $label;
X $mydata{$label} = $value;
X $i++;
X }
X} # end definition of subroutine parse_form
X
Xsub append_database
X{
X local(*fields, *mydata, *database) = @_;
X local($value); #buffer for value of a database field
X local($i); # counter
X local($schema); # structure of database records
X local($record); # buffer for a database record
X#print "append database called";
X#print "database is ", @database;
X#print "fields are ", @fields;
X# note that fields in field line (schema) are not quoted
X $schema = join(',',@fields[0 .. $#fields]);
X $schema .= "\n";
X if($schema ne $database[0] && $database[0] ne '')
X {
X warn "Warning: database schema mismatch!\n";
X warn "Warning: current schema is $database[0]\n";
X warn "Warning: new schema is $schema\n";
X splice(@database,0,1,$schema);
X }
X elsif($schema eq $database[0])
X {
X# do nothing; is ok
X }
X elsif($#database == -1) # empty list
X {
X push(@database,$schema);
X }
X else
X {
X die "Aborting in append_database: unexpected condition!\n";
X }
X# default to quoting all fields in a record line
X $record = ''; # start with blank record
X for $i (0 .. $#fields)
X {
X $value = $mydata{$fields[$i]};
X $value =~ s/^(.*)$/\"$1\"/;
X if($i == 0)
X {
X $record .= $value;
X }
X else
X {
X $record .= ',';
X $record .= $value;
X }
X }
X $record .= "\n";
X push(@database,$record); # add record to end of database list
X# print "Leaving append database: database is\n",@database;
X}
X
X
Xsub GetDate
X{
X
X local($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
X local($thisday) = (Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday)[$wday];
X local($thismonth) = (January, February, March, April, May, June, July, August, September, October, November, December)[$mon];
X local($thisyear) = 19 . $year;
X local($today) = $thismonth . " " . $mday .", " . $thisyear;
X return $today;
X}
X
X
Xsub BuiltInMacros
X{
X local(*template) = @_;
X local($i);
X local($macro, $subr, $today);
X $macro = '';
X $subr = '';
X $today = '';
X
X# print "Into BuiltInMacros ... \n";
X# print "Associative array builtin is ", %builtin, "\n";
X
X foreach $i (0 .. $#template)
X {
X# print "Built In: Doing iteration $i \n";
X# print $template[$i];
X local($iter) = 0;
X local($loop) = 0;
X $_ = $template[$i];
X while(/#(\w+)#/g)
X {
X# print "Built In: Found match $1 Loop $loop \n";
X $loop++;
X $macro = $1;
X $subr = $builtin{$macro};
X if($subr)
X {
X $today = eval "&$subr";
X
X $template[$i] =~ s/#$macro#/$today/ge;
X }
X else
X {
X warn "Warning: $1 is not built in macro!\n";
X if($iter > 10)
X {
X die "In infinite loop ... \n";
X }
X $iter++;
X }
X } # close while patterns match a line in template
X
X } # close loop over lines in template
X} # close subroutine BuiltInMacros
X
Xsub SubFields
X{
X local(*mydata, *template) = @_;
X local($i);
X local($key);
X
X foreach $i (0 .. $#template)
X {
X $_ = $template[$i];
X if(/^#(\w+)\s.*$/)
X {
X $word = $1;
X }
X else
X {
X $word = 'nop'; # no operation
X }
X if($reserved{$word}) # ignore lines starting with #{reserved word}
X {
X# do nothing
X }
X elsif(/\$(\w+)/)
X {
X while(/\$(\w+)/g)
X {
X $key = $1;
X# print "Found $key \n";
X if($mydata{$key})
X {
X $template[$i] =~ s/\$$key/$mydata{$key}/g;
X }
X elsif ($mydata{$key.'*'})
X {
X $template[$i] =~ s/\$$key/$mydata{$key.'*'}/g;
X }
X else
X {
X# warn "Warning! $key not in record in database!\n"
X }
X }
X }
X else
X {
X }
X } # end loop over lines in the template
X}
X
Xsub Particle
X{
X# print "Particle: Doing particle subroutine ... \n";
X local(@chars) = split(//,$');
X local($i) = 0;
X local($answer);
X
X
X
X while($chars[$i] eq ' ')
X {
X $i++;
X }
X
X if($chars[$i] =~ /[aeiouAEIOUhH]/)
X {
X $answer = 'an';
X }
X else
X {
X $answer = 'a';
X };
X return $answer;
X
X}
X
Xsub Includes
X{
X local(*template) = @_;
X local($i, @included);
X
X foreach $i (0 .. $#template)
X {
X $_ = $template[$i];
X if(/^#include *"(.*)".*$/)
X {
X open(INCLUDE,$1);
X @included = <INCLUDE>;
X close INCLUDE;
X splice(@template,$i,1,@included);
X } # end if include line
X } # end loop over lines in template
X} # close definition of subroutine Includes
X
Xsub ConvertDBase
X{
X# convert a text database to an associative array database
X local(*textDB, *assocDB) = @_;
X local(@fields);
X local($field);
X local($line);
X local($i);
X local(%theData) = ();
X local(@theKeys) = ();
X local(@theValues) = ();
X
X $line = $textDB[0];
X chop $line;
X @fields = split(',',$line);
X foreach $field (@fields)
X {
X if($field =~ /\*$/)
X {
X $fieldType{$field} = "KEY"; # this is a key field
X push(@theKeys,$field);
X }
X else
X {
X $fieldType{$field} = "VAL"; # this is a value field
X push(@theValues,$field);
X }
X }
X
X # generate line for data names
X $theData{"\036" . join("\034",@theKeys)} = join("\034",@theValues);
X
X foreach $i (1..$#textDB)
X {
X local($_) = $textDB[$i];
X @theKeys = ();
X @theValues = ();
X &parse_data(@fields,%theData);
X foreach $key (keys %theData)
X {
X if($fieldType{$key} =~ "KEY")
X {
X push(@theKeys,$theData{$key});
X }
X else
X {
X push(@theValues, $theData{$key});
X }
X }
X $assocDB{join("\034",@theKeys)} = join("\034",@theValues);
X }
X
X
X}
X
X##############################################
X# Next few lines are legal in both perl and nroff
X.00; # finish .ig
X
X'di \" finish diversion -- previous line must be blank
X.nr nl 0-1 \" fake up transition to first page again
X.nr % 0 \" start at page 1
X';__END__ #### From here on it's a standard manual page ###
X
X.TH JUNKMAIL 1 "May 22, 1993"
X.AT 3
X.SH NAME
Xjunkmail \- sends customized mailings to multiple e-mail addresses
X.SH SYNOPSIS
X.B junkmail [ -h -d -t -c -s -f -b ]
X.SH DESCRIPTION
X.I junkmail
Xsends a customized mailing to each of a list of e-mail addresses. The recipients of the mailings are referred to as targets. The mailings are customized based on the contents of a database record provided for each target. The user provides the database of e-mail addresses and other information about each target. There must be a field labeled e-mail which contains the e-mail address of the target. The user may define any other fields as desired. The user writes a template file that junkmail uses to g
enerate each mailing.
X.SH TEMPLATE FILE
X.I junkmail
Xis constrolled by a template file used to generate the custom mailings.
XJunkmail template file directives include:
X
X#include "<file-spec>"
X
X#if $field eq /value/
X
X#if $field ne /value/
X
X#endif
X
XBuilt-In Macros:
X
X#date# substitute the current date
X
X#particle# a or an depending on subsequent word
X
XSubstitute value from database: $field replaced by value of field in database.
X
X.SH ENVIRONMENT
XEDITOR defines the text editor used for form entry of target information. Editor defaults to emacs if EDITOR variable is not defined.
X.SH FILES
XNone.
X.SH AUTHOR
XJohn F. McGowan
X.SH DIAGNOSTICS
X
X.SH BUGS
X
X.SH COPYRIGHTS
X(C) Copyright 1993 by John F. McGowan
X
END_OF_FILE
if test 24949 -ne `wc -c <'junkmail'`; then
echo shar: \"'junkmail'\" unpacked with wrong size!
fi
chmod +x 'junkmail'
# end of 'junkmail'
fi
if test -f 'entry' -a "${1}" != "-c" ; then
echo shar: Will not clobber existing file \"'entry'\"
else
echo shar: Extracting \"'entry'\" \(90 characters\)
sed "s/^X//" >'entry' <<'END_OF_FILE'
XName*:
XCompany*:
Xdate*:
Xpaper:
Xe-mail:
XJob*:
Xjtype:
XAddress:
XCity:
XState:
XZip:
END_OF_FILE
if test 90 -ne `wc -c <'entry'`; then
echo shar: \"'entry'\" unpacked with wrong size!
fi
# end of 'entry'
fi
if test -f 'template' -a "${1}" != "-c" ; then
echo shar: Will not clobber existing file \"'template'\"
else
echo shar: Extracting \"'template'\" \(1224 characters\)
sed "s/^X//" >'template' <<'END_OF_FILE'
X
X
X 1 Silicon Gulch
X Hackervale, CA 94049
X #date#
X
X
X
X
X
X#if $name ne /Sir/
X$name
X#endif
X$company
X$address
X$city, $state $zip
X
XDear $name:
X
X I am writing in response to an advertisement for #particle#
X$job that your organization placed in the $date $paper.
X
X#if $jtype eq /graphic/
X I have extensive experience in computer graphics. Please note
Xmy experience at Bob's Startup where I developed the multimedia
XBob's Projectware which includes a tool for producing 3D animations of
Xprojects for proposals and program reviews.
X#endif
X#if $jtype eq /real-time/
X I have extensive experience in real-time programming. This work
Xwas conducted at Yoyodyne Propulsion Systems and is classified Above Top
XSecret. I can't say anything more, but trust me, it worked.
X#endif
X#if $jtype eq /government/
X I have extensive experience in insuring that a project is never
Xcompleted regardless of how much money is thrown at it. Please see the
Xproceedings of the Senate Subcommitee on Investigations investigation into
Xcost overruns at Yoyodyne Propulsion Systems.
X#endif
X
X Please see appended resume for a detailed exposition of my
Xunparalleled qualifications.
X
X Sincerely,
X
X James Nerdly
X
X#include "sample.resume"
X
END_OF_FILE
if test 1224 -ne `wc -c <'template'`; then
echo shar: \"'template'\" unpacked with wrong size!
fi
# end of 'template'
fi
if test -f 'sample.resume' -a "${1}" != "-c" ; then
echo shar: Will not clobber existing file \"'sample.resume'\"
else
echo shar: Extracting \"'sample.resume'\" \(3367 characters\)
sed "s/^X//" >'sample.resume' <<'END_OF_FILE'
X James Nerdly
X 1 Silicon Gulch
X Hackervale, CA 94049
X
Xe-mail:
[email protected] FAX: (415) 555-5555 Voice: (415) 555-5554
X******************************************************************************
X
XJob Objective: Ultimate Software Guru
X
XEducation:
XPh.D., Computer Science (1988), Massachusetts Institute of Technology
XM.S., Computer Science (1986), Massachusetts Institute of Technology
XB.S., Mathematics (1985), California Institute of Technology
X
XExperience:
X
XSenior Software Engineer (1992-present)
XGeek Systems Incorporated
X
X Developed control software for animatronic geek sold to
Xamusement parks, carnivals, and theme parks worldwide. A geek
X(incidentally) is a carnival performer who bites the heads off of
Xsnakes and other reptiles. Developed fully reentrant, multitasking
XUnix-like real-time operating system (GeekX) for the Geek Systems
XCompuGeek animatronic creation. Also developed an X11R5 server to run
Xon GeekX. Developed Common LISP interpreter to run under GeekX.
XWrote artificial intelligence program in GeekX Common LISP to operate
Xthe CompuGeek. Developed user-friendly GUI for CompuGeek allowing
Xordinary programmers to program CompuGeek to perform simple tasks.
X
XSoftware Engineer (1990 - 1992)
XBob's Start Up
X
X Performed object oriented requirements analysis, designed, and
Xdeveloped all software for Bob's Start Up, notably the award winning
XBob's Projectware, a multimedia project management software package
Xwidely used in the Defense and Aerospace industries. Bob's
XProjectware implements the DOD Cost/Schedule Control System, PERT/CPM,
Xand other critical project management methodologies and government
Xrequirements using the latest multimedia presentation methods. Bob's
XProjectware runs on all systems from PC's to Mac's to all brands of
XUnix workstations. Developed a platform independent GUI layered on
Xtop of MS Windows 3.1, X Windows, and the Macintosh graphical user
Xinterface systems allowing Bob's Projectware to run on all systems.
XBob's Projectware was written in C++ and Ada using object oriented
Xanalysis, design, and programming.
X
X Conceived and developed the highly popular add-on Bob's
XOverrun module for Bob's Projectware. Overrun is an AI package
Xwritten in Bob's Prolog that generates detailed reports explaining and
Xjustifying actual and projected cost overruns. Overrun includes a
Xvery popular menu driven scapegoat generator. The project manager can
Xselect one of several popular scapegoats - including one or more
Xsubordinate managers, the project cost analyst or analysts, the
Xworkers actually doing the work, and the ever popular janitor who
Xaccidentally threw out the only working prototype - and Overrun will
Xgenerate a multimedia report demonstrating that the cost overrun is
Xthe fault of the scapegoat. Bob's Overrun also generates several
Xdifferent reports demonstrating that cancelling the project will cost
Xthe customer organization more money than providing additional funding.
X
XSoftware Engineer (1988-1990)
XYoyodyne Propulsion Systems
X
X All work classified Above Top Secret.
X
XOther: Author of the widely used public domain bsit and morebs spreadsheet
Xand report generation tools.
X---------------------------------------End of Resume-----------------------
X
END_OF_FILE
if test 3367 -ne `wc -c <'sample.resume'`; then
echo shar: \"'sample.resume'\" unpacked with wrong size!
fi
# end of 'sample.resume'
fi
if test -f 'sample.text' -a "${1}" != "-c" ; then
echo shar: Will not clobber existing file \"'sample.text'\"
else
echo shar: Extracting \"'sample.text'\" \(392 characters\)
sed "s/^X//" >'sample.text' <<'END_OF_FILE'
Xname*,company*,date*,paper,e-mail,job*,jtype,address,city,state,zip
X"John","John's Company","Oct. 10, 1993","ba.jobs.offered","
[email protected]","Software Guru","government","1 Infinite Loop","Hackervale","CA","94045"
X"Tom","Tom's Company","Oct. 10, 1993","ba.jobs.offered","
[email protected]","Software Guru","government","2 Infinite Loop","Hackervale","CA","94045"
END_OF_FILE
if test 392 -ne `wc -c <'sample.text'`; then
echo shar: \"'sample.text'\" unpacked with wrong size!
fi
# end of 'sample.text'
fi
if test -f 'manual' -a "${1}" != "-c" ; then
echo shar: Will not clobber existing file \"'manual'\"
else
echo shar: Extracting \"'manual'\" \(10078 characters\)
sed "s/^X//" >'manual' <<'END_OF_FILE'
X Junkmail Programmer's Manual and Reference
X by John F. McGowan
X
[email protected]
X
X Copyright (C) 1993 by John F. McGowan
X****************************************************************************
X
XPreface:
X
XJunkmail is a Perl script to automate sending customized mailings to
Xmultiple e-mail addresses. Junkmail is relatively simple at this
Xpoint in time. However, users may wish to add additional features or
Xotherwise customize the source. For this reason, I provide this
XProgrammer's Manual and Reference to assist in adding features not
Xprovided in the main release.
X
XOverview:
X
XJunkmail consists of a main routine which parses the command line flags
Xand calls appropriate subroutines. The main routine reads the
Xtemplate file and stores it in @template if specified with -t flag.
XThe main routine reads the data entry form file and stores it in @form if
Xspecified with -f flag. The main routine reads a text database into the
Xinternal junkmail database if specified with the -d flag. The internal
Xjunkmail database is stored in two forms @database and %theBase which can
Xbe interconverted.
X
Xjunkmail loops over the records in the internal junkmail database. It
Xuses the template to generate a custom letter for each record. This
Xis done by the parse_template subroutine. First, parse_template calls
XIncludes subroutine to replace #include <file-spec> lines with the
Xincluded file. Second, parse_template calls SubFields to substitute
Xjunkmail database values from the database record into the letter.
XThen, parse_template calls BuiltInMacros to substitute for builtin
Xmacros such as #date#. Junkmail then mails letter to the target.
XThis is done by the mail_letter subroutine. Each record in the
Xdatabase must contain a field named 'e-mail' which gives the Internet
Xaddress of the target. Otherwise the user is free to define fields as
Xhe or she likes.
X
XWhen junkmail quits, the main routine asks the user if he/she wants to
Xsave the database as a text ASCII file and as a DBM file if this has
Xnot been requested using the command line flags -s and -b.
X
X
XJunkmail Structure Chart:
X
X |----------------------------|
X | |
X | junkmail |
X | main |
X | |
X |----------------------------|
X | | | | | |
X | | | | | |
X | | | | | --------------|
X | | | | | |
X | | | | |---------| |
X | | | | | |
X | | | | | |
X | | | |-------------| | |
X | | | | | |
X | | | | | |----------------|
X --------------- | --------------| | | | |
X | | | | | | QueryMail |
X | | | | | | |
X | | | | | | If -m not |
X | | | | | | given ask |
X | | | | | | user if he |
X | | | | | | wants to |
X | | | | | | mail letters. |
X | | | | | | |
X | | | | | |----------------|
X | | | | |
X | | | | |
X | | | | |-----------|
X | | | | |
X | | | | |-------------------|
X | | | | | |
X | | | | | |
X | | | | | append_database |
X | | | | | |
X | | | | | Add new entry to |
X | | | | | the internal |
X | | | | | junkmail database |
X | | | | | |
X | | | | | |
X | | | | |-------------------|
X | | | |
X | | | ------------|
X | | | |
X | | | |
X | | | |
X | |-----------------| | |------------------|
X | | | | | |
X | | parse_data | | | parse_form |
X | | | | | |
X | | Get a record | | | Use a Data |
X | | from internal | | | Entry Form to |
X | | database. | | | input a record |
X | | | | | |
X | |-----------------| | | |
X | | |------------------|
X | |
X | |
X |-----------------| |--------------------|
X | | | |
X | parse_template | | mail_letter |
X | | | |
X |-----------------| |--------------------|
X | | |
X | | |
X | | --------------------------------------------|
X | | |
X | --------------------------| |
X | | |
X |----------------| |------------------| |------------------|
X | | | | | |
X | Includes | | SubFields | | BuiltInMacros |
X | | | | | |
X | Substitute | | Substitute | | Substitute |
X | include file | | values for | | the builtin |
X | in the letter | | fields from | | macros into |
X | | | internal | | the letter |
X | | | database | | |
X | | | | | |
X |----------------| |------------------| |------------------|
X | | |
X | | |
X | | |
X |---------------------------------------------- | |
X | | |
X | |----------------------------| |
X | | |
X | | |
X | | |----------------
X | | |
X |--------------| |----------------| |----------------|
X | | | | | |
X | GetDate | | Particle | | Error! |
X | | | | | not a macro |
X |--------------| |----------------| |----------------|
X
X
X |-------------------------------|
X | |
X | |
X | ConvertDBase |
X | |
X | utility function |
X | |
X |-------------------------------|
X
X
X
X
X
X------------------------->End of Manual<-------------------------------------
X
X Junkmail Reference Section
X
XSubroutines:
X
XQueryMail
X
X Ask user if he/she wants to mail letters to targets.
X
Xparse_data
X
X Parse a line of the ASCII text format, store results in
Xmydata and fields.
X
Xmail_letter
X
X mail letter (@letter) to a target.
X
Xparse_template
X
X Parses a junkmail template.
X
XSubFields
X
X Substitutes a junkmail database field value for $<field name> in
Xa template.
X
Xparse_form
X
X Parses a Data Entry Form.
X
Xappend_database
X
X Add new record to database.
X
XGetDate
X
X Gets todays date in format Month DD, YYYY.
X
XBuiltInMacros
X
X Calls appropriate subroutines for builtin macros. The associative
Xarray %builtin gives the macro and associated function, e.g.
X#date# and GetDate.
X
XParticle
X
X Substitutes a or an for #particle# in template depending on first
Xcharacter of word following #particle#. #particle# is a junkmail builtin
Xmacro.
X
XIncludes
X
X Substitutes contents of a file for the
X#include <file-spec>
X line in a junkmail template.
X
XConvertDBase
X
X Converst an ASCII text format database to an associative array
Xdatabase that can be written to a DBM file. An ASCII text format database
Xif of form:
X
Xkey-field-name*,value-field-name,value-field-name,key-field-name*,value-field-name
X"Schwarzenegger","Slater","Jack","Arnold","hero"
X"O'Brien","Madigan","Daniel","Austin","comic sidekick"
X
XThe associative array database format consists of
X
XKey Value
X
Xkey-field-value\034key-field-value value-field-value\034value-field-value
X
X
XData Structures:
X
X%builtin
X
X associative array of built-in macros and perl subroutines to call
Xif macro encountered (in template file).
X
X%reserved
X
X associative array of reserved words such as if, endif, and include.
X
X%theBase
X
X Associative array containing the database in format saved to
XDBM file. The associative array key is a join of the values of the
Xjunkmail database Key fields. Junkmail identifies Key fields with a
Xtrailing asterisk. The associative array values is a join of the
Xvalues of the junkmail database Value fields. The junkmail database
Xis ASCII only.
X
X@template
X
X This Perl array contains text of the template file.
X
X@form
X
X This Perl array contains text of the data entry form.
X
X@database
X
X This Perl array contains alternate format (text ASCII) of the
Xjunkmail database. The first line $database[0] lists the database field
Xnames. junkmail database key fields have a trailing asterisk. Subsequent
Xlines are the records.
X
X@fields
X
X An array of the junkmail databse field names.
X
X@mydata
X
X An array for a single record in the junkmail database.
X
X@letter
X
X Buffer for the letter sent to a target.
X
X@keyField
X
X An array of junkmail database key fields.
X
X@valuefields
X
X An array of junkmail database value fields.
X
X@theKeys
X
X An array of junkmail database key fields.
X
X@theValue
X
X An array of junkmail database value fields.
X
X$user
X
X Buffer for address.
X
X
X
END_OF_FILE
if test 10078 -ne `wc -c <'manual'`; then
echo shar: \"'manual'\" unpacked with wrong size!
fi
# end of 'manual'
fi
echo shar: End of shell archive.
exit 0