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
period_name = "ISO"
paper = None
year = None
end_of_week = 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
self.end_of_week = args.end_of_week
# 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)
self.period_name = period_config["name"]
# 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