#!/usr/bin/env python3 import fnmatch import locale import os from dataclasses import dataclass from datetime import date, datetime from html import escape from pathlib import Path from time import perf_counter import mistune from jinja2 import Environment as Env from jinja2 import FileSystemLoader from minicli import cli, run, wrap from mistune.directives import DirectiveInclude from slugify import slugify # Useful for dates rendering within Jinja2. locale.setlocale(locale.LC_ALL, "fr_FR.UTF-8") HERE = Path(".") DAVID = HERE / "david" DOMAIN = "https://larlet.fr" # Hardcoding publication at 12 in Paris timezone. NORMALIZED_STRFTIME = "%Y-%m-%dT12:00:00+01:00" class CustomHTMLRenderer(mistune.HTMLRenderer): def heading(self, text, level): # Set an anchor to h2 headings. if level == 2: slug = slugify(text) return ( f'

' f"{text} " f'#' f"

" ) else: return super().heading(text, level) markdown = mistune.create_markdown( renderer=CustomHTMLRenderer(escape=False), plugins=[DirectiveInclude()] ) environment = Env(loader=FileSystemLoader(str(DAVID / "templates"))) def neighborhood(iterable, first=None, last=None): """ Yield the (previous, current, next) items given an iterable. You can specify a `first` and/or `last` item for bounds. """ iterator = iter(iterable) previous = first current = next(iterator) # Throws StopIteration if empty. for next_ in iterator: yield (previous, current, next_) previous = current current = next_ yield (previous, current, last) def each_markdown_from(source_dir, file_name="*.md"): """Walk across the `source_dir` and return the md file paths.""" for filename in fnmatch.filter(os.listdir(source_dir), file_name): yield os.path.join(source_dir, filename) @dataclass class Page: title: str content: str file_path: str lang: str = "fr" def __post_init__(self): suffix = len(".md") prefix = len("YYYY/MM-DD") + suffix date_str = self.file_path[-prefix:-suffix].replace("-", "/") self.url = f"/david/{date_str}/" self.date = datetime.strptime(date_str, "%Y/%m/%d").date() self.full_url = f"{DOMAIN}{self.url}" self.normalized_date = self.date.strftime(NORMALIZED_STRFTIME) self.escaped_title = escape(self.title) self.escaped_content = escape( self.content.replace('href="/', f'href="{DOMAIN}/') .replace('src="/', f'src="{DOMAIN}/') .replace('href="#', f'href="{self.full_url}#') ) # Extract first paragraph. self.extract = self.content.split("

", 1)[0] + "

" def __lt__(self, other: "Page"): if not isinstance(other, Page): return NotImplemented return self.date < other.date @staticmethod def all(source: Path): """Retrieve all pages sorted by desc.""" page_list = [] for file_path in each_markdown_from(source): result = markdown.read(file_path) title, content = result.split("", 1) h1_opening_size = len("

") title = title[h1_opening_size:] page = Page(title, content, file_path) page_list.append(page) return sorted(page_list, reverse=True) @cli def fragment(title: str): """Create a new fragment and open it in iA Writer.""" fragment_path = DAVID / "2020" / "fragments" / f"{title}.md" open(fragment_path, "w+").write(f"## {title}") os.popen(f'open -a "iA Writer" "{fragment_path}"') @cli def pages(): """Build the agregations from fragments.""" root_path = DAVID / "2020" page_list = Page.all(source=root_path) for previous, page, next_ in neighborhood( page_list, last={"url": "/david/stream/", "title": "Streams 2009-2019"} ): template = environment.get_template("article_2020.html") content = template.render(page=page, next=previous, prev=next_,) target_path = Path(page.url[1:]) target_path.mkdir(parents=True, exist_ok=True) open(target_path / "index.html", "w").write(content) template = environment.get_template("archives_2020.html") content = template.render(page_list=page_list) open(root_path / "index.html", "w").write(content) @cli def home(): """Build the home page with last published items.""" template = environment.get_template("profil.html") content = template.render(page_list=Page.all(source=DAVID / "2020"),) open(DAVID / "index.html", "w").write(content) @cli def feed(): """Generate a feed from last published items.""" template = environment.get_template("feed.xml") content = template.render( page_list=Page.all(source=DAVID / "2020"), current_dt=datetime.now().strftime(NORMALIZED_STRFTIME), BASE_URL=f"{DOMAIN}/david/", ) open(DAVID / "log" / "index.xml", "w").write(content) @wrap def perf_wrapper(): start = perf_counter() yield elapsed = perf_counter() - start print(f"Done in {elapsed:.5f} seconds.") if __name__ == "__main__": run()