from contextlib import contextmanager from dataclasses import dataclass from datetime import datetime import locale from operator import attrgetter from os import path from pathlib import Path import re from typing import Iterator, Optional def guess_language(filename): parent = str(Path(filename).parent) if parent == '.': return 'fr' return parent def relative_path(*, to, ref): # pathlib.Path(x).relative_to(y) cannot handle y not being under x, # os.path.dirname('x') yields '' rather than '.'. # 😮‍💨 return path.relpath(to, Path(ref).parent) @contextmanager def tmplocale(lang): old_lang, encoding = locale.getlocale() try: locale.setlocale(locale.LC_TIME, (lang, encoding)) yield finally: locale.setlocale(locale.LC_TIME, (old_lang, encoding)) _LICENSE_URLS = { 'CC0': 'https://creativecommons.org/publicdomain/zero', 'CC BY': 'https://creativecommons.org/licenses/by', 'CC BY-SA': 'https://creativecommons.org/licenses/by-sa', } _LICENSE_RE = re.compile( '('+'|'.join(_LICENSE_URLS.keys())+')' + ' ([0-9.]+)' ) @dataclass class LicenseInfo: tag: str version: str @classmethod def deserialize(cls, info): if info is None: return None return cls(*_LICENSE_RE.fullmatch(info).groups()) def format(self): url = f'{_LICENSE_URLS[self.tag]}/{self.version}/' return f'{self.tag}' @dataclass class Illustration: file: str alt_text: str source_name: str source_link: Optional[str] license_info: Optional[LicenseInfo] position: Optional[str] @classmethod def deserialize(cls, d): return cls(d['pic_file'], d['pic_alt'], d['pic_src'], d['pic_link'], LicenseInfo.deserialize(d['pic_license']), d['pic_position']) @property def style(self): if self.position is None: return None return f'object-position: {self.position}' @dataclass class Concert: time: datetime place: str address: str pieces: Iterator[str] instructions: str illustration: Illustration warning: Optional[str] @classmethod def deserialize(cls, d): return cls( time=datetime.strptime(d['time'], '%d/%m/%Y %Hh%M'), place=d['place'], address=d['address'], pieces=d['pieces'], instructions=d['instructions'], illustration=Illustration.deserialize(d), warning=d['warning'] ) def _optional(line): return f'(?:{line})?' _CONCERT_LINES = ( r'QUAND : (?P