[alt.sources] pcal v2.5, part 1 of 2

jbr0@cbnews.att.com (joseph.a.brownlee) (10/06/90)

This is newest version of "pcal", the Postscript calendar program.  This
version completely replaces both Version 2.3 and the patch to 2.4 recently
posted to "alt.sources".  While I have included the functionality provided
by the 2.4 patch in this version of "pcal", I have done so in a different way.

In this version, "pcal" supports a feature allowing the user to select which
day is displyed in the left column, but as a command line option rather than
as a compile time selection.  Use -F<dayname> to make the given day appear as
the first day of the week.  You may place the -F option in your ".calendar"
file.  Overkill perhaps, but it was as easy as allowing only Sunday or Monday.

I have also done some minor cleanup on the Postscript code and a little
reformatting of the calendar.  By making the day numbers slightly smaller, you
can now fit 9 lines of text into a single day.

One other note: several people have sent me e-mail suggesting that "pcal" be
released less frequently as changes are made.  I'm going to abide by that
because keeping up with the many changes used to be a problem for me, too.
So if you have an idea for a change you would like to make to "pcal", please
send me e-mail first so that I can provide you with the most up-to-date
sources before you hack up and old cut.

Enjoy.

   -      _   Joe Brownlee, Analysts International Corp. @ AT&T Network Systems
  /_\  @ / `  471 E Broad St, Suite 1610, Columbus, Ohio 43215   (614) 860-7461
 /   \ | \_,  E-mail: jbr@cblph.att.com     Who pays attention to what _I_ say?
 "Scotty, we need warp drive in 3 minutes or we're all dead!" --- James T. Kirk

------------------------------- 8<  cut here  >8 -------------------------------
#!/bin/sh
# This is a shell archive (produced by shar 3.49)
# To extract the files from this archive, save it to a file, remove
# everything above the "!/bin/sh" line above, and type "sh file_name".
#
# existing files will NOT be overwritten unless -c is specified
#
# This shar contains:
# length  mode       name
# ------ ---------- ------------------------------------------
#    433 -rw-r--r-- Makefile
#   1734 -rw-r--r-- ReadMe
#    984 -rw-r--r-- ReadMe.orig
#   1464 -rw-r--r-- calendar
#    397 -rw-r--r-- make_pcal.com
#  46687 -rw-r--r-- pcal.c
#   8035 -rw-r--r-- pcal.hlp
#   7393 -rw-r--r-- pcal.man
#   1792 -rw-r--r-- pcalinit.c
#  11138 -rw-r--r-- pcalinit.ps
#
# ============= Makefile ==============
if test -f 'Makefile' -a X"$1" != X"-c"; then
	echo 'x - skipping Makefile (File already exists)'
else
echo 'x - extracting Makefile (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'Makefile' &&
# Set the configuration variables below to taste.
X
CC	= /bin/cc
MANDIR	= /usr1/jad/man
X
pcal:	pcal.c pcalinit.h
X	$(CC) $(CFLAGS) $(LDFLAGS) $(COPTS) -o pcal pcal.c
X	@ echo "+ Compile of pcal complete!"
X
pcalinit: pcalinit.c
X	$(CC) $(CFLAGS) $(LDFLAGS) $(COPTS) -o pcalinit pcalinit.c
X
pcalinit.h: pcalinit pcalinit.ps
X	pcalinit pcalinit.ps pcalinit.h
X
man:	pcal.man
X	nroff -man pcal.man > pcal.1
X	pack pcal.1
#	mv pcal.1.z $(MANDIR)
SHAR_EOF
chmod 0644 Makefile ||
echo 'restore of Makefile failed'
Wc_c="`wc -c < 'Makefile'`"
test 433 -eq "$Wc_c" ||
	echo 'Makefile: original size 433, current size' "$Wc_c"
fi
# ============= ReadMe ==============
if test -f 'ReadMe' -a X"$1" != X"-c"; then
	echo 'x - skipping ReadMe (File already exists)'
else
echo 'x - extracting ReadMe (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'ReadMe' &&
"pcal" Version 2.5
X
This is newest version of "pcal", the Postscript calendar program.  This
version completely replaces both Version 2.3 and the patch to 2.4 recently
posted to "alt.sources".  While I have included the functionality provided
by the 2.4 patch in this version of "pcal", I have done so in a different way.
X
In this version, "pcal" supports a feature allowing the user to select which
day is displyed in the left column, but as a command line option rather than
as a compile time selection.  Use -F<dayname> to make the given day appear as
the first day of the week.  You may place the -F option in your ".calendar"
file.  Overkill perhaps, but it was as easy as allowing only Sunday or Monday.
X
I have also done some minor cleanup on the Postscript code and a little
reformatting of the calendar.  By making the day numbers slightly smaller, you
can now fit 9 lines of text into a single day.
X
One other note: several people have sent me e-mail suggesting that "pcal" be
released less frequently as changes are made.  I'm going to abide by that
because keeping up with the many changes used to be a problem for me, too.
So if you have an idea for a change you would like to make to "pcal", please
send me e-mail first so that I can provide you with the most up-to-date
sources before you hack up and old cut.
X
The accompanying file called "ReadMe.orig" came with the original distribution
as README and states this program is copyrighted but with permission to modify
and redistribute.
X
Joe Brownlee
jbr@cblph.att.com
X
Additional note: This distribution includes a VMS HELP file written by
Richard Dyson.  Countless other people worked on pcal long before me; see
the ReadMe.orig file and topline comments in pcal.c. 
SHAR_EOF
chmod 0644 ReadMe ||
echo 'restore of ReadMe failed'
Wc_c="`wc -c < 'ReadMe'`"
test 1734 -eq "$Wc_c" ||
	echo 'ReadMe: original size 1734, current size' "$Wc_c"
fi
# ============= ReadMe.orig ==============
if test -f 'ReadMe.orig' -a X"$1" != X"-c"; then
	echo 'x - skipping ReadMe.orig (File already exists)'
else
echo 'x - extracting ReadMe.orig (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'ReadMe.orig' &&
"Pcal" is a program to print PostScript calendars for any month and year.
By default, it looks for a file in the home directory named "calendar"
for entries with leading dates matching dates on the calendar, and prints
any following text under the appropriate day.
X
The program may be a little System V flavored (getopt, time routines)
but should be easily portable to other vintages of UNIX.
X
Pcal is the combined effort of several people, most notably Patrick Wood
of Pipeline Associates, Inc. for the original PostScript code and Bill
Vogel of AT&T for the calendar file mechanism.  My part was simple
translation to a "C" program, the addition of a couple options and a more
generalized date searching routine (oh yes, and a manual page :-).
X
The original calendar PostScript was Copyright (c) 1987 by Patrick Wood
and Pipeline Associates, Inc. with permission to modify and redistribute.
Please retain this README file with the package.
X
X
Ken Keirnan
Pacific Bell
San Ramon, CA.
SHAR_EOF
chmod 0644 ReadMe.orig ||
echo 'restore of ReadMe.orig failed'
Wc_c="`wc -c < 'ReadMe.orig'`"
test 984 -eq "$Wc_c" ||
	echo 'ReadMe.orig: original size 984, current size' "$Wc_c"
fi
# ============= calendar ==============
if test -f 'calendar' -a X"$1" != X"-c"; then
	echo 'x - skipping calendar (File already exists)'
else
echo 'x - extracting calendar (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'calendar' &&
# Sample calendar file for pcal
#
# This should be ~/.calendar on Unix, SYS$LOGIN:CALENDAR.DAT on VMS
#
# Valid entries are of the following forms:
#
#	opt <options>
#	year <year>
#	<month_name> <day>{*} {<text>}
#	<month><sep><day>{<sep><year>}{*} {<text>}
#	note <month> {<text>}
#	note <month_name> {<text>}
#
# where:
#	<options> := one or more valid command-line options (except -e and -f)
#	<month_name> := first 3+ characters of name of month (in English)
#	<sep> := one or more non-numeric, non-space, non-'*' characters
#	<text> is the text to be printed in the calendar box
#	<day>, <month>, and <year> are appropriate integers
#
#	whitespace is to be used/avoided as implied by the above productions
#	'*' flags the date as a holiday (to be printed in gray)
#	comments run from '#' through end-of-line
X
# A sample "opt" line to change the fonts and output file names, to print
# only Sundays in gray, and to print moons on all days:
#
#opt -d Helvetica-Bold -t Helvetica-Bold -o myfile.ps -b all -g sun -M
X
year 1990				# set year explicitly
X
5/28* Memorial Day (observed)		# '*' prints holiday in gray
5/31 Memorial Day
X
7/4/90* Independence Day		# full date format
X
Sep 3* Labor Day			# month written out
X
10/8* Columbus Day (observed)
10/12 Columbus Day
X
11/22* Thanksgiving
11/23*					# holiday without text
X
12/24* # Christmas Eve
12/25* Christmas
X
note Dec Some consider Christmas Eve a holiday
X
1/1/91* New Year's Day			# set new year implicitly
SHAR_EOF
chmod 0644 calendar ||
echo 'restore of calendar failed'
Wc_c="`wc -c < 'calendar'`"
test 1464 -eq "$Wc_c" ||
	echo 'calendar: original size 1464, current size' "$Wc_c"
fi
# ============= make_pcal.com ==============
if test -f 'make_pcal.com' -a X"$1" != X"-c"; then
	echo 'x - skipping make_pcal.com (File already exists)'
else
echo 'x - extracting make_pcal.com (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'make_pcal.com' &&
$! make_pcal.com - VMS command script to build/run pcalinit, create pcalinit.h
$! from pcalinit.ps, and compile/link pcalinit.c
$
$ cc pcalinit.c
$ link pcalinit
$ pcalinit = "$" + f$environment("DEFAULT") + "pcalinit"
$ pcalinit pcalinit.ps pcalinit.h
$ delete/exclude=(*.c,*.h,*.ps) pcalinit.*;*
$
$ cc pcal.c
$ link pcal.obj
$ pcal == "$" + f$environment("DEFAULT") + "pcal"
$ show symbol pcal
SHAR_EOF
chmod 0644 make_pcal.com ||
echo 'restore of make_pcal.com failed'
Wc_c="`wc -c < 'make_pcal.com'`"
test 397 -eq "$Wc_c" ||
	echo 'make_pcal.com: original size 397, current size' "$Wc_c"
fi
# ============= pcal.c ==============
if test -f 'pcal.c' -a X"$1" != X"-c"; then
	echo 'x - skipping pcal.c (File already exists)'
else
echo 'x - extracting pcal.c (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'pcal.c' &&
static char  VERISON_STRING[]	= "@(#)pcal v2.5 - print Postscript calendars";
/*
X * pcal.c - generate PostScript file to print calendar for any month and year
X *
X * The original PostScript code to generate the calendars was written by
X * Patrick Wood (Copyright (c) 1987 by Patrick Wood of Pipeline Associates,
X * Inc.), and authorized for modification and redistribution.  The calendar
X * file inclusion code was originally written in "bs(1)" by Bill Vogel of
X * AT&T.  Patrick's original PostScript was modified and enhanced several
X * times by others whose names have regrettably been lost.  This C version
X * was originally created by Ken Keirnan of Pacific Bell; additional
X * enhancements by Joseph P. Larson, Ed Hand, and Andrew Rogers (who also
X * did the VMS port), Mark Kantrowitz, Joe Brownlee.  The moon routines were
X * originally written by Mark Hanson, and were improved and incorporated into
X * this version by Jamie Zawinski.
X *
X * Revision history:
X *
X *	2.5	JAB	10/04/90	added -F option
X *
X *	2.4	---	10/01/90	* no modifications *
X *
X *	2.3	jwz	09/18/90	added moon routines
X *
X *	2.2	AWR	09/17/90	revise logic of parse(); new usage
X *					message
X *
X *		JAB/AWR	09/14/90	support "note" lines in date file
X *
X *	2.1	MK/AWR	08/27/90	support -L, -C, -R, -n options;
X *					print holiday text next to date
X *
X *		AWR	08/24/90	incorporate cpp-like functionality;
X *					add -D and -U options; save date file
X *					information in internal data structure;
X *					look for PCAL_OPTS and PCAL_DIR; look
X *					for ~/.calendar and ~/calendar
X *
X *	2.0	AWR	08/08/90	included revision history; replaced -r
X *					flag with -l and -p; replaced -s and -S
X *					flags with -b and -g; recognize flags
X *					set in date file; translate ( and ) in
X *					text to octal escape sequence; usage()
X *					message condensed to fit 24x80 screen
X *
X *	Parameters:
X *
X *		pcal [opts]		generate calendar for current month/year
X *
X *		pcal [opts] yy		generate calendar for entire year yy
X *
X *		pcal [opts] mm yy	generate calendar for month mm
X *					(Jan = 1), year yy (19yy if yy < 100)
X *
X *		pcal [opts] mm yy n	as above, for n consecutive months
X *
X *	Output:
X *
X *		PostScript file to print calendars for all selected months.
X *
X *	Options:
X *
X *		-b <DAY>	print specified weekday in black
X *		-g <DAY>	print specified weekday in gray
X *				(default: print Saturdays and Sundays in gray)
X *		
X *		-d <FONT>	specify alternate font for day names
X *				(default: Times-Bold)
X *
X *		-n <FONT>	specify alternate font for notes in boxes
X *				(default: Helvetica-Narrow)
X *
X *		-t <FONT>	specify alternate font for titles
X *				(default: Times-Bold)
X *
X *		-D <SYM>	define preprocessor symbol
X *		-U <SYM>	un-define preprocessor symbol
X *
X *		-e		generate empty calendar (ignore date file)
X *
X *		-f <FILE>	specify alternate date file (default:
X *				~/.calendar on Un*x, SYS$LOGIN:CALENDAR.DAT
X *				on VMS; if environment variable [logical
X *				name on VMS] PCAL_DIR exists, looks there
X *				instead)
X *
X *		-o <FILE>	specify alternate output file (default:
X *				stdout on Un*x, CALENDAR.PS on VMS)
X *
X *		-L <STRING>	specify left foot string   (default: "")
X *		-C <STRING>	specify center foot string (default: "")
X *		-R <STRING>	specify right foot string  (default: "")
X *
X *		-l		generate landscape-mode calendars
X *		-p		generate portrait-mode calendars
X *				(default: landscape-mode)
X *
X *		-m		draw a small moon icon on the days of the
X *				full, new, and half moons.
X *		-M		draw a small moon icon every day.  
X *				(default: no moons).
X *
X *		-F <DAY>	select any day to be displayed as the first
X *				day of the weel
X *
X *	There are many ways to specify these options in addition to using the
X *	command line; this facilitates customization to the user's needs.
X *
X *	If the environment variable (global symbol on VMS) PCAL_OPTS is
X *	present, its value will be parsed as if it were a command line.
X *	Any options specified will override the program defaults.
X *
X *	All but the -e, -f, -D, and -U options may be specified in the date
X *	file by the inclusion of one or more lines of the form "opt <options>".
X *	Any such options override any previous values set either as program
X *	defaults, via PCAL_OPTS, or in previous "opt" lines.
X *
X *	Options explicitly specified on the command line in turn override all
X *	of the above.
X *
X *	Any flag which normally takes an argument may also be specified without
X *	an argument; this resets the corresponding option to its default.  -D
X *	alone un-defines all symbols; -U alone has no effect.
X *
X *	Parameters and flags may be mixed on the command line.  In some cases
X *	(e.g., when a parameter follows a flag without its optional argument)
X *	this may lead to ambiguity; the dummy flag '-' (or '--') may be used
X *	to separate them, i.e. "pcal -t - 9 90".
X *
X *	Simple cpp-like functionality is provided.  The date file may include
X *	the following commands, which work like their cpp counterparts:
X *
X *		define <sym>
X *		undef <sym>
X *
X *		if{n}def <sym>
X *		   ...
X *		{ else
X *		   ... }
X *		endif
X *
X *		include <file>
X *
X *	Note that these do not start with '#', which is reserved as a comment
X *	character.
X *
X *	"define" alone deletes all the current definitions; "ifdef" alone is
X *	always false; "ifndef" alone is always true.  All defined symbols are
X *	treated in a case-insensitive manner.
X *
X *	The file name in the "include" directive may optionally be surrounded
X *	by "" or <>.
X *
X *	Additional notes may be propagated to an empty calendar box by the
X *	inclusion of one or more lines of the form "note <month> <text>",
X *	where <month> may be numeric or alphabetic.
X *
X */
X
X
#include <stdio.h>
#include <ctype.h>
#include <time.h>
#include <string.h>
X
#ifdef VMS		/* VMS oddities isolated here */
X
#include <ssdef.h>	/* required for trnlog() */
#include <descrip.h>
X
#define HOME_DIR	"SYS$LOGIN"
#define DATEFILE	"calendar.dat"
#define OUTFILE		"calendar.ps"
#define START_PATH	'['
#define END_PATH	']'
X
#define EXIT_SUCCESS 1
#define EXIT_FAILURE 3
X
#else			/* non-VMS - assume Un*x of some sort */
X
#define HOME_DIR	"HOME"
#define DATEFILE	".calendar"
#define ALT_DATEFILE	"calendar"	/* for backward compatibility */
#define OUTFILE		""
#define START_PATH	'/'
#define END_PATH	'/'
X
#define EXIT_SUCCESS 0
#define EXIT_FAILURE 1
X
#endif
X
#define PCAL_OPTS	"PCAL_OPTS"	/* environment variables */
#define PCAL_DIR	"PCAL_DIR"
X
#define IS_LEAP(y)	((y) % 4 == 0 && ((y) % 100 != 0 || (y) % 400 == 0))
#define INIT_COLORS	memcpy(color, default_color, sizeof(color))
#define LASTCHAR(p)	((p) && *(p) ? (p) + strlen(p) - 1 : NULL)
X
#ifdef __STDC__
#define TOLOWER(c)	tolower(c)
#else
#define TOLOWER(c)	(isupper(c) ? tolower(c) : (c))
#endif
X
#define PRT		(void)printf
#define FPR		(void)fprintf
X
#define FALSE	0
#define TRUE	1
X
#define ALL_FLAGS	"bCDdefgLlMmnopRtUF"	/* all command-line flags */
#define DATEFILE_FLAGS	"DefU"			/* parsed before opening datefile */
#define OTHER_FLAGS	"bCdgLlMmnopRtF"	/* parsed inside datefile */
X
#define DAYFONT		"Times-Bold"		/* default font names */
#define TITLEFONT	"Times-Bold"
#define NOTESFONT	"Helvetica-Narrow"
X
#define LFOOT 		""                        /* default foot strings */
#define CFOOT 		""
#define RFOOT 		""
X
#define LANDSCAPE  90		/* degrees to rotate for landscape/portrait */
#define PORTRAIT    0
#define ROTATE	   LANDSCAPE	/* default */
X
#define BLACK		0	/* colors for dates */
#define GRAY		1
X
#define NO_DATEFILE 	0	/* date file (if any) to use */
#define USER_DATEFILE	1
#define SYS_DATEFILE	2
X
/* preprocessor token codes - must be contiguous range of integers starting
X * at 0 and ending with code for non-tokens (cf. pp_info[], pp_token())
X */
#define PP_DEFINE	0
#define PP_ELSE		1
#define PP_ENDIF	2
#define PP_IFDEF	3
#define PP_IFNDEF	4
#define PP_INCLUDE	5
#define PP_UNDEF	6
#define PP_OTHER	7	/* not pp token */
X
#define MAX_NESTING	10	/* maximum nesting level for file inclusion */
X
#define MAX_PP_SYMS	100	/* number of definable preprocessor symbols */
#define PP_SYM_UNDEF     -1	/* flag for undefined symbol */
X
#define MIN_YR		1900	/* significant years (calendar limits) */
#define MAX_YR		9999
X
#define JAN		 1	/* significant months */
#define FEB		 2
#define DEC		12
X
#define DAY_TEXT	0	/* types of text in data structure */
#define HOLIDAY_TEXT	1
#define NOTE_TEXT	2
X
#define NOTE_DAY	32	/* dummy day for notes text */
X
#define DF_YEAR		0	/* returns from date_type() */
#define DF_OPT		1
#define DF_NOTE		2
#define DF_MONTH	3
#define DF_DATE		4
#define DF_OTHER	5
X
#define PARSE_OK	0	/* returns from parse(), enter_day_info() */
#define PARSE_INVDATE	1
#define PARSE_INVLINE	2
X
#define STRSIZ	200		/* size of misc. strings */
X
#define MAXARGS 3		/* numeric command-line args */
X
#define WHITESPACE " \t"	/* token delimiters in date file */
X
/*
X * Global typedef declarations for data structure
X */
X
typedef struct d_i {
X	int is_holiday;
X	char *text;
X	struct d_i *next;
X	} day_info;
X
typedef struct m_i {
X	unsigned long holidays;
X	day_info *day[32];	/* including NOTE_DAY */
X	} month_info;
X
typedef struct y_i {
X	int year;
X	month_info *month[12];
X	struct y_i *next;
X	} year_info;
X
X
/*
X * Global variables:
X */
X
int do_define(), do_ifdef(), do_ifndef(), do_include(), do_undef();
char *trnlog(), *mk_path(), *mk_filespec();
X
extern char *getenv();
X
year_info *head = NULL;		/* head of internal data structure */
int nesting_level = 0;		/* level of include file nesting */
int init_month;			/* initial month, year, number of months */
int init_year;
int nmonths;
int curr_year;			/* current default year for date file entries */
char *words[100];		/* maximum number of words per date file line */
char lbuf[512];			/* maximum date file line size */
char *pp_sym[MAX_PP_SYMS];	/* preprocessor defined symbols */
char progname[STRSIZ];		/* program name (for error messages) */
char color[7];			/* colors of weekdays - cf. default_color[] */
int first_day_of_week = 0;	/* first day of week; 0 = Sun, 1 = Mon, etc. */
X
/*
X * Default values for command-line options:
X */
X
char default_color[7] = {		/* -b, -g */
X        GRAY, BLACK, BLACK, BLACK, BLACK, BLACK, GRAY   /* cf. COLOR_MSG */
X	};
X
int datefile_type = SYS_DATEFILE;	/* -e, -f */
char datefile[STRSIZ] = "";
char default_dir[STRSIZ] = "";
X
int rotate = ROTATE;			/* -l, -p */
X
char *draw_moons = "false";		/* -m, -M */
X
char dayfont[STRSIZ] = DAYFONT;		/* -d, -t, -n */
char titlefont[STRSIZ] = TITLEFONT;
char notesfont[STRSIZ] = NOTESFONT;
X
char lfoot[STRSIZ] = LFOOT;             /* -L, -C, -R */
char cfoot[STRSIZ] = CFOOT;
char rfoot[STRSIZ] = RFOOT;
X
char outfile[STRSIZ] = OUTFILE;		/* -o */
X
/*
X * Language-dependent strings (month and day names, option file keywords,
X * preprocessor tokens):
X */
X
static char *months[12] = {
X	"January", "February", "March", "April", "May", "June",
X	"July", "August", "September", "October", "November", "December"
X	};
X
static char *days[7] = {
X        "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday",
X        "Saturday"
X	};
X
/* preprocessor tokens - must be in same order as PP_XXXXX (cf. pp_token()) */
static struct pp {
X	char	*token;		/* name */
X	int	(*pfcn)();	/* dispatch routine */
X	} pp_info[] = {
X		{ "define",  do_define  },		/* PP_DEFINE	*/
X		{ "else",    NULL       },		/* PP_ELSE	*/
X		{ "endif",   NULL       },		/* PP_ENDIF	*/
X		{ "ifdef",   do_ifdef   },		/* PP_IFDEF	*/
X		{ "ifndef",  do_ifndef  },		/* PP_IFNDEF	*/
X		{ "include", do_include },		/* PP_INCLUDE	*/
X		{ "undef",   do_undef   },		/* PP_UNDEF	*/
X		{ NULL,      NULL       }		/* PP_OTHER	*/
X	};
X
#define MIN_DAY_LEN   2		/* minimum size of abbreviations  */
#define MIN_MONTH_LEN 3
#define MIN_PPTOK_LEN 3
X
#define ALL	"all"		/* command-line or date file keywords */
#define NOTE	"note"
#define OPT	"opt"
#define YEAR	"year"
X
#define COLOR_MSG	"Sat/Sun in gray, others in black"    /* cf. usage() */
X
/*
X * PostScript boilerplate
X */
X
#include "pcalinit.h"
X
X
/*
X * Main program - parse and validate command-line arguments, open files,
X * generate PostScript boilerplate and code to generate calendars.
X *
X * Program structure:
X *
X * main() looks for the environment variable (global symbol on VMS) PCAL_OPTS
X * and calles get_args() to parse it.  It then calls get_args() again to parse
X * the command line for the date file name, -D and -U options to be in effect
X * prior to reading the date file, and any numeric arguments (month, year, 
X * number of months).  It then calls read_datefile() to read and parse the
X * date file; any "opt" lines present will override the defaults for the
X * command-line flags.  It then calls get_args() again to process the other
X * command-line flags, which in turn override any specified earlier.
X *
X * main() then generates the common PostScript code and then calls pmonth() to
X * print the calendars.
X *
X * read_datefile() calls getline() to read the date file, do_xxxxx() to process
X * the preprocessor tokens, and parse() to parse each date line.
X *
X * getline() reads one or more lines from the date file, stripping comments
X * (# through end-of-line) and ignoring blank lines.
X *
X * parse() parses a line from the date file and processes it.  If "opt", it
X * calls loadwords() to split the line into tokens and get_args() to
X * process them.  If the line contains "note" or a date, it calls
X * enter_day_info() to enter the day and related text into the data structure.
X *
X * pmonth() calls find_holidays() to generate the list of holidays to be
X * printed in gray; it then calls find_daytext() to generate the text to
X * be printed inside the calendar boxes.
X *
X */
main(argc, argv)
X	int argc;
X	char **argv;
{
X	FILE *dfp = NULL;		/* date file pointer */
X	char *p, **ap;
X	int i, month, year, ngray;
X
#define DO_HEADER(phdr)	for (ap = phdr; *ap; ap++) PRT("%s\n", *ap)
X
X	INIT_COLORS;		/* set up default colors */
X
X	/* isolate root program name (for use in error messages) */
X
X	strcpy(progname, **argv ? *argv : "pcal");
X
X	if ((p = strrchr(progname, END_PATH)) != NULL)
X		strcpy(progname, ++p);
X	if ((p = strchr(progname, '.')) != NULL)
X		*p = '\0';
X
X	/*
X	 * Get the arguments from a) the environment variable, b) "opt" lines
X	 * in the date file, and c) the command line, in that order
X	 */
X
X	/* look for environment variable for options */
X
X	if ((p = getenv(PCAL_OPTS)) != NULL) {
X		strcpy(lbuf, "x ");	/* prepend a dummy token */
X		strcat(lbuf, p);
X		loadwords();
X		if (! get_args(words, ALL_FLAGS, FALSE)) {
X			usage();
X			exit(EXIT_FAILURE);
X		}
X	}
X
X	/* parse command-line arguments once to find name of date file, etc. */
X
X	if (!get_args(argv, DATEFILE_FLAGS, TRUE)) {
X		usage();
X		exit(EXIT_FAILURE);
X	}
X
X	/* Attempt to open the date file as specified by the [-e | -f] flags */
X
X	switch (datefile_type) {
X	case NO_DATEFILE:
X		dfp = NULL;
X		break;
X
X	case USER_DATEFILE:	
X		/* Attempt to open user-specified calendar file */
X		if ((dfp = fopen(datefile, "r")) == NULL) {
X			FPR(stderr, "%s: can't open file %s\n", progname, 
X				datefile);
X			exit(EXIT_FAILURE);
X		}
X		mk_path(default_dir, datefile);	/* extract path */
X		break;
X
X	case SYS_DATEFILE:
X		/* Attempt to open system-specified calendar file */
X		if ((p = trnlog(PCAL_DIR)) || (p = trnlog(HOME_DIR)))
X			strcpy(default_dir, p);
X
X		mk_filespec(datefile, default_dir, DATEFILE);
X		dfp = fopen(datefile, "r");	/* no error if nonexistent */
#ifdef ALT_DATEFILE
X		if (!dfp) {		/* try again with alternate file */
X			mk_filespec(datefile, default_dir, ALT_DATEFILE);
X			dfp = fopen(datefile, "r");
X		}
#endif
X		break;
X	}
X
X	/* read the date file (if any) and build internal data structure */
X
X	if (dfp) {
X		curr_year = init_year;
X		read_datefile(dfp, datefile);
X		fclose(dfp);
X	}
X
X	/* reparse command line - flags there supersede those in date file */
X
X	get_args(argv, OTHER_FLAGS, FALSE);
X
X	/* reshuffle days depending on first day of week selected by user */
X
X	set_first_day(first_day_of_week);
X
X	/* done with the arguments and flags - try to open the output file */
X
X	if (*outfile && freopen(outfile, "w", stdout) == (FILE *) NULL) {
X		FPR(stderr, "%s: can't open file %s\n", progname, outfile);
X		exit(EXIT_FAILURE);
X	}
X
X	/*
X	 * Write out PostScript prolog
X	 */
X
X	/* font names */
X
X 	PRT("%%!\n");
X	PRT("/titlefont /%s def\n/dayfont /%s def\n/notesfont /%s def\n", titlefont, dayfont, notesfont);
X
X	/* set offset from Sunday to selected first day of week */
X
X	PRT("/dayoffset %d def\n", first_day_of_week);
X
X	/* foot strings */
X
X	def_footstring(lfoot, 'L');
X	def_footstring(cfoot, 'C');
X	def_footstring(rfoot, 'R');
X
X	/* month names */
X
X	PRT("/month_names [");
X	for (i = 0; i < 12; i++)
X		PRT("%s(%s) ", i % 6 == 0 ? "\n\t" : "", months[i]);
X	PRT("] def\n");
X
X	/* day names */
X
X	PRT("/day_names [");
X	for (i = 0; i < 7; i++)
X		PRT("%s(%s) ", i % 6 == 0 ? "\n\t" : "", days[i]);
X	PRT("] def\n");
X
X	/* colors (black/gray) to print weekdays and holidays */
X
X	PRT("/day_gray [");
X	for (ngray = i = 0; i < 7; ngray += color[i++] == GRAY)
X		PRT(" %s", color[i] == GRAY ? "true" : "false");
X	PRT(" ] def\n");
X	PRT("/holiday_gray %s def\n", ngray <= 3 ? "true" : "false");
X
X	/* PostScript boilerplate (part 1 of 1) */
X	DO_HEADER(header);
X
X	/* landscape or portrait mode */
X
X	PRT("\n/landscape-p %s def\n", (rotate == LANDSCAPE) ? "true":"false");
X	PRT("/draw-moons %s def\n", draw_moons);
X
X	/*
X	 * Write out PostScript code to print calendars
X	 */
X
X	month = init_month;
X	year = init_year;
X
X	while (nmonths--) {
X		pmonth(month, year);
X		if (++month > DEC) {
X			month = JAN;
X			year++;
X		}
X	}
X
X	cleanup();
X
#ifdef VMS
X	FPR(stderr, "Output is in file %s\n", outfile);
#endif
X	exit(EXIT_SUCCESS);
}
X
/*
X * get_args - walk the argument list, parsing all arguments but processing only
X * those specified in "flags".  If "do_numargs" is TRUE, processes numeric
X * arguments (month, year, number of months) as well.
X */
int get_args(argv, flags, do_numargs)
X	char **argv;		/* argument list */
X	char *flags;		/* which flags to process */
X	int do_numargs;		/* process numeric arguments? */
{
X	char *p, *opt;
X	int i, do_flag;
X	long tmp;			/* for getting current month/year */
X	struct tm *p_tm;
X	int badopt = FALSE;		/* flag set if bad param   */
X	int nargs = 0;			/* count of non-flag args  */
X	int numargs[MAXARGS];		/* non-flag (numeric) args */
X
/* Look for the argument following flag - may be separated by spaces or
X * not (bumps argv in former case).  If no non-flag argument appears, set
X * "arg" to NULL (-b, -C, -d, -g, -L, -n, -o, -R, and -t without an argument
X * reset the corresponding option to its default value).
X */
#define GETARG(arg) arg = *(*argv + 2) ? *argv + 2 : \
X			(*(argv+1) && **(argv+1) != '-' ? *++argv : NULL)
X
X	/* Walk argument list, ignoring first element (program name) */
X
X 	while (*++argv) {
X
X		/* Assume that any non-flag argument is a numeric argument */
X		if (**argv != '-') {
X		    	if (do_numargs && nargs < MAXARGS)
X				numargs[nargs++] = atoi(*argv);
X			continue;
X		}
X
X		/* Is this flag among those to be processed beyond parsing? */
X
X		do_flag = strchr(flags, *(opt = *argv + 1)) != NULL;
X
X		switch (*opt) {
X
X		case '\0':		/* take - or -- as dummy flags */
X		case '-' :
X			break;
X
X		case 'b':		/* print day in black or gray */
X		case 'g':
X			GETARG(p);
X			if (do_flag)
X				if (p)
X					set_color(p, *opt == 'b' ? BLACK : GRAY);
X				else
X					INIT_COLORS;	/* reset to defaults */
X			break;
X
X		case 'C':		/* specify alternate center foot */
X			GETARG(p);
X			if (do_flag)
X				strcpy(cfoot, p ? p : CFOOT);
X			break;
X
X 		case 'd':		/* specify alternate day font */
X 			GETARG(p);
X			if (do_flag)
X				strcpy(dayfont, p ? p : DAYFONT);
X 			break;
X
X		case 'D':		/* define preprocessor symbol */
X			GETARG(p);
X			if (do_flag)
X				do_define(p);
X			break;
X
X		case 'e':		/* generate empty calendar */
X			if (do_flag) {
X				datefile_type = NO_DATEFILE;
X				datefile[0] = '\0';
X			}
X			break;
X
X		case 'f':		/* specify alternate date file */
X			GETARG(p);
X			if (p && do_flag) {
X				datefile_type = USER_DATEFILE;
X				strcpy(datefile, p);
X			}
X			break;
X
X		case 'L':		/* specify alternate left foot */
X			GETARG(p);
X			if (do_flag)
X				strcpy(lfoot, p ? p : LFOOT);
X			break;
X
X 		case 'l':		/* generate landscape calendar */
X			if (do_flag)
X	 			rotate = LANDSCAPE;
X 			break;
X 
X		case 'n':		/* specify alternate notes font */
X			GETARG(p);
X			if (do_flag)
X				strcpy(notesfont, p ? p : NOTESFONT);
X			break;
X
X		case 'o':		/* specify alternate output file */
X			GETARG(p);
X			if (do_flag)
X				strcpy(outfile, p ? p : OUTFILE);
X			break;
X
X 		case 'p':		/* generate portrait calendar */
X			if (do_flag)
X	 			rotate = PORTRAIT;
X 			break;
X
X 		case 'R':		/* specify alternate right foot */
X			GETARG(p);
X			if (do_flag)
X				strcpy(rfoot, p ? p : RFOOT);
X			break;
X
X
X 		case 't':		/* specify alternate title font */
X 			GETARG(p);
X			if (do_flag)
X				strcpy(titlefont, p ? p : TITLEFONT);
X 			break;
X
X		case 'U':		/* undef preprocessor symbol */
X			GETARG(p);
X			if (do_flag)
X				do_undef(p);
X			break;
X
X		case 'm':		/* draw four moons */
X			if (do_flag)
X				draw_moons = "4";
X			break;
X
X		case 'M':		/* draw a moon for each day */
X			if (do_flag)
X				draw_moons = "true";
X			break;
X
X		case 'F':		/* select starting day of week */
X			GETARG(p);
X			if (do_flag)  {
X				if (p)  {
X					for (i = 0; i < 7; i++)
X						if (ci_strncmp(p, days[i],
X							    MIN_DAY_LEN) == 0)
X							first_day_of_week = i;
X				}  else
X					first_day_of_week = 0;	/* default */
X			}
X			break;
X
X		default:		/* unrecognized flag */
X			FPR(stderr, "%s: illegal option -%s\n", progname, opt);
X			badopt = TRUE;
X			break;
X		}
X        }
X
X	if (!do_numargs)
X		return !badopt;		/* return TRUE if OK, FALSE if error */
X
X	/* Validate non-flag (numeric) parameters */
X
X	switch (nargs) {
X	case 0:		/* no arguments - print current month/year */
X		time(&tmp);
X		p_tm = localtime(&tmp);
X		init_month = p_tm->tm_mon + 1;
X		init_year = p_tm->tm_year;
X		nmonths = 1;
X		break;			
X	case 1:		/* one argument - print entire year */
X		init_month = JAN;
X		init_year = numargs[0];
X		nmonths = 12;
X		break;
X	default:	/* two or three arguments - print one or more months */
X		init_month = numargs[0];
X		init_year = numargs[1];
X		nmonths = nargs > 2 ? numargs[2] : 1;
X		break;
X	}
X
X	if (nmonths < 1)		/* ensure at least one month */
X		nmonths = 1;
X
X	/* check range of month and year */
X
X	if (init_month < JAN || init_month > DEC) {
X		FPR(stderr, "%s: month %d not in range 1 .. 12\n", progname,
X			init_month);
X		badopt = TRUE;
X	}
X	
X	if (init_year > 0 && init_year < 100)	/* treat nn as 19nn */
X		init_year += 1900;
X	
X	if (init_year < MIN_YR || init_year > MAX_YR) {
X		FPR(stderr, "%s year %d not in range %d .. %d\n", progname,
X			init_year, MIN_YR, MAX_YR);
X		badopt = TRUE;
X	}
X
X	return !badopt;		/* return TRUE if OK, FALSE if error */
}
X
X
X
/*
X *	usage - print message explaining correct usage of the command-line
X *	arguments and flags
X */
usage()
{
X	FPR(stderr, "\nUsage:\t%s [-b|-g DAY]* [-d|-n|-t FONT] [-e | -f FILE] [-o FILE] [-l | -p]\n", progname);
X	FPR(stderr, "\t[-m|-M] [-D|-U SYM] [-L|-C|-R STRING] [ [ [mm] yy ] | [mm yy n] ]\n");
X	FPR(stderr, "\n");
X	FPR(stderr, "\t-b DAY\t\tprint weekday DAY in black\n");
X	FPR(stderr, "\t-g DAY\t\tprint weekday DAY in gray\n");
X	FPR(stderr, "\t\t\t(default: %s)\n", COLOR_MSG);
X	FPR(stderr, "\n");
X	FPR(stderr, "\t-d FONT\t\tspecify alternate day name font (default: %s)\n",
X		DAYFONT);
X	FPR(stderr, "\t-n FONT\t\tspecify alternate notes font (default: %s)\n",
X		NOTESFONT);
X	FPR(stderr, "\t-t FONT\t\tspecify alternate title font (default: %s)\n",
X		TITLEFONT);
X	FPR(stderr, "\n");
X	FPR(stderr, "\t-e\t\tgenerate empty calendar (ignore date file)\n");
X	FPR(stderr, "\t-f FILE\t\tspecify alternate date file (default: %s)\n",
X		DATEFILE);
X	FPR(stderr, "\t-o FILE\t\tspecify alternate output file (default: %s)\n",
X		OUTFILE[0] ? OUTFILE : "stdout");
X	FPR(stderr, "\n");
X	FPR(stderr, "\t-l\t\tgenerate landscape-style calendars");
#if (ROTATE == LANDSCAPE)
X	FPR(stderr, " (default)");
#endif
X	FPR(stderr, "\n\t-p\t\tgenerate portrait-style calendars");
#if (ROTATE == PORTRAIT)
X	FPR(stderr, " (default)");
#endif
X	FPR(stderr, "\n\n");
X	FPR(stderr, "\t-m\t\t\draw a \"moon\" icon on days of full, new, and half moons\n");
#ifdef VMS
X	FPR(stderr, "\t-\"M\"\t\t\draw a \"moon\" icon every day (default: no moons)\n");
X	FPR(stderr, "\n");
X	FPR(stderr, "\t-\"D\" SYM\tdefine preprocessor symbol\n");
X	FPR(stderr, "\t-\"U\" SYM\tundefine preprocessor symbol\n");
X	FPR(stderr, "\n");
X	FPR(stderr, "\t-\"L\" STRING\tspecify left foot string (default: \"%s\")\n",
X	    LFOOT);
X	FPR(stderr, "\t-\"C\" STRING\tspecify center foot string (default: \"%s\")\n",
X	    CFOOT);
X	FPR(stderr, "\t-\"R\" STRING\tspecify right foot string (default: \"%s\")\n",
X	    RFOOT);
#else
X	FPR(stderr, "\t-M\t\t\draw a \"moon\" icon every day (default: no moons)\n");
X	FPR(stderr, "\n");
X	FPR(stderr, "\t-D SYM\t\tdefine preprocessor symbol\n");
X	FPR(stderr, "\t-U SYM\t\tundefine preprocessor symbol\n");
X	FPR(stderr, "\n");
X	FPR(stderr, "\t-L STRING\tspecify left foot string (default: \"%s\")\n",
X	    LFOOT);
X	FPR(stderr, "\t-C STRING\tspecify center foot string (default: \"%s\")\n",
X	    CFOOT);
X	FPR(stderr, "\t-R STRING\tspecify right foot string (default: \"%s\")\n",
X	    RFOOT);
#endif
X	FPR(stderr, "\n");
X	FPR(stderr, "\tyy\t\tgenerate calendar for year yy (19yy if yy < 100)\n");
X	FPR(stderr, "\tmm yy\t\tgenerate calendar for month mm (Jan = 1), year yy\n");
X	FPR(stderr, "\tmm yy n\t\tgenerate calendars for n months, starting at mm/yy\n");
X	FPR(stderr, "\t(default)\tgenerate calendar for current month/year\n");
}
X
X
/*
X * General-purpose utility routines
X */
X
X
/*
X * alloc - interface to calloc(); terminates if unsuccessful
X */
char *alloc(size)
X	int size;
{
X	char *p;
X	extern char *calloc();
X
X	if (size == 0)		/* not all calloc()s like null requests */
X		size = 1;
X
X	if ((p = calloc(1, size)) == NULL) {
X		FPR(stderr, "%s: out of memory\n", progname);
X		exit(EXIT_FAILURE);
X	}
X
X	return p;
}
X
X
/*
X * ci_str{n}cmp - case-insensitive flavors of strcmp(), strncmp()
X */
int ci_strcmp(s1, s2)
register char *s1, *s2;
{
X	register char c1, c2;
X
X	for ( ; (c1 = TOLOWER(*s1)) == (c2 = TOLOWER(*s2)); s1++, s2++)
X		if (c1 == '\0')
X			return 0;
X
X	return c1 - c2;
}
X
X
int ci_strncmp(s1, s2, n)
register char *s1, *s2;
int n;
{
X	register char c1, c2;
X
X	for ( ; --n >= 0 && (c1 = TOLOWER(*s1)) == (c2 = TOLOWER(*s2)); s1++, s2++)
X		if (c1 == '\0')
X			return 0;
X
X	return n < 0 ? 0 : c1 - c2;
}
X
X
/*
X * Preprocessor token and symbol table routines
X */
X
X
/*
X * find_sym - look up symbol; return symbol table index if found, PP_SYM_UNDEF
X * if not found
X */
int find_sym(sym)
X	char *sym;
{
X	int i;
X
X	if (!sym)
X		return PP_SYM_UNDEF;
X
X	for (i = 0; i < MAX_PP_SYMS; i++)
X		if (pp_sym[i] && ci_strcmp(pp_sym[i], sym) == 0)
X			return i;
X
X	return PP_SYM_UNDEF;
}
X
X
/*
X * do_ifdef - return TRUE if 'sym' is currently defined; FALSE if not
X */
int do_ifdef(sym)
X	char *sym;
{
X	return find_sym(sym) != PP_SYM_UNDEF;
}
X
X
/*
X * do_ifndef - return FALSE if 'sym' is currently defined; TRUE if not
X */
int do_ifndef(sym)
X	char *sym;
{
X	return find_sym(sym) == PP_SYM_UNDEF;
}
X
X
/*
X * do_define - enter 'sym' into symbol table; if 'sym' NULL, clear symbol table
X */
do_define(sym)
X	char *sym;
{
X	int i;
X
X	if (! sym) {		/* null argument - clear all definitions */
X		clear_syms();
X		return;
X	}
X
X	if (do_ifdef(sym))	/* already defined? */
X		return;
X
X	for (i = 0; i < MAX_PP_SYMS; i++)	/* find room for it */
X		if (! pp_sym[i]) {
X			strcpy(pp_sym[i] = alloc(strlen(sym)+1), sym);
X			return;
X		}
X
X	FPR(stderr, "%s: no room to define %s\n", progname, sym);
}
X
X
/*
X * do_undef - undefine 'sym' and free its space; no error if not defined
X */
do_undef(sym)
X	char *sym;
{
X	int i;
X
X	if (! sym) 
X		return;
X
X	if ((i = find_sym(sym)) != PP_SYM_UNDEF) {
X		free(pp_sym[i]);
X		pp_sym[i] = NULL;
X	}
}
X
X
/*
X * do_include - include specified file (optionally in "" or <>)
X */
do_include(path, name)
X	char *path;		/* path to file */
X	char *name;		/* file name */
{
X	FILE *fp;
X	char *p, incfile[STRSIZ], tmpnam[STRSIZ];
X
X	if (! name)		/* whoops, no date file */
X		return;
X
X	/* copy name, stripping "" or <> */
X	strcpy(tmpnam, name + (*name == '"' || *name == '<'));
X	if ((p = LASTCHAR(tmpnam)) && *p == '"' || *p == '>')
X		*p = '\0';
X
X	if ((fp = fopen(mk_filespec(incfile, path, tmpnam), "r")) == NULL) {
X		FPR(stderr, "%s: can't open file %s\n",	progname, incfile);
X		exit(EXIT_FAILURE);
X	}
X
X	read_datefile(fp, incfile);
X	fclose(fp);
}
X
X
/*
X * pp_token - look up 'token' in list of preprocessor tokens; return its
X * index if found, PP_OTHER if not (N.B.: this relies on the ordering of
X * PP_XXXXX and pp_info[]; see comments at their definitions).
X */
int pp_token(token)
X	char *token;
{
X	struct pp *p;
X
X	for (p = pp_info;
X             p->token && ci_strncmp(token, p->token, MIN_PPTOK_LEN);
X	     p++)
X		;
X
X	return p - pp_info;
}
X
/*
X * Internal data structure routines
X */
X
X
/*
X * find_year - find record in year list; optionally create if not present 
X */
year_info *find_year(year, insert)	/* find record in year list */
X	int year;
X	int insert;			/* insert if missing */
{
X	year_info *pyear, *plast, *p;
X
X	for (plast = NULL, pyear = head;		/* search linked list */
X	     pyear && pyear->year < year;
X	     plast = pyear, pyear = pyear->next)
X		;
X
X	if (pyear && pyear->year == year)		/* found - return it */
X		return pyear;
X
X	if (insert) {		/* not found - insert it if requested */
X		p = (year_info *) alloc(sizeof(year_info));	/* create new record */
X		p->year = year;
X
X		p->next = pyear;				/* link it in */
X		return *(plast ? &plast->next : &head) = p;
X	}
X	else
X		return NULL;
}
X
X
/*
X * enter_day_info - enter text for specified day; avoid entering duplicates.
X * returns PARSE_INVDATE if date invalid, PARSE_OK if OK
X */
int enter_day_info(m, d, y, text_type, pword)	/* fill in information for given day */
X	int m, d, y;
X	int text_type;
X	char **pword;
{
X	static year_info *pyear;
X	static int prev_year = 0;
X	month_info *pmonth;
X	day_info *pday, *plast;
X	int is_holiday = text_type == HOLIDAY_TEXT;
X
X	if (! is_valid(m, d == NOTE_DAY && text_type == NOTE_TEXT ? 1 : d, y))
X		return PARSE_INVDATE;
X
X	if (y != prev_year)		/* avoid unnecessary year lookup */
X		pyear = find_year(y, 1);
X
X	--m, --d;			/* adjust for use as subscripts */
X
X	if ((pmonth = pyear->month[m]) == NULL)	/* find/create month record */
X		pyear->month[m] = pmonth = (month_info *) alloc(sizeof(month_info));
X
X	if (is_holiday)
X		pmonth->holidays |= (1 << d);
X
X	/* insert text for day at end of list (preserving the order of entry
X	 * for multiple lines on same day); eliminate those differing only
X	 * in spacing and capitalization from existing entries
X         */
X
X	get_text(pword);	/* consolidate text into lbuf */
X
X	if (*lbuf) {
X		for (plast = NULL, pday = pmonth->day[d];
X		     pday;
X		     plast = pday, pday = pday->next)
X			if (ci_strcmp(pday->text, lbuf) == 0) {
X				pday->is_holiday |= is_holiday;
X				return PARSE_OK;
X			}
X
X		/* unique - add to end of list */
X
X		pday = (day_info *) alloc(sizeof(day_info));
X		pday->is_holiday = is_holiday;
X		strcpy(pday->text = (char *) alloc(strlen(lbuf)+1), lbuf);
X		pday->next = NULL;
X		*(plast ? &plast->next : &pmonth->day[d]) = pday;
X	}
X
X	return PARSE_OK;
}
X
X
/*
X * Housekeeping routines to free allocated data
X */
X
X
/*
X * clear_syms - clear and deallocate the symbol table
X */
clear_syms()
{
X	int i;
X
X	for (i = 0; i < MAX_PP_SYMS; i++)
X		if (pp_sym[i]) {
X			free(pp_sym[i]);
X			pp_sym[i] = NULL;
X		}
}
X
X
/*
X * cleanup - free all allocated data
X */
cleanup()
{
X	int i, j;
X	year_info *py, *pny;
X	month_info *pm;
X	day_info *pd, *pnd;
X
X	for (py = head; py; py = pny) {		/* main data structure */
X		pny = py->next;
X		for (i = 0; i < 12; i++) {
X			if ((pm = py->month[i]) == NULL)
X				continue;
X			for (j = 0; j < NOTE_DAY; j++)
X				for (pd = pm->day[j]; pd; pd = pnd) {
X					pnd = pd->next;
X					free(pd->text);
X					free(pd);
X				}
X			free(pm);
X		}
X	free(py);
X	}
X
X	clear_syms();				/* symbol table */
X
}
X
X
/*
X * Utility routines for date and text parsing and entry
X */
X
/*
X * get_month - convert numeric or alpha string to month; return 1-12 if valid,
X * 0 if not valid
X */
int get_month(cp)
X	char *cp;
{
X	int mm;
X
X	if (! cp)
X		return 0;
X
X	if (isdigit(*cp))
X		mm = atoi(cp);
X	else
X		for (mm = JAN;
X		     mm <= DEC && ci_strncmp(cp, months[mm-1], MIN_MONTH_LEN);
X		     mm++)
X			;
X
X	return mm >= JAN && mm <= DEC ? mm : 0;
}
X
X
/*
X * date_type - examine token and return date type code; if DF_MONTH, fill
X * in number of month
X */
int date_type(cp, pm)
X	char *cp;	/* pointer to start of token */
X	int *pm;	/* month value (returned)    */
{
X	int mm;
X
X	if (isdigit(*cp))
X		return DF_DATE;
X
X	if (ci_strncmp(cp, YEAR, strlen(YEAR)) == 0)
X		return DF_YEAR;
X
X	if (ci_strncmp(cp, OPT, strlen(OPT)) == 0)
X		return DF_OPT;
X
X	if (ci_strncmp(cp, NOTE, strlen(NOTE)) == 0)
X		return DF_NOTE;
X
X	return (mm = get_month(cp)) != 0 ? (*pm = mm, DF_MONTH) : DF_OTHER;
}
X
X
/*
X * is_valid - return TRUE if m/d/y is a valid date
X */
int is_valid(m, d, y)
X	register int m, d, y;
{
X	static char len[13] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
X
X	return m >= JAN && m <= DEC && 
X		d >= 1 && d <= (len[m] + (m == FEB && IS_LEAP(y)));
}
X
X
/*
X * loadwords - tokenize line buffer into word array, return word count.
X * differs from old loadwords() in that it handles quoted (" or ') strings
X */
X
int loadwords()
{
X	register char *pstr, *ptok;
X	char *delim, **ap;
X	register int i;
X
X	pstr = lbuf;
X
X	for (i = 0, ap = words; TRUE; i++, ap++) {
X		delim =	*pstr == '"'  ? "\"" :
X			*pstr == '\'' ? "'"  :
X			WHITESPACE;
X
X		ptok = pstr += strspn(pstr, delim); /* look for next token */
X
X		if (! *pstr) {		/* end of lbuf? */
X			*ap = NULL;	/* add null ptr at end */
X			return i;	/* return count of non-null ptrs */
X			}
X
X		if (*ptok == '"' || *ptok == '\'')	/* bump past quote */
X			ptok++;
X
X		*ap = ptok;				/* save token ptr */
X
X		pstr += strcspn(pstr, delim);	 	/* skip past token */
X
X		if (*pstr)				/* terminate token */
X			*pstr++ = '\0';
X		}
X
}
X
X
/*
X * get_text - retrieve remaining text in lbuf and transform in place, removing
X * leading/trailing whitespace and condensing runs of whitespace to one blank
X */
get_text(pword)
X	char **pword;		/* pointer to first desired word in "words" */
{
char *pbuf, *p;
X
/* copy words back to lbuf in place, separating by one blank */
X
for (pbuf = lbuf; p = *pword; *pbuf++ = *++pword ? ' ' : '\0')
X	while (*p)
X		*pbuf++ = *p++;
X
if (pbuf == lbuf)
X	*lbuf = '\0';
}
X
X
/*
X * print_text - print tokens in text (assumed separated by single blank)
X * in PostScript format; convert '(' or ')' to octal escape
X */
print_text(p)
X	char *p;
{
X	char c;
X
X	PRT("(");
X	for ( ; c = *p ; p++) {
X		if (c == ' ')
X			PRT(")\n(");
X		else
X			PRT(c == '(' || c == ')' ? "\\%03o" : "%c", c);
X	}
X	PRT(")\n");
}
X
X
/*
X * def_footstring - print definition for foot string, again converting '('
X * or ')' to octal escape
X */
def_footstring(p, c)
X	char *p;			/* definition */
X	char c;				/* L, C, or R */
{
X
X	PRT("/%cfootstring (", c);
X
X	while (c = *p++)
X		PRT(c == '(' || c == ')' ? "\\%03o" : "%c", c);
X
X	PRT(") def\n");
}
X
X
/*
X * set_color - set one or all weekdays to print in black or gray
X */
set_color(day, col)
X	char *day;		/* weekday name (or "all") */
X	int  col;		/* select black or gray */
{
X	int i, do_all;
X
X	do_all = ci_strncmp(day, ALL, strlen(ALL)) == 0;	/* set all days? */
X
X	for (i = 0; i < 7; i++)
X		if (do_all || ci_strncmp(day, days[i], MIN_DAY_LEN) == 0)
X			color[i] = col;
}
X
/*
X * set_first_day - change the first day of the week from Sunday to any day
X */
set_first_day(day)
X	int day;		/* day of week number; 0 = Sun, 1 = Mon, etc. */
{
X	char temp_color[7], *temp_days[7];
X	int i, from_index;
X
X	if (day == 0)		/* if Sunday, no need to change things */
X		return;
X
X	/*
X	 * first we save off the data items in the "color" and "days"
X	 * arrays which must be reshuffled.
X	 */
X
X	for (i = 0; i < 7; i++)  {
X		temp_color[i] = color[i];
X		temp_days[i] = days[i];
X	}
X
X	/*
X	 * now we reshuffle the copies back into the originals after
X         * offsetting by the apprpriate number of days
X	 */
X
X	for (i = 0; i < 7; i++)  {
X		from_index = (i + day) % 7;
X		color[i] = temp_color[from_index];
X		days[i] = temp_days[from_index];
X	}
}
X
/*
X * parse - enter date file data (in lbuf[]) into data structure
X *
X * Looks for an entry of one of the following forms:
X *
X *	year <year>
X *	<month_name> <day>{*} {<text>}
X *	<month><sep><day>{<sep><year>}{*} {<text>}
X *	opt <options>
X *	note <month_name> <text>
X *	note <month> <text>
X *
X * where
X *	<month_name> := first 3+ characters of name of month (in English)
X *	<sep> := one or more non-numeric, non-space, non-'*' characters
X *	<options> := any command-line option except -e, -f, -D, -U
X *
X * Enters information for year, month, and day into data structure for
X * subsequent retrieval.  Lines of form "opt <options>" override any
X * defaults.
X *
X * N.B.: "inc" and other cpp-like lines are handled in read_datefile().
X *
X */
int parse()
{
X	register char *cp;
X	char **pword, *p;
X	int mm, dd, yy;
X	int text_type;
X
/* macro to skip numeric field */
X
#define SKIP_FIELD(p) \
X	if (1) {while (isdigit(*p)) p++; while (*p && !isdigit(*p)) p++;} else
X
X	/*
X	 * Get first field - can be either "year", "opt", "note", a month
X	 * name, or a (complete) numeric date spec
X         */
X
X	cp = *(pword = words);
X
X	switch (date_type(cp, &mm)) {
X
X	case DF_YEAR:
X		if ((cp = *++pword) != NULL && (yy = atoi(cp)) > 0) {
X			if (yy < 100)
X				yy += 1900;
X			curr_year = yy;
X			return PARSE_OK;
X		}
X		return PARSE_INVDATE;	/* year missing or invalid */
X		break;
X
X	case DF_OPT:
X	 	if (!get_args(words, OTHER_FLAGS, FALSE)) {
X			usage();
X			exit(EXIT_FAILURE);
X		}
X		return PARSE_OK;
X		break;
X
X	case DF_NOTE:
X		mm = get_month(*++pword);
X		return enter_day_info(mm, NOTE_DAY, curr_year, NOTE_TEXT,
X			++pword);
X		break;
X
X	case DF_MONTH:
X		if ((cp = *++pword) == NULL || (dd = atoi(cp)) == 0)
X			return PARSE_INVDATE;
X		text_type = cp[strlen(cp) - 1] == '*' ? HOLIDAY_TEXT : DAY_TEXT;
X
X		return enter_day_info(mm, dd, curr_year, text_type, ++pword);
X		break;
X
X	case DF_DATE:
X		text_type = cp[strlen(cp) - 1] == '*' ? HOLIDAY_TEXT : DAY_TEXT;
X 
X		/* extract month and day fields */
X
X		mm = atoi(cp);
X		SKIP_FIELD(cp);
X
X		dd = atoi(cp);
X		SKIP_FIELD(cp);
X
X		/* Numeric dates may (or may not) have a year */
X
X		if ((yy = atoi(cp)) > 0) {
X			if (yy < 100)
X				yy += 1900;
X			curr_year = yy; /* if present, reset current year */
X		}
X
X		return enter_day_info(mm, dd, curr_year, text_type, ++pword);
X		break;
X
X	case DF_OTHER:
X		return PARSE_INVLINE;		/* line not recognized */
X		break;
X	}
}
X
X
/*
X * getline - read next non-null line of input file into lbuf; return 0 on EOF
X */
int getline(dfp, pline)
X	FILE *dfp;
X	int *pline;
{
X	register char *cp;
X	register int c;
X	int in_comment;		/* comments: from '#' to end-of-line */
X
X	cp = lbuf;
X	do {
X		in_comment = FALSE;
X		while ((c = getc(dfp)) != '\n' && c != EOF) {
X			if (c == '#')
X				in_comment = TRUE;
X
X			/* ignore comments and leading white space */
X			if (in_comment ||
X			    (cp == lbuf && (c == ' ' || c == '\t')))
X				continue;
X			*cp++ = c;
X		}
X		if (c == EOF)
X			return FALSE;
X
X		(*pline)++;	/* bump line number */
X
X	} while (cp == lbuf);	/* ignore empty lines */
X
X	*cp = '\0';
X	return TRUE;
}
X
X
/*
X * read_datefile - read and parse date file, handling preprocessor lines
X */
read_datefile(fp, filename)
X	FILE *fp;		/* file pointer (assumed open) */
X	char *filename;		/* file name (for error messages) */
{
X	static int file_level = 0;
X	int if_level = 0;
X	int restart_level = 0;
X	int line = 0;
X	int processing = TRUE;
X
X	int pptype, extra, ntokens, save_year;
X	int (*pfcn)();
X	char *ptok;
X	char **pword;
X	char msg[STRSIZ], incpath[STRSIZ];
X
#define ERR(errmsg)	FPR(stderr, "%s: %s in file %s, line %d\n", \
X			progname, errmsg, filename, line);
X
X	if (fp == NULL)		/* whoops, no date file */
X		return;
X
X	if (++file_level > MAX_NESTING) {
X		ERR("maximum file nesting level exceeded");
X		exit(EXIT_FAILURE);
X	}
X
X	save_year = curr_year;		/* save default year */
X
X	/* read lines until EOF */
X
X	while (getline(fp, &line)) {
X
X		ntokens = loadwords();		/* split line into tokens */
X		pword = words;			/* point to the first */
X
X		/* get token type and pointers to function and name */
X
X		pptype = pp_token(*pword++);
X		pfcn = pp_info[pptype].pfcn;
X		ptok = pp_info[pptype].token;
X
X		switch (pptype) {
X
X		case PP_DEFINE:
X		case PP_UNDEF:
X			if (processing)
X				(*pfcn)(*pword);
X			extra = ntokens > 2;
X			break;
X
X		case PP_ELSE:
X			if (if_level < 1) {
X				ERR("unmatched \"else\"");
X				break;
X			}
X
X			if (processing) {
X				processing = FALSE;	/* disable processing */
X				restart_level = if_level;
X			} else if (if_level == restart_level) {
X				processing = TRUE;	/* re-enable processing */
X				restart_level = 0;
X			}
X			extra = ntokens > 1;
X			break;
X
X		case PP_ENDIF:
X			if (if_level < 1) {
X				ERR("unmatched \"end\"");
X				break;
X			}
X
X			if (! processing && if_level == restart_level) {
X				processing = TRUE;	/* re-enable processing */
X				restart_level = 0;
X			}
X			if_level--;
X			extra = ntokens > 1;
X			break;
X
X		case PP_IFDEF:
X		case PP_IFNDEF:
X			if_level++;
X			if (processing) {
X				if (! (*pfcn)(*pword)) {
X					processing = FALSE;
X					restart_level = if_level;
X				}
X			}
X			extra = ntokens > 2;
X			break;
X
X		case PP_INCLUDE:
X			if (processing)
X				do_include(mk_path(incpath, filename), *pword);
X			extra = ntokens > 2;
X			break;
X
X		case PP_OTHER:	/* none of the above - parse as date */
X			if (processing) {
X				switch (parse()) {
X
X				case PARSE_INVDATE:
X					ERR("invalid date");
X					break;
X
X				case PARSE_INVLINE:
X					ERR("unrecognized line");
X					break;
X
X				}
X			}
X			extra = FALSE;
X			break;
X
X		} /* end switch */
X
X		if (extra) {		/* extraneous data? */
X			sprintf(msg, "extraneous data on \"%s\" line", ptok);
X			ERR(msg);
X		}
X
X	} /* end while */
X
X	if (if_level > 0)
X		FPR(stderr, "%s: unterminated if{n}def..{else..}endif in %s\n",
X			progname, filename);
X
X	file_level--;
X	curr_year = save_year;		/* restore default year */
}
X
/*
X * Routines to extract and print data
X */
X
X
/*
X * Browse through the data structure looking for day, holiday, or notes text
X * in the specified month/year
X */
find_daytext(month, year, is_holiday)
X	int month, year;
X	int is_holiday;
{
X	register int day;
X	year_info *py;
X	month_info *pm;
X	register day_info *pd;
X	int first;
X	char *fcn = is_holiday ? "holidaytext" : "daytext";
X
X	/* if no text for this year and month, return */
X
X	if ((py = find_year(year, FALSE)) == NULL ||
X	    (pm = py->month[month-1]) == NULL)
X		return;
X
X	/* walk array of day text pointers and linked lists of text */
X
X	for (day = 1; day <= NOTE_DAY; day++) {
X		for (pd = pm->day[day-1], first = TRUE;
X		     pd;
X		     pd = pd->next) {
X			if (pd->is_holiday != is_holiday)
X				continue;
X			if (first) {
X				if (day != NOTE_DAY)	/* set up call */
X					PRT("%d ", day);
X				printf("[ \n");
X			} else
X				PRT("(.p)\n");		/* separate text */
X			print_text(pd->text);
X			first = FALSE;
X		}
X		if (! first)		/* wrap up call (if one made) */
X			PRT("] %s\n", day == NOTE_DAY ? "notetext" : fcn);
X	}
}
X
X
/*
X * Browse through the date file looking for holidays in specified month/year
X */
find_holidays(month, year)
X	int month, year;
{
X	register int day;
X	register unsigned long holidays;
X	year_info *py;
X	month_info *pm;
X
X	pm = (py = find_year(year, FALSE)) ? py->month[month-1] : NULL;
X
X 	PRT("/note_block %s def\n", pm && pm->day[NOTE_DAY-1] ? "true" :
X	    "false");		/* are there notes? */
X
X	PRT("/holidays [");	/* start definition of list */
X
X	for (holidays = pm ? pm->holidays : 0, day = 1;
X	     holidays;
X	     holidays >>= 1, day++)
X		if (holidays & 01)
X			PRT(" %d", day);
X
X	PRT(" 99 ] def\n");	/* terminate with dummy entry */
X
}
X
X
/*
X * pmonth - generate calendar for specified month/year
X */
pmonth(month, year)
X	int month, year;
{
X
X	PRT("/year %d def\n", year);		/* set up year and month */
X	PRT("/month %d def\n", month);
X	find_holidays(month, year);		/* make list of holidays */
X	PRT("printmonth\n");
X	find_daytext(month, year, TRUE);	/* holiday text */
X	find_daytext(month, year, FALSE);	/* day and note text */
X	PRT("showpage\n");
}
X
X
/*
X * Routines dealing with translation of file specifications (VMS, Un*x)
X */
X
#ifdef VMS
/*
X * mk_path - extract the path component from VMS file spec
X */
char *mk_path(path, filespec)
X	char *path;		/* output path */
X	char *filespec;		/* input filespec */
{
X	char *p;
X
X	strcpy(path, filespec);
X	if (!(p = strchr(path, ']')) && !(p = strchr(path, ':')))
X		p = path - 1;	/* return null string if no path */
X	*++p = '\0';
X
X	return path;
}
X
X
/*
X * mk_filespec - merge VMS path and file names, where latter can be relative
X */
X
char *mk_filespec(filespec, path, name)
X	char *filespec;		/* output filespec */
X	char *path;		/* input path */
X	char *name;		/* input file name */
{
X	char *p;
X
X	*filespec = '\0';
X
X	/* copy name intact if absolute; else merge path and relative name */
X	if (!strchr(name, ':')) {
X		strcpy(filespec, path);
X		if ((p = LASTCHAR(filespec)) && *p == END_PATH &&
X		    name[0] == START_PATH && strchr(".-", name[1]))
X			*p = *++name == '-' ? '.' : '\0';
X	}
X
X	return strcat(filespec, name);
}
X
X
/*
X * trnlog - return translation of VMS logical name (null if missing)
X */
char *trnlog(logname)	/* look up logical name */
X    char *logname;
{
static char trnbuf[STRSIZ];
X
$DESCRIPTOR(src, logname);
$DESCRIPTOR(dst, trnbuf);
short len;
int ret;
X
src.dsc$w_length  = strlen(logname);
ret = LIB$SYS_TRNLOG(&src, &len, &dst);
return ret == SS$_NORMAL ? (trnbuf[len] = '\0', trnbuf) : NULL;
}
X
#else
X
/*
X * mk_path - extract the path component from a Un*x file spec
X */
char *mk_path(path, filespec)
X	char *path;		/* output path */
X	char *filespec;		/* input filespec */
{
X	char *p;
X
X	strcpy(path, filespec);
X	if (! (p = strrchr(path, END_PATH)) )
X		p = path - 1;	/* return null string if no path */
X
X	*++p = '\0';
X	return path;
}
X
X
/*
X * mk_filespec - merge Un*x path and file names, where latter can be relative
X */
X
char *mk_filespec(filespec, path, name)
X	char *filespec;		/* output filespec */
X	char *path;		/* input path */
X	char *name;		/* input file name */
{
X	char *p;
X
X	*filespec = '\0';
X
X	/* copy name intact if absolute; else merge path and relative name */
X
X	/* if path starts with "~/", translate it for user */
X	if (strncmp(name, "~/", 2) == 0 && (p = trnlog(HOME_DIR)) != NULL) {
X		strcpy(filespec, p);
X		if ((p = LASTCHAR(filespec)) && *p != END_PATH)
X			*++p = END_PATH, *++p = '\0';
X		name += 2;		/* skip "~/" */
X	}
X	else if (*name != START_PATH) {		/* relative path */
X		strcpy(filespec, path);
X		if ((p = LASTCHAR(filespec)) && *p != END_PATH)
X			*++p = END_PATH, *++p = '\0';
X	}
X
X	return strcat(filespec, name);
}
X
X
/*
X * trnlog - return translation of Un*x environment variable
X */
char *trnlog(logname)	/* look up logical name */
X    char *logname;
{
return getenv(logname);
}
X
#endif
SHAR_EOF
chmod 0644 pcal.c ||
echo 'restore of pcal.c failed'
Wc_c="`wc -c < 'pcal.c'`"
test 46687 -eq "$Wc_c" ||
	echo 'pcal.c: original size 46687, current size' "$Wc_c"
fi
true || echo 'restore of pcal.hlp failed'
echo End of part 1, continue with part 2
exit 0