GitBucket
4.21.2
Toggle navigation
Snippets
Sign in
Files
Branches
1
Releases
Issues
1
Pull requests
Labels
Priorities
Milestones
Wiki
Forks
nigel.stanger
/
Handbook
Browse code
Switched to new calendar initialisation
Can now pass configuration as a constructor argument.
master
1 parent
3de82f5
commit
b2edb0b71bf0fbada22627f07ff4ecf58559ac69
Nigel Stanger
authored
on 12 Jan 2021
Patch
Showing
2 changed files
calendar/teachingdates/app.py
calendar/teachingdates/calendars/basecalendar.py
Ignore Space
Show notes
View
calendar/teachingdates/app.py
import argparse import copy import pathlib import sys from jinja2 import TemplateNotFound import teachingdates.calendars as calendars import teachingdates.config as configuration from teachingdates import PROG def run(): args = configuration.parse_command_line() if args.debug: print("{prog}: debug: args: {a}".format(prog=PROG, a=args)) try: dates_config = configuration.Configuration(args) if args.debug: print("{prog}: debug: period config: {c}".format( prog=PROG, c=dates_config.get_period_config())) print("{prog}: debug: paper config: {c}".format( prog=PROG, c=dates_config.get_paper_config())) cal = calendars.TeachingCalendar(dates_config) if args.debug: print("Calendar: {cal}".format(cal=cal.calendar())) print("Lectures: {lec}".format(lec=cal.lecture_dates())) print(cal.render(args.style, args.format)) except configuration.ConfigurationError as e: print("{prog}: error: failed to load configuration from " "'{file}'".format(prog=PROG, file=e)) except configuration.PaperKeyError as e: print("{prog}: error: paper '{paper}' in '{file}' is missing " "required key '{key}'".format(prog=PROG, paper=e.parent, file=e.file, key=e.key)) except configuration.PeriodKeyError as e: print("{prog}: error: teaching period '{period}' in '{file}' is " "missing required key '{key}'".format(prog=PROG, period=e.parent, file=e.file, key=e.key)) except configuration.PaperError as e: print("{prog}: error: no entry for paper '{paper}' ({period}) in " "'{file}.'".format(prog=PROG, file=e, paper=args.paper, period=args.period)) except configuration.PeriodError as e: print("{prog}: error: no entry for teaching period '{period}' in " "'{file}'".format(prog=PROG, file=e, period=args.period)) except TemplateNotFound as e: print("{prog}: error: no template found for output style '{style}' " "and output format '{format}' (expected '{file}')".format( prog=PROG, style=args.style, format=args.format, file=e))
import argparse import copy import pathlib import sys from jinja2 import TemplateNotFound import teachingdates.calendars as calendars import teachingdates.config as configuration from teachingdates import PROG def run(): args = configuration.parse_command_line() if args.debug: print("{prog}: debug: args: {a}".format(prog=PROG, a=args)) try: dates_config = configuration.Configuration(args) if args.debug: print("{prog}: debug: period config: {c}".format( prog=PROG, c=dates_config.get_period_config())) print("{prog}: debug: paper config: {c}".format( prog=PROG, c=dates_config.get_paper_config())) cal = calendars.TeachingCalendar.from_configuration(dates_config) if args.debug: print("Calendar: {cal}".format(cal=cal.calendar())) print("Lectures: {lec}".format(lec=cal.lecture_dates())) print(cal.render(args.style, args.format)) except configuration.ConfigurationError as e: print("{prog}: error: failed to load configuration from " "'{file}'".format(prog=PROG, file=e)) except configuration.PaperKeyError as e: print("{prog}: error: paper '{paper}' in '{file}' is missing " "required key '{key}'".format(prog=PROG, paper=e.parent, file=e.file, key=e.key)) except configuration.PeriodKeyError as e: print("{prog}: error: teaching period '{period}' in '{file}' is " "missing required key '{key}'".format(prog=PROG, period=e.parent, file=e.file, key=e.key)) except configuration.PaperError as e: print("{prog}: error: no entry for paper '{paper}' ({period}) in " "'{file}.'".format(prog=PROG, file=e, paper=args.paper, period=args.period)) except configuration.PeriodError as e: print("{prog}: error: no entry for teaching period '{period}' in " "'{file}'".format(prog=PROG, file=e, period=args.period)) except TemplateNotFound as e: print("{prog}: error: no template found for output style '{style}' " "and output format '{format}' (expected '{file}')".format( prog=PROG, style=args.style, format=args.format, file=e))
Ignore Space
Show notes
View
calendar/teachingdates/calendars/basecalendar.py
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("-", "") } 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 = [] period = None period_name = None paper = None end_of_week = None year = datetime.date.today().year def __init__(self, config): self.config = config cal = calendar.Calendar().yeardatescalendar(config.year, width=12) # Extract the Mondays at the start of each week. Because Calendar # is designed to produce data suitable for a printed calendar, # it includes all weeks that overlap with the calendar year, # regardless of whether they're actually part of the ISO year. # We therefore also need to filter on the current ISO year. self.mondays = [ week[0] for month in cal[0] for week in month if week[0].isocalendar()[0] == config.year ] # Similarly, each month generated by Calendar includes all days # from the first and last weeks of the month, which may be from # the preceding or succeeding month, respectively. These show # up as duplicates that need to be removed. self.mondays = list(dict.fromkeys(self.mondays)) self.period_weeks = {} self.calendars = {} for period in self.config.config.keys(): self.period_weeks[period] = self.generate_period_weeks( self.config.config[period]["weeks"]) self.calendars[period] = self.generate_calendar(period) self.period_weeks["ISO"] = self.generate_period_weeks( [{"iso": [ self.mondays[0].isocalendar()[1], # first ISO week self.mondays[-1].isocalendar()[1] # last ISO week ]}]) self.calendars["ISO"] = self.generate_calendar("ISO") # print("mondays (__init__): ", self.mondays) # print("period_weeks (__init__): ", self.period_weeks) # print("calendars (__init__): ", self.calendars) # def __init__(self, # year, # period, # paper, # period_name="ISO", # end_of_week=None, # period_config=None, # paper_config=None, # 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.config = config # # 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"]) 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 generate_period_weeks(self, week_list): weeks = [] for w in week_list: for t, r in w.items(): if t == "teach": weeks += self.make_week(TeachingWeek, r[0], r[1]) elif t == "break": weeks += self.make_week(BreakWeek, r[0], r[1]) elif t == "iso": weeks += self.make_week(IsoWeek, r[0], r[1]) else: print("{prog}: warning: ignored unknown week type " "'{type}'.".format(prog=PROG, type=t)) return weeks 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 generate_calendar(self, period): result = {} week_num = 0 break_num = 0 for w in self.period_weeks[period]: 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 result[week_num] = w # if period == "ISO": # print("week ", 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 result[week_num + break_num] = w return result # 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_latex(self): 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) def render_text(self): env = jinja2.Environment( loader=jinja2.PackageLoader("teachingdates", "templates"), autoescape=False) env.filters.update(filters) def render_xml(self): env = jinja2.Environment( loader=jinja2.PackageLoader("teachingdates", "templates"), autoescape=jinja2.select_autoescape(["xml"])) env.filters.update(filters) template = env.get_template("paper-calendar-dates.xml.j2") return template.render( calendars=self.calendars, eow_offset=datetime.timedelta(self.config.end_of_week)) def render(self, style, fmt): if fmt == "latex": return(self.render_latex()) elif fmt == "text": return(self.render_text()) elif fmt == "xml": return(self.render_xml()) else: return None
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)
Show line notes below