home *** CD-ROM | disk | FTP | other *** search
- /*
- * pcal.c - generate PostScript file to print calendar for any month and year
- *
- * The original PostScript code to generate the calendars was written by
- * Patrick Wood (Copywrite (c) 1987 by Patrick Wood of Pipeline Associates,
- * Inc.), and authorized for modification and redistribution. The calendar
- * file inclusion code was originally written in "bs(1)" by Bill Vogel of
- * AT&T. Patrick's original PostScript was modified and enhanced several
- * times by others whose names have regrettably been lost. This C version
- * was originally created by Ken Keirnan of Pacific Bell; additional
- * enhancements by Joseph P. Larson, Ed Hand, and Andrew Rogers (who also
- * did the VMS port).
- *
- * Parameters:
- *
- * pcal [opts] generate calendar for current month/year
- *
- * pcal [opts] yy generate calendar for entire year yy
- *
- * pcal [opts] mm yy generate calendar for month mm
- * (Jan = 1), year yy (19yy if yy < 100)
- *
- * pcal [opts] mm yy n as above, for n consecutive months
- *
- * Output:
- *
- * PostScript file to print calendars for all selected months.
- *
- * Options:
- *
- * -d <FONT> specify alternate font for day names
- * (default: Times-Bold)
- *
- * -e generate empty calendar (ignore date file)
- *
- * -f <FILE> specify alternate date file (default:
- * ~/calendar on Un*x, SYS$LOGIN:CALENDAR.DAT
- * on VMS)
- *
- * -o <FILE> specify alternate output file (default:
- * stdout on Un*x, CALENDAR.PS on VMS)
- *
- * -r generate portrait-style calendars
- * (default: landscape)
- *
- * -s print Saturdays in black
- * -S print Saturdays and Sundays in black
- * (default: print Saturdays and Sundays in gray)
- *
- * -t <FONT> specify alternate font for titles
- * (default: Times-Bold)
- *
- * Parameters and flags may be mixed on the command line.
- */
-
-
- #include <stdio.h>
- #include <ctype.h>
- #include <time.h>
- #include <string.h>
-
- #ifdef VMS /* VMS oddities isolated here */
-
- #define INFILE "CALENDAR.DAT"
- #define OUTFILE "CALENDAR.PS"
- #define S_OPT "\"S\""
- #define END_PATH ']'
- #define EXIT_SUCCESS 1
- #define EXIT_FAILURE 3
-
- #else /* non-VMS - assume Un*x of some sort */
-
- #define INFILE "calendar"
- #define OUTFILE NULL
- #define S_OPT "S"
- #define END_PATH '/'
- #define EXIT_SUCCESS 0
- #define EXIT_FAILURE 1
- extern char *getenv(); /* to translate "HOME" to path name */
-
- #endif
-
-
- #define DAYFONT "Times-Bold"
- #define TITLEFONT "Times-Bold"
-
- #define FALSE 0
- #define TRUE 1
-
- #define PRT (void)printf
- #define FPR (void)fprintf
-
- #define is_leap(y) ((y) % 4 == 0 && ((y) % 100 != 0 || (y) % 400 == 0))
-
- #define MIN_YR 1900 /* significant years (calendar limits) */
- #define MAX_YR 9999
-
- #define JAN 1 /* significant months */
- #define FEB 2
- #define DEC 12
-
- #define MAXARGS 3 /* numeric command-line args */
- #define HOLIDAY (1 << 6) /* bit set to flag day as holiday */
-
- #define WHITESPACE " \t" /* token delimiters in date file */
-
- #include "pcalinit.h" /* PostScript boilerplate */
-
- /*
- * Global variables:
- */
-
- FILE *dfp = NULL; /* date file pointer */
- int init_year; /* initial default year for date file entries */
- int curr_year; /* current default year for date file entries */
- char *words[100]; /* maximum number of words per date file line */
- char lbuf[512]; /* maximum date file line size */
-
-
- /*
- * Main program - parse and validate command-line arguments, open files,
- * generate PostScript boilerplate and code to generate calendars.
- *
- * Program structure:
- *
- * main() generates the common PostScript code and then calls pmonth() to
- * print the calendars.
- *
- * pmonth() calls find_holidays() to make a first pass through the date file
- * to generate the list of holidays to be printed in gray; it then calls
- * find_daytext() to make a second pass to generate the text to be printed
- * inside the calendar boxes.
- *
- * find_holidays() and find_daytext() both call getday() to retrieve the next
- * day of interest from the date file.
- *
- * getday() reads successive lines from the date file (stripping comments and
- * ignoring blank lines), stopping when parse() determines that the line
- * contains a usable date or when EOF is reached.
- *
- * parse() parses a line from the date file, determining whether or not it
- * contains a date in the current month and year. It calls utility routines
- * is_valid() to validate the date found and loadwords() to split any
- * accompanying text into individual tokens for PostScript to print.
- *
- */
- main(argc, argv)
- int argc;
- char **argv;
- {
-
- /* Look for the argument following a flag - may be separated by spaces or
- * not. If no non-flag argument appears, leave "arg" alone
- */
- #define GETARG(arg) if ((parg = *++opt ? opt : \
- (*(argv+1) && **(argv+1) != '-' ? *++argv : NULL) ) \
- != NULL) arg = parg; else
-
- /* Loop through one of the header sections in pcalinit.h */
- #define DOHEADER(phdr) for (ap = phdr; *ap; ap++) PRT("%s\n", *ap)
-
- char *progname = **argv ? *argv : "pcal";
- struct tm *p_tm;
- register char **ap;
- char *date_file = NULL;
- char *opt, *parg, *p;
- long tmp;
- int nocal = FALSE;
- int sat_gray = TRUE;
- int sun_gray = TRUE;
- char *titlefont = TITLEFONT;
- char *dayfont = DAYFONT;
- char *outfile = OUTFILE;
- int rotate = 90;
- int month, year, nmonths;
- int badopt = FALSE; /* flag set if bad option */
- int badpar = FALSE; /* flag set if bad param */
- int default_out = TRUE; /* flag if default output file (VMS) */
- int nargs = 0; /* count of non-flag args */
- int numargs[MAXARGS]; /* non-flag (numeric) args */
-
- /* isolate root program name (for use in error messages) */
-
- if ((p = strrchr(progname, END_PATH)) != NULL)
- progname = ++p;
- if ((p = strchr(progname, '.')) != NULL)
- *p = '\0';
-
- /* walk through command-line arguments */
-
- while (*++argv) {
-
- if (**argv != '-') { /* assume numeric argument */
- if (nargs < MAXARGS)
- numargs[nargs++] = atoi(*argv);
- continue;
- }
-
- opt = (*argv) + 1;
- switch (*opt) {
-
- case 'd': /* specify alternate day font */
- GETARG(dayfont);
- break;
-
- case 'e': /* generate empty calendar */
- nocal = TRUE;
- date_file = NULL;
- break;
-
- case 'f': /* specify alternate date file */
- GETARG(date_file);
- nocal = FALSE;
- break;
-
- case 'o': /* specify alternate output file */
- GETARG(outfile);
- default_out = FALSE;
- break;
-
- case 'r': /* generate portrait calendar */
- rotate = 0;
- break;
-
- case 'S': /* Saturdays and Sundays in black */
- sun_gray = FALSE;
- case 's': /* Saturdays in black */
- sat_gray = FALSE;
- break;
-
- case 't': /* specify alternate title font */
- GETARG(titlefont);
- break;
-
- default:
- FPR(stderr, "%s: illegal option -%s\n", progname, opt);
- badopt = TRUE;
- break;
- }
- }
-
- /* Get and validate non-flag (numeric) parameters */
-
- switch (nargs) {
- case 0: /* no arguments - print current month/year */
- time(&tmp);
- p_tm = localtime(&tmp);
- month = p_tm->tm_mon + 1;
- year = p_tm->tm_year;
- nmonths = 1;
- break;
- case 1: /* one argument - print entire year */
- month = 1;
- year = numargs[0];
- nmonths = 12;
- break;
- default: /* two or three arguments - print one or more months */
- month = numargs[0];
- year = numargs[1];
- nmonths = nargs > 2 ? numargs[2] : 1;
- break;
- }
-
- if (year > 0 && year < 100) /* treat nn as 19nn */
- year += 1900;
-
- if (nmonths < 1) /* ensure at least one month */
- nmonths = 1;
-
- if (month < 1 || month > 12) { /* check range of month and year */
- FPR(stderr, "%s: month %d not in range 1 .. 12\n", progname,
- month);
- badpar = TRUE;
- }
-
- if (year < MIN_YR || year > MAX_YR) {
- FPR(stderr, "%s year %d not in range %d .. %d\n", progname,
- year, MIN_YR, MAX_YR);
- badpar = TRUE;
- }
-
- /* command-line errors? generate usage message and quit */
-
- if (badpar || badopt) {
- usage(progname);
- exit(EXIT_FAILURE);
- }
-
- /* flag and numeric parameters OK - now try to open the files */
-
- if (outfile && freopen(outfile, "w", stdout) == (FILE *) NULL) {
- FPR(stderr, "%s: can't open file %s\n", progname, outfile);
- exit(EXIT_FAILURE);
- }
-
- /*
- * In case we don't encounter any year data in the date file,
- * assume the current year.
- */
- init_year = year;
-
- /*
- * Attempt to open user-specified calendar file first
- */
- if (date_file != NULL) {
- if ((dfp = fopen(date_file, "r")) == NULL) {
- FPR(stderr, "%s: can't open file %s\n", progname,
- date_file);
- exit(EXIT_FAILURE);
- }
- }
-
- /*
- * Else see if the default calendar file exists (no error if
- * nonexistent; program will just print empty calendar)
- */
- else if (nocal == FALSE) {
- #ifdef VMS
- strcpy(lbuf, "SYS$LOGIN:"); /* get home directory (VMS) */
- #else
- *lbuf = '\0'; /* translate "HOME" to path (Un*x) */
- if ((p = getenv("HOME")) != NULL) {
- strcat(lbuf, p);
- strcat(lbuf, "/");
- }
- #endif
- strcat(lbuf, INFILE);
- dfp = fopen(lbuf, "r");
- }
-
- /*
- * Write out PostScript prolog
- */
- PRT("%%!\n");
- PRT("/titlefont /%s def\n/dayfont /%s def\n", titlefont, dayfont);
-
- DOHEADER(header_1);
- if (sun_gray) {
- PRT("\t\t\tday start add 7 mod 1 %s {\n",
- sat_gray ? "le" : "eq" );
- PRT("\t\t\t\t.8 setgray\n");
- PRT("\t\t\t} if\n");
- }
- DOHEADER(header_2);
-
- PRT("\t%d rotate\n", rotate);
- if (rotate)
- PRT("\t50 -120 translate\n");
- else
- PRT("\t0.75 0.75 scale\n\t50 460 translate\n");
-
- DOHEADER(header_3);
-
- /*
- * Loop through all the requested months
- */
- while (nmonths--) {
- pmonth(month, year);
- if (++month > DEC) {
- month = JAN;
- year++;
- }
- }
-
- if (dfp) /* close date file */
- fclose(dfp);
-
- #ifdef VMS
- if (default_out) /* inform VMS users where output is */
- FPR(stderr, "Output is in file %s\n", outfile);
- #endif
- exit(EXIT_SUCCESS);
- }
-
- /*
- * Print message explaining correct usage of the command-line
- * arguments and flags
- */
- usage(prog)
- char *prog;
- {
- FPR(stderr, "\nUsage:\n\n");
- FPR(stderr, "\t%s [-d FONT] [-e | -f FILE] [-o FILE] [-r] [-s | -%s] [-t FONT]\n",
- prog, S_OPT);
- FPR(stderr, "\t\t[ [ [mm] yy ] | [mm yy n] ]\n\n");
- FPR(stderr, "\t\t-d FONT\t\tspecify alternate font for day names\n");
- FPR(stderr, "\t\t\t\t(default: %s)\n", DAYFONT);
- FPR(stderr, "\n");
- FPR(stderr, "\t\t-e\t\tgenerate empty calendar (ignore date file)\n");
- FPR(stderr, "\n");
- FPR(stderr, "\t\t-f FILE\t\tspecify alternate date file\n");
- FPR(stderr, "\t\t\t\t(default: %s)\n", INFILE);
- FPR(stderr, "\n");
- FPR(stderr, "\t\t-o FILE\t\tspecify alternate output file\n");
- FPR(stderr, "\t\t\t\t(default: %s)\n", OUTFILE ? OUTFILE : "stdout");
- FPR(stderr, "\n");
- FPR(stderr, "\t\t-r\t\tgenerate portrait-style calendars\n");
- FPR(stderr, "\t\t\t\t(default: landscape)\n");
- FPR(stderr, "\n");
- FPR(stderr, "\t\t-s\t\tprint Saturdays in black\n");
- FPR(stderr, "\t\t-%s\t\tprint Saturdays and Sundays in black\n", S_OPT);
- FPR(stderr, "\t\t\t\t(default: print Saturdays and Sundays in gray)\n");
- FPR(stderr, "\n");
- FPR(stderr, "\t\t-t FONT\t\tspecify alternate font for titles\n");
- FPR(stderr, "\t\t\t\t(default: %s)\n", TITLEFONT);
- FPR(stderr, "\n");
- FPR(stderr, "\t%s [opts]\t\tgenerate calendar for current month/year\n",
- prog);
- FPR(stderr, "\n");
- FPR(stderr, "\t%s [opts] yy\t\tgenerate calendar for entire year yy\n",
- prog);
- FPR(stderr, "\n");
- FPR(stderr, "\t%s [opts] mm yy\tgenerate calendar for month mm\n", prog);
- FPR(stderr, "\t\t\t\t(Jan = 1), year yy (19yy if yy < 100)\n");
- FPR(stderr, "\n");
- FPR(stderr, "\t%s [opts] mm yy n\tas above, for n consecutive months\n",
- prog);
- FPR(stderr, "\n");
- }
-
-
- /*
- * is_valid - return TRUE if m/d/y is a valid date
- */
- int is_valid(m, d, y)
- register int m, d, y;
- {
- static char len[13] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
-
- return (d >= 1 && d <= (len[m] + (m == FEB && is_leap(y))) );
- }
-
-
- /*
- * loadwords - tokenize line buffer into word array, return word count
- * (assumes strtok() has already been called with non-null first arg)
- */
- int loadwords()
- {
- register char **ap = words;
- register i;
-
- for (i = 0; *ap = strtok(NULL, WHITESPACE) ; ap++, i++) ;
- return(i);
- }
-
-
- /*
- * parse - check date file entry for (in lbuf[]) for desired month/year
- *
- * Looks for an entry of one of the following forms:
- *
- * year <year>
- * <month_name> <day>{*} {<text>}
- * <month><sep><day>{<sep><year>}{*} {<text>}
- *
- * where
- * <month_name> := first 3+ characters of name of month (in English)
- * <sep> := one or more non-numeric, non-space, non-'*' characters
- *
- * Returns day (with holiday flag set if specified) if date file entry
- * is applicable to current month/year; 0 if not; parses any following
- * text into global array words[].
- */
- int parse(month, year)
- register int month, year;
- {
- register char *cp;
- int mm, dd, yy;
- int is_holiday, valid;
-
- static char *months[13] = { /* used to match alpha months */
- "", "jan", "feb", "mar", "apr", "may", "jun",
- "jul", "aug", "sep", "oct", "nov", "dec",
- };
-
- #define SKIP_FIELD(p) \
- if (1) {while (isdigit(*p)) p++; while (*p && !isdigit(*p)) p++;} else
-
- /*
- * Get first field - can be either "year", a month name, or a (complete)
- * numeric date spec
- */
- cp = strtok(lbuf, WHITESPACE);
-
- while (*cp) {
- if (isupper(*cp))
- *cp = tolower(*cp);
- cp++;
- }
- cp = lbuf;
-
- /*
- * Check for "year" line
- */
- if (strcmp(cp, "year") == 0) {
- cp = strtok(NULL, WHITESPACE);
- if ((yy = atoi(cp)) > 0) {
- if (yy < 100)
- yy += 1900;
- curr_year = yy;
- }
- return(0);
- }
-
- /*
- * If field begins with alpha, try to decode month name
- */
- if (isalpha(*cp)) {
- /* are month and year the ones we want? */
- if (year != curr_year || strncmp(cp, months[month], 3) != 0)
- return(0);
-
- /* month found, get and validate day */
-
- if ((cp = strtok(NULL, WHITESPACE)) == NULL ||
- !is_valid(month, dd = atoi(cp), year))
- return(0);
-
- is_holiday = cp[strlen(cp) - 1] == '*'; /* look for holiday flag */
-
- if (loadwords() || is_holiday)
- return(dd | is_holiday * HOLIDAY);
- return(0);
- }
-
- /*
- * Not alpha month, try numeric date (parse completely in case year
- * has changed)
- */
-
- is_holiday = cp[strlen(cp) - 1] == '*'; /* look for holiday flag */
-
- /* get month and compare against desired month */
-
- valid = (mm = atoi(cp)) == month;
- SKIP_FIELD(cp);
-
- /* now get and validate day */
-
- valid &= is_valid(month, dd = atoi(cp), year);
- SKIP_FIELD(cp);
-
- /* Numeric dates may (or may not) have a year */
-
- if ((yy = atoi(cp)) > 0) {
- if (yy < 100)
- yy += 1900;
- curr_year = yy;
- }
-
- valid &= curr_year == year; /* is year the desired one? */
-
- /* if date is valid and significant (text or holiday flag), return it */
-
- if (valid && (loadwords() || is_holiday))
- return(dd | is_holiday * HOLIDAY);
- return(0);
- }
-
-
- /*
- * getday - find next day entry for specified month and year in the date file
- */
- int getday(month, year, reset)
- register int month, year;
- int reset; /* TRUE: rewind date file */
- {
- static int eof = FALSE;
- register char *cp;
- register int c;
- int day;
- int in_comment; /* comments: from '#' to end-of-line */
-
- if (dfp == NULL) /* whoops, no date file */
- return(0);
-
- if (reset) { /* rewind file, reset default year, clear eof */
- rewind(dfp);
- curr_year = init_year;
- eof = FALSE;
- }
-
- if (eof)
- return(0);
-
- do {
- cp = lbuf;
- do {
- in_comment = FALSE;
- while ((c = getc(dfp)) != '\n' && c != EOF) {
- if (c == '#')
- in_comment = TRUE;
- /* ignore comments and leading white space */
- if (in_comment ||
- (cp == lbuf && (c == ' ' || c == '\t')))
- continue;
- *cp++ = c;
- }
- if (c == EOF) {
- eof = TRUE;
- return(0);
- }
- } while (cp == lbuf); /* ignore empty lines */
-
- *cp = '\0';
-
- /* examine the line, see if its one we want */
- } while ( (day = parse(month, year)) == 0);
-
- return(day);
- }
-
- /*
- * Browse through the date file looking for day text in specified month/year
- */
- find_daytext(month, year)
- int month, year;
- {
- register char **s;
- register int oldday = -1;
- register int day;
-
- for (day = getday(month, year, TRUE);
- day != 0;
- day = getday(month, year, FALSE))
- if (*words) {
- day &= ~HOLIDAY;
- if (day != oldday) {
- if (oldday != -1)
- PRT("] daytext\n");
- PRT("%d [ \n", day);
- oldday = day;
- } else
- PRT("(.p)\n");
- for (s = words; *s; s++)
- PRT("(%s)\n", *s);
- }
-
- if (oldday != -1) /* terminate last call to daytext (if any) */
- PRT("] daytext\n");
- }
-
-
- /*
- * Browse through the date file looking for holidays in specified month/year
- */
- find_holidays(month, year)
- int month, year;
- {
- register int day;
- unsigned long holidays = 0;
-
- /* get unique, sorted list of holidays by setting bits in flag word */
-
- for (day = getday(month, year, TRUE);
- day != 0;
- day = getday(month, year, FALSE))
- if (day & HOLIDAY)
- holidays |= 1 << (day & ~HOLIDAY);
-
- PRT("/holidays ["); /* start definition of list */
- for (day = 1; day <= 31; day++)
- if (holidays & (1 << day))
- PRT(" %d", day);
- PRT(" 99 ] def\n"); /* terminate with dummy entry */
- }
-
-
- /*
- * pmonth - generate calendar for specified month/year
- */
- pmonth(month, year)
- int month, year;
- {
-
- PRT("/year %d def\n", year); /* set up year and month */
- PRT("/month %d def\n", month);
- find_holidays(month, year); /* first pass - make list of holidays */
- PRT("printmonth\n");
- find_daytext(month, year); /* second pass - add text to boxes */
- PRT("showpage\n");
- }
-