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