|
|
@@ -2,11 +2,13 @@ |
|
|
|
import fnmatch |
|
|
|
import locale |
|
|
|
import os |
|
|
|
from collections import defaultdict |
|
|
|
from dataclasses import dataclass |
|
|
|
from datetime import datetime, timedelta |
|
|
|
from html import escape |
|
|
|
from itertools import groupby |
|
|
|
from pathlib import Path |
|
|
|
from string import Template |
|
|
|
from textwrap import dedent |
|
|
|
from time import perf_counter |
|
|
|
|
|
|
@@ -34,6 +36,8 @@ TODAY = datetime.today() + timedelta(hours=6) |
|
|
|
PUBLICATION_BUFFER = TODAY - timedelta(days=7) |
|
|
|
NB_ITEMS_IN_FEED = 30 |
|
|
|
|
|
|
|
all_tags = set() |
|
|
|
pages_by_tags = defaultdict(list) |
|
|
|
pages_by_url = {} |
|
|
|
|
|
|
|
|
|
|
@@ -61,6 +65,20 @@ class MarkRenderer(mistune.HTMLRenderer): |
|
|
|
return "<mark>" + text + "</mark>" |
|
|
|
|
|
|
|
|
|
|
|
class TagsRenderer(mistune.HTMLRenderer): |
|
|
|
"""Make the asumption each line starting with a `#` is a tag.""" |
|
|
|
|
|
|
|
def paragraph(self, text): |
|
|
|
if text.startswith("#"): |
|
|
|
tags = " ".join( |
|
|
|
f'<a href="/david/2021/{slugify(tag.strip())}/">#{tag.strip()}</a>' |
|
|
|
for tag in text.split("#") |
|
|
|
if tag.strip() |
|
|
|
) |
|
|
|
return f"<nav><p>{tags}</p></nav>\n" |
|
|
|
return super().paragraph(text) |
|
|
|
|
|
|
|
|
|
|
|
class FrenchTypographyRenderer(mistune.HTMLRenderer): |
|
|
|
"""Apply French typographic rules to text.""" |
|
|
|
|
|
|
@@ -89,7 +107,7 @@ class InternalLinkTitleRenderer(mistune.HTMLRenderer): |
|
|
|
|
|
|
|
|
|
|
|
class CustomAndBlockquoteLanguageRenderer( |
|
|
|
FrenchTypographyRenderer, InternalLinkTitleRenderer, MarkRenderer |
|
|
|
FrenchTypographyRenderer, InternalLinkTitleRenderer, MarkRenderer, TagsRenderer |
|
|
|
): |
|
|
|
"""Sets the English language attribute for blockquotes with `[en]` prefix.""" |
|
|
|
|
|
|
@@ -114,7 +132,7 @@ class ImgsWithSizesRenderer(CustomAndBlockquoteLanguageRenderer): |
|
|
|
# In case of a figure, we do not want the (non-standard) paragraph. |
|
|
|
if text.strip().startswith("<figure>"): |
|
|
|
return text |
|
|
|
return f"<p>{text}</p>\n" |
|
|
|
return super().paragraph(text) |
|
|
|
|
|
|
|
def image(self, src, alt="", title=None): |
|
|
|
full_path = STATIC / Path(src[1:]) |
|
|
@@ -224,6 +242,7 @@ def each_markdown_from(source_dir, file_name="*.md"): |
|
|
|
class Page: |
|
|
|
title: str |
|
|
|
content: str |
|
|
|
tags: list |
|
|
|
file_path: str |
|
|
|
lang: str = "fr" |
|
|
|
|
|
|
@@ -234,15 +253,26 @@ class Page: |
|
|
|
self.full_url = f"{DOMAIN}{self.url}" |
|
|
|
self.normalized_date = self.date.strftime(NORMALIZED_STRFTIME) |
|
|
|
self.escaped_title = escape(self.title) |
|
|
|
tag_template = Template( |
|
|
|
f'<a href="{DOMAIN}/david/2021/$tag_slug/">#$tag_name</a>' |
|
|
|
) |
|
|
|
tag_links = " ".join( |
|
|
|
tag_template.substitute(tag_slug=slugify(tag), tag_name=tag) |
|
|
|
for tag in self.tags |
|
|
|
) |
|
|
|
self.escaped_content = escape( |
|
|
|
self.content.replace('href="/', f'href="{DOMAIN}/') |
|
|
|
.replace('src="/', f'src="{DOMAIN}/') |
|
|
|
.replace('href="#', f'href="{self.full_url}#') |
|
|
|
+ f"<nav><p>{tag_links}</p></nav>" |
|
|
|
+ '<hr/><p><a href="mailto:david@larlet.fr">Réagir ?</a></p>' |
|
|
|
) |
|
|
|
# Extract first paragraph. |
|
|
|
self.extract = self.content.split("</p>", 1)[0] + "</p>" |
|
|
|
|
|
|
|
def __eq__(self, other): |
|
|
|
return self.url == other.url |
|
|
|
|
|
|
|
def __lt__(self, other: "Page"): |
|
|
|
if not isinstance(other, Page): |
|
|
|
return NotImplemented |
|
|
@@ -259,8 +289,24 @@ class Page: |
|
|
|
title, content = result.split("</h1>", 1) |
|
|
|
h1_opening_size = len("<h1>") |
|
|
|
title = title[h1_opening_size:] |
|
|
|
page = Page(title, content, file_name) |
|
|
|
tags = {} |
|
|
|
if "<nav><p>" in content: |
|
|
|
# Extract the tags from the generated page. |
|
|
|
content, tags_links = content.split("<nav><p>", 1) |
|
|
|
nav_closing_size = len("</p></nav>\n") |
|
|
|
tags_links = tags_links[:-nav_closing_size] |
|
|
|
tags = { |
|
|
|
tag.strip().split("#", 1)[1] |
|
|
|
for tag in tags_links.split("</a>") |
|
|
|
if tag.strip() |
|
|
|
} |
|
|
|
page = Page(title, content, tags, file_name) |
|
|
|
pages_by_url[page.url] = page |
|
|
|
if not page.is_draft: |
|
|
|
all_tags.update(tags) |
|
|
|
for tag in tags: |
|
|
|
if page not in pages_by_tags[tag]: |
|
|
|
pages_by_tags[tag].append(page) |
|
|
|
if only_published and page.is_draft: |
|
|
|
continue |
|
|
|
page_list.append(page) |
|
|
@@ -276,7 +322,7 @@ class Page: |
|
|
|
|
|
|
|
@cli |
|
|
|
def pages(): |
|
|
|
"""Build the agregations from fragments.""" |
|
|
|
"""Build article pages.""" |
|
|
|
root_path = DAVID / "2021" |
|
|
|
source_path = root_path / "sources" |
|
|
|
for previous, page, next_ in neighborhood( |
|
|
@@ -288,11 +334,7 @@ def pages(): |
|
|
|
}, |
|
|
|
): |
|
|
|
template = environment.get_template("article_2020.html") |
|
|
|
content = template.render( |
|
|
|
page=page, |
|
|
|
prev=previous, |
|
|
|
next=next_, |
|
|
|
) |
|
|
|
content = template.render(page=page, prev=previous, next=next_, slugify=slugify) |
|
|
|
target_path = Path(page.url[1:]) |
|
|
|
target_path.mkdir(parents=True, exist_ok=True) |
|
|
|
open(target_path / "index.html", "w").write(content) |
|
|
@@ -303,6 +345,24 @@ def pages(): |
|
|
|
open(root_path / "index.html", "w").write(content) |
|
|
|
|
|
|
|
|
|
|
|
@cli |
|
|
|
def tags(): |
|
|
|
"""Build tags pages.""" |
|
|
|
root_path = DAVID / "2021" |
|
|
|
source_path = root_path / "sources" |
|
|
|
# Parse all pages to collect tags. |
|
|
|
Page.all(source=source_path, only_published=True) |
|
|
|
for tag in all_tags: |
|
|
|
template = environment.get_template("tag_2021.html") |
|
|
|
content = template.render( |
|
|
|
page_list=pages_by_tags[tag], |
|
|
|
tag_name=tag, |
|
|
|
) |
|
|
|
target_path = DAVID / "2021" / slugify(tag) |
|
|
|
target_path.mkdir(parents=True, exist_ok=True) |
|
|
|
open(target_path / "index.html", "w").write(content) |
|
|
|
|
|
|
|
|
|
|
|
@cli |
|
|
|
def home(): |
|
|
|
"""Build the home page with last published items.""" |
|
|
@@ -311,10 +371,10 @@ def home(): |
|
|
|
return item.date.strftime("%B %Y").title() |
|
|
|
|
|
|
|
template = environment.get_template("profil.html") |
|
|
|
page_list = Page.all(source=DAVID / "2021" / "sources") |
|
|
|
tags = sorted((slugify(tag), tag, len(pages_by_tags[tag])) for tag in all_tags) |
|
|
|
content = template.render( |
|
|
|
page_list=groupby( |
|
|
|
Page.all(source=DAVID / "2021" / "sources"), key=group_by_month_year |
|
|
|
), |
|
|
|
page_list=groupby(page_list, key=group_by_month_year), tags=tags |
|
|
|
) |
|
|
|
open(DAVID / "index.html", "w").write(content) |
|
|
|
|