Listing 1: calendar.sh, the enhanced calendar program

###########################################################
# BEGIN PROGRAM LISTING
###########################################################

:
# calendar.sh - enhanced calendar program.  Add a .calendar file to 
# your $HOME directory and call this program nightly from cron.
#                Copyright (C) 1995 Steven G. Isaacson

# syntax
test "$1" = "-h" && {
    echo "Syntax: `basename $0` [calendar]"
    exit 1
}

# calendar file.  If not specifed on command-line, use $HOME/.calendar
calfile=${1:-$HOME/.calendar}

# do we have a valid file?
test -f "$calfile" || {
    echo "Error: could not read: $calfile"
    exit 1
}

# make sure we have all that we need.  set appropriately for your 
# system. 
PATH=$PATH:/usr/bin ; export PATH

# we're currently using elm
: ${mailer:=/usr/bin/elm}

# set -xv # uncomment for debugging

# if no mail-to is specified in calendar file, send mail to whoever is 
# running this program.  If logname program not available you can strip 
# out the name from id.
def_mail="`logname`"

# tmp file for Regular Expressions
refile=/tmp/refile.$$

# tmp file
tfile=/tmp/tfile.$$

# get 3-character day of week, to be used with our custom keywords.
today=`date '+%a'`

{ # BEGIN BRACE

  # all stdout between the braces will be funneled through the closing 
  # brace.  This is similiar to using parens to spawn a sub-shell, but 
  # we don't need a subshell.  Could also have done something like this:
  #   prog1    >  outfile
  #   prog2    >> outfile
  #   prog_etc >> outfile
  # and then used the contents of outfile.

  # This is how the stock calendar program generates r.e. for dates
  /usr/lib/calprog
  
  # Look for our custom 'daily' keyword, but not on comment lines.
  echo "^[^#]*[Dd]aily"
  
  # Look for custom day keywords.  If this is Mon, then look for 
  # monday, etc.  Again, skip comment lines.  Case sensitivity is 
  # consistent with calprog.
  case "$today" in
    Mon) echo "^[^#]*[Mm]onday" ;;
    Tue) echo "^[^#]*[Tt]uesday" ;;
    Wed) echo "^[^#]*[Ww]ednesday" ;;
    Thu) echo "^[^#]*[Tt]hursday" ;;
    Fri) echo "^[^#]*[Ff]riday" ;;
    Sat) echo "^[^#]*[Ss]aturday" ;;
    Sun) echo "^[^#]*[Ss]unday" ;;
      *) echo "ERROR: date" >&2 ; exit 1 ;;
  esac
  
  # Custom weekday and weekend keywords
  case "$today" in
    Mon|Tue|Wed|Thu|Fri) echo "^[^#]*[Ww]eekday" ;;
    Sat|Sun) echo "^[^#]*[Ww]eekend" ;;
  esac

} | # END BRACE
awk '{
    # Now that we have all of our regular expressions ... egrep 
    # complains that we have too many and fails.  So forget about 
    # egrep.  Create an awk script egrep work-alike, on-the-fly.  Like
    # this:
    #  /Oct 20/ { print; next; }
    #  /Oct 21/ { print; next; }

    # Need to escape slashes from calprog output because slashes are 
    # the delimiter for awk.  old awk does not have gsub.  use nawk or 
    # gawk or sed.

    gsub(/\//, "\\/")

    printf("/%s/ {print; next;}\n", $0)

}' > $refile

# We've got the regular expression file.  Time to pre-process the 
# calendar file.  Do two things: convert multi-line entries (designated 
# with escapes at end of line) into single-line entries.  And second...
#
# The following calendar file line does not work because we need a 
# space between the pipe and Oct
#
#     mail_to|Oct 3 does not work.  [need space]
#
# but I expected it to, so, make it work by adding a space after 
# the first pipe and then, after we're done with the regular 
# expressions, taking the space out again.

awk '/^[^#]*\|/ {
    # sub not gsub; only do first one
    sub(/\|/, "| <PIPE_SPACE> ")
}
/\\[ 	]*$/ {
    # convert multi-line entries
    printf("%s <nl>", $0)
    next
}
{ print }' $calfile |

# pipe the calendar entries through the date/r.e. filter
awk -f $refile |

# read the lines that come out and send mail, etc.
while read line
do
    # get the mail-to address, everything up to the first pipe symbol
    mail_to=`echo $line | awk -F\| 'NF > 1 {print $1}'`

    # if none found, use default address
    test "$mail_to" || mail_to="$def_mail"

    # initialize temp file
    > $tfile

    # create subject line for mailer
    subject="calendar.sh `date`"

    # undo the multi-line and pipe-space conversion that was possibly 
    # done above
    line=`echo "$line" | awk '{
        gsub(/<nl>/, "\n")
        sub(/\| <PIPE_SPACE> /, "|")
        print
    }'`

    # echo the entry to the temp file
    echo "$line
" >> $tfile

    # Check for additional file or system command to be run.
    echo "$line" | awk '
    /#[f!]:.*#[f!]:/ {
        print "ERROR: multiple #f: or #!: expressions"
        next
    }
    /#f:/ {
        # delete everything up to #f:
        sub(/^.*#f:/,"")

        # delete everything after the first space, which means only one 
        # file at a time; one per line.
        sub(/ .*/, "")
        str=sprintf("cat %s", $0)
        system(str)
        next
    }
    /#!:/ {
        sub(/^.*#!:/,"")
        sub(/#.*/, "")
        str=sprintf("%s", $0)
        system(str)
        next
    }' >> $tfile

    # send the mail.
    $mailer -s "$subject" "$mail_to" < $tfile > /dev/null
done

# cleanup
rm -f $refile $tfile
exit 0

###########################################################
# END PROGRAM LISTING
###########################################################


