import dataclasses import re from enum import Enum from typing import Optional, cast import bs4 from PFERD.utils import soupify _link_template_plain = "{{link}}" _link_template_fancy = """ ILIAS - Link: {{name}}
{{description}}
""".strip() # noqa: E501 line too long _link_template_internet_shortcut = """ [InternetShortcut] URL={{link}} """.strip() _learning_module_template = """ {{name}} {{body}} """ _forum_thread_template = """ ILIAS - Forum: {{name}} {{heading}} {{content}} """.strip() # noqa: E501 line too long def learning_module_template(body: bs4.Tag, name: str, prev: Optional[str], next: Optional[str]) -> str: # Seems to be comments, ignore those. for elem in body.select(".il-copg-mob-fullscreen-modal"): elem.decompose() nav_template = """ """ if prev and body.select_one(".ilc_page_lnav_LeftNavigation"): text = cast(bs4.Tag, body.select_one(".ilc_page_lnav_LeftNavigation")).get_text().strip() left = f'{text}' else: left = "" if next and body.select_one(".ilc_page_rnav_RightNavigation"): text = cast(bs4.Tag, body.select_one(".ilc_page_rnav_RightNavigation")).get_text().strip() right = f'{text}' else: right = "" if top_nav := body.select_one(".ilc_page_tnav_TopNavigation"): top_nav.replace_with( soupify(nav_template.replace("{{left}}", left).replace("{{right}}", right).encode()) ) if bot_nav := body.select_one(".ilc_page_bnav_BottomNavigation"): bot_nav.replace_with(soupify(nav_template.replace( "{{left}}", left).replace("{{right}}", right).encode()) ) body_str = cast(str, body.prettify()) return _learning_module_template.replace("{{body}}", body_str).replace("{{name}}", name) def forum_thread_template(name: str, url: str, heading: bs4.Tag, content: bs4.Tag) -> str: if title := cast(Optional[bs4.Tag], heading.find(name="b")): title.wrap(bs4.Tag(name="a", attrs={"href": url})) return _forum_thread_template \ .replace("{{name}}", name) \ .replace("{{heading}}", cast(str, heading.prettify())) \ .replace("{{content}}", cast(str, content.prettify())) @dataclasses.dataclass class LinkData: name: str url: str description: str class Links(Enum): IGNORE = "ignore" PLAINTEXT = "plaintext" FANCY = "fancy" INTERNET_SHORTCUT = "internet-shortcut" def template(self) -> Optional[str]: if self == Links.FANCY: return _link_template_fancy elif self == Links.PLAINTEXT: return _link_template_plain elif self == Links.INTERNET_SHORTCUT: return _link_template_internet_shortcut elif self == Links.IGNORE: return None raise ValueError("Missing switch case") def collection_as_one(self) -> bool: if self == Links.FANCY: return True return False def extension(self) -> Optional[str]: if self == Links.FANCY: return ".html" elif self == Links.PLAINTEXT: return ".txt" elif self == Links.INTERNET_SHORTCUT: return ".url" elif self == Links.IGNORE: return None raise ValueError("Missing switch case") def interpolate(self, redirect_delay: int, collection_name: str, links: list[LinkData]) -> str: template = self.template() if template is None: raise ValueError("Cannot interpolate ignored links") if len(links) == 1: link = links[0] content = template content = content.replace("{{link}}", link.url) content = content.replace("{{name}}", link.name) content = content.replace("{{description}}", link.description) content = content.replace("{{redirect_delay}}", str(redirect_delay)) return content if self == Links.PLAINTEXT or self == Links.INTERNET_SHORTCUT: return "\n".join(f"{link.url}" for link in links) # All others get coerced to fancy content = cast(str, Links.FANCY.template()) repeated_content = cast( re.Match[str], re.search(r"([\s\S]+)", content) ).group(1) parts = [] for link in links: instance = repeated_content instance = instance.replace("{{link}}", link.url) instance = instance.replace("{{name}}", link.name) instance = instance.replace("{{description}}", link.description) instance = instance.replace("{{redirect_delay}}", str(redirect_delay)) parts.append(instance) content = content.replace(repeated_content, "\n".join(parts)) content = content.replace("{{name}}", collection_name) content = re.sub(r"[\s\S]+", "", content) return content @staticmethod def from_string(string: str) -> "Links": try: return Links(string) except ValueError: options = [f"'{option.value}'" for option in Links] raise ValueError(f"must be one of {', '.join(options)}")