Newer
Older
Handbook / calendar / teachingdates / config / config.py
  1. import datetime
  2. import importlib.resources as pkg_resources
  3. from pathlib import Path
  4. from shutil import copyfile
  5.  
  6. import yaml
  7.  
  8. from teachingdates import PROG
  9.  
  10. from .errors import (ConfigurationError, PaperError, PaperKeyError,
  11. PeriodError, PeriodKeyError)
  12.  
  13. CONFIG_FILENAME = "config.yml"
  14. CONFIG_DIR = Path.home().joinpath(".config", PROG)
  15. CONFIG_FILE = CONFIG_DIR.joinpath(CONFIG_FILENAME)
  16.  
  17.  
  18. class Configuration(object):
  19. """Validate and structure the configuration for easy access.
  20.  
  21. Includes methods to return the period configuration and the paper
  22. configuration, as applicable. (Yes, the period configuration
  23. includes the paper configuration, but it may also include other
  24. papers, so it's more convenient to return these separately.)
  25.  
  26. The configuration file will normally contain specifications for
  27. several teaching periods and papers. We don't need all of this:
  28. usually we're only interested in one teaching period and one
  29. paper (if relevant).
  30. """
  31. style = None
  32. period = None
  33. paper = None
  34. year = None
  35. config = None
  36. config_file = None
  37.  
  38. def __init__(self, args):
  39. self.load_config(file=args.config_file)
  40. self.style = args.style
  41. self.period = args.period
  42. self.paper = args.paper
  43. # priority: year on command line (defaults to current year),
  44. # year in config
  45. self.year = (self.config["year"]
  46. if "year" in self.config.keys() else args.year)
  47.  
  48. period_config = paper_config = None
  49. if self.style != "iso":
  50. if self.period not in self.config.keys():
  51. raise PeriodError(str(self.config_file))
  52.  
  53. period_config = self.config[self.period]
  54. # the "name" and "weeks" keys are required
  55. for key in ["name", "weeks"]:
  56. if key not in period_config.keys():
  57. raise PeriodKeyError(key, self.period, self.config_file)
  58.  
  59. # Papers are only relevant in "lecture" style, which by definition
  60. # must specify a paper.
  61. try:
  62. if self.style == "lecture":
  63. if ("papers" in period_config.keys()
  64. and self.paper in period_config["papers"].keys()):
  65. paper_config = period_config["papers"][self.paper]
  66. # we expect at least a "lectures" entry
  67. if (not paper_config
  68. or "lectures" not in paper_config.keys()):
  69. raise PaperKeyError("lectures", self.paper,
  70. self.config_file)
  71. else:
  72. raise PaperError(str(self.config_file))
  73. except AttributeError as e:
  74. raise PaperError(str(self.config_file))
  75.  
  76. def load_config(self, file=None):
  77. """Load a configuration file.
  78.  
  79. If no file is specified, try the default configuration in
  80. ~/.config. If that doesn't exist, copy the default configuration
  81. from teachingdates.config.
  82. """
  83. cfg = None
  84. if not file:
  85. file = CONFIG_FILE
  86. if not CONFIG_FILE.exists():
  87. CONFIG_DIR.mkdir(exist_ok=True)
  88. configmgr = pkg_resources.path("teachingdates.config",
  89. CONFIG_FILENAME)
  90. with configmgr as f:
  91. copyfile(f, CONFIG_FILE)
  92. print("{prog}: created new configuration file at "
  93. "'{f}'".format(prog=PROG, f=str(CONFIG_FILE)))
  94. try:
  95. with open(file) as f:
  96. cfg = yaml.safe_load(f.read())
  97. except FileNotFoundError as e:
  98. print("{prog}: error: no such file '{file}'".format(prog=PROG,
  99. file=file))
  100. if not cfg:
  101. raise ConfigurationError(str(file))
  102. self.config_file = file
  103. self.config = cfg
  104.  
  105. def get_period_config(self, period=None):
  106. """Get the configuration details for a teaching period.
  107.  
  108. No need to specify the year as all configurations are for a
  109. single year anyway.
  110. """
  111. period = self.period if period is None else period
  112. if period in self.config.keys():
  113. return self.config[period]
  114. else:
  115. return None
  116.  
  117. def get_paper_config(self, period=None, paper=None):
  118. """Get the configuration details for a paper.
  119.  
  120. Can optionally specify the teaching period. No need to specify
  121. the year as all configurations are for a single year anyway.
  122. """
  123. paper = self.paper if paper is None else paper
  124. period_config = self.get_period_config(period=period)
  125. if (period_config and "papers" in period_config.keys()
  126. and paper in period_config["papers"].keys()):
  127. return period_config["papers"][paper]
  128. else:
  129. return None