home *** CD-ROM | disk | FTP | other *** search
/ Frozen Fish 2: PC / frozenfish_august_1995.bin / bbs / d09xx / d0975.lha / PCal / pcal.c < prev    next >
Text File  |  1992-02-19  |  46KB  |  1,561 lines

  1. static char  VERSION_STRING[]    = "@(#)pcal v4.3 - generate Postscript calendars";
  2. /*
  3.  * pcal.c - generate PostScript file to print calendar for any month and year
  4.  *
  5.  * The original PostScript code to generate the calendars was written by
  6.  * Patrick Wood (Copyright (c) 1987 by Patrick Wood of Pipeline Associates,
  7.  * Inc.), and authorized for modification and redistribution.  The calendar
  8.  * file inclusion code was originally written in "bs(1)" by Bill Vogel of
  9.  * AT&T.  Patrick's original PostScript was modified and enhanced several
  10.  * times by King Ables, Tom Tessin, and others whose names have regrettably 
  11.  * been lost.  This C version was originally created by Ken Keirnan of Pacific
  12.  * Bell; additional enhancements by Joseph P. Larson, Ed Hand, Andrew Rogers, 
  13.  * Mark Kantrowitz, Joe Brownlee, Andy Fyfe, and Geoff Kuenning.  The moon
  14.  * routines were originally written by Jef Poskanzer and Craig Leres, and
  15.  * were incorporated into Pcal by Richard Dyson.
  16.  *
  17.  * Contents:
  18.  *
  19.  *        alt_fopen
  20.  *        check_numargs
  21.  *        color_msg
  22.  *        gen_shading
  23.  *        get_args
  24.  *        get_flag
  25.  *        init_misc
  26.  *        main
  27.  *        set_color
  28.  *        set_debug_flag
  29.  *        usage
  30.  *
  31.  * Revision history:
  32.  *
  33.  *    4.3    AWR    12/06/91    Attempted to simplify some of the
  34.  *                    mysteries surrounding command-line
  35.  *                    parsing; moved some processing from
  36.  *                    get_args() to new check_numargs()
  37.  *
  38.  *            12/05/91    Search for moon file in directory
  39.  *                    where Pcal lives (cf. moonphas.c)
  40.  *
  41.  *            12/03/91    Add -s flag to override default values
  42.  *                    for date/fill box shading
  43.  *
  44.  *            11/22/91    Use cvt_escape() (new; cf. pcalutil.c)
  45.  *                    to convert escape sequences in command
  46.  *                    line strings
  47.  *
  48.  *            11/18/91    Improve documentation; add init_misc()
  49.  *                    as catch-all for various initializations
  50.  *
  51.  *            10/25/91    Support moon phases as wildcards
  52.  *
  53.  *            10/17/91    Add -Z flag to generate debugging
  54.  *                    information; add a pre-pass through
  55.  *                    command line flags to detect -ZO
  56.  *                    prior to parsing PCAL_OPTS
  57.  *
  58.  *            10/15/91    Revise logic of date file search
  59.  *
  60.  *    4.2    AWR    10/08/91    Add -k and -K flags to control
  61.  *                    positioning of small calendars
  62.  *
  63.  *            10/03/91    Add "note{/<n>}" to select box for
  64.  *                    note text (as per Geoff Kuenning)
  65.  *
  66.  *                    Add -S flag to suppress generation
  67.  *                    of the small calendars
  68.  *
  69.  *            10/02/91    Add -N flag to specify alternate
  70.  *                    heading for notes box
  71.  *
  72.  *                    Allow user to specify alternate notes
  73.  *                    font size (-n <name>/<size>)
  74.  *
  75.  *            10/01/91    Add -u flag to generate version info
  76.  *                    and parameter usage message
  77.  *
  78.  *            09/30/91    Support "if" and "elif" in date file
  79.  *
  80.  *            09/19/91    Add -c flag to generate input file for
  81.  *                    Un*x "calendar" utility
  82.  *
  83.  *    4.11    AWR    08/20/91    Add support for "nearest" keyword
  84.  *                    (as per Andy Fyfe)
  85.  *
  86.  *                    define "whole_year" when -w set
  87.  *
  88.  *        AWR    08/21/91    Support %u, %w, %D, %M format specs
  89.  *                    and optional number following %[+-]
  90.  *                    (cf. writefil.c)
  91.  *
  92.  *    4.1    AWR    08/16/91    Add -G flag to print "gray" dates as
  93.  *                    outlined, gray-filled characters
  94.  *
  95.  *                    Fix potential bug in julday() (cf.
  96.  *                    moonphas.c)
  97.  *
  98.  *    4.02    AWR    07/02/91    Add -v flag to print version info only;
  99.  *                    call find_executable() to get true
  100.  *                    program pathname (cf. pcalutil.c);
  101.  *                    add format specifiers to text strings
  102.  *                    (cf. writefil.c)
  103.  *
  104.  *    4.01    AWR    03/19/91    Incorporate revised moonphas.c (q.v.)
  105.  *
  106.  *    4.0    AWR    02/24/91    Add alt_fopen() to search for file
  107.  *                    in alternate path; use to look for
  108.  *                    date file in same directory as
  109.  *                    Pcal executable (as per Floyd Miller)
  110.  *
  111.  *                    Support negative ordinals (cf.
  112.  *                    readfile.c, pcalutil.c)
  113.  *
  114.  *                    Support expressions in preprocessor
  115.  *                    "if{n}def" lines (cf. exprpars.c)
  116.  *
  117.  *                    Support "even", "odd" ordinals (cf.
  118.  *                    readfile.c) and ordinals > 5th
  119.  *
  120.  *                    Support -B (leave unused boxes blank)
  121.  *                    flag
  122.  *
  123.  *                    Separated into moonphas.c, pcal.c,
  124.  *                    pcalutil.c, readfile.c, and writefil.c;
  125.  *                    added support for moon phase file
  126.  *
  127.  *                    Support -w (whole year) flag; fix
  128.  *                    various bugs and nonportable constructs
  129.  *
  130.  *    3.0    AWR    12/10/90    Support concept of "weekday", "workday",
  131.  *                    and "holiday" (and converses)
  132.  *
  133.  *                    Substantial revision of program logic:
  134.  *                    extracted pcaldefs.h and pcallang.h,
  135.  *                    moving all language dependencies (even
  136.  *                    flag names) to the latter.
  137.  *
  138.  *                    add -I flag to reinitialize all
  139.  *                     flags to program defaults; -j and -J
  140.  *                    flags (print Julian dates); add -x,
  141.  *                     -y, -X, -Y flags (as per Ed Hand)
  142.  *                    for output scaling and translation
  143.  *
  144.  *                    allow "wildcard" dates (e.g., "all
  145.  *                    Thursday{s} in Oct", "last Friday in
  146.  *                    all") and notes ("note all <text>);
  147.  *                    print full "help" message (including
  148.  *                    date file syntax)
  149.  *
  150.  *    2.6    AWR    10/15/90    parse floating dates (e.g. "first
  151.  *                    Monday in September") and relative
  152.  *                    floating dates (e.g., "Friday after
  153.  *                    fourth Thursday in November"); simplify
  154.  *                    logic of -F option; add -F to usage 
  155.  *                    message; replace COLOR_MSG() string
  156.  *                    with color_msg() routine; add -h
  157.  *                    (help message) and -A | -E (American |
  158.  *                    European date format) flags; renamed
  159.  *                    flag sets for clarity; more comments
  160.  *
  161.  *    2.5    JAB    10/04/90    added -F option
  162.  *
  163.  *    2.4    ---    10/01/90    * no modifications *
  164.  *
  165.  *    2.3    JWZ/AWR    09/18/90    added moon routines
  166.  *
  167.  *    2.2    AWR    09/17/90    revise logic of parse(); new usage
  168.  *                    message
  169.  *
  170.  *        JAB/AWR    09/14/90    support "note" lines in date file
  171.  *
  172.  *    2.1    MK/AWR    08/27/90    support -L, -C, -R, -n options;
  173.  *                    print holiday text next to date
  174.  *
  175.  *        AWR    08/24/90    incorporate cpp-like functionality;
  176.  *                    add -D and -U options; save date file
  177.  *                    information in internal data structure;
  178.  *                    look for PCAL_OPTS and PCAL_DIR; look
  179.  *                    for ~/.calendar and ~/calendar
  180.  *
  181.  *    2.0    AWR    08/08/90    included revision history; replaced -r
  182.  *                    flag with -l and -p; replaced -s and -S
  183.  *                    flags with -b and -g; recognize flags
  184.  *                    set in date file; translate ( and ) in
  185.  *                    text to octal escape sequence; usage()
  186.  *                    message condensed to fit 24x80 screen
  187.  *
  188.  *    Parameters:
  189.  *
  190.  *        pcal [opts]        generate calendar for current month/year
  191.  *                    (current year if -w flag specified)
  192.  *
  193.  *        pcal [opts] yy        generate calendar for entire year yy
  194.  *
  195.  *        pcal [opts] mm yy    generate calendar for month mm
  196.  *                    (Jan = 1), year yy (19yy if yy < 100)
  197.  *                    (12 months starting with mm/yy if -w
  198.  *                    specified)
  199.  *
  200.  *        pcal [opts] mm yy n    as above, for n consecutive months (n
  201.  *                    rounded to next multiple of 12 if -w
  202.  *                    specified)
  203.  *
  204.  *    Output:
  205.  *
  206.  *        PostScript file to print calendars for all selected months.
  207.  *
  208.  *    Options:
  209.  *
  210.  *        -I        initialize all parameters to program defaults
  211.  *
  212.  *        -b <DAY>    print specified weekday in black
  213.  *        -g <DAY>    print specified weekday in gray
  214.  *                (default: print Saturdays and Sundays in gray)
  215.  *
  216.  *        -G        print "gray" dates as filled outlines
  217.  *        -O        print "gray" dates as unfilled outlines
  218.  *                (default: gray characters)
  219.  *
  220.  *        -s {<DATE>}{/<FILL>}
  221.  *                specify alternate shading values for dates
  222.  *                and fill boxes
  223.  *                (default: dates = 0.8, fill boxes = 0.9)
  224.  *
  225.  *        -d <FONT>    specify alternate font for day names
  226.  *                (default: Times-Bold)
  227.  *
  228.  *        -n <FONT>{/<SIZE>}
  229.  *                specify alternate font/size for notes in boxes
  230.  *                (default: Helvetica-Narrow/6)
  231.  *
  232.  *        -t <FONT>    specify alternate font for titles
  233.  *                (default: Times-Bold)
  234.  *
  235.  *        -D <SYM>    define preprocessor symbol
  236.  *        -U <SYM>    un-define preprocessor symbol
  237.  *
  238.  *        -e        generate empty calendar (ignore date file)
  239.  *
  240.  *        -f <FILE>    specify alternate date file (default:
  241.  *                ~/.calendar on Un*x, SYS$LOGIN:CALENDAR.DAT
  242.  *                on VMS, s:calendar.dat on Amiga; if
  243.  *                environment variable [logical name on VMS]
  244.  *                PCAL_DIR exists, looks there instead; if
  245.  *                not found in either place, looks in same
  246.  *                directory as Pcal executable)
  247.  *
  248.  *        -o <FILE>    specify alternate output file (default:
  249.  *                stdout on Un*x, CALENDAR.PS on VMS, 
  250.  *                RAM:calendar.ps on Amiga)
  251.  *
  252.  *        -L <STRING>    specify left foot string   (default: "")
  253.  *        -C <STRING>    specify center foot string (default: "")
  254.  *        -R <STRING>    specify right foot string  (default: "")
  255.  *
  256.  *        -N <STRING>    specify notes box header (default: "Notes")
  257.  *
  258.  *        -l        generate landscape-mode calendars
  259.  *        -p        generate portrait-mode calendars
  260.  *                (default: landscape-mode)
  261.  *
  262.  *        -h        (command line only) write version information
  263.  *                and full help message to stdout
  264.  *        -u        (command line only) write version information
  265.  *                and parameter usage message to stdout
  266.  *        -v        (command line only) write version information
  267.  *                alone to stdout
  268.  *
  269.  *        -m        draw a small moon icon on the days of the
  270.  *                full, new, and half moons.
  271.  *        -M        draw a small moon icon every day.  
  272.  *                (default: no moons)
  273.  *
  274.  *        -F <DAY>    select alternate day to be displayed as the 
  275.  *                first day of the week (default: Sunday)
  276.  *
  277.  *        -A        dates are in American format (e.g., 10/15/90,
  278.  *                Oct 15) (default)
  279.  *        -E        dates are in European format (e.g., 15.10.90,
  280.  *                15 Oct)
  281.  *
  282.  *        -x <XSCALE>    These two options can be used to change
  283.  *        -y <YSCALE>    the size of the calendar.
  284.  *
  285.  *        -X <XTRANS>    These two options can be used to relocate
  286.  *        -Y <YTRANS>    the position of the calendar on the page.
  287.  *
  288.  *        -j        print Julian dates (day of year)
  289.  *        -J        print Julian dates and days remaining
  290.  *                (default: neither)
  291.  *
  292.  *        -w        print whole year (12 months) per page
  293.  *
  294.  *        -c        generate input for Un*x calendar(1) utility
  295.  *
  296.  *        -B        leave unused calendar boxes blank
  297.  *
  298.  *
  299.  *    There are many ways to specify these options in addition to using the
  300.  *    command line; this facilitates customization to the user's needs.
  301.  *
  302.  *    If the environment variable (global symbol on VMS) PCAL_OPTS is
  303.  *    present, its value will be parsed as if it were a command line.
  304.  *    Any options specified will override the program defaults.
  305.  *
  306.  *    All options but -[cefhuvDU] may be specified in the date file by
  307.  *    including one or more lines of the form "opt <options>".  Any such
  308.  *    options override any previous values set either as program defaults,
  309.  *    via PCAL_OPTS, or in previous "opt" lines.
  310.  *
  311.  *    Options explicitly specified on the command line in turn override all
  312.  *    of the above.
  313.  *
  314.  *    Any flag which normally takes an argument may also be specified without
  315.  *    an argument; this resets the corresponding option to its default.  -D
  316.  *    alone un-defines all symbols; -U alone has no effect.
  317.  *
  318.  *    Parameters and flags may be mixed on the command line.  In some cases
  319.  *    (e.g., when a parameter follows a flag without its optional argument)
  320.  *    this may lead to ambiguity; the dummy flag '-' (or '--') may be used
  321.  *    to separate them, i.e. "pcal -t - 9 90".
  322.  *
  323.  *
  324.  *    Date file syntax:
  325.  *
  326.  *    The following rules describe the syntax of date file entries:
  327.  *
  328.  *      year <year>
  329.  *
  330.  *      opt <options>
  331.  *
  332.  *      note{/<number>} <month_spec> <text>
  333.  *      note{/<number>} <month> <text>
  334.  *
  335.  *      if -A flag (American date formats) specified:
  336.  *        <month_name> <day>{*} {<text>}
  337.  *        <month><sep><day>{<sep><year>}{*} {<text>}
  338.  *
  339.  *      if -E flag (European date formats) specified:
  340.  *        <day> <month_name>{*} {<text>}
  341.  *        <day><sep><month>{<sep><year>}{*} {<text>}
  342.  *
  343.  *      <ordinal> <day_name> in <month_spec>{*} {<text>}
  344.  *      <day_name> <prep> <date_spec>
  345.  *
  346.  *    where
  347.  *
  348.  *      {x}          means x is optional
  349.  *
  350.  *      <date_spec> := any of the above date specs (not year, note, or opt)
  351.  *      <month_name> := first 3+ characters of name of month, or "all"
  352.  *      <month_spec> := <month_name>, or "year"
  353.  *      <day_name> := first 3+ characters of name of weekday, "day",
  354.  *            "weekday", "workday", "holiday", "nonweekday",
  355.  *            "nonworkday", "nonholiday", "new_moon",
  356.  *            "first_quarter", "full_moon", or "last_quarter"
  357.  *      <ordinal> := ordinal number ("1st", "2nd", etc.), "first" .. "fifth",
  358.  *            "last", "even", "odd", or "all"
  359.  *      <prep> := "before", "preceding", "after", "following", "nearest",
  360.  *            "on_or_before", or "on_or_after"
  361.  *      <sep> := one or more non-numeric, non-space, non-'*' characters
  362.  *      <month>, <day>, <year> are the numeric forms
  363.  *
  364.  *      <options> := any command-line option except -[cefhuvDU]
  365.  *
  366.  *    Comments start with '#' and run through end-of-line.
  367.  *
  368.  *    Holidays may be flagged by specifying '*' as the last character of
  369.  *    the date field(s), e.g. "10/12* Columbus Day", "July 4* Independence
  370.  *    Day", etc.  Any dates flagged as holidays will be printed in gray, and
  371.  *    any associated text will appear adjacent to the date.
  372.  *
  373.  *    Note that the numeric date formats (mm/dd{/yy}, dd.mm{.yy}) support
  374.  *    an optional year, which will become the subsequent default year.  The
  375.  *    alphabetic date formats (month dd, dd month) do not support a year
  376.  *    field; the "year yy" command is provided to reset the default year.
  377.  *
  378.  *    "Floating" days may be specified in the date file as "first Mon in 
  379.  *    Sep", "last Mon in May", "4th Thu in Nov", etc.; any word may be
  380.  *    used in place of "in".  "Relative floating" days (e.g. "Fri after 4th 
  381.  *    Thu in Nov") are also accepted; they may span month/year bounds.
  382.  *    Pcal also accepts date specs such as "all Friday{s} in October", "last
  383.  *    Thursday in all", etc., and produces the expected results; "each" and
  384.  *    "every" are accepted as synonyms for "all".  Negative ordinals are
  385.  *    allowed; "-2nd" means "next to last".
  386.  *
  387.  *    The words "day", "weekday", "workday", and "holiday" may be used as
  388.  *    wildcards: "day" matches any day, "weekday" matches any day normally
  389.  *    printed in black, "workday" matches any day normally printed in black
  390.  *    and not explicitly flagged as a holiday, and "holiday" matches any
  391.  *    day explicitly flagged as a holiday.  "Nonweekday", "nonworkday",
  392.  *    and "nonholiday" are also supported and have the obvious meanings.
  393.  *    Moon phases may also appear as wildcards; "nm" is accepted as a
  394.  *    synonym for "new_moon", "1q" and "fq" for "first_quarter", "fm" for
  395.  *    "full_moon", and "3q", "lq", and "third_quarter" for "last_quarter". 
  396.  *
  397.  *    "Odd" and "even" do not refer to the actual date; instead, "odd"
  398.  *    means "alternate, starting with the first"; "even" means "alternate,
  399.  *    starting with the second".  Thus, "odd Fridays in March" refers to
  400.  *    the first, third, and (if present) fifth Fridays in March - not to
  401.  *    those Fridays falling on odd dates.
  402.  *
  403.  *    "All" refers to each individual month; "year" refers to the year
  404.  *    as an entity.  Thus "odd Fridays in all" refers to the first/third/
  405.  *    fifth Friday of each month, while "odd Fridays in year" refers to
  406.  *    the first Friday of January and every other Friday thereafter.
  407.  *
  408.  *    Additional notes may be propagated to an empty calendar box by the
  409.  *    inclusion of one or more lines of the form "note{/<number>} <month>
  410.  *    <text>", where <month> may be numeric or alphabetic; "note{/<number>}
  411.  *    all <text>" propagates <text> to each month in the current year.
  412.  *    <number> is an optional positive or negative number specifying the
  413.  *    empty box where the associated text is to be placed: if positive,
  414.  *    Pcal counts forward from the first empty box; if negative, Pcal counts
  415.  *    backward from the last empty box.  Thus, "note/1 ..." places the
  416.  *    associated text in the first empty box, and "note/-3 ..." in the
  417.  *    third-to-last; the default is -1 (last empty box).  (Note that if the
  418.  *    -S option is used, it must be specified either on the command line or
  419.  *    prior to any "note" lines in the date file.)
  420.  *
  421.  *    Pcal also allows format specifiers in the text (and foot strings -
  422.  *    cf. the -L, -C, -R, and -N options); each will be replaced by its
  423.  *    equivalent string as outlined in the table below.  (Most of these are
  424.  *    derived from the strftime() function; %[louwMD0+-] are Pcal-specific.)
  425.  *
  426.  *        %a : abbreviated weekday
  427.  *        %A : full weekday
  428.  *        %b : abbreviated month name
  429.  *        %B : full month name
  430.  *        %d : day of month (1-31)
  431.  *        %j : day of year (1-366)
  432.  *        %l : days left in year (0-365)
  433.  *        %m : month (1-12)
  434.  *        %u : week number (1-54)
  435.  *        %U : week number (0-53)
  436.  *        %w : week number (1-54)
  437.  *        %W : week number (0-53)
  438.  *        %y : year w/o century (00-99)
  439.  *        %Y : year w/century
  440.  *        %% : '%' character
  441.  *
  442.  *        %o : print number as ordinal
  443.  *        %0 : print number with leading zeroes
  444.  *        %+ : use following month or year
  445.  *        %- : use previous month or year
  446.  *        %{+N}[DWMY] : adjust date by +N days/weeks/months/years
  447.  *        %{-N}[DWMY] : adjust date by -N days/weeks/months/years
  448.  *
  449.  *    %u considers the week containing 1/1 as week 1 and the following
  450.  *    logical Sunday (the first day of the week as printed; cf. the -F
  451.  *    flag) as the start of week 2; %U considers the first logical Sunday
  452.  *    as the first day of week 1.  %w and %W behave like %u and %U
  453.  *    respectively, but use the first logical Monday instead.  (Note that
  454.  *    %w has a different meaning to strftime().)
  455.  *
  456.  *    %o prints a number as an ordinal, with the appropriate suffix ("st",
  457.  *    "nd", "rd", or "th" in English) appended; for example, "%od" prints
  458.  *    the day of the month as "1st", "2nd", "3rd", etc.
  459.  *
  460.  *    Unlike strftime(), Pcal's default is to print numbers (except %y)
  461.  *    without leading zeroes.  If leading zeroes are desired, the '0'
  462.  *    prefix may be used; for example, "%0j" prints the day of year as
  463.  *    001-365.
  464.  *
  465.  *    %+ and %- direct Pcal to substitute the following/previous month/year
  466.  *    in the following [bBmyY] specifier; for example, "%+B" prints the
  467.  *    name of the next month.
  468.  *
  469.  *    %{[+-]N}[DWMY] do not print anything, but instead adjust the
  470.  *    working date by +-N days (D), weeks (W), months (M), or years (Y);
  471.  *    subsequent format specifiers use the adjusted date instead of the
  472.  *    current date.  For example, "%+1M %B %Y" adjusts the date forward by
  473.  *    one month and then prints the resulting month and year ("January
  474.  *    1991" in December, 1990); "%-2W %b %d" adjusts the date backward
  475.  *    by two weeks and prints the resulting month and day ("Jul 26" on
  476.  *    August 9).
  477.  *
  478.  *    Such date adjustments are normally cumulative; for example,
  479.  *    "%+1Y%-1D" adjusts the date forward by one year and then backward
  480.  *    by one day.  If %D or %M is specified alone (or if N is zero), Pcal
  481.  *    restores the original date.  (Note that %M has a different meaning
  482.  *    to strftime().)
  483.  *
  484.  *    The "Notes" box uses the first of the current month as the default
  485.  *    date.  All foot strings use the first of the current month in single-
  486.  *    month mode and the first of the starting month in whole-year mode.
  487.  *
  488.  *    Simple cpp-like functionality is provided.  The date file may include
  489.  *    the following commands, which work like their cpp counterparts:
  490.  *
  491.  *        define <sym>
  492.  *        undef <sym>
  493.  *
  494.  *        if{{n}def} <expr>
  495.  *           ...
  496.  *        { elif <expr>
  497.  *           ... }*
  498.  *        { else
  499.  *           ... }
  500.  *        endif
  501.  *
  502.  *        include <file>
  503.  *
  504.  *    Note that these do not start with '#', which is reserved as a comment
  505.  *    character.
  506.  *
  507.  *    <sym> is a symbol name consisting of a letter followed by zero or
  508.  *    more letters, digits, or underscores ('_').  Symbol names are always
  509.  *    treated in a case-insensitive manner.
  510.  *
  511.  *    <expr> is an expression consisting of symbol names joined by the logical
  512.  *    operators (in order of precedence, high to low) '!' (unary negate), '&'
  513.  *    (and), '^' (exclusive or), and '|' (inclusive or).  '&&' and '||' are
  514.  *    accepted as synonyms for '&' and '|' respectively; the order of
  515.  *    evaluation may be altered by the use of parentheses.  A symbol whose
  516.  *    name is currently defined evaluates to TRUE; one whose name is not
  517.  *    currently defined evaluates to FALSE.  Thus "ifdef A | B | C" is TRUE
  518.  *    if any of the symbols A, B, and C is currently defined, and
  519.  *    "ifdef A & B & C" is TRUE if all of them are.
  520.  *
  521.  *    "ifndef A | B | C" is equivalent to "ifdef !(A | B | C)" (or, using
  522.  *    DeMorgan's Law, "ifdef !A & !B & !C") - in other words, TRUE if none of
  523.  *    the symbols A, B, and C is currently defined.
  524.  *
  525.  *    "if" is accepted as a synonym for "ifdef".
  526.  *
  527.  *    "elif A" is TRUE if A is defined.  Multiple "elif" clauses may appear;
  528.  *    at most one "if{{n}def}", "elif", or "else" clause in a given block
  529.  *    will be processed.
  530.  *
  531.  *    "define" alone deletes all the current definitions; "if{def}" alone is
  532.  *    always false; "ifndef" alone is always true.
  533.  *
  534.  *    The file name in the "include" directive may optionally be surrounded
  535.  *    by "" or <>; in any case, path names are taken to be relative to
  536.  *    the location of the file containing the "include" directive.  If the
  537.  *    string "%y" appears in the file name, it is replaced by the last two
  538.  *    digits of the current year.
  539.  *
  540.  *    The "-w" flag defines the symbol "whole_year", which may be tested
  541.  *    in the same manner as any user-defined symbol.
  542.  *
  543.  *
  544.  *    Moon file syntax:
  545.  *
  546.  *    The user may enter the dates and (optionally) times of quarter
  547.  *    moons (from a reliable source such as an almanac or astronomical
  548.  *    table) into a file called .moon%y (moon%y.dat on VMS), where %y is
  549.  *    the last two digits of the year.  If such a file exists (in the
  550.  *    same directory as the date file, or in the directory where Pcal
  551.  *    itself lives), Pcal will interpolate the phase of the moon from the
  552.  *    information in this file instead of using the default algorithm.
  553.  *
  554.  *    (Pcal originally used an extremely simplistic moon phase algorithm;
  555.  *    the moon file was added to v4.0 to enable Pcal to interpolate the
  556.  *    phase of the moon from the [presumably more accurate] information
  557.  *    within.  More recently, the original moon phase algorithm was
  558.  *    superseded by an astronomer-strength version, largely obviating
  559.  *    the need for a moon file; however, it will continue to be
  560.  *    supported for the foreseeable future.)
  561.  *
  562.  *    Entries in the moon file must conform to the following syntax:
  563.  *
  564.  *      if -A flag (American date formats) specified:
  565.  *        <quarter> <month><sep><day> {<hour><sep><min>}
  566.  *
  567.  *      if -E flag (European date formats) specified:
  568.  *        <quarter> <day><sep><month> {<hour><sep><min>}
  569.  *
  570.  *    where
  571.  *
  572.  *      <quarter> := "nm", "fq" or "1q", "fm", "lq" or "3q" (new
  573.  *            moon, first quarter, full moon, last quarter)
  574.  *      <hour>    := number 0-23 (24-hour clock)
  575.  *      <min>     := number 0-59
  576.  *
  577.  *    This file must contain entries for all quarter moons in the year,
  578.  *    in chronological order; if any errors are encountered, Pcal will
  579.  *    revert to using its default algorithm.
  580.  *
  581.  *    As in the date file, comments start with '#' and run through
  582.  *    end-of-line.  
  583.  */
  584.  
  585.  
  586. /*
  587.  * Standard headers:
  588.  */
  589.  
  590. #include <stdio.h>
  591. #include <ctype.h>
  592. #include <time.h>
  593. #include <string.h>
  594.  
  595. /*
  596.  * Pcal-specific definitions:
  597.  */
  598.  
  599. #define MAIN_MODULE    1
  600. #include "pcaldefs.h"
  601. #include "pcalglob.h"
  602. #include "pcallang.h"
  603.  
  604. /*
  605.  * Globals:
  606.  */
  607.  
  608. static int nargs = 0;                /* count of non-flag args  */
  609. static int numargs[MAXARGS];            /* non-flag (numeric) args */
  610. static int init_month, init_year, nmonths;    /* parsed values of above  */
  611.  
  612.  
  613. /*
  614.  * Main program - parse and validate command-line arguments, open files,
  615.  * generate PostScript boilerplate and code to generate calendars.
  616.  *
  617.  * Program structure:
  618.  *
  619.  * For maximum user flexibility, Pcal gives the user several different ways
  620.  * to set program flags and/or override earlier choices.  This necessitates
  621.  * that main() call get_args() (directly or indirectly) several times:
  622.  *
  623.  *    a) to parse the command line, looking only for -Z flags (which turn on
  624.  *       debugging information) and numeric parameters
  625.  *
  626.  *    b) to parse environment variable (global symbol on VMS) PCAL_OPTS, if
  627.  *       defined
  628.  *
  629.  *    c) to parse the command line a second time, looking for options
  630.  *       related to finding/interpreting the date file: -[cefhuvDU]
  631.  *
  632.  *     d) main() calls read_datefile() to read and parse the date file; it
  633.  *       in turn calls get_args() once per "opt" line in the date file
  634.  *
  635.  *    e) to parse the command line one final time, allowing the user to
  636.  *       override any flags other than those listed in c) above
  637.  *
  638.  * The rest of it is straightforward: main() attempts to open the output file
  639.  * (if any), and, if successful, calls write_psfile() to generate the
  640.  * PostScript output (or write_calfile() to generate the "calendar" input).
  641.  * Some minor housekeeping and we're done.
  642.  *
  643.  */
  644. #ifdef PROTOS
  645. int main(int argc,
  646.      char **argv)
  647. #else
  648. int main(argc, argv)
  649.     int argc;
  650.     char **argv;
  651. #endif
  652. {
  653.     FILE *dfp = NULL;        /* date file pointer */
  654.     char *p, **ap, *pathlist[10];
  655.     char tmp[STRSIZ];
  656.     int n;
  657.  
  658.     init_misc();            /* handle initialization warts */
  659.  
  660.     /* extract root program name and program path - note that some
  661.      * systems supply the full pathname and others just the root
  662.      */
  663.  
  664.     strcpy(progname, **argv ? *argv : "pcal");
  665.  
  666.     if ((p = strrchr(progname, END_PATH)) != NULL)
  667.         strcpy(progname, ++p);
  668.     if ((p = strchr(progname, '.')) != NULL)
  669.         *p = '\0';
  670.  
  671.     mk_path(progpath, find_executable(*argv));
  672.  
  673.     /* get version from VERSION_STRING (for use in PostScript comment) */
  674.     strcpy(tmp, VERSION_STRING + 4);
  675.     p = strchr(tmp, ' ') + 1;    /* skip program name */
  676.     *strchr(p, ' ') = '\0';        /* terminate after version */
  677.     strcpy(version, p);
  678.  
  679.     /*
  680.      * Get the arguments from a) the command line (pre-pass to pick up
  681.      * debug flags only), b) the environment variable PCAL_OPTS, c) the
  682.      * first command line pass, d) "opt" lines in the date file, and e)
  683.      * a final command line pass, in that order
  684.      */
  685.  
  686.     /* make an preliminary pass to look for the debug flags (to ensure
  687.      * ensure that -ZO will print any flags set in PCAL_OPTS); also
  688.      * get and validate the numeric command-line arguments
  689.      */
  690.     if (!get_args(argv, P_CMD0, NULL, TRUE)) {
  691.         usage(stderr, FALSE);        /* invalid flag or param */
  692.         exit(EXIT_FAILURE);
  693.     }
  694.  
  695.     /* parse environment variable PCAL_OPTS as a command line */
  696.  
  697.     if ((p = getenv(PCAL_OPTS)) != NULL) {
  698.         strcpy(lbuf, "pcal ");        /* dummy program name */
  699.         strcat(lbuf, p);
  700.         (void) loadwords(words, lbuf);    /* split string into words */
  701.         if (! get_args(words, P_ENV, PCAL_OPTS, FALSE)) {
  702.             usage(stderr, FALSE);
  703.             exit(EXIT_FAILURE);
  704.         }
  705.     }
  706.  
  707.     /* parse command-line arguments once to find name of date file, etc. */
  708.  
  709.     (void) get_args(argv, P_CMD1, NULL, FALSE);
  710.  
  711.     /* Attempt to open the date file as specified by the [-e | -f] flags */
  712.  
  713.     switch (datefile_type) {
  714.     case NO_DATEFILE:
  715.         dfp = NULL;
  716.         break;
  717.  
  718.     case USER_DATEFILE:    
  719.         /* Attempt to open user-specified calendar file: search
  720.          * first in the current directory, then in PCAL_DIR (if
  721.          * defined), and finally in the directory where the Pcal
  722.          * executable lives.  It is a fatal error if the
  723.          * user-specified date file cannot be found.
  724.          */
  725.         n = 0;
  726.         pathlist[n++] = "";
  727.         if ((p = trnlog(PCAL_DIR)) != NULL)
  728.             pathlist[n++] = p;
  729.         pathlist[n++] = progpath;
  730.         pathlist[n] = NULL;
  731.         
  732.         strcpy(tmp, datefile);    /* save original name for error msg */
  733.  
  734.         if ((dfp = alt_fopen(datefile, tmp, pathlist, "r")) == NULL) {
  735.             FPR(stderr, E_FOPEN_ERR, progname, tmp);
  736.             exit(EXIT_FAILURE);
  737.         }
  738.         break;
  739.  
  740.     case SYS_DATEFILE:
  741.         /* Attempt to open system-specified calendar file: search
  742.          * first in PCAL_DIR, then in HOME_DIR (current directory
  743.          * if neither is defined) and finally in the directory where
  744.          * the Pcal executable lives.  It is not an error if the
  745.          * system-specified date file cannot be found; Pcal will
  746.          * simply generate an empty calendar.
  747.          */
  748.         n = 0;
  749.         if ((p = trnlog(PCAL_DIR)) != NULL)
  750.             pathlist[n++] = p;
  751.         if ((p = trnlog(HOME_DIR)) != NULL)
  752.             pathlist[n++] = p;
  753.         if (n == 0)
  754.             pathlist[n++] = "";
  755.         pathlist[n++] = progpath;
  756.         pathlist[n] = NULL;
  757.         
  758.         dfp = alt_fopen(datefile, DATEFILE, pathlist, "r");
  759.  
  760.         /* if the date file has not been found and ALT_DATEFILE is
  761.          * defined, search same paths for ALT_DATEFILE before
  762.          * giving up
  763.          */
  764. #ifdef ALT_DATEFILE
  765.         if (!dfp)
  766.             dfp = alt_fopen(datefile, ALT_DATEFILE, pathlist, "r");
  767. #endif
  768.         break;
  769.     }
  770.  
  771.     /* read the date file (if any) and build internal data structure */
  772.  
  773.     if (dfp) {
  774.         curr_year = init_year;
  775.         read_datefile(dfp, datefile);
  776.         fclose(dfp);
  777.     } else
  778.         datefile[0] = '\0';        /* for PostScript comment */
  779.  
  780.     /* reparse command line - flags there supersede those in date file */
  781.  
  782.     (void) get_args(argv, P_CMD2, NULL, FALSE);
  783.  
  784.     /* if in whole-year mode, round number of months up to full year and
  785.      * set default starting month to January of current year
  786.      */
  787.     if (do_whole_year) {
  788.         nmonths = ((nmonths + 11) / 12) * 12;
  789.         if (nargs == 0)
  790.             init_month = JAN;
  791.     }
  792.  
  793.     /* done with the arguments and flags - try to open the output file */
  794.  
  795.     if (*outfile && freopen(outfile, "w", stdout) == (FILE *) NULL) {
  796.         FPR(stderr, E_FOPEN_ERR, progname, outfile);
  797.         exit(EXIT_FAILURE);
  798.     }
  799.  
  800.     /* generate the "calendar" or PostScript code (cf. writefil.c) */
  801.  
  802.     if (calendar_out)
  803.         write_calfile(init_month, init_year, nmonths);
  804.     else
  805.         write_psfile(init_month, init_year, nmonths);
  806.     
  807.     cleanup();        /* free allocated data */
  808.  
  809.     /* if output was written to a non-obvious location, tell user where */
  810.  
  811. #ifdef DEFAULT_OUTFILE
  812.     FPR(stderr, I_OUT_NAME, progname, outfile);
  813. #endif
  814.  
  815.     exit(EXIT_SUCCESS);
  816. }
  817.  
  818.  
  819. /*
  820.  * init_misc - various initializations not easily handled in .h files
  821.  */
  822. #ifdef PROTOS
  823. void init_misc(void)
  824. #else
  825. void init_misc()
  826. #endif
  827. {
  828.     INIT_COLORS;        /* copy default_color to day_color */
  829.     strcpy(notes_hdr, default_notes_hdr);    /* initialize notes_hdr */
  830. }
  831.  
  832.  
  833. /*
  834.  * gen_shading - update the date and fill box shading values in "currstr"
  835.  * as specified in "newstr"; one or both new values may be omitted
  836.  */
  837. #ifdef PROTOS
  838. void gen_shading(char *currstr,
  839.          char *newstr)
  840. #else
  841. void gen_shading(currstr, newstr)
  842.     char *currstr;
  843.     char *newstr;
  844. #endif
  845. {
  846.     char tmp1[STRSIZ], tmp2[STRSIZ];
  847.     char *dval, *fval, *p;
  848.  
  849.     /* copy current date/fill string and split into its components */
  850.     strcpy(tmp1, currstr);
  851.     dval = tmp1;
  852.     *(p = strchr(tmp1, '/')) = '\0';
  853.     fval = p + 1;
  854.  
  855.     /* new value is of form <date> - use new date, current fill */
  856.     if ((p = strchr(newstr, '/')) == NULL) {
  857.         dval = newstr;
  858.  
  859.     /* new value is of form /<fill> - use current date, new fill */
  860.     } else if (p == newstr) {
  861.         if (p[1])
  862.             fval = p + 1;
  863.  
  864.     /* new value is of form <date>/<fill> - use both new values */
  865.     } else {
  866.         strcpy(tmp2, newstr);
  867.         dval = tmp2;
  868.         *(p = strchr(tmp2, '/')) = '\0';
  869.         if (p[1])    
  870.             fval = p + 1;
  871.     }
  872.  
  873.     /* concatenate date/fill values back into original string */
  874.     strcpy(currstr, dval);
  875.     strcat(currstr, "/");
  876.     strcat(currstr, fval);
  877. }
  878.  
  879.  
  880. /*
  881.  * set_color - set one or all weekdays to print in black or gray
  882.  */
  883. #ifdef PROTOS
  884. void set_color(char *day,
  885.            int col)
  886. #else
  887. void set_color(day, col)
  888.     char *day;        /* weekday name (or "all") */
  889.     int  col;        /* select black or gray */
  890. #endif
  891. {
  892.     int i;
  893.  
  894.     if (ci_strncmp(day, ALL, strlen(ALL)) == 0)    /* set all days */
  895.         for (i = 0; i < 7; i++)
  896.             day_color[i] = col;
  897.     else                        /* set single day */
  898.         if ((i = get_weekday(day, FALSE)) != NOT_WEEKDAY)
  899.             day_color[i] = col;
  900. }
  901.  
  902.  
  903. /*
  904.  * get_flag() - look up flag in flag_tbl; return pointer to its entry
  905.  * (NULL if not found)
  906.  */
  907. #ifdef PROTOS
  908. FLAG_USAGE *get_flag(char flag)
  909. #else
  910. FLAG_USAGE *get_flag(flag)
  911.     char flag;
  912. #endif
  913. {
  914.     FLAG_USAGE *pflag;
  915.  
  916.     for (pflag = flag_tbl; pflag->flag; pflag++)
  917.         if (flag == pflag->flag)
  918.             return pflag;
  919.  
  920.     return flag ? NULL : pflag;        /* '\0' is a valid flag */
  921. }
  922.  
  923.  
  924. /*
  925.  * set_debug_flag - look up "flag" in debug flag table and set selected
  926.  * bits in debug flag word (clear word if "flag" is NULL)
  927.  */
  928. #ifdef PROTOS
  929. void set_debug_flag(char *flag)
  930. #else
  931. void set_debug_flag(flag)
  932.     char *flag;
  933. #endif
  934. {
  935.     char f;
  936.     DEBUG_INFO *pd;
  937.  
  938.     if (!flag) {            /* clear all if NULL */
  939.         debug_flags = 0;
  940.         return;
  941.     }
  942.  
  943.     /* loop through all characters in "flag", setting corresponding
  944.      * bits in debug_flags
  945.      */
  946.     while (f = *flag++)
  947.         for (pd = debug_info; pd->flag; pd++)
  948.             if (pd->flag == f)
  949.                 debug_flags |= pd->value;
  950.  
  951. }
  952.  
  953. /*
  954.  * get_args - walk the argument list, parsing all arguments but processing only
  955.  * those specified (in flag_tbl[]) to be processed this pass; return TRUE if
  956.  * OK, FALSE if invalid flag found.
  957.  */
  958. #ifdef PROTOS
  959. int get_args(char **argv,
  960.          int curr_pass,
  961.          char *where,
  962.          int get_numargs)
  963. #else
  964. int get_args(argv, curr_pass, where, get_numargs)
  965.     char **argv;        /* argument list */
  966.     int  curr_pass;        /* current pass (P_xxx) */
  967.     char *where;        /* for error messages */
  968.     int  get_numargs;    /* parse and save numeric arguments? */
  969. #endif
  970. {
  971.     char *parg, *opt, *p, *pass, tmpbuf[STRSIZ];
  972.     FLAG_USAGE *pflag, *pf;
  973.     int i, flag, sv_debug;
  974.     int flags_ok = TRUE;    /* return value */
  975.  
  976. /*
  977.  * If argument follows flag (immediately or as next parameter), return
  978.  * pointer to it (and bump argv if necessary); else return NULL
  979.  */
  980. #define GETARG() (*(*argv + 2) ? *argv + 2 : \
  981.           (*(argv+1) && **(argv+1) != '-' ? *++argv : NULL))
  982.  
  983.     /* set up pass name to print in debug messages */
  984.  
  985.     pass = where ? where :
  986.            curr_pass == P_CMD0 ? "CMD0" :
  987.            curr_pass == P_ENV  ? "ENV"  :
  988.            curr_pass == P_CMD1 ? "CMD1" :
  989.            curr_pass == P_CMD2 ? "CMD2" :
  990.                          "OPT"  ;
  991.  
  992.     /* Walk argument list, ignoring first element (program name) */
  993.  
  994.      while (opt = *++argv) {
  995.  
  996.         /* Assume that any non-flag argument is a numeric argument */
  997.         if (*opt != '-') {
  998.                 if (get_numargs && nargs < MAXARGS) {
  999.                 if (! IS_NUMERIC(opt))
  1000.                     goto bad_par;
  1001.                 numargs[nargs++] = atoi(opt);
  1002.                 if (DEBUG(DEBUG_OPTS))
  1003.                     FPR(stderr, "%s: %s\n", pass, opt);
  1004.             }
  1005.             continue;
  1006.         }
  1007.  
  1008.         /* Check that flag is a) legal, and b) to be processed this pass */
  1009.  
  1010.         if (! (pflag = get_flag(flag = opt[1])) )
  1011.             goto bad_par;
  1012.  
  1013.         /* get optional argument even if flag not processed this pass */
  1014.  
  1015.         parg = pflag->has_arg ? GETARG() : NULL;
  1016.  
  1017.         if (! (pflag->passes & curr_pass)) {    /* skip flag this pass? */
  1018.             if (curr_pass == P_OPT)
  1019.                 FPR(stderr, E_FLAG_IGNORED, progname, flag,
  1020.                     DATE_FILE, where);
  1021.             continue;
  1022.         }
  1023.  
  1024.         /* echo pass name and flag if debugging on */
  1025.         if (DEBUG(DEBUG_OPTS))
  1026.             FPR(stderr, "%s: -%c%s%s\n", pass, flag,
  1027.                 parg ? " " : "", parg ? parg : "");
  1028.  
  1029.         /* convert escape sequences in command-line parameters */
  1030.         if (parg && (curr_pass == P_CMD1 || curr_pass == P_CMD2)) {
  1031.             cvt_escape(tmpbuf, parg);
  1032.             parg = tmpbuf;
  1033.         }
  1034.  
  1035.         switch (flag) {
  1036.  
  1037.         case F_INITIALIZE:    /* reset all flags to defaults */
  1038.  
  1039.             /* set up a command line to reset all of the
  1040.              * flags; call get_args() recursively to parse it
  1041.              * (note that some of the flags must be reset
  1042.              * explicitly, as no command-line flags exist to
  1043.              * reset them)
  1044.              */
  1045.  
  1046.             /* reset flags described above */
  1047.             julian_dates  = JULIAN_DATES;
  1048.             draw_moons    = DRAW_MOONS;
  1049.             do_whole_year = FALSE;
  1050.             blank_boxes   = FALSE;
  1051.             num_style     = NUM_STYLE;
  1052.             calendar_out  = FALSE;
  1053.             small_cal_pos = SMALL_CAL_POS;
  1054.  
  1055.             /* select program default for landscape/portrait
  1056.              * mode (must be done first because -[xXyY] depend
  1057.              * on it) and US/European date styles
  1058.              */
  1059.             sprintf(lbuf, "pcal -%c -%c",
  1060. #if (ROTATE == LANDSCAPE)
  1061.                 F_LANDSCAPE,
  1062. #else
  1063.                 F_PORTRAIT,
  1064. #endif
  1065. #if (DATE_STYLE == USA_DATES)
  1066.                 F_USA_DATES);
  1067. #else
  1068.                 F_EUR_DATES);
  1069. #endif
  1070.             p = lbuf + strlen(lbuf);
  1071.  
  1072.             /* all other flags take arguments and are reset
  1073.              * by specifying the flag without an argument
  1074.              */
  1075.             for (pf = flag_tbl; pf->flag; pf++)
  1076.                 if ((pf->passes & curr_pass) && pf->has_arg) {
  1077.                     sprintf(p, " -%c", pf->flag);
  1078.                     p += strlen(p);
  1079.                 }
  1080.  
  1081.             /* split new command line into words; parse it */
  1082.             (void) loadwords(words, lbuf);
  1083.             (void) get_args(words, curr_pass, NULL, FALSE);
  1084.  
  1085.             break;
  1086.  
  1087.         case F_BLACK_DAY:    /* print day in black or gray */
  1088.         case F_GRAY_DAY:
  1089.             if (parg)
  1090.                 set_color(parg, flag == F_BLACK_DAY ?
  1091.                           BLACK : GRAY);
  1092.             else
  1093.                 INIT_COLORS;    /* reset to defaults */
  1094.             break;
  1095.  
  1096.          case F_DAY_FONT:    /* specify alternate day font */
  1097.             strcpy(dayfont, parg ? parg : DAYFONT);
  1098.              break;
  1099.  
  1100.         case F_NOTES_FONT:    /* specify alternate notes font */
  1101.             strcpy(notesfont, parg ? parg : NOTESFONT);
  1102.             break;
  1103.  
  1104.          case F_TITLE_FONT:    /* specify alternate title font */
  1105.             strcpy(titlefont, parg ? parg : TITLEFONT);
  1106.              break;
  1107.  
  1108.         case F_EMPTY_CAL:    /* generate empty calendar */
  1109.             datefile_type = NO_DATEFILE;
  1110.             strcpy(datefile, "");
  1111.             break;
  1112.  
  1113.         case F_DATE_FILE:    /* specify alternate date file */
  1114.             datefile_type = parg ? USER_DATEFILE : SYS_DATEFILE;
  1115.             strcpy(datefile, parg ? parg : "");
  1116.             break;
  1117.  
  1118.         case F_OUT_FILE:    /* specify alternate output file */
  1119.             strcpy(outfile, parg ? parg : OUTFILE);
  1120.             break;
  1121.  
  1122.         case F_LANDSCAPE:    /* generate landscape calendar */
  1123.              rotate = LANDSCAPE;
  1124.             strcpy(xsval, XSVAL_L);
  1125.             strcpy(ysval, YSVAL_L);
  1126.             strcpy(xtval, XTVAL_L);
  1127.             strcpy(ytval, YTVAL_L);
  1128.              break;
  1129.  
  1130.         case F_PORTRAIT:    /* generate portrait calendar */
  1131.              rotate = PORTRAIT;
  1132.             strcpy(xsval, XSVAL_P);
  1133.             strcpy(ysval, YSVAL_P);
  1134.             strcpy(xtval, XTVAL_P);
  1135.             strcpy(ytval, YTVAL_P);
  1136.              break;
  1137.  
  1138.         case F_HELP:        /* request version info and/or help */
  1139.         case F_USAGE:
  1140.         case F_VERSION:
  1141.             FPR(stdout, "%s\n", VERSION_STRING + 4);
  1142.             if (flag != F_VERSION)
  1143.                 usage(stdout, flag == F_HELP);
  1144.             exit(EXIT_SUCCESS);
  1145.             break;
  1146.  
  1147.         case F_MOON_4:        /* draw four moons */
  1148.         case F_MOON_ALL:    /* draw a moon for each day */
  1149.             draw_moons = flag == F_MOON_ALL ? ALL_MOONS : SOME_MOONS;
  1150.             break;
  1151.  
  1152.         case F_DEFINE:        /* define preprocessor symbol */
  1153.             (void) do_define(parg);
  1154.             break;
  1155.  
  1156.         case F_UNDEF:        /* undef preprocessor symbol */
  1157.             (void) do_undef(parg);
  1158.             break;
  1159.  
  1160.         case F_L_FOOT:        /* specify alternate left foot */
  1161.             strcpy(lfoot, parg ? parg : LFOOT);
  1162.             break;
  1163.  
  1164.         case F_C_FOOT:        /* specify alternate center foot */
  1165.             strcpy(cfoot, parg ? parg : CFOOT);
  1166.             break;
  1167.  
  1168.          case F_R_FOOT:        /* specify alternate right foot */
  1169.             strcpy(rfoot, parg ? parg : RFOOT);
  1170.             break;
  1171.  
  1172.          case F_NOTES_HDR:    /* specify alternate notes header */
  1173.             strcpy(notes_hdr, parg ? parg : default_notes_hdr);
  1174.             break;
  1175.  
  1176.         case F_FIRST_DAY:    /* select starting day of week */
  1177.             if (parg) {
  1178.                 if ((i = get_weekday(parg, FALSE)) != NOT_WEEKDAY)
  1179.                     first_day_of_week = i;
  1180.             }
  1181.             else
  1182.                 first_day_of_week = FIRST_DAY;
  1183.             break;
  1184.  
  1185.         case F_USA_DATES:    /* select American date style */
  1186.         case F_EUR_DATES:    /* select European date style */
  1187.             date_style = flag == F_USA_DATES ? USA_DATES : EUR_DATES;
  1188.             break;
  1189.  
  1190.         case F_X_TRANS:        /* set x-axis translation factor */
  1191.             strcpy(xtval, parg ? parg :
  1192.                 (rotate == LANDSCAPE ? XTVAL_L : XTVAL_P));
  1193.             break;
  1194.  
  1195.         case F_Y_TRANS:        /* set y-axis translation factor */
  1196.             strcpy(ytval, parg ? parg :
  1197.                 (rotate == LANDSCAPE ? YTVAL_L : YTVAL_P));
  1198.             break;
  1199.  
  1200.         case F_X_SCALE:        /* set x-axis scaling factor */
  1201.             strcpy(xsval, parg ? parg :
  1202.                 (rotate == LANDSCAPE ? XSVAL_L : XSVAL_P));
  1203.             break;
  1204.  
  1205.         case F_Y_SCALE:        /* set y-axis scaling factor */
  1206.             strcpy(ysval, parg ? parg :
  1207.                 (rotate == LANDSCAPE ? YSVAL_L : YSVAL_P));
  1208.             break;
  1209.  
  1210.         case F_JULIAN:
  1211.         case F_JULIAN_ALL:
  1212.             julian_dates = flag == F_JULIAN_ALL ? ALL_JULIANS :
  1213.                                   SOME_JULIANS;
  1214.             break;
  1215.  
  1216.         case F_WHOLE_YEAR:
  1217.             do_whole_year = TRUE;
  1218.             (void) do_define(DEF_WHOLE_YEAR);
  1219.             break;
  1220.  
  1221.         case F_CALENDAR:
  1222.             calendar_out = TRUE;
  1223.             break;
  1224.  
  1225.         case F_BLANK_BOXES:
  1226.             blank_boxes = TRUE;
  1227.             break;
  1228.  
  1229.         case F_SC_NONE:
  1230.             small_cal_pos = SC_NONE;
  1231.             break;
  1232.  
  1233.         case F_SC_FIRST:
  1234.         case F_SC_SPLIT:
  1235.             small_cal_pos = flag == F_SC_FIRST ? SC_FIRST :
  1236.                                  SC_SPLIT;
  1237.             break;
  1238.  
  1239.         case F_OUTLINE:
  1240.         case F_OUTLINE_GRAY:
  1241.             num_style = flag == F_OUTLINE ? OUTLINE_NUMS :
  1242.                             FILLED_NUMS;
  1243.             break;
  1244.  
  1245.         case F_SHADING:        /* set date/fill shading levels */
  1246.             if (parg)
  1247.                 gen_shading(shading, parg);
  1248.             else
  1249.                 strcpy(shading, SHADING);
  1250.             break;
  1251.  
  1252.         case F_DEBUG:
  1253.             sv_debug = DEBUG(DEBUG_OPTS);
  1254.             set_debug_flag(parg);
  1255.  
  1256.             /* print -ZO flag if first time set */
  1257.             if (!sv_debug && DEBUG(DEBUG_OPTS))
  1258.                 FPR(stderr, "%s: -%c%s\n", pass, flag,
  1259.                     parg ? parg : "");
  1260.             break;
  1261.  
  1262.         case '-' :        /* accept - and -- as dummy flags */
  1263.         case '\0':
  1264.             break;
  1265.  
  1266.         default:        /* missing case label if reached!!! */
  1267.  
  1268. bad_par:                /* unrecognized parameter */
  1269.  
  1270.             FPR(stderr, E_ILL_OPT, progname, opt);
  1271.             if (where)
  1272.                 FPR(stderr, E_ILL_OPT2,
  1273.                     curr_pass == P_ENV ? ENV_VAR :
  1274.                     curr_pass == P_OPT ? DATE_FILE : "",
  1275.                     where);
  1276.             FPR(stderr, "\n");
  1277.             flags_ok = FALSE;
  1278.             break;
  1279.         }
  1280.         }
  1281.  
  1282.     /* if we read the numeric arguments, validate and interpret them */
  1283.     if (get_numargs)
  1284.         flags_ok &= check_numargs();
  1285.  
  1286.     return flags_ok;
  1287. }
  1288.  
  1289.  
  1290. /*
  1291.  * check_numargs - validate and interpret numeric command-line parameters;
  1292.  * return TRUE if all OK, print error message and return FALSE if not
  1293.  */
  1294. #ifdef PROTOS
  1295. int check_numargs(void)
  1296. #else
  1297. int check_numargs()
  1298. #endif
  1299. {
  1300.     /* Validate non-flag (numeric) parameters */
  1301.  
  1302.     struct tm *p_tm;    /* for getting current month/year */
  1303.     long tmp;
  1304.     int params_ok = TRUE;    /* return value */
  1305.  
  1306.     switch (nargs) {
  1307.     case 0:        /* no arguments - print current month and/or year */
  1308.         time(&tmp);
  1309.         p_tm = localtime(&tmp);
  1310.         init_month = p_tm->tm_mon + 1;
  1311.         init_year = p_tm->tm_year;
  1312.         nmonths = 1;
  1313.         break;            
  1314.     case 1:        /* one argument - print entire year */
  1315.         init_month = JAN;
  1316.         init_year = numargs[0];
  1317.         nmonths = 12;
  1318.         break;
  1319.     default:    /* two or three arguments - print one or more months */
  1320.         init_month = numargs[0];
  1321.         init_year = numargs[1];
  1322.         nmonths = nargs > 2 ? numargs[2] : 1;
  1323.         break;
  1324.     }
  1325.  
  1326.     if (nmonths < 1)        /* ensure at least one month */
  1327.         nmonths = 1;
  1328.  
  1329.     /* check range of month and year */
  1330.  
  1331.     if (init_month < JAN || init_month > DEC) {
  1332.         FPR(stderr, E_ILL_MONTH, progname, init_month, JAN, DEC);
  1333.         params_ok = FALSE;
  1334.     }
  1335.     
  1336.     if (init_year > 0 && init_year < 100)    /* treat nn as 19nn */
  1337.         init_year += CENTURY;
  1338.     
  1339.     if (init_year < MIN_YR || init_year > MAX_YR) {
  1340.         FPR(stderr, E_ILL_YEAR, progname, init_year, MIN_YR, MAX_YR);
  1341.         params_ok = FALSE;
  1342.     }
  1343.  
  1344.     return params_ok;
  1345. }
  1346.  
  1347.  
  1348.  
  1349. /*
  1350.  * color_msg - return character string explaining default day colors
  1351.  */
  1352. #ifdef PROTOS
  1353. char *color_msg(void)
  1354. #else
  1355. char *color_msg()
  1356. #endif
  1357. {
  1358.     int i, ngray = 0, others;
  1359.     static char msg[80];
  1360.  
  1361.     for (i = SUN; i <= SAT; i++)    /* count gray weekdays */
  1362.         if (default_color[i] == GRAY)
  1363.             ngray++;
  1364.  
  1365.     if (ngray == 0 || ngray == 7) {        /* all same color? */
  1366.         sprintf(msg, COLOR_MSG_1, ngray ? W_GRAY : W_BLACK);
  1367.         return msg;
  1368.     }
  1369.  
  1370.     others = ngray <= 3 ? BLACK : GRAY;    /* no - get predominant color */
  1371.     msg[0] = '\0';
  1372.     for (i = SUN; i <= SAT; i++)
  1373.         if (default_color[i] != others) {
  1374.             strncat(msg, days[i], MIN_DAY_LEN);
  1375.             strcat(msg, "/");
  1376.         }
  1377.     LASTCHAR(msg) = ' ';
  1378.  
  1379.     sprintf(msg + strlen(msg), COLOR_MSG_2,
  1380.         others == BLACK ? W_GRAY : W_BLACK,
  1381.                 others == BLACK ? W_BLACK : W_GRAY);
  1382.     return msg;
  1383. }
  1384.  
  1385.  
  1386. /*
  1387.  * usage - print message explaining correct usage of the command-line
  1388.  * arguments and flags.  If "fullmsg" is true, print associated text
  1389.  */
  1390. #ifdef PROTOS
  1391. void usage(FILE *fp,
  1392.        int fullmsg)
  1393. #else
  1394. void usage(fp, fullmsg)
  1395.     FILE *fp;    /* destination of usage message */
  1396.     int fullmsg;    /* print complete message? */
  1397. #endif
  1398. {
  1399.     FLAG_MSG *pflag;
  1400.     PARAM_MSG *ppar;
  1401.     DATE_MSG *pdate;
  1402.     char buf[30], *p, flag, *meta;
  1403.     int nchars, first, i, indent, n;
  1404.  
  1405.     sprintf(buf, "%s: %s", W_USAGE, progname);
  1406.     nchars = indent = strlen(buf);
  1407.     first = TRUE;
  1408.     meta = p = NULL;
  1409.     FPR(fp, "\n%s", buf);
  1410.  
  1411.     /* loop to print command-line syntax message (by group of flags) */
  1412.  
  1413.     for (pflag = flag_msg; (flag = pflag->flag) != '\0'; pflag++) {
  1414.         if (flag == '\n') {        /* end of group? */
  1415.             if (p)
  1416.                 *p = '\0';
  1417.             if (meta) {        /* append metavariable name */
  1418.                 strcat(buf, " ");
  1419.                 strcat(buf, meta);
  1420.             }
  1421.             strcat(buf, "]");
  1422.             n = strlen(buf);
  1423.             if (nchars + n > SCREENWIDTH) {    /* does it fit on line? */
  1424.                 FPR(fp, "\n");        /* no - start new one */
  1425.                 for (nchars = 0; nchars < indent; nchars++)
  1426.                     FPR(fp, " ");
  1427.             }
  1428.             FPR(fp, "%s", buf);
  1429.             nchars += n;
  1430.             first = TRUE;
  1431.         }
  1432.         else if (flag != ' ') {        /* accumulate flags for group */
  1433.             if (first) {
  1434.                 sprintf(buf, " [");
  1435.                 p = buf + strlen(buf);
  1436.             }
  1437.             else
  1438.                 *p++ = '|';
  1439.             *p++ = '-';
  1440.             *p++ = flag;
  1441.             meta = pflag->meta;    /* save metavariable name */
  1442.             first = FALSE;
  1443.         }
  1444.     }
  1445.  
  1446.     /* loop to print selected numeric parameter descriptions */
  1447.  
  1448.     for (i = 0; i < PARAM_MSGS; i++) {
  1449.         sprintf(buf, " [%s]%s", param_msg[i].desc,
  1450.             i < PARAM_MSGS - 1 ? " |" : "");
  1451.         n = strlen(buf);
  1452.         if (nchars + n > SCREENWIDTH) {    /* does it fit on line? */
  1453.             FPR(fp, "\n");        /* no - start new one */
  1454.             for (nchars = 0; nchars < indent; nchars++)
  1455.                 FPR(fp, " ");
  1456.         }
  1457.         FPR(fp, "%s", buf);
  1458.         nchars += n;
  1459.     }
  1460.  
  1461.     FPR(fp, "\n\n");
  1462.     if (! fullmsg) {
  1463.         FPR(fp, USAGE_MSG, progname, F_HELP);
  1464.         return;
  1465.     }
  1466.     
  1467.     /* loop to print the full flag descriptions */
  1468.  
  1469.     for (pflag = flag_msg; (flag = pflag->flag) != '\0'; pflag++) {
  1470.         if (flag == '\n') {    /* newline?  print and quit */
  1471.             FPR(fp, "\n");
  1472.             continue;
  1473.         }
  1474.         p = buf;        /* copy flag and metavariable to buffer */
  1475.         if (flag != ' ')
  1476.             *p++ = '-';
  1477.     /* special hack for VMS - surround upper-case flags in quotes */
  1478. #ifdef VMS
  1479.         if (isupper(flag)) {
  1480.             *p++ = '"';
  1481.             *p++ = flag;
  1482.             *p++ = '"';
  1483.         }
  1484.         else
  1485.             *p++ = flag;
  1486. #else
  1487.         *p++ = flag;
  1488. #endif
  1489.         *p = '\0';
  1490.         if (pflag->meta)
  1491.             sprintf(p, " %s", pflag->meta);
  1492.         FPR(fp, "\t%-16.16s", buf);
  1493.         if (pflag->text)
  1494.             FPR(fp, "%s", pflag->text);
  1495.  
  1496.         /* print default value if specified */
  1497.         if (pflag->def)
  1498.             FPR(fp, " (%s: %s)", W_DEFAULT, pflag->def[0] ? pflag->def : "\"\"" );
  1499.         FPR(fp, "\n");
  1500.  
  1501.         /* special case - print color messages after F_GRAY_DAY */
  1502.         if (flag == F_GRAY_DAY)
  1503.             FPR(fp, "\t\t\t  (%s: %s)\n", W_DEFAULT, color_msg());
  1504.  
  1505.     }
  1506.     
  1507.     /* now print the information about the numeric parameters */
  1508.  
  1509.     for (ppar = param_msg; ppar->desc; ppar++)
  1510.         FPR(fp, "\t%-16.16s%s\n", ppar->desc, ppar->text);
  1511.     
  1512.     /* print the date file syntax message */
  1513.  
  1514.     FPR(fp, "\n");
  1515.     for (pdate = date_msg; *pdate; pdate++)
  1516.         FPR(fp, "\t%s\n", *pdate);
  1517.  
  1518. }
  1519.  
  1520.  
  1521. /*
  1522.  * alt_fopen - attempt to open a file in one of several paths in a
  1523.  * NULL-terminated path list.  If successful, return (opened) file pointer
  1524.  * and fill in full path name; if not, return NULL
  1525.  */
  1526. #ifdef PROTOS
  1527. FILE *alt_fopen(char *fullpath,
  1528.         char *name,
  1529.         char *pathlist[],
  1530.         char *access) 
  1531. #else
  1532. FILE *alt_fopen(fullpath, name, pathlist, access)
  1533.     char *fullpath;        /* full path name (output) */
  1534.     char *name;        /* base name (or full path spec) */
  1535.     char *pathlist[];    /* NULL-terminated path list */
  1536.     char *access;        /* permission requested */
  1537. #endif
  1538. {
  1539.     char **path;
  1540.     FILE *fp;
  1541.  
  1542.     if (DEBUG(DEBUG_PATHS)) {
  1543.         FPR(stderr, "Searching for %s in the following paths:\n",
  1544.             name);
  1545.         for (path = pathlist; *path; path++)
  1546.             FPR(stderr, "  %s\n", **path ? *path : ".");
  1547.     }
  1548.  
  1549.     for (path = pathlist; *path; path++) {
  1550.         mk_filespec(fullpath, *path, name);
  1551.         if ((fp = fopen(fullpath, access)) != NULL) {
  1552.             if (DEBUG(DEBUG_PATHS))
  1553.                 FPR(stderr, "found %s\n", fullpath);
  1554.             return fp;
  1555.         }
  1556.     }
  1557.  
  1558.     fullpath[0] = '\0';        /* file not found */
  1559.     return NULL;
  1560. }
  1561.