Newer
Older
Handbook / calendar / teachingdates / config / config.py
import datetime
import importlib.resources as pkg_resources
from pathlib import Path
from shutil import copyfile

import yaml

from teachingdates import PROG

from .errors import (ConfigurationError, PaperError, PaperKeyError,
                     PeriodError, PeriodKeyError)

CONFIG_FILENAME = "config.yml"
CONFIG_DIR = Path.home().joinpath(".config", PROG)
CONFIG_FILE = CONFIG_DIR.joinpath(CONFIG_FILENAME)


class Configuration(object):
    """Validate and structure the configuration for easy access.

    Includes methods to return the period configuration and the paper
    configuration, as applicable. (Yes, the period configuration
    includes the paper configuration, but it may also include other
    papers, so it's more convenient to return these separately.)

    The configuration file will normally contain specifications for
    several teaching periods and papers. We don't need all of this:
    usually we're only interested in one teaching period and one
    paper (if relevant).
    """
    style = None
    period = None
    paper = None
    year = None
    config = None
    config_file = None

    def __init__(self, args):
        self.load_config(file=args.config_file)
        self.style = args.style
        self.period = args.period
        self.paper = args.paper
        # priority: year on command line (defaults to current year),
        # year in config
        self.year = (self.config["year"]
                     if "year" in self.config.keys() else args.year)

        period_config = paper_config = None
        if self.style != "iso":
            if self.period not in self.config.keys():
                raise PeriodError(str(self.config_file))

            period_config = self.config[self.period]
            # the "name" and "weeks" keys are required
            for key in ["name", "weeks"]:
                if key not in period_config.keys():
                    raise PeriodKeyError(key, self.period, self.config_file)

            # Papers are only relevant in "lecture" style, which by definition
            # must specify a paper.
            try:
                if self.style == "lecture":
                    if ("papers" in period_config.keys()
                            and self.paper in period_config["papers"].keys()):
                        paper_config = period_config["papers"][self.paper]
                        # we expect at least a "lectures" entry
                        if (not paper_config
                                or "lectures" not in paper_config.keys()):
                            raise PaperKeyError("lectures", self.paper,
                                                self.config_file)
                    else:
                        raise PaperError(str(self.config_file))
            except AttributeError as e:
                raise PaperError(str(self.config_file))

    def load_config(self, file=None):
        """Load a configuration file.

        If no file is specified, try the default configuration in
        ~/.config. If that doesn't exist, copy the default configuration
        from teachingdates.config.
        """
        cfg = None
        if not file:
            file = CONFIG_FILE
            if not CONFIG_FILE.exists():
                CONFIG_DIR.mkdir(exist_ok=True)
                configmgr = pkg_resources.path("teachingdates.config",
                                               CONFIG_FILENAME)
                with configmgr as f:
                    copyfile(f, CONFIG_FILE)
                    print("{prog}: created new configuration file at "
                          "'{f}'".format(prog=PROG, f=str(CONFIG_FILE)))
        try:
            with open(file) as f:
                cfg = yaml.safe_load(f.read())
        except FileNotFoundError as e:
            print("{prog}: error: no such file '{file}'".format(prog=PROG,
                                                             file=file))
        if not cfg:
            raise ConfigurationError(str(file))
        self.config_file = file
        self.config = cfg

    def get_period_config(self, period=None):
        """Get the configuration details for a teaching period.

        No need to specify the year as all configurations are for a
        single year anyway.
        """
        period = self.period if period is None else period
        if period in self.config.keys():
            return self.config[period]
        else:
            return None

    def get_paper_config(self, period=None, paper=None):
        """Get the configuration details for a paper.

        Can optionally specify the teaching period. No need to specify
        the year as all configurations are for a single year anyway.
        """
        paper = self.paper if paper is None else paper
        period_config = self.get_period_config(period=period)
        if (period_config and "papers" in period_config.keys()
                and paper in period_config["papers"].keys()):
            return period_config["papers"][paper]
        else:
            return None