diff --git a/omicron/ssg/output/article.py b/omicron/ssg/output/article.py index e97c35d..068ceca 100644 --- a/omicron/ssg/output/article.py +++ b/omicron/ssg/output/article.py @@ -13,10 +13,11 @@ else: class Article(Content): - def __init__(self, source: Path, meta: dict[str, Any]): + def __init__(self, site: Site, source: Path, meta: dict[str, Any]): if "tags" not in meta: log.warning("no tags for article in '%s'", source) super().__init__( + site, source, meta["section"], meta["slug"], @@ -27,15 +28,15 @@ class Article(Content): ) self.title: str = meta["title"] - def write(self, site: Site) -> None: + def write(self) -> None: content, summary = md.parse(self.read_body(), self.summary is None) if summary is not None: self.summary = summary - template = site.jinja_env.get_template("article.html") - html = template.render(article=self, content=content, site=site) + template = self.site.jinja_env.get_template("article.html") + html = template.render(page=self, content=content, site=self.site) - out_path = site.output_path / self.destination + out_path = self.site.output_path / self.destination out_path.parent.mkdir(parents=True, exist_ok=True) out_path.write_text(html, encoding="utf-8") log.debug("Rendered %s -> %s", self.source, out_path) diff --git a/omicron/ssg/output/content.py b/omicron/ssg/output/content.py index 6de01da..9071d40 100644 --- a/omicron/ssg/output/content.py +++ b/omicron/ssg/output/content.py @@ -1,8 +1,14 @@ import re +from typing import TYPE_CHECKING from omicron.ssg.output.output import Output from pathlib import Path from datetime import date +if TYPE_CHECKING: + from omicron.ssg.site import Site +else: + Site = "omicron.ssg.site.Site" + class ContentError(RuntimeError): pass @@ -13,6 +19,7 @@ class Content(Output): def __init__( self, + site: Site, source: Path, section: str, slug: str, @@ -21,7 +28,7 @@ class Content(Output): tags: set[str] | None = None, summary: str | None = None, ): - super().__init__(Content.build_path(section, slug)) + super().__init__(site, Content.build_path(section, slug)) if tags is None: tags = set() self.source = source diff --git a/omicron/ssg/output/factory.py b/omicron/ssg/output/factory.py index 98fdd08..5bae048 100644 --- a/omicron/ssg/output/factory.py +++ b/omicron/ssg/output/factory.py @@ -1,9 +1,14 @@ import yaml from pathlib import Path -from typing import Any, cast +from typing import Any, cast, TYPE_CHECKING from omicron.ssg.output.content import Content, ContentError from omicron.ssg.output.article import Article +if TYPE_CHECKING: + from omicron.ssg.site import Site +else: + Site = "omicron.ssg.site.Site" + FRONTMATTER_CONTENT = {".md"} CONTENT_EXTENSIONS = FRONTMATTER_CONTENT | {".yml"} @@ -31,7 +36,7 @@ def read_frontmatter(path: Path) -> dict[str, Any]: return cast(dict[str, Any], data) -def create_content(path: Path) -> Content: +def create_content(site: Site, path: Path) -> Content: if path.suffix in FRONTMATTER_CONTENT: meta = read_frontmatter(path) elif path.suffix == ".yml": @@ -40,6 +45,6 @@ def create_content(path: Path) -> Content: raise ContentError(f"Unhandled file type for '{path}'") if meta["type"] == "article": - return Article(path, meta) + return Article(site, path, meta) else: raise ContentError(f"Invalid type '{meta['type']}' for '{path}'") diff --git a/omicron/ssg/output/file.py b/omicron/ssg/output/file.py index e470dad..9874be9 100644 --- a/omicron/ssg/output/file.py +++ b/omicron/ssg/output/file.py @@ -13,12 +13,12 @@ else: class File(Output): - def __init__(self, path: Path, source: Path): - super().__init__(path) + def __init__(self, site: Site, path: Path, source: Path): + super().__init__(site, path) self.source = source - def write(self, site: Site) -> None: - dest = site.output_path / self.destination + def write(self) -> None: + dest = self.site.output_path / self.destination dest.parent.mkdir(parents=True, exist_ok=True) shutil.copyfile(self.source, dest) log.debug("Copied %s -> %s", self.source, dest) diff --git a/omicron/ssg/output/index.py b/omicron/ssg/output/index.py index 094c0ac..c39d0de 100644 --- a/omicron/ssg/output/index.py +++ b/omicron/ssg/output/index.py @@ -17,19 +17,24 @@ else: class Index(Output): def __init__( self, + site: Site, kind: Literal["root", "section", "tag"], items: list[Content], page_num: int, total_pages: int, label: str | None, ): - super().__init__(Index.build_path(kind, page_num, label)) + super().__init__(site, Index.build_path(kind, page_num, label)) self.kind = kind self.items = items self.page_num = page_num self.total_pages = total_pages self.label = label + def pagination_url(self, n: int) -> str: + path = Index.build_path(self.kind, n, self.label) + return self.site.by_path[path].url + @staticmethod def build_path( kind: Literal["root", "section", "tag"], @@ -51,23 +56,17 @@ class Index(Output): path = Path("tags") / label / file return path - def write(self, site: Site) -> None: - template = site.jinja_env.get_template("index.html") - html = template.render( - site=site, - items=self.items, - page_num=self.page_num, - total_pages=self.total_pages, - kind=self.kind, - label=self.label, - ) - dest = site.output_path / self.destination + def write(self) -> None: + template = self.site.jinja_env.get_template("index.html") + html = template.render(site=self.site, page=self) + dest = self.site.output_path / self.destination dest.parent.mkdir(parents=True, exist_ok=True) dest.write_text(html, encoding="utf-8") log.debug("Wrote %s", dest) def make_index_pages( + site: Site, items: list[Content], kind: Literal["root", "section", "tag"], label: str | None, @@ -80,19 +79,23 @@ def make_index_pages( kind, f" ({label})" if label else "", ) - return [Index(kind, [], 1, 1, label)] + return [Index(site, kind, [], 1, 1, label)] num_pages = ceil(len(sorted_items) / items_per_page) return [ - Index(kind, list(page_items), i + 1, num_pages, label) + Index(site, kind, list(page_items), i + 1, num_pages, label) for i, page_items in enumerate(batched(sorted_items, items_per_page)) ] def discover_index(site: Site) -> list[Index]: outputs: list[Index] = [] - outputs.extend(make_index_pages(site.content, "root", None, site.items_per_page)) + outputs.extend( + make_index_pages(site, site.content, "root", None, site.items_per_page) + ) for section, items in site.by_section.items(): - outputs.extend(make_index_pages(items, "section", section, site.items_per_page)) + outputs.extend( + make_index_pages(site, items, "section", section, site.items_per_page) + ) for tag, items in site.by_tag.items(): - outputs.extend(make_index_pages(items, "tag", tag, site.items_per_page)) + outputs.extend(make_index_pages(site, items, "tag", tag, site.items_per_page)) return outputs diff --git a/omicron/ssg/output/memory.py b/omicron/ssg/output/memory.py index 8d6f5c4..2d018ce 100644 --- a/omicron/ssg/output/memory.py +++ b/omicron/ssg/output/memory.py @@ -12,12 +12,12 @@ else: class Memory(Output): - def __init__(self, destination: Path, content: str): - super().__init__(destination) + def __init__(self, site: Site, destination: Path, content: str): + super().__init__(site, destination) self.content = content - def write(self, site: Site) -> None: - dest = site.output_path / self.destination + def write(self) -> None: + dest = self.site.output_path / self.destination dest.parent.mkdir(parents=True, exist_ok=True) dest.write_text(self.content, encoding="utf-8") log.debug("Wrote %s", dest) diff --git a/omicron/ssg/output/output.py b/omicron/ssg/output/output.py index 2140104..0826698 100644 --- a/omicron/ssg/output/output.py +++ b/omicron/ssg/output/output.py @@ -1,3 +1,4 @@ +import weakref from typing import TYPE_CHECKING from abc import ABC, abstractmethod from pathlib import Path @@ -9,14 +10,22 @@ else: class Output(ABC): - def __init__(self, destination: Path): + def __init__(self, site: Site, destination: Path): + self._site_ref = weakref.ref(site) self.destination = destination - url = "/" + destination.as_posix() - url = url.removesuffix("/index.html") - if url == "": - url = "/" + url = site.base_dir + "/" + destination.as_posix() + url = url.removesuffix("/index.html") or "/" self.url = url + @property + def site(self) -> Site: + obj = self._site_ref() + if obj is None: + raise ReferenceError( + f"Site for output {self.destination} has been garbage collected" + ) + return obj + @abstractmethod - def write(self, site: Site) -> None: + def write(self) -> None: pass diff --git a/omicron/ssg/output/tags.py b/omicron/ssg/output/tags.py index 5ac2f40..b0a1b19 100644 --- a/omicron/ssg/output/tags.py +++ b/omicron/ssg/output/tags.py @@ -12,18 +12,20 @@ else: class Tags(Output): - def __init__(self, tags: list[tuple[str, int]]): - super().__init__(Path("tags/index.html")) + PATH = Path("tags/index.html") + + def __init__(self, site: Site, tags: list[tuple[str, int]]): + super().__init__(site, Tags.PATH) self.tags = tags - def write(self, site: Site) -> None: - template = site.jinja_env.get_template("tags.html") - html = template.render(site=site, tags=self.tags) - dest = site.output_path / self.destination + def write(self) -> None: + template = self.site.jinja_env.get_template("tags.html") + html = template.render(site=self.site, page=self) + dest = self.site.output_path / self.destination dest.parent.mkdir(parents=True, exist_ok=True) dest.write_text(html, encoding="utf-8") log.debug("Wrote %s", dest) def discover_tags(site: Site) -> Tags: - return Tags(site.tags_by_count) + return Tags(site, site.tags_by_count) diff --git a/omicron/ssg/site.py b/omicron/ssg/site.py index d700494..78cabd6 100644 --- a/omicron/ssg/site.py +++ b/omicron/ssg/site.py @@ -9,6 +9,8 @@ from omicron.ssg.output import ( is_content, create_content, File, + Index, + Tags, Memory, discover_index, discover_tags, @@ -98,6 +100,20 @@ class Site: ) self.by_path[output.destination] = output + def home_url(self) -> str: + return self.base_dir or "/" + + def section_url(self, section: str) -> str: + path = Index.build_path("section", 1, section) + return self.by_path[path].url + + def tag_url(self, tag: str) -> str: + path = Index.build_path("tag", 1, tag) + return self.by_path[path].url + + def tags_url(self) -> str: + return self.by_path[Tags.PATH].url + def update_tags_by_count(self) -> None: tags = [(tag, len(items)) for tag, items in self.by_tag.items()] tags.sort(key=lambda x: x[1], reverse=True) @@ -110,7 +126,7 @@ class Site: for file in assets_path.rglob("*"): if file.is_file(): path = Path("assets") / file.relative_to(assets_path) - asset = File(path, file) + asset = File(self, path, file) self.add_by_path(asset) self.other_outputs.append(asset) log.debug("Discovered template asset %s", path) @@ -118,13 +134,13 @@ class Site: def discover(self) -> None: log.info("Discovering content...") self.discover_assets() - pygments = Memory(Path("assets/pygments.css"), highlight_style()) + pygments = Memory(self, Path("assets/pygments.css"), highlight_style()) self.other_outputs.append(pygments) self.add_by_path(pygments) content_path = self.site_path / "content" for file in content_path.rglob("*"): if file.is_file() and is_content(file): - content = create_content(file) + content = create_content(self, file) self.add_by_path(content) self.add_by_section(content) self.add_by_tag(content) @@ -149,6 +165,6 @@ class Site: self.discover() self.output_path.mkdir(parents=True, exist_ok=True) for content in self.content: - content.write(self) + content.write() for output in self.other_outputs: - output.write(self) + output.write() diff --git a/omicron/ssg/templates/plain/article.html b/omicron/ssg/templates/plain/article.html index 7727a48..18fa936 100644 --- a/omicron/ssg/templates/plain/article.html +++ b/omicron/ssg/templates/plain/article.html @@ -1,31 +1,29 @@ {% extends "base.html" %} -{% block title %}{{ article.title }} — {{ site.name }}{% endblock %} - -{% block head %}{% endblock %} +{% block title %}{{ page.title }} — {{ site.name }}{% endblock %} {% block breadcrumb %} {% endblock %} {% block main %}
-

{{ article.title }}

+

{{ page.title }}

- - {% if article.updated %} - · Updated + + {% if page.updated %} + · Updated {% endif %}

- {% if article.tags %} + {% if page.tags %} {% endif %} diff --git a/omicron/ssg/templates/plain/base.html b/omicron/ssg/templates/plain/base.html index 9f6760a..e2042a6 100644 --- a/omicron/ssg/templates/plain/base.html +++ b/omicron/ssg/templates/plain/base.html @@ -6,11 +6,12 @@ {% block title %}{{ site.name }}{% endblock %} + {% block head %}{% endblock %}
- {% block breadcrumb %}{{ site.name }}{% endblock %} + {% block breadcrumb %}{{ site.name }}{% endblock %}
{% block main %}{% endblock %} diff --git a/omicron/ssg/templates/plain/index.html b/omicron/ssg/templates/plain/index.html index 1d55df7..b6f3ef4 100644 --- a/omicron/ssg/templates/plain/index.html +++ b/omicron/ssg/templates/plain/index.html @@ -1,25 +1,22 @@ {% extends "base.html" %} -{% set base_path = ("/" + label) if kind == "section" else (("/tags/" + label) if kind == "tag" else "") %} -{% set heading = label or "" %} -{% block title %}{% if heading %}{{ heading }} — {% endif %}{{ site.name }}{% if page_num > 1 %} — Page {{ page_num }}{% endif %}{% endblock %} +{% block title %}{% if page.label %}{{ page.label }} — {% endif %}{{ site.name }}{% if page.page_num > 1 %} — Page {{ page.page_num }}{% endif %}{% endblock %} {% block head %} - -{% if page_num > 1 %}{% endif %} +{% if page.page_num > 1 %}{% endif %} {% endblock %} {% block breadcrumb %} -{% if kind == "section" %} +{% if page.kind == "section" %} -{% elif kind == "tag" %} +{% elif page.kind == "tag" %} {% else %} {{ super() }} @@ -27,28 +24,28 @@ {% endblock %} {% block main %} -{% if heading %}

{{ heading }}

{% endif %} -{% for item in items %} +{% if page.label %}

{{ page.label }}

{% endif %} +{% for item in page.items %} {% endfor %} -{% if total_pages > 1 %} +{% if page.total_pages > 1 %} {% endif %} diff --git a/omicron/ssg/templates/plain/tags.html b/omicron/ssg/templates/plain/tags.html index 948596e..851bf37 100644 --- a/omicron/ssg/templates/plain/tags.html +++ b/omicron/ssg/templates/plain/tags.html @@ -2,13 +2,9 @@ {% block title %}Tags — {{ site.name }}{% endblock %} -{% block head %} - -{% endblock %} - {% block breadcrumb %} {% endblock %} @@ -16,8 +12,8 @@ {% block main %}

Tags

    -{% for tag, count in tags %} -
  • {{ tag }} ({{ count }})
  • +{% for tag, count in page.tags %} +
  • {{ tag }} ({{ count }})
  • {% endfor %}
{% endblock %}