Newer
Older
Handbook / calendar / teachingdates / config / cli.py
import argparse
import datetime

from teachingdates import PROG


def oxford_comma_list(words):
    if isinstance(words, list) or isinstance(words, tuple):
        if len(words) == 0:
            return ""
        elif len(words) == 1:
            return words[0]
        elif len(words) == 2:
            return " and ".join(words)
        else:
            return ", ".join(words[:-1]) + ", and " + words[-1]
    else:
        raise TypeError("expected list, got {t}".format(t=str(type(words))))


def switch_message(switches, context, required=False):
    switch_string = oxford_comma_list(switches)
    return ("{switches} {verb} {required} for {context}".format(
                switches=switch_string,
                verb="are" if len(switches) > 1 else "is",
                required="required" if required else "ignored",
                context=context))


def warning(message):
    # This is somewhat trivial, but does provide output equivalence
    # with parser.error().
    print("{prog}: warning: {message}".format(
        prog=PROG,
        message=message
    ))


def parse_command_line():
    """Parse command-line arguments.

    Example command lines:
    generate_dates --style calendar --format latex --year 2020 --period S1 --paper INFO201
    generate_dates --style lecture --format xml --period S2 --paper INFO202
    """
    format_map = {
        "t": "text",
        "text": "text",
        "l": "latex",
        "latex": "latex",
        "x": "xml",
        "xml": "xml",
    }
    style_map = {
        "c": "calendar",
        "calendar": "calendar",
        "l": "lecture",
        "lecture": "lecture",
        "p": "lab",
        "lab": "lab",
        "i": "iso",
        "iso": "iso",
    }

    parser = argparse.ArgumentParser(prog=PROG, description="")

    parser.add_argument(
        "--config",
        "-c",
        default=None,
        dest="config_file",
        help="file name of the configuration file "
        "[default '~/.config/{prog}/config.yml']".format(prog=PROG))

    parser.add_argument("--debug",
                        "-d",
                        action='store_true',
                        help="enable debugging output")

    parser.add_argument(
        "--end-of-week",
        "-e",
        type=int,
        default=None,
        help="offset to end of week from Monday (= 0) [default 4 (= Friday)]")

    parser.add_argument("--format",
                        "-f",
                        default="latex",
                        choices=["text", "t", "latex", "l", "xml", "x"],
                        help="output format [default 'latex']")

    parser.add_argument("--output",
                        "-o",
                        type=argparse.FileType("w", encoding="UTF-8"),
                        help="file name to write the output to; existing "
                        "files of the same name will be overwritten!")

    parser.add_argument(
        "--paper",
        "-p",
        default=None,
        help="paper code (e.g., INFO201) [required for 'lecture' style, "
        "ignored otherwise]")

    parser.add_argument(
        "--style",
        "-s",
        default="calendar",
        choices=["calendar", "c", "lecture", "l", "lab", "p", "iso", "i"],
        help="output style: 'calendar' for teaching calendars in course "
            "outlines, 'lecture'/'lab' for individual lecture/lab dates, "
            "or 'iso' for dates across the entire ISO-8601 year [default "
            "'calendar']")

    parser.add_argument("--teaching-period",
                        "-t",
                        default=None,
                        choices=["SS", "S1", "S2", "FY"],
                        dest="period",
                        help="teaching period [required]")

    parser.add_argument(
        "--year",
        "-y",
        type=int,
        default=datetime.date.today().year,
        help="the year to generate dates for [default {y}]".format(
            y=datetime.date.today().year))

    args = parser.parse_args()
    # normalise format and style
    args.format = format_map[args.format]
    args.style = style_map[args.style]

    if args.style == "calendar":
        # --teaching-period is required
        if not args.period:
            parser.error(switch_message(
                ["--teaching-period/-t"],
                context="'{style}' style".format(style=args.style),
                required=True))
        # --paper is irrelevant
        if args.paper:
            args.paper = ""
            warning(switch_message(
                ["--paper/-p"],
                context="'{style}' style".format(style=args.style)
            ))

    elif args.style == "iso":
        # both --paper and --teaching-period are irrelevant
        if any([args.paper, args.period]):
            args.paper = ""
            args.period = "iso"
            warning(switch_message(
                ["--paper/-p", "--teaching-period/-t"],
                context="'{style}' style".format(style=args.style)
            ))

    elif args.style in ["lecture", "lab"]:
        # both --paper and --teaching-period are required
        if not all([args.paper, args.period]):
            parser.error(switch_message(
                ["-paper/-p", "--teaching-period/-t"],
                context="'{style}' style".format(style=args.style),
                required=True
            ))
        # --end-of-week is irrelevant
        if args.end_of_week is not None:
            args.end_of_week = None
            warning(switch_message(
                ["--end-of-week/-e"],
                context="'{style}' style".format(style=args.style)
            ))
            # ignored_args(["--end-of-week/-e"], args.style))

    else:
        pass

    # Can't set default week offset until after error processing above,
    # otherwise it generates a spurious warning in 'lecture' style.
    if args.end_of_week is None:
        args.end_of_week = 4

    return args