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
Got LaTeX lecture date template working
master
1 parent
60485ac
commit
17c0efb5eae5d57d02b88f042cfde135c94b49ae
Nigel Stanger
authored
on 3 Feb 2021
Patch
Showing
2 changed files
calendar/teachingdates/calendars/basecalendar.py
calendar/teachingdates/templates/lecture.tex.j2
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), "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. """ 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.get_config_keys(): self.period_weeks[period] = self.generate_period_weeks( self.config.get_period_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") self.lecture_offsets = [] self.skipped_lectures = [] if self.config.paper: self.update_lectures(self.config.get_paper_config(self.config.paper)) 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 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["lectures"]: 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.calendars[self.config.period].values() 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) template = env.get_template( "{style}.tex.j2".format(style=self.config.style)) if self.config.style == "lecture": return template.render( weeks=self.lecture_dates(), paper=self.config.paper, period=self.config.period, year=self.config.year, eow_offset=datetime.timedelta(self.config.end_of_week)) else: return template.render( weeks=self.calendars[self.config.period], paper=self.config.paper, period=self.config.period, year=self.config.year, eow_offset=datetime.timedelta(self.config.end_of_week)) def render_text(self): env = jinja2.Environment( loader=jinja2.PackageLoader("teachingdates", "templates"), autoescape=False) env.filters.update(filters) template = env.get_template( "{style}.txt.j2".format(style=self.config.style)) if self.config.style == "lecture": return template.render( weeks=self.lecture_dates(), paper=self.config.paper, period=self.config.period, year=self.config.year, eow_offset=datetime.timedelta(self.config.end_of_week)) else: return template.render( weeks=self.calendars[self.config.period], paper=self.config.paper, period=self.config.period, year=self.config.year, eow_offset=datetime.timedelta(self.config.end_of_week)) 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), "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. """ 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.get_config_keys(): self.period_weeks[period] = self.generate_period_weeks( self.config.get_period_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") self.lecture_offsets = [] self.skipped_lectures = [] if self.config.paper: self.update_lectures(self.config.get_paper_config(self.config.paper)) 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 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["lectures"]: 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.calendars[self.config.period].values() 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) template = env.get_template( "{style}.txt.j2".format(style=self.config.style)) if self.config.style == "lecture": return template.render( weeks=self.lecture_dates(), paper=self.config.paper, period=self.config.period, year=self.config.year, eow_offset=datetime.timedelta(self.config.end_of_week)) else: return template.render( weeks=self.calendars[self.config.period], paper=self.config.paper, period=self.config.period, year=self.config.year, eow_offset=datetime.timedelta(self.config.end_of_week)) 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
Ignore Space
Show notes
View
calendar/teachingdates/templates/lecture.tex.j2
% Lecture dates for \VAR{paper} \VAR{period} \VAR{year}. \newcount\lecnum \newcount\weeknum \newcommand{\LectureDate}[1]{% \lecnum #1\relax% \advance\lecnum by -1\relax% \ifcase\lecnum% \BLOCK{for lecture, date in weeks.items()} \BLOCK{if not loop.first}\or \BLOCK{endif -} \VAR{date.day} \VAR{date.strftime("%B")}% \BLOCK{endfor} \else ????% \fi% } \newcommand{\LectureDateByWeek}[2]{% \weeknum #1\relax% \advance\weeknum by -1\relax% \multiply\weeknum by 2\relax% \advance\weeknum by #2\relax% \LectureDate{\the\weeknum}% }
% Lecture dates for \VAR{paper} \VAR{period} \VAR{year}. \newcount\lecnum \newcount\weeknum \newcommand{\LectureDate}[1]{% \lecnum #1\relax% \advance\lecnum by -1\relax% \ifcase\lecnum \BLOCK{for lecture, date in data.items()} \BLOCK{if not loop.first}\or \BLOCK{endif -} \VAR{date.day} \VAR{date.strftime("%B")}}% \BLOCK{endfor} \else ????% \fi% } \newcommand{\LectureDateByWeek}[2]{% \weeknum #1\relax% \advance\weeknum by -1\relax% \multiply\weeknum by 2\relax% \advance\weeknum by #2\relax% \LectureDate{\the\weeknum}% }
Show line notes below