diff options
| author | Florian Fischer <florian.fischer@muhq.space> | 2023-08-23 11:29:14 +0200 |
|---|---|---|
| committer | Florian Fischer <florian.fischer@muhq.space> | 2023-08-23 11:29:14 +0200 |
| commit | cee9aa15ff85f4dc09fecf45c5e1523690266bb9 (patch) | |
| tree | a7e341a2d0253bcc6322d63ccf149fb95587f4ea | |
| parent | 515ca7fadd11276421ba9d9be217a07b2e6b1652 (diff) | |
| download | unterricht-cee9aa15ff85f4dc09fecf45c5e1523690266bb9.tar.gz unterricht-cee9aa15ff85f4dc09fecf45c5e1523690266bb9.zip | |
support buffers between sections
| -rw-r--r-- | scripts/gen_map/Pipfile | 1 | ||||
| -rw-r--r-- | scripts/gen_map/Pipfile.lock | 17 | ||||
| -rwxr-xr-x | scripts/gen_map/gen_map.py | 142 |
3 files changed, 103 insertions, 57 deletions
diff --git a/scripts/gen_map/Pipfile b/scripts/gen_map/Pipfile index b9679260..12ffb01f 100644 --- a/scripts/gen_map/Pipfile +++ b/scripts/gen_map/Pipfile @@ -12,6 +12,7 @@ requests = "*" yapf = "*" pylint = "*" mypy = "*" +types-requests = "*" [requires] python_version = "3.11" diff --git a/scripts/gen_map/Pipfile.lock b/scripts/gen_map/Pipfile.lock index 57cd50e0..ae76a390 100644 --- a/scripts/gen_map/Pipfile.lock +++ b/scripts/gen_map/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "830ab904fb5b6e26018ed1c9d3178f092b491f05f62e1bc43c170bc174f7587c" + "sha256": "2614162fe5887d0eb4939203f1a844084f3afeb19bf210f7da73548d8f1a0bea" }, "pipfile-spec": 6, "requires": { @@ -408,6 +408,21 @@ "markers": "python_version >= '3.7'", "version": "==0.12.1" }, + "types-requests": { + "hashes": [ + "sha256:56d181c85b5925cbc59f4489a57e72a8b2166f18273fd8ba7b6fe0c0b986f12a", + "sha256:6aa3f7faf0ea52d728bb18c0a0d1522d9bfd8c72d26ff6f61bfc3d06a411cf40" + ], + "index": "pypi", + "version": "==2.31.0.2" + }, + "types-urllib3": { + "hashes": [ + "sha256:229b7f577c951b8c1b92c1bc2b2fdb0b49847bd2af6d1cc2a2e3dd340f3bda8f", + "sha256:9683bbb7fb72e32bfe9d2be6e04875fbe1b3eeec3cbb4ea231435aa7fd6b4f0e" + ], + "version": "==1.26.25.14" + }, "typing-extensions": { "hashes": [ "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36", diff --git a/scripts/gen_map/gen_map.py b/scripts/gen_map/gen_map.py index 2f1089de..75aa7a48 100755 --- a/scripts/gen_map/gen_map.py +++ b/scripts/gen_map/gen_map.py @@ -1,4 +1,16 @@ #!/usr/bin/env python3 +# Copyright 2023 Florian Fischer +# This program is free software: you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation, either version 3 of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# this program. If not, see <https://www.gnu.org/licenses/>. +"""simple script to generate a rough markdown map of a school year""" import argparse from datetime import datetime, timedelta @@ -7,6 +19,7 @@ import os import re import sys import urllib +import typing as t from bs4 import BeautifulSoup import icalevents @@ -45,6 +58,12 @@ WEEKLY_HOURS = { } +def die(msg: str, status=1): + """Exit with status printing a msg to stderr""" + print(msg, file=sys.stderr) + sys.exit(status) + + def default_year() -> int: """return the default year""" now = datetime.now() @@ -107,6 +126,7 @@ def get_public_holidays(land: int, start: datetime, class LehrplanSection: + """Representation of a section of Lehrplanplus""" def __init__(self, num, summary, duration, subsections=None): self.num = num @@ -114,18 +134,37 @@ class LehrplanSection: self.duration = duration self.subsections = subsections or [] - def has_subsections(self) -> bool: - return bool(self.subsections) - def __str__(self): return f'{self.num}: {self.summary} ({self.duration}h) {self.subsections}' def __repr__(self): - return f'LehrplanSection({self.num}, {self.summary}, {self.duration}h, {self.subsections})' + return (f'LehrplanSection({self.num}, {self.summary}, ' + f'{self.duration}h, {self.subsections})') Lehrplan = list[LehrplanSection] +SUMMARY_RE = re.compile(r'(.*) \(ca\. ([1-9][0-9]*) Std\.\)') + + +def parse_section_html(section, summary_class: str) -> t.Tuple[str, str, int]: + """Parse the html for a section from the Lehrplanplus webpage""" + num = list(section.find(**{ + 'class': 'head-absatz-num' + }).children)[2].strip() + + summary = section.find(**{'class': summary_class}).string.strip() + summary = summary.replace('\xa0', ' ') + + match = SUMMARY_RE.match(summary) + if not match is None: + summary = match.group(1) + duration = int(match.group(2)) + else: + die(f'summary: {summary} does not match our re') + + return num, summary, duration + def get_lehrplan(subject: str, form: str) -> Lehrplan: """Retrieve the sections and expected duration from lehrplanplus""" @@ -134,51 +173,44 @@ def get_lehrplan(subject: str, form: str) -> Lehrplan: resp = requests.get(lpp_url, timeout=10) html = BeautifulSoup(resp.text) - summary_re = re.compile(r'(.*) \(ca\. ([1-9][0-9]*) Std\.\)') - lehrplan: Lehrplan = [] top_level_sections = html.find_all(**{'class': 'headline_lvl1'}) for section in top_level_sections: - num = list(section.find(**{ - 'class': 'head-absatz-num' - }).children)[2].strip() - - summary = section.find(**{ - 'class': 'head-absatz-title-short' - }).string.strip() - summary = summary.replace('\xa0', ' ') - - m = summary_re.match(summary) - summary = m.group(1) - duration = int(m.group(2)) + num, summary, duration = parse_section_html(section, + 'head-absatz-title-short') lehrplan.append(LehrplanSection(num, summary, duration)) second_level_sections = html.find_all(**{'class': 'headline_lvl2'}) for section in second_level_sections: - summary = section.find(**{ - 'class': 'head-absatz-title-long' - }).string.strip() - summary = summary.replace('\xa0', ' ') - - m = summary_re.match(summary) - summary = m.group(1) - duration = int(m.group(2)) - num = list(section.find(**{ - 'class': 'head-absatz-num' - }).children)[2].strip() + num, summary, duration = parse_section_html(section, + 'head-absatz-title-long') lehrplan[int(num.split('.')[0]) - 1].subsections.append( LehrplanSection(num, summary, duration)) return lehrplan -def gen_section_iter(lehrplan): - sections = [] +def gen_section_iter(lehrplan, inter_section_buffer_duration: int): + """Create an iterator returning the current section""" + end_section_buffer = LehrplanSection('', 'Puffer', 0) + inter_section_buffer = LehrplanSection('', 'Puffer', + inter_section_buffer_duration) + + _sections = [] for s in lehrplan: - if not s.has_subsections(): - sections.append(s) + if not s.subsections: + _sections.append(s) else: - sections.extend(s.subsections) + _sections.extend(s.subsections) + + # Add buffer sections to the lehrplan + sections = [] + for s in _sections: + sections.append(s) + if inter_section_buffer_duration: + sections.append(inter_section_buffer) + + sections.append(end_section_buffer) next_section_in = sections[0].duration cur = 0 @@ -186,29 +218,27 @@ def gen_section_iter(lehrplan): def _section_iter(weekly_hours, total_hours) -> list[LehrplanSection]: nonlocal cur if cur == len(sections) - 1: - return [sections[-1]] - - cur_sections = [] + return sections[-1] nonlocal next_section_in # advance section - if total_hours > next_section_in: - if total_hours - weekly_hours < next_section_in: - cur_sections.append(sections[cur]) - + if total_hours > next_section_in + (weekly_hours - + next_section_in % weekly_hours): cur += 1 next_section_in += sections[cur].duration - cur_sections.append(sections[cur]) - - return cur_sections + return sections[cur] return _section_iter -def gen_latex_table(weekly_hours: int, lehrplan: Lehrplan, school_holidays, - public_holidays) -> list[str]: - """generate a latex table matching lehrplan section on kalendarweeks""" +def gen_latex_table( + weekly_hours: int, + lehrplan: Lehrplan, + school_holidays: list[icalevents.icalparser.Event], + public_holidays: list[icalevents.icalparser.Event], # # pylint: disable=unused-argument + inter_section_buffer: int) -> list[str]: + """generate a latex table matching lehrplan section on calendar weeks""" rows: list[str] = [] @@ -220,7 +250,7 @@ def gen_latex_table(weekly_hours: int, lehrplan: Lehrplan, school_holidays, holiday_iter = iter(school_holidays[1:]) next_holiday = next(holiday_iter) - section_iter = gen_section_iter(lehrplan) + section_iter = gen_section_iter(lehrplan, inter_section_buffer) total_hours = 0 week_iter = start + timedelta(days=1) @@ -234,13 +264,11 @@ def gen_latex_table(weekly_hours: int, lehrplan: Lehrplan, school_holidays, next_holiday = next(holiday_iter) continue - lp_sections = section_iter(weekly_hours, total_hours) - - lp_level = ' \\& '.join([s.num for s in lp_sections]) - lp_summary = ' \\& '.join([s.summary for s in lp_sections]) + lp_section = section_iter(weekly_hours, total_hours) rows.append( - f'{week_iter.isocalendar().week} & {lp_level} & {lp_summary} \\\\') + f'{week_iter.isocalendar().week} & {lp_section.num} & {lp_section.summary} \\\\' + ) week_iter += timedelta(days=7) total_hours += weekly_hours @@ -248,8 +276,9 @@ def gen_latex_table(weekly_hours: int, lehrplan: Lehrplan, school_holidays, return rows -def print_map(rows: list[str], template: Path, outfile): - with open(template, 'r', encoding='utf-8') as template_file: +def print_map(rows: list[str], template_path: Path, outfile): + """Print the template including the generated table""" + with open(template_path, 'r', encoding='utf-8') as template_file: template = template_file.readlines() template_prefix = template[:-6] @@ -269,6 +298,7 @@ def main(): parser.add_argument('--year', default=default_year()) parser.add_argument('--land', default=BAYERN) parser.add_argument('-o', '--output', default=sys.stdout) + parser.add_argument('-b', '--inter-section-buffer', type=int) args = parser.parse_args() @@ -280,7 +310,7 @@ def main(): lehrplan = get_lehrplan(args.subject, str(args.form)) rows = gen_latex_table(weekly_hours, lehrplan, school_holidays, - public_holidays) + public_holidays, args.inter_section_buffer) if args.template_dir: template_path = Path(args.template_dir) / TEMPLATE_FILE |
