from collections import defaultdict from dataclasses import dataclass, field from itertools import chain from os import environ, path from pathlib import Path from subprocess import run from typing import Dict, Iterator, Union @dataclass class Directory: files: Iterator[str] = field(default_factory=list) subfolders: Iterator[str] = field(default_factory=set) def serialize(self): return { 'files': sorted(self.files), 'subfolders': sorted(self.subfolders) } @classmethod def deserialize(cls, d): return cls(**d) def _find_files(extensions, repository): patterns = (f'**.{ext}' for ext in extensions) zero = '\x00' return repository.git.ls_files('-z', *patterns).strip(zero).split(zero) def _fill_directories(files, top_dir): directories = defaultdict(Directory) for f in files: fdir, fname = path.split(f) directories[fdir].files.append(fname) while fdir: parent, child = path.split(fdir) directories[parent].subfolders.add(child) fdir = parent return directories def compute_directories(extensions, repository): files = _find_files(extensions, repository) top_dir = path.relpath(repository.working_dir, path.curdir) return _fill_directories(files, top_dir) def deserialize_directories(directories): return { k: Directory.deserialize(v) for k, v in directories.items() } _PathArg = Union[Path, str, bytes] @dataclass class PandocRunner: output: _PathArg template: _PathArg filters: Iterator[_PathArg] stylesheets: Iterator[_PathArg] variables: Dict[str, str] = field(default_factory=dict) def run(self, page, include_after=(), metadata=None): cmd = ( 'pandoc', '-s', page, '-o', self.output, '--template', self.template, *chain(*(('--lua-filter', f) for f in self.filters)), *chain(*(('--css', s) for s in self.stylesheets)), *chain(*(('--include-after-body', f) for f in include_after)) ) cmd += tuple(chain( *(('-V', f'{k}={v}') for k, v in self.variables.items()) )) if metadata is not None: cmd += tuple(chain( *(('-M', f'{k}={v}') for k, v in metadata.items()) )) environ['LUA_PATH'] = '.cache/?.lua;;' run(cmd, check=True) def generate_crumbs(target): parts = ('(top)', *target.parts) if parts[-1] == 'index': *crumbs, current = parts[:-1] else: crumbs = parts[:-1] current, _ = path.splitext(parts[-1]) crumbs_li = ( '