[alt.sources] Pcal v3.0, part 3 of 3

jbr0@cbnews.att.com (joseph.a.brownlee) (01/02/91)

#!/bin/sh
# This is part 03 of a multipart archive
# ============= 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  VERSION_STRING[]	= "@(#)pcal v3.0 - 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, Andrew Rogers (who also did
X * the VMS port), Mark Kantrowitz, and 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 *	3.0	AWR	12/10/90	Support concept of "weekday", "workday", *					and "holiday" (and converses)
X *
X *		AWR	11/13/90	Substantial revision of program logic:
X *					extracted pcaldefs.h and pcallang.h,
X *					moving all language dependencies (even
X *					flag names) to the latter.
X *
X *					add -I flag to reinitialize all
X *	 				flags to program defaults; -j and -J
X *					flags (print Julian dates); add -x,
X *				 	-y, -X, -Y flags (as per Ed Hand)
X *					for output scaling and translation
X *
X *					allow "wildcard" dates (e.g., "all
X *					Thursday{s} in Oct", "last Friday in
X *					all") and notes ("note all <text>);
X *					print full "help" message (including
X *					date file syntax)
X *
X *	2.6	AWR	10/15/90	parse floating dates (e.g. "first
X *					Monday in September") and relative
X *					floating dates (e.g., "Friday after
X *					fourth Thursday in November"); simplify
X *					logic of -F option; add -F to usage 
X *					message; replace COLOR_MSG() string
X *					with color_msg() routine; add -h
X *					(help message) and -A | -E (American |
X *					European date format) flags; renamed
X *					flag sets for clarity; more comments
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/AWR	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 *		-h		(command line only) write "usage" message
X *				to stdout
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 alternate day to be displayed as the 
X *				first day of the week (default: Sunday)
X *
X *		-A		dates are in American format (e.g., 10/15/90,
X *				Oct 15) (default)
X *		-E		dates are in European format (e.g., 15.10.90,
X *				15 Oct)
X *
X *		-x <XSCALE>	These two options can be used to change
X *		-y <YSCALE>	the size of the calendar.
X *
X *		-X <XTRANS>	These two options can be used to relocate
X *		-Y <YTRANS>	the position of the calendar on the page.
X *
X *		-j		print Julian dates (day of year)
X *		-J		print Julian dates and days remaining
X *				(default: neither)
X *
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, -h, -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 *
X *	Date file syntax:
X *
X *	The following rules describe the syntax of date file entries:
X *
X *	  year <year>
X *
X *	  opt <options>
X *
X *	  note <month_name> <text>
X *	  note <month> <text>
X *	  note all <text>
X *
X *	  if -A flag (American date formats) specified:
X *	    <month_spec> <day>{*} {<text>}
X *	    <month><sep><day>{<sep><year>}{*} {<text>}
X *
X *	  if -E flag (European date formats) specified:
X *	    <day> <month_spec>{*} {<text>}
X *	    <day><sep><month>{<sep><year>}{*} {<text>}
X *
X *	  <ordinal> <day_spec> in <month_spec>{*} {<text>}
X *	  <day_spec> <prep> <date_spec>
X *
X *	where
X *	  {x}	  means x is optional
X *
X *	  <date_spec> := any of the above date specs (not year, note, or opt)
X *	  <month_name> := first 3+ characters of name of month, or "all"
X *	  <day_name> := first 3+ characters of name of weekday, "day",
X *			"weekday", "workday", "holiday", "nonweekday",
X *			"nonworkday", or "nonholiday"
X *	  <ordinal> := "first", "1st", ... "fifth", "5th", "last", or "all"
X *	  <prep> := "before", "preceding", "after", "following", "on_or_before",
X *			or "on_or_after"
X *	  <sep> := one or more non-numeric, non-space, non-'*' characters
X *	  <month>, <day>, <year> are the numeric forms
X *
X *	  <options> := any command-line option except -e, -f, -h, -D, -U
X *
X *	Comments start with '#' and run through end-of-line.
X *
X *	Holidays may be flagged by specifying '*' as the last character of
X *	the date field(s), e.g. "10/12* Columbus Day", "July 4* Independence
X *	Day", etc.  Any dates flagged as holidays will be printed in gray, and
X *	any associated text will appear adjacent to the date.
X *
X *	Note that the numeric date formats (mm/dd{/yy}, dd.mm{.yy}) support
X *	an optional year, which will become the subsequent default year.  The
X *	alphabetic date formats (month dd, dd month) do not support a year
X *	field; the "year yy" command is provided to reset the default year.
X *
X *	"Floating" days may be specified in the date file as "first Mon in 
X *	Sep", "last Mon in May", "4th Thu in Nov", etc.; any word may be
X *	used in place of "in".  "Relative floating" days (e.g. "Fri after 4th 
X *	Thu in Nov") are also accepted; they may span month/year bounds.
X *	Pcal also accepts date specs such as "all Friday{s} in October", "last
X *	Thursday in all", etc., and produces the expected results; "each" and
X *	"every" are accepted as synonyms for "all".
X *
X *	The words "day", "weekday", "workday", and "holiday" may be used as
X *	wildcards: "day" matches any day, "weekday" matches any day normally
X *	printed in black, "workday" matches any day normally printed in black
X *	and not explicitly flagged as a holiday, and "holiday" matches any
X *	day explicitly flagged as a holiday.  "Nonweekday", "nonworkday",
X *	and "nonholiday" are also supported and have the obvious meanings.
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; "note all <text>"
X *	propagates <text> to each month in the current year.
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 <>.  In any case, path names are taken to be relative to
X *	the location of the file containing the "include" directive.
X *
X */
X
X
/*
X * Standard headers:
X */
X
#include <stdio.h>
#include <ctype.h>
#include <time.h>
#include <string.h>
X
/*
X * Pcal-specific definitions:
X */
X
#include "pcaldefs.h"
X
/*
X * Global variables:
X */
X
/* forward references to functions */
X
int do_define(), do_ifdef(), do_ifndef(), do_include(), do_undef(),
X    get_args(), ci_strcmp(), ci_strncmp(), find_sym(), get_token(),
X    is_anyday(), is_weekday(), is_workday(), is_holiday(), not_weekday(),
X    not_workday(), not_holiday(), enter_day_info(), get_month(), 
X    get_weekday(), get_keywd(), get_ordinal(), get_prep(), calc_weekday(), 
X    calc_day(), parse_ord(), parse_rel(), date_type(), is_valid(),
X    loadwords(), parse_date(), parse(), getline();
void copy_text(), print_text(), def_footstring(), set_color(), read_datefile(),
X     find_daytext(), find_holidays(), pmonth(), cleanup(), clear_syms(),
X     usage(), copy_text(), print_text(), def_footstring(), set_color(), 
X     read_datefile(), find_daytext(), find_holidays(), pmonth();
char *trnlog(), *mk_path(), *mk_filespec(), *print_word();
year_info *find_year();
FLAG_USAGE *get_flag();
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[MAXWORD];		/* maximum number of words per date file line */
char lbuf[LINSIZ];		/* date file source line buffer */
char *pp_sym[MAX_PP_SYMS];	/* preprocessor defined symbols */
char progname[STRSIZ];		/* program name (for error messages) */
DATE dates[MAX_DATES+1];	/* array of dates - cf. parse_ord() */
X
/* lengths and offsets of months in common year */
static char month_len[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
static char month_off[12] = { 0, 3, 3, 6, 1, 4, 6, 2, 5, 0, 3, 5 };
X
/* moon style flags - cf. pcaldefs.h, pcalinit.ps */
static char *cond[3] = {"false", "true", "(some)"};
X
/* dispatch functions for wildcard matching - cf. pcaldefs.h */
int (*pdatefcn[])() = { is_anyday, is_weekday, is_workday, is_holiday,
X			not_weekday, not_workday, not_holiday };
X
/*
X * Default values for command-line options:
X */
X
char day_color[7];			/* -b, -g */
X
int datefile_type = SYS_DATEFILE;	/* -e, -f */
char datefile[STRSIZ] = "";
X
int rotate = ROTATE;			/* -l, -p */
X
int draw_moons = DRAW_MOONS;		/* -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
int first_day_of_week = FIRST_DAY;	/* -F */
X
int date_style = DATE_STYLE;		/* -A, -E */
X
char outfile[STRSIZ] = OUTFILE;		/* -o */
X
char xsval[12] = XSVAL;			/* -x, -y, -X, -Y */
char ysval[12] = YSVAL;
char xtval[12] = XTVAL;
char ytval[12] = YTVAL;
X
int julian_dates = JULIAN_DATES;	/* -j */
X
/*
X * Language-dependent strings:
X */
X
#include "pcallang.h"
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 calls 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	char default_dir[STRSIZ];
X	int i, month, year, ngray;
X
#define DO_HEADER(phdr)	for (ap = phdr; *ap; ap++) PRT("%s\n", *ap)
X
X	INIT_COLORS;		/* copy default_color to day_color */
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	/* parse environment variable PCAL_OPTS as a command line */
X
X	if ((p = getenv(PCAL_OPTS)) != NULL) {
X		strcpy(lbuf, "pcal ");		/* dummy program name */
X		strcat(lbuf, p);
X		(void) loadwords();		/* split string into words */
X		if (! get_args(words, P_ENV)) {
X			usage(stderr, FALSE);
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, P_CMD1)) {
X		usage(stderr, FALSE);
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, E_FOPEN_ERR, progname, datefile);
X			exit(EXIT_FAILURE);
X		}
X		break;
X
X	case SYS_DATEFILE:
X		/* Attempt to open system-specified calendar file */
X		(p = trnlog(PCAL_DIR)) || (p = trnlog(HOME_DIR));
X		strcpy(default_dir, p ? 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	(void) get_args(argv, P_CMD2);
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, E_FOPEN_ERR, 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",
X	    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 = JAN; i <= DEC; i++) {
X		PRT(i % 6 == 1 ? "\n\t" : " ");
X		(void) print_word(months[i-1]);
X	}
X	PRT(" ] def\n");
X
X	/* day names */
X
X	PRT("/day_names [");
X	for (i = SUN; i <= SAT; i++) {
X		PRT(i % 6 == 0 ? "\n\t" : " ");
X		(void) print_word(days[(i + first_day_of_week) % 7]);
X		}
X	PRT(" ] def\n");
X
X	/* colors (black/gray) to print weekdays and holidays */
X
X	PRT("/day_gray [");
X	for (ngray = 0, i = SUN; i <= SAT; ngray += day_color[i++] == GRAY)
X		PRT(" %s", cond[day_color[(i + first_day_of_week) % 7]]);
X	PRT(" ] def\n");
X	PRT("/holiday_gray %s def\n", cond[ngray <= 3]);
X
X 	/* rotation, scale, and translate values. */
X 
X 	PRT("/rval %d def\n", rotate);
X 	PRT("/xsval %s def\n/ysval %s def\n", xsval, ysval);
X 	PRT("/xtval %s def\n/ytval %s def\n", xtval, ytval);
X
X	/* draw moons? */
X
X	PRT("/draw-moons %s def\n", cond[draw_moons]);
X
X	/* Julian dates? */
X
X	PRT("/julian-dates %s def\n", cond[julian_dates]);
X
X	/* PostScript boilerplate (part 1 of 1) */
X
X	DO_HEADER(header);
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, I_OUT_NAME, progname, outfile);
#endif
X	exit(EXIT_SUCCESS);
}
X
/*
X * get_args - walk the argument list, parsing all arguments but processing only
X * those specified (in flag_tbl[]) to be processed this pass.
X */
int get_args(argv, curr_pass)
X	char **argv;		/* argument list */
X	int  curr_pass;		/* current pass (P_xxx) */
{
X	char *parg, *opt, *p;
X	FLAG_USAGE *pflag, *pf;
X	int i, 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
/*
X * If argument follows flag (immediately or as next parameter), return
X * pointer to it (and bump argv if necessary); else return NULL
X */
#define GETARG() (*(*argv + 2) ? *argv + 2 : \
X		  (*(argv+1) && **(argv+1) != '-' ? *++argv : NULL))
X
X	/* Walk argument list, ignoring first element (program name) */
X
X 	while (opt = *++argv) {
X
X		/* Assume that any non-flag argument is a numeric argument */
X		if (*opt != '-') {
X		    	if (curr_pass == P_CMD1 && nargs < MAXARGS) {
X				if (! IS_NUMERIC(opt))
X					goto bad_par;
X				numargs[nargs++] = atoi(opt);
X			}
X			continue;
X		}
X
X		/* Check that flag is a) legal, and b) to be processed this pass */
X
X		if (! (pflag = get_flag(flag = opt[1])) )
X			goto bad_par;
X
X		/* get optional argument even if flag not processed this pass */
X
X		parg = pflag->has_arg ? GETARG() : NULL;
X
X		if (! (pflag->passes & curr_pass))	/* skip flag this pass? */
X			continue;
X
X		switch (flag) {
X
X		case F_INITIALIZE:	/* reset all flags to defaults */
X
X			/* set up a command line to reset all of the
X			 * flags; call get_args() recursively to parse it
X			 * (note that the Julian dates and moons must be
X			 * reset explicitly, as no flag exists to do so)
X			 */
X
X			/* reset Julian dates and moons */
X			julian_dates = JULIAN_DATES;
X			draw_moons   = DRAW_MOONS;
X
X			/* select program default for landscape/portrait
X			 * mode and US/European date styles
X			 */
X			sprintf(lbuf, "pcal -%c -%c",
#if (ROTATE == LANDSCAPE)
X				F_LANDSCAPE,
#else
X				F_PORTRAIT,
#endif
#if (DATE_STYLE == USA_DATES)
X				F_USA_DATES);
#else
X				F_EUR_DATES);
#endif
X			p = lbuf + strlen(lbuf);
X
X			/* all other flags take arguments and are reset
X			 * by specifying the flag without an argument
X			 */
X			for (pf = flag_tbl; pf->flag; pf++)
X				if ((pf->passes & curr_pass) && pf->has_arg) {
X					sprintf(p, " -%c", pf->flag);
X					p += strlen(p);
X				}
X
X			/* split new command line into words; parse it */
X			(void) loadwords();
X			(void) get_args(words, curr_pass);
X
X			break;
X
X		case F_BLACK_DAY:	/* print day in black or gray */
X		case F_GRAY_DAY:
X			if (parg)
X				set_color(parg, flag == F_BLACK_DAY ?
X						  BLACK : GRAY);
X			else
X				INIT_COLORS;	/* reset to defaults */
X			break;
X
X 		case F_DAY_FONT:	/* specify alternate day font */
X			strcpy(dayfont, parg ? parg : DAYFONT);
X 			break;
X
X		case F_NOTES_FONT:	/* specify alternate notes font */
X			strcpy(notesfont, parg ? parg : NOTESFONT);
X			break;
X
X 		case F_TITLE_FONT:	/* specify alternate title font */
X			strcpy(titlefont, parg ? parg : TITLEFONT);
X 			break;
X
X		case F_EMPTY_CAL:	/* generate empty calendar */
X			datefile_type = NO_DATEFILE;
X			strcpy(datefile, "");
X			break;
X
X		case F_DATE_FILE:	/* specify alternate date file */
X			datefile_type = parg ? USER_DATEFILE : SYS_DATEFILE;
X			strcpy(datefile, parg ? parg : "");
X			break;
X
X		case F_OUT_FILE:	/* specify alternate output file */
X			strcpy(outfile, parg ? parg : OUTFILE);
X			break;
X
X		case F_LANDSCAPE:	/* generate landscape calendar */
X 			rotate = LANDSCAPE;
X			strcpy(xsval, XSVAL_L);
X			strcpy(ysval, YSVAL_L);
X			strcpy(xtval, XTVAL_L);
X			strcpy(ytval, YTVAL_L);
X 			break;
X 
X		case F_PORTRAIT:	/* generate portrait calendar */
X 			rotate = PORTRAIT;
X			strcpy(xsval, XSVAL_P);
X			strcpy(ysval, YSVAL_P);
X			strcpy(xtval, XTVAL_P);
X			strcpy(ytval, YTVAL_P);
X 			break;
X
X		case F_HELP:		/* request "help" message */
X			FPR(stdout, "%s\n", VERSION_STRING + 4);
X			usage(stdout, TRUE);
X			exit(EXIT_SUCCESS);
X			break;
X
X		case F_MOON_4:		/* draw four moons */
X		case F_MOON_ALL:	/* draw a moon for each day */
X			draw_moons = flag == F_MOON_ALL ? ALL_MOONS : SOME_MOONS;
X			break;
X
X		case F_DEFINE:		/* define preprocessor symbol */
X			do_define(parg);
X			break;
X
X		case F_UNDEF:		/* undef preprocessor symbol */
X			do_undef(parg);
X			break;
X
X		case F_L_FOOT:		/* specify alternate left foot */
X			strcpy(lfoot, parg ? parg : LFOOT);
X			break;
X
X		case F_C_FOOT:		/* specify alternate center foot */
X			strcpy(cfoot, parg ? parg : CFOOT);
X			break;
X
X 		case F_R_FOOT:		/* specify alternate right foot */
X			strcpy(rfoot, parg ? parg : RFOOT);
X			break;
X
X		case F_FIRST_DAY:	/* select starting day of week */
X			if (parg) {
X				if ((i = get_weekday(parg, FALSE)) != NOT_WEEKDAY)
X					first_day_of_week = i;
X			}
X			else
X				first_day_of_week = FIRST_DAY;
X			break;
X
X		case F_USA_DATES:	/* select American date style */
X		case F_EUR_DATES:	/* select European date style */
X			date_style = flag == F_USA_DATES ? USA_DATES : EUR_DATES;
X			break;
X
X		case F_X_TRANS:		/* set x-axis translation factor */
X			strcpy(xtval, parg ? parg : (rotate == LANDSCAPE ?
X						     XTVAL_L : XTVAL_P));
X			break;
X
X		case F_Y_TRANS:		/* set y-axis translation factor */
X			strcpy(ytval, parg ? parg : (rotate == LANDSCAPE ?
X						     YTVAL_L : YTVAL_P));
X			break;
X
X		case F_X_SCALE:		/* set x-axis scaling factor */
X			strcpy(xsval, parg ? parg : (rotate == LANDSCAPE ?
X						     XSVAL_L : XSVAL_P));
X			break;
X
X		case F_Y_SCALE:		/* set y-axis scaling factor */
X			strcpy(ysval, parg ? parg : (rotate == LANDSCAPE ?
X						     YSVAL_L : YSVAL_P));
X			break;
X
X		case F_JULIAN:
X		case F_JULIAN_ALL:
X			julian_dates = flag == F_JULIAN_ALL ? ALL_JULIANS :
X							      SOME_JULIANS;
X			break;
X
X		case '-' :		/* accept - and -- as dummy flags */
X		case '\0':
X			break;
X
X		default:		/* missing case label if reached!!! */
X
bad_par:				/* unrecognized parameter */
X
X			FPR(stderr, E_ILL_OPT, progname, opt);
X			badopt = TRUE;
X			break;
X		}
X        }
X
X	if (curr_pass != P_CMD1)
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, E_ILL_MONTH, progname, init_month, JAN, DEC);
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, E_ILL_YEAR, progname, 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 * color_msg - return character string explaining default day colors
X */
char *color_msg()
{
X	int i, ngray = 0, others;
X	static char msg[80];
X
X	for (i = SUN; i <= SAT; i++)	/* count gray weekdays */
X		if (default_color[i] == GRAY)
X			ngray++;
X
X	if (ngray == 0 || ngray == 7) {		/* all same color? */
X		sprintf(msg, COLOR_MSG_1, ngray ? W_GRAY : W_BLACK);
X		return msg;
X	}
X
X	others = ngray <= 3 ? BLACK : GRAY;	/* no - get predominant color */
X	msg[0] = '\0';
X	for (i = SUN; i <= SAT; i++)
X		if (default_color[i] != others) {
X			strncat(msg, days[i], MIN_DAY_LEN);
X			strcat(msg, "/");
X		}
X	LASTCHAR(msg) = ' ';
X
X	sprintf(msg + strlen(msg), COLOR_MSG_2,
X		others == BLACK ? W_GRAY : W_BLACK,
X                others == BLACK ? W_BLACK : W_GRAY);
X	return msg;
}
X
X
/*
X * usage - print message explaining correct usage of the command-line
X * arguments and flags.  If "fullmsg" is true, print associated text
X */
void usage(fp, fullmsg)
X	FILE *fp;	/* destination of usage message */
X	int fullmsg;	/* print complete message? */
{
X	FLAG_MSG *pflag;
X	PARAM_MSG *ppar;
X	DATE_MSG *pdate;
X	char buf[30], *p, flag, *meta;
X	int nchars, first, i, indent, n;
X
X	sprintf(buf, "%s: %s", W_USAGE, progname);
X	nchars = indent = strlen(buf);
X	first = TRUE;
X	FPR(fp, "\n%s", buf);
X
X	/* loop to print command-line syntax message (by group of flags) */
X
X	for (pflag = flag_msg; (flag = pflag->flag) != '\0'; pflag++) {
X		if (flag == '\n') {		/* end of group? */
X			*p = '\0';
X			if (meta) {		/* append metavariable name */
X				strcat(buf, " ");
X				strcat(buf, meta);
X			}
X			strcat(buf, "]");
X			n = strlen(buf);
X			if (nchars + n > SCREENWIDTH) {	/* does it fit on line? */
X				FPR(fp, "\n");		/* no - start new one */
X				for (nchars = 0; nchars < indent; nchars++)
X					FPR(fp, " ");
X			}
X			FPR(fp, "%s", buf);
X			nchars += n;
X			first = TRUE;
X		}
X		else if (flag != ' ') {		/* accumulate flags for group */
X			if (first) {
X				sprintf(buf, " [");
X				p = buf + strlen(buf);
X			}
X			else
X				*p++ = '|';
X			*p++ = '-';
X			*p++ = flag;
X			meta = pflag->meta;	/* save metavariable name */
X			first = FALSE;
X		}
X	}
X
X	/* loop to print selected numeric parameter descriptions */
X
X	for (i = 0; i < PARAM_MSGS; i++) {
X		sprintf(buf, " %s[%s]%s", i == 0 ? " " : "", 
X			param_msg[i].desc, i < PARAM_MSGS - 1 ? " |" : "");
X		n = strlen(buf);
X		if (nchars + n > SCREENWIDTH) {	/* does it fit on line? */
X			FPR(fp, "\n");		/* no - start new one */
X			for (nchars = 0; nchars < indent; nchars++)
X				FPR(fp, " ");
X		}
X		FPR(fp, "%s", buf);
X		nchars += n;
X	}
X
X	FPR(fp, "\n\n");
X	if (! fullmsg) {
X		FPR(fp, USAGE_MSG, progname, F_HELP);
X		return;
X	}
X	
X	/* loop to print the full flag descriptions */
X
X	for (pflag = flag_msg; (flag = pflag->flag) != '\0'; pflag++) {
X		if (flag == '\n') {	/* newline?  print and quit */
X			FPR(fp, "\n");
X			continue;
X		}
X		p = buf;		/* copy flag and metavariable to buffer */
X		if (flag != ' ')
X			*p++ = '-';
X	/* special hack for VMS - surround upper-case flags in quotes */
#ifdef VMS
X		if (isupper(flag)) {
X			*p++ = '"';
X			*p++ = flag;
X			*p++ = '"';
X		}
X		else
X			*p++ = flag;
#else
X		*p++ = flag;
#endif
X		*p = '\0';
X		if (pflag->meta)
X			sprintf(p, " <%s>", pflag->meta);
X		FPR(fp, "\t%-16.16s", buf);
X		if (pflag->text)
X			FPR(fp, "%s", pflag->text);
X
X		/* print default value if specified */
X		if (pflag->def)
X			FPR(fp, " (%s: %s)", W_DEFAULT, pflag->def[0] ? pflag->def : "\"\"" );
X		FPR(fp, "\n");
X
X		/* special case - print color messages after F_GRAY_DAY */
X		if (flag == F_GRAY_DAY)
X			FPR(fp, "\t\t\t  (%s: %s)\n", W_DEFAULT, color_msg());
X
X	}
X	
X	/* now print the information about the numeric parameters */
X
X	for (ppar = param_msg; ppar->desc; ppar++)
X		FPR(fp, "\t%-16.16s%s\n", ppar->desc, ppar->text);
X	
X	/* print the date file syntax message */
X
X	FPR(fp, "\n");
X	for (pdate = date_msg; *pdate; pdate++)
X		FPR(fp, "\t%s\n", *pdate);
X
}
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, E_ALLOC_ERR, 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 * Always returns 0 (for compatibility with other dispatch functions).
X */
int do_define(sym)
X	char *sym;
{
X	int i;
X
X	if (! sym) {		/* null argument - clear all definitions */
X		clear_syms();
X		return 0;
X	}
X
X	if (do_ifdef(sym))	/* already defined? */
X		return 0;
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 0;
X		}
X
X	FPR(stderr, E_SYMFULL, progname, sym);
X	return 0;
}
X
X
/*
X * do_undef - undefine 'sym' and free its space; no error if not defined.
X * Always return 0 (for compatibility with other dispatch fcns).
X */
int do_undef(sym)
X	char *sym;
{
X	int i;
X
X	if (! sym) 
X		return 0;
X
X	if ((i = find_sym(sym)) != PP_SYM_UNDEF) {
X		free(pp_sym[i]);
X		pp_sym[i] = NULL;
X	}
X
X	return 0;
}
X
X
/*
X * do_include - include specified file (optionally in "" or <>); always
X * returns 0 (for compatibility with related functions returning int)
X */
int 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 0;
X
X	/* copy name, stripping "" or <> */
X	strcpy(tmpnam, name + (*name == '"' || *name == '<'));
X	if ((p = P_LASTCHAR(tmpnam)) && *p == '"' || *p == '>')
X		*p = '\0';
X
X	if ((fp = fopen(mk_filespec(incfile, path, tmpnam), "r")) == NULL) {
X		FPR(stderr, E_FOPEN_ERR, progname, incfile);
X		exit(EXIT_FAILURE);
X	}
X
X	read_datefile(fp, incfile);
X	fclose(fp);
X
X	return 0;
}
X
X
/*
X * get_token - look up 'token' in list of preprocessor tokens; return its
X * index if found, PP_OTHER if not
X */
int get_token(token)
X	char *token;
{
X	KWD_F *p;
X
X	for (p = pp_info;
X             p->name && ci_strncmp(token, p->name, MIN_PPTOK_LEN);
X	     p++)
X		;
X
X	return p->code;
}
X
X
/*
X * get_flag() - look up flag in flag_tbl; return pointer to its entry
X * (NULL if not found)
X */
FLAG_USAGE *get_flag(flag)
X	char flag;
{
X	FLAG_USAGE *pflag;
X
X	for (pflag = flag_tbl; pflag->flag; pflag++)
X		if (flag == pflag->flag)
X			return pflag;
X
X	return flag ? NULL : pflag;		/* '\0' is a valid flag */
}
X
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((int) 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	char text[LINSIZ];
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((int) 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	copy_text(text, pword);	/* consolidate text from lbuf into text */
X
X	if (*text) {
X		for (plast = NULL, pday = pmonth->day[d];
X		     pday;
X		     plast = pday, pday = pday->next)
X			if (ci_strcmp(pday->text, text) == 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(text)+1), text);
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 */
void 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 */
void 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 alpha (or, optionally, numeric) string to month; return 
X * 1..12 if valid, NOT_MONTH if not, ALL_MONTHS if "all"
X */
int get_month(cp, numeric_ok)
X	char *cp;		/* string to convert */
X	int numeric_ok;		/* accept numeric string ? */
{
X	int mm;
X
X	if (! cp)
X		return NOT_MONTH;
X
X	if (get_keywd(cp) == DT_ALL)
X		return ALL_MONTHS;
X
X	if (numeric_ok && 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 : NOT_MONTH;
}
X
X
/*
X * get_weekday - look up string in weekday list; return 0-6 if valid,
X * NOT_WEEKDAY if not.  If wild_ok flag is set, accept "day", "weekday",
X * "workday", or "holiday" and return appropriate value.
X */
int get_weekday(cp, wild_ok)
X	char *cp;
X	int wild_ok;
{
X	int w;
X
X	if (!cp)
X		return NOT_WEEKDAY;
X
X	if (wild_ok) {		/* try wildcards first */
X		for (w = WILD_FIRST; w <= WILD_LAST; w++)
X			if (ci_strncmp(cp, days[w], strlen(days[w])) == 0)
X				return w;
X	}
X
X	for (w = SUN; w <= SAT; w++)
X		if (ci_strncmp(cp, days[w], MIN_DAY_LEN) == 0)
X			return w;
X
X	return NOT_WEEKDAY;
}
X
X
/*
X * get_keywd - look up string in misc. keyword list; return keyword code
X * if valid, DT_OTHER if not
X */
int get_keywd(cp)
X	char *cp;
{
X	KWD *k;
X
X	if (!cp)
X		return DT_OTHER;
X
X	for (k = keywds;
X	     k->name && ci_strncmp(cp, k->name, strlen(k->name));
X	     k++)
X		;
X
X	return k->code;
}
X
X
/*
X * get_ordinal - look up string in ordinal list; return ordinal code if valid,
X * ORD_OTHER if not
X */
int get_ordinal(cp)
X	char *cp;
{
X	KWD *o;
X
X	if (!cp)
X		return ORD_OTHER;
X
X	for (o = ordinals; o->name && ci_strncmp(cp, o->name, MIN_ORD_LEN); o++)
X		;
X
X	return o->code;
}
X
X
/*
X * get_prep - look up string in preposition list; return preposition code if 
X * valid, PR_OTHER if not
X */
int get_prep(cp)
X	char *cp;
{
X	KWD *p;
X
X	if (!cp)
X		return PR_OTHER;
X
X	for (p = preps; p->name && ci_strncmp(cp, p->name, MIN_PREP_LEN); p++)
X		;
X
X	return p->code;
}
X
X
/*
X * calc_weekday - return the weekday (0-6) of mm/dd/yy (mm: 1-12)
X */
int calc_weekday(mm, dd, yy)
X	int mm;
X	int dd;
X	int yy;
{
X	return (yy + (yy-1)/4 - (yy-1)/100 + (yy-1)/400 + OFFSET_OF(mm, yy) + (dd-1)) % 7;
}
X
X
X
/*
X * Dispatch functions for wildcard matching
X */
X
X
/*
X * is_anyday - dummy function which always returns TRUE
X */
int is_anyday(mm, dd, yy)
X	int mm;
X	int dd;
X	int yy;
{
X	return TRUE;
}
X
X
/*
X * is_weekday - determine whether or not mm/dd/yy is a weekday (i.e., the
X * day of the week normally prints in black)
X */
int is_weekday(mm, dd, yy)
X	int mm;
X	int dd;
X	int yy;
{
X	return day_color[calc_weekday(mm, dd, yy)] == BLACK;
}
X
X
/*
X * is_workday - determine whether or not mm/dd/yy is a workday (i.e., the
X * day of the week normally prints in black and the date is not a holiday)
X */
int is_workday(mm, dd, yy)
X	int mm;
X	int dd;
X	int yy;
{
X	return is_weekday(mm, dd, yy) && ! is_holiday(mm, dd, yy);
}
X
X
/*
X * is_holiday - determine whether or not mm/dd/yy is a holiday
X */
int is_holiday(mm, dd, yy)
X	int mm;
X	int dd;
X	int yy;
{
X	year_info *py;
X	month_info *pm;
X
X	pm = (py = find_year(yy, FALSE)) ? py->month[mm-1] : NULL;
X	return pm ? (pm->holidays & (1 << (dd-1))) != 0 : FALSE;
}
X
X
/*
X * not_XXXXX - converses of is_XXXXX above
X */
int not_weekday(mm, dd, yy)
X	int mm, dd, yy;
{
X	return !is_weekday(mm, dd, yy);
}
X
X
int not_workday(mm, dd, yy)
X	int mm, dd, yy;
{
X	return !is_workday(mm, dd, yy);
}
X
X
int not_holiday(mm, dd, yy)
X	int mm, dd, yy;
{
X	return !is_holiday(mm, dd, yy);
}
X
X
X
/*
X * normalize - adjust day in case it has crossed month (or year) bounds 
X */
void normalize(pd)
X	DATE *pd;		/* pointer to date */
{
X	int len;
X
X	if (pd->dd <= 0) {		/* day in previous month? */
X		if (--pd->mm < JAN) {
X			pd->mm = DEC;
X			pd->yy--;
X		}
X	pd->dd += LENGTH_OF(pd->mm, pd->yy);
X	}
X	else {		 		/* day in following month? */
X		len = LENGTH_OF(pd->mm, pd->yy);
X		if (pd->dd > len) {
X			pd->dd -= len;
X			if (++pd->mm > DEC) {
X				pd->mm = JAN;
X				pd->yy++;
X			}
X		}
X	}
}
X
X
/*
X * calc_day - calculate calendar date from ordinal date (e.g., "first Friday
X * in November", "last day in October"); return calendar date if it exists, 
X * 0 if it does not
X */
int calc_day(ord, wkd, mm)
X	int ord;
X	int wkd;
X	int mm;
{
X	int first, last, day, (*pfcn)();
X
X	if (IS_WILD(wkd)) {	/* "day", "weekday", "workday", or "holiday" */
X		pfcn = pdatefcn[wkd - ANY_DAY];
X		last = LENGTH_OF(mm, curr_year);
X
X		if (ord == ORD_LAST) {
X			for (day = last; 
X			     day >= 1 && !(*pfcn)(mm, day, curr_year);
X			     day--)
X				;
X		} else {
X			for (day = 1; 
X			     day <= last && 
X				!((*pfcn)(mm, day, curr_year) && --ord == 0);
X			     day++)	
X				;
X		}
X		return is_valid(mm, day, curr_year) ? day : 0; 
X
X	} else {
X		first = (wkd - calc_weekday(mm, 1, curr_year) + 7) % 7 + 1;
X		if (ord == ORD_LAST) {		/* try 5th, then 4th */
X			if (! is_valid(mm, day = first + 28, curr_year))
X				day -= 7;
X		}
X		else 
X			if (!is_valid(mm, day = first + 7 * (ord-1), curr_year))
X				day = 0;
X
X		return day;
X	}
X
}
X
X
/*
X * parse_ord - parse an ordinal date spec (e.g. "first Monday in September",
X * "every Sunday in October", "last workday in all"); return PARSE_OK if line
X * syntax valid, PARSE_INVLINE if not.  Write all matching dates (if any) to
X * global array dates[]; terminate date list with null entry.
X */
int parse_ord(ord, pword)
X	int ord;		/* valid ordinal code - from get_ordinal() */
X	char **pword;		/* pointer to word after ordinal */
{
X	int wkd, mm, dd, len, (*pfcn)();
X	int ord_first, ord_last, mon_first, mon_last;
X	DATE *pdate;
X
X	if ((wkd = get_weekday(*pword, TRUE)) == NOT_WEEKDAY ||	/* weekday */
X	    *++pword == NULL ||					/* any word */
X	    (mm = get_month(*++pword, FALSE)) == NOT_MONTH)	/* month */
X		return PARSE_INVLINE;
X
X	/* set up loop boundaries */
X	ord_first = ord == ORD_ALL ? ORD_FIRST : ord;
X	ord_last  = ord == ORD_ALL ? ORD_FIFTH : ord;
X	mon_first = mm == ALL_MONTHS ? JAN : mm;
X	mon_last  = mm == ALL_MONTHS ? DEC : mm;
X
X	if (ord == ORD_ALL && IS_WILD(wkd)) {
X		/* special case of "all day{s} in ..." */
X		pfcn = pdatefcn[wkd - ANY_DAY];
X		for (pdate = dates, mm = mon_first; mm <= mon_last; mm++) {
X			len = LENGTH_OF(mm, curr_year);
X			for (dd = 1; dd <= len; dd++)
X				if ((*pfcn)(mm, dd, curr_year))
X					ADD_DATE(mm, dd, curr_year);
X		}
X	}
X	else {
X		/* all other combinations of ordinal and day */
X		for (pdate = dates, mm = mon_first; mm <= mon_last; mm++)
X			for (ord = ord_first; ord <= ord_last; ord++)
X				if ((dd = calc_day(ord, wkd, mm)) != 0)
X					ADD_DATE(mm, dd, curr_year);
X
X	}
X
X	TERM_DATES;		/* terminate array with null entry */
X	return PARSE_OK;
X
}
X
X
/*
X * parse_rel - parse a relative date spec (e.g. "Friday after fourth Thursday
X * in November", "Saturday after first Friday in all"; return PARSE_OK if
X * line syntax valid, PARSE_INVLINE if not.  Transform all dates that match
X * the base date to the appropriate day, month, and year.
X */
int parse_rel(wkd, pword, ptype, pptext)
X	int wkd;		/* valid weekday code - from get_weekday() */
X	char **pword;		/* pointer to word after weekday */
X	int *ptype;		/* return text type (holiday/non-holiday) */
X	char ***pptext;		/* return pointer to first word of text */
{
X	int prep, rtn, base_wkd, incr, (*pfcn)();
X	DATE *pd;
X
X	/* we have the weekday - now look for the preposition */
X	if ((prep = get_prep(*pword++)) == PR_OTHER)
X		return PARSE_INVLINE;
X
X	/* get the base date */
X	if ((rtn = parse_date(pword, ptype, pptext)) != PARSE_OK)
X		return rtn;
X
X	/* transform date array in place - note that the relative date may
X	   not be in the same month or even year */
X
X	if (IS_WILD(wkd)) {		/* wildcard for weekday name? */
X		pfcn = pdatefcn[wkd - ANY_DAY];
X		incr = prep == PR_BEFORE || prep == PR_ON_BEFORE ? -1 : 1;
X
X		for (pd = dates; pd->mm; pd++) {
X			if (prep == PR_BEFORE || prep == PR_AFTER) {
X				pd->dd += incr;
X				normalize(pd);
X			}
X			while (!(*pfcn)(pd->mm, pd->dd, pd->yy)) {
X				pd->dd += incr;
X				normalize(pd);
X			}
X		}
X
X	} else  {			/* explicit weekday name */
X		for (pd = dates; pd->mm; pd++) {
X			base_wkd = calc_weekday(pd->mm, pd->dd, pd->yy);
X
X			if (prep == PR_BEFORE ||
X			    (prep == PR_ON_BEFORE && wkd != base_wkd))
X				pd->dd -= 7 - (wkd - base_wkd + 7) % 7;
X
X			if (prep == PR_AFTER ||
X			    (prep == PR_ON_AFTER && wkd != base_wkd))
X				pd->dd += (wkd - base_wkd + 6) % 7 + 1;
X
X			normalize(pd);	/* adjust for month/year crossing */
X		}
X	}
X
X	return PARSE_OK;
}
X
X
/*
X * date_type - examine token and return date type code; if DT_MONTH, DT_ORDINAL,
X * or DT_WEEKDAY, fill in appropriate code
X */
int date_type(cp, pn)
X	char *cp;	/* pointer to start of token */
X	int *pn;	/* numeric value (returned)    */
{
X	int n;
X
X	if ((n = get_ordinal(cp)) != ORD_OTHER)		/* ordinal? */
X		return (*pn = n, DT_ORDINAL);
X
X	if (isdigit(*cp))				/* other digit? */
X		return IS_NUMERIC(cp) ? DT_EURDATE : DT_DATE;
X
X	if ((n = get_weekday(cp, TRUE)) != NOT_WEEKDAY)	/* weekday name? */
X		return (*pn = n, DT_WEEKDAY);
X
X	/* "all" can be either a keyword or a month wildcard - look for
X	   the former usage first */
X
X	if ((n = get_keywd(cp)) != DT_OTHER)
X		return n;
X
X	if ((n = get_month(cp, FALSE)) != NOT_MONTH)	/* month name? */
X		return (*pn = n, DT_MONTH);
X
X	return DT_OTHER;		/* unrecognized keyword - give up */
X
}
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	return m >= JAN && m <= DEC && 
X		d >= 1 && d <= LENGTH_OF(m, 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
X	pstr = lbuf;
X
X	for (ap = words; TRUE; 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 ap - words;	/* number 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 * copy_text - retrieve remaining text in lbuf and copy to output string,
X * removing leading/trailing whitespace and condensing runs of whitespace to a
X * single blank
X */
void copy_text(pbuf, ptext)
X	char *pbuf;		/* output buffer */
X	char **ptext;		/* pointer to first text word in "words" */
{
X	char *p;
X
X	/* copy words to pbuf, separating by one blank */
X
X	for (*pbuf = '\0'; p = *ptext; *pbuf++ = *++ptext ? ' ' : '\0')
X		while (*p)
X			*pbuf++ = *p++;
X
}
X
X
/*
X * print_word - print a single word, representing parentheses and non-ASCII
X * characters as octal literals; return pointer to character following word
X * (NULL if no word follows)
X */
char *print_word(p)
X	char *p;
{
X	char c;
X
X	if (*p == '\0' || *(p += strspn(p, WHITESPACE)) == '\0')
X		return NULL;
X
X	PRT("(");
X	for ( ; (c = *p) && !isspace(c); p++)
X		PUTCHAR(c);
X	PRT(")");
X
X	return p;
}
X
X
/*
X * print_text - print tokens in text (assumed separated by single blank)
X * in PostScript format
X */
void print_text(p)
X	char *p;
{
X
X	while (p = print_word(p))
X		PRT("\n");
}
X
X
/*
X * def_footstring - print definition for foot string, again converting '('
X * or ')' to octal escape
X */
void def_footstring(p, c)
X	char *p;			/* definition */
X	char c;				/* L, C, or R */
{
X
X	PRT("/%cfootstring (", c);
X	for ( ; c = *p; p++) 
X		PUTCHAR(c);
X	PRT(") def\n");
}
X
X
/*
X * set_color - set one or all weekdays to print in black or gray
X */
void set_color(day, col)
X	char *day;		/* weekday name (or "all") */
X	int  col;		/* select black or gray */
{
X	int i;
X
X	if (ci_strncmp(day, ALL, strlen(ALL)) == 0)	/* set all days */
X		for (i = 0; i < 7; i++)
X			day_color[i] = col;
X	else						/* set single day */
X		if ((i = get_weekday(day, FALSE)) != NOT_WEEKDAY)
X			day_color[i] = col;
X
}
X
X
/*
X * parse_date - parse a date specification in any of its myriad forms; upon
X * return, array dates[] will contain a list of all the dates that matched,
X * terminated by a null entry.  Also fill in the date type (holiday/non-
X * holiday) code and the pointer to the first word of text.
X */
int parse_date(pword, ptype, pptext)
X	char **pword;		/* first word to parse */
X	int *ptype;		/* return date type (holiday/non-holiday) */
X	char ***pptext;		/* return pointer to first word of text */
{
X	int mm, dd, yy;
X	int token, n, ord, wkd, rtn;
X	DATE *pdate;
X	char *cp;
X
#define SKIP_FIELD(p) \
X	if (1) {while (isdigit(*p)) p++; while (*p && !isdigit(*p)) p++;} else
X
X	pdate = dates;
X
X	switch (token = date_type(*pword, &n)) {
X
X	case DT_MONTH:		/* <month> dd */
X		if (date_style != USA_DATES)
X			return PARSE_INVLINE;
X
X		if ((cp = *++pword) == NULL)
X			return PARSE_INVLINE;
X
X		ADD_DATE(n, atoi(cp), curr_year);
X		TERM_DATES;
X
X		break;
X
X	case DT_DATE:		/* mm/dd{/yy} | dd/mm{/yy} */
X		cp = *pword;
X 
X		/* extract month and day fields */
X
X		*(date_style == USA_DATES ? &mm : &dd) = atoi(cp);
X		SKIP_FIELD(cp);
X
X		*(date_style == USA_DATES ? &dd : &mm) = 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		ADD_DATE(mm, dd, curr_year);
X		TERM_DATES;
X
X		break;
X
X	case DT_EURDATE:	/* dd [ <month> | "all" ] */
X		if (date_style != EUR_DATES)
X			return PARSE_INVLINE;
X
X		dd = atoi(*pword);
X
X		if (get_keywd(*++pword) == DT_ALL) {
X			for (mm = JAN; mm <= DEC; mm++)		/* wildcard */
X				ADD_DATE(mm, dd, curr_year);
X		}
X		else {						/* one month */
X			if ((mm = get_month(*pword, FALSE)) == NOT_MONTH)
X				return PARSE_INVLINE;
X
X			ADD_DATE(mm, dd, curr_year);
X		}
X
X		TERM_DATES;
X		break;
X
X	case DT_ALL:		/* "all" <weekday> "in" [ <month> | "all" ] */
X				/* or "all" <day>" */
X
X		if ((cp = *(pword+1)) && (*(cp += strspn(cp, DIGITS)) == '\0' ||
X		    *cp == '*')) {
X			dd = atoi(*++pword);		/* "all" <day> */
X			for (mm = JAN; mm <= DEC; mm++)
X				ADD_DATE(mm, dd, curr_year);
X			TERM_DATES;
X			break;		/* leave switch */
X		}
X
X		n = ORD_ALL;	/* select all ordinals; fall through */
X
X	case DT_ORDINAL:	/* <ordinal> <weekday> in [ <month> | "all" ] */
X		ord = n;
X		if ((rtn = parse_ord(ord, pword + 1)) != PARSE_OK)
X			return rtn;
X
X		pword += 3;		/* last word of date */
X		break;
X
X	case DT_WEEKDAY:	/* <weekday> <prep> <date> */
X		wkd = n;
X
X		/* parse_rel() calls parse_date() recursively */
X		return parse_rel(wkd, ++pword, ptype, pptext);
X		break;
X
X	default:
X		return PARSE_INVLINE;
X		break;
X	}
X
X	/* at this point, pword points to the last component of the date;
X	 * fill in type code and pointer to following word (start of text)
X	 */
X	*ptype = LASTCHAR(*pword) == '*' ? HOLIDAY_TEXT : DAY_TEXT;
X	*pptext = ++pword;
X
X	return PARSE_OK;
}
X
/*
X * parse - enter date file data (in lbuf[]) into data structure
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(pword)
X	char **pword;		/* pointer to first word to parse */
{
X	register char *cp;
X	char **ptext;
X	int mm, yy;
X	int text_type, n, rtn, match;
X	int token;
X	DATE *pd;
X
X	/*
X	 * Get first field and call date_type() to decode it
X         */
X	cp = *pword;
X
X	switch (token = date_type(cp, &n)) {
X
X	case DT_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_INVLINE;	/* year missing or non-numeric */
X		break;
X
X	case DT_OPT:
X	 	if (!get_args(pword, P_OPT)) {
X			usage(stderr, FALSE);
X			exit(EXIT_FAILURE);
X		}
X		return PARSE_OK;
X		break;
X
X	case DT_NOTE:
X		if ((mm = get_month(*++pword, TRUE)) == NOT_MONTH)
X			return PARSE_INVLINE;
X
X		if (mm == ALL_MONTHS)		/* "note all"? */
X			for (mm = JAN; mm <= DEC; mm++)
X				enter_day_info(mm, NOTE_DAY, curr_year,
X					NOTE_TEXT, pword+1);
X		else
X			enter_day_info(mm, NOTE_DAY, curr_year, NOTE_TEXT,
X				pword+1);
X
X		return PARSE_OK;
X		break;
X
X	case DT_OTHER:		/* unrecognized token */
X		return PARSE_INVLINE;
X		break;
X
X	/* assume anything else is a date */
X
X	default:
X		if ((rtn = parse_date(pword, &text_type, &ptext)) == PARSE_OK) {
X			match = FALSE;	/* is at least one date valid? */
X			for (pd = dates; pd->mm; pd++)
X				match |= enter_day_info(pd->mm, pd->dd, pd->yy,
X					       text_type, ptext) == PARSE_OK;
X			rtn = match ? PARSE_OK : PARSE_INVDATE;
X		}
X		return rtn;
X		break;
X
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 */
void 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, E_ILL_LINE, progname, errmsg, filename, line);
X
X	if (fp == NULL)		/* whoops, no date file */
X		return;
X
X	if (++file_level > MAX_NESTING) {
X		ERR(E_NESTING);
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 = get_token(*pword++);
X		pfcn = pp_info[pptype].pfcn;
X		ptok = pp_info[pptype].name;
X
X		switch (pptype) {
X
X		case PP_DEFINE:
X		case PP_UNDEF:
X			if (processing)
X				(void) (*pfcn)(*pword);
X			extra = ntokens > 2;
X			break;
X
X		case PP_ELSE:
X			if (if_level < 1) {
X				ERR(E_ELSE_ERR);
X				break;
X			}
X
X			if (processing) {
X				processing = FALSE;	/* disable processing */
X				restart_level = if_level;
X			}
X			else
X			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(E_END_ERR);
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(words)) {
X
X				case PARSE_INVDATE:
X					ERR(E_INV_DATE);
X					break;
X
X				case PARSE_INVLINE:
X					ERR(E_INV_LINE);
X					break;
X
X				}
X			}
X			extra = FALSE;
X			break;
X
X		} /* end switch */
X
X		if (extra) {		/* extraneous data? */
X			sprintf(msg, E_GARBAGE, ptok);
X			ERR(msg);
X		}
X
X	} /* end while */
X
X	if (if_level > 0)
X		FPR(stderr, E_UNT_IFDEF, 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 */
void 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			}
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 */
void 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", cond[pm && pm->day[NOTE_DAY-1]]);
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 */
void 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 = 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 = 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 = 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 61911 -eq "$Wc_c" ||
	echo 'pcal.c: original size 61911, current size' "$Wc_c"
fi
exit 0