import calendar
import datetime
import jinja2
from num2words import num2words
from teachingdates import PROG
from .errors import TemplateStyleError
from .weeks import BreakWeek, IsoWeek, TeachingWeek
suffix_map = {
"latex": "tex",
"text": "txt",
"xml": "xml",
}
filters = {
"isteaching": lambda w: isinstance(w, TeachingWeek),
"isbreak": lambda w: isinstance(w, BreakWeek),
"eow": lambda d, o: d + datetime.timedelta(days=o),
"pad": lambda s, n: str(s).rjust(n),
"num2words": lambda s: num2words(str(s)).title().replace("-", "")
}
env = jinja2.Environment(
loader=jinja2.PackageLoader("teachingdates", "templates"),
autoescape=jinja2.select_autoescape(["html", "xml"]))
latex_env = jinja2.Environment(
block_start_string="\\BLOCK{",
block_end_string="}",
variable_start_string="\\VAR{",
variable_end_string="}",
comment_start_string="\\#{",
comment_end_string="}",
line_statement_prefix="%%",
line_comment_prefix="%#",
trim_blocks=True,
autoescape=False,
loader=jinja2.PackageLoader("teachingdates", "templates"))
env.filters.update(filters)
latex_env.filters.update(filters)
class TeachingCalendar():
"""This class generates teaching-related dates for a specific paper
offered in a specific teaching period of a specific year. If you
don't provide teaching period details, it generates dates for the
current ISO-8601 year.
"""
weeks = []
lecture_offsets = []
skipped_lectures = []
mondays = []
period = None
period_name = None
paper = None
end_of_week = None
year = datetime.date.today().year
def __init__(self,
year,
period,
paper,
period_name="ISO",
end_of_week=None,
period_config=None,
paper_config=None):
self.year = year
self.period = period
self.period_name = period_name.replace(" ", "")
self.paper = paper
self.end_of_week = end_of_week
# self.env.filters.update(filters)
# This is likely to end up with one week too many at the end,
# but that doesn't matter as there will never be any teaching
# during the last week of December!
cal = calendar.Calendar().yeardatescalendar(self.year, width=12)
self.mondays = [week[0] for month in cal[0] for week in month]
self.mondays = list(dict.fromkeys(self.mondays))
if period_config:
self.update_weeks(period_config["weeks"])
else:
last_week = datetime.date(self.year, 12, 28).isocalendar()[1]
self.update_weeks([{"iso": [1, last_week]}])
if paper_config:
self.update_lectures(paper_config["lectures"])
@classmethod
def from_configuration(cls, config):
if config:
return cls(config.year,
config.period,
config.paper,
period_name=config.period_name,
end_of_week=config.end_of_week,
period_config=config.get_period_config(),
paper_config=config.get_paper_config())
else:
return None
def make_week(self, week_type, start, end=None):
end = start if end is None else end
return [
week_type(self.mondays[w].year, self.mondays[w].month,
self.mondays[w].day) for w in range(start - 1, end)
]
def teaching_date(self, period, week, offset=0):
pass
def update_weeks(self, week_list):
for w in week_list:
for t, r in w.items():
if t == "teach":
self.weeks += self.make_week(TeachingWeek, r[0], r[1])
elif t == "break":
self.weeks += self.make_week(BreakWeek, r[0], r[1])
elif t == "iso":
self.weeks += self.make_week(IsoWeek, r[0], r[1])
else:
print("{prog}: warning: ignored unknown week type "
"'{type}'.".format(prog=PROG, type=t))
def update_lectures(self, lecture_list):
for l in lecture_list:
for t, v in l.items():
if t == "offsets":
self.lecture_offsets = v
elif t == "skip":
self.skipped_lectures = v
else:
print("{prog}: warning: ignored unknown lecture key "
"'{key}'.".format(prog=PROG, key=t))
# turn this into a generator?
def calendar(self):
period = {}
week_num = break_num = 0
for w in self.weeks:
if isinstance(w, (TeachingWeek, IsoWeek)):
# Increment first so that break weeks have the
# same week number as the preceding teaching week.
# This ensures the keys are always chronologically
# sorted. Reset the break number for each new
# teaching week.
week_num += 1
break_num = 0
period[week_num] = w
elif isinstance(w, BreakWeek):
# Allow for up to 99 consecutive break weeks,
# which should be sufficient :). Should probably
# throw an exception if we exceed that.
break_num += 0.01
period[week_num + break_num] = w
return period
# turn this into a generator?
def lecture_dates(self):
dates = {}
lecture_num = 1
teaching_weeks = [t for t in self.weeks if isinstance(t, TeachingWeek)]
for week_index, monday in enumerate(teaching_weeks):
for offset_index, offset in enumerate(self.lecture_offsets):
lec = week_index * 2 + offset_index + 1
if lec in self.skipped_lectures:
continue
dates[lecture_num] = monday + datetime.timedelta(offset)
lecture_num += 1
return dates
def render(self, style, fmt):
suffix = suffix_map[fmt]
if fmt == "latex":
template = latex_env.get_template("{style}.{suffix}.j2".format(
style=style, suffix=suffix))
else:
template = env.get_template("{style}.{suffix}.j2".format(
style=style, suffix=suffix))
if style in ["calendar", "iso"]:
data = self.calendar()
elif style == "lecture":
data = self.lecture_dates()
else:
pass
return template.render(period=self.period,
period_name=self.period_name,
year=self.year,
paper=self.paper,
end_of_week=self.end_of_week,
data=data)