Create Index(Output) class and migrate index generation from Site
Like previous Content refactor, this allows validating file collisions in the discover/write build pipeline.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
from omicron.ssg.output.output import Output
|
||||
from omicron.ssg.output.content import Content, ContentError
|
||||
from omicron.ssg.output.factory import create_content, is_content
|
||||
from omicron.ssg.output.index import Index, discover_index
|
||||
from omicron.ssg.output.memory import Memory
|
||||
|
||||
__all__ = [
|
||||
@@ -9,5 +10,7 @@ __all__ = [
|
||||
"ContentError",
|
||||
"create_content",
|
||||
"is_content",
|
||||
"Index",
|
||||
"discover_index",
|
||||
"Memory",
|
||||
]
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
import logging
|
||||
from math import ceil
|
||||
from itertools import batched
|
||||
from typing import TYPE_CHECKING, Literal
|
||||
from omicron.ssg.output.output import Output
|
||||
from omicron.ssg.output.content import Content
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from omicron.ssg.site import Site # for static checking with mypy
|
||||
else:
|
||||
Site = "omicron.ssg.site.Site" # for runtime checking with beartype
|
||||
|
||||
|
||||
class Index(Output):
|
||||
def __init__(
|
||||
self,
|
||||
kind: Literal["root", "section", "tag"],
|
||||
items: list[Content],
|
||||
page_num: int,
|
||||
total_pages: int,
|
||||
label: str | None,
|
||||
):
|
||||
super().__init__(Index.build_uri(kind, page_num, label))
|
||||
self.kind = kind
|
||||
self.items = items
|
||||
self.page_num = page_num
|
||||
self.total_pages = total_pages
|
||||
self.label = label
|
||||
|
||||
@staticmethod
|
||||
def build_uri(
|
||||
kind: Literal["root", "section", "tag"],
|
||||
page_num: int,
|
||||
label: str | None,
|
||||
) -> str:
|
||||
if page_num == 1:
|
||||
file = "index.html"
|
||||
else:
|
||||
file = f"page-{page_num}.html"
|
||||
|
||||
if kind == "root":
|
||||
uri = file
|
||||
elif kind == "section":
|
||||
assert label, "label can't be None if kind is not root"
|
||||
uri = f"{label}/{file}"
|
||||
else:
|
||||
assert label, "label can't be None if kind is not root"
|
||||
uri = f"tags/{label}/{file}"
|
||||
return uri
|
||||
|
||||
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.uri
|
||||
dest.parent.mkdir(parents=True, exist_ok=True)
|
||||
dest.write_text(html, encoding="utf-8")
|
||||
log.debug("Wrote %s", dest)
|
||||
|
||||
|
||||
def make_index_pages(
|
||||
items: list[Content],
|
||||
kind: Literal["root", "section", "tag"],
|
||||
label: str | None,
|
||||
items_per_page: int,
|
||||
) -> list[Index]:
|
||||
sorted_items = sorted(items, key=lambda c: c.created, reverse=True)
|
||||
if not sorted_items:
|
||||
log.warning(
|
||||
"No content for %s index%s, generating empty page",
|
||||
kind,
|
||||
f" ({label})" if label else "",
|
||||
)
|
||||
return [Index(kind, [], 1, 1, label)]
|
||||
num_pages = ceil(len(sorted_items) / items_per_page)
|
||||
return [
|
||||
Index(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))
|
||||
for section, items in site.by_section.items():
|
||||
outputs.extend(make_index_pages(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))
|
||||
return outputs
|
||||
+5
-59
@@ -1,8 +1,6 @@
|
||||
import logging
|
||||
from typing import Any, cast
|
||||
from urllib.parse import urlparse
|
||||
from itertools import batched
|
||||
from math import ceil
|
||||
|
||||
from omicron.ssg.output import (
|
||||
Output,
|
||||
@@ -11,6 +9,7 @@ from omicron.ssg.output import (
|
||||
is_content,
|
||||
create_content,
|
||||
Memory,
|
||||
discover_index,
|
||||
)
|
||||
from omicron.ssg.markdown import highlight_style
|
||||
|
||||
@@ -118,51 +117,11 @@ class Site:
|
||||
self.add_by_tag(content)
|
||||
self.content.append(content)
|
||||
self.update_tags_by_count()
|
||||
for index in discover_index(self):
|
||||
self.add_by_uri(index)
|
||||
self.other_outputs.append(index)
|
||||
log.info("Discovered %d content items", len(self.content))
|
||||
|
||||
def render_single_index_page(
|
||||
self,
|
||||
out_dir: Path,
|
||||
items: tuple[Content, ...],
|
||||
page_num: int,
|
||||
total_pages: int,
|
||||
section: str | None,
|
||||
tag: str | None = None,
|
||||
) -> None:
|
||||
template = self.jinja_env.get_template("index.html")
|
||||
html = template.render(
|
||||
site=self,
|
||||
items=items,
|
||||
page_num=page_num,
|
||||
total_pages=total_pages,
|
||||
section=section,
|
||||
tag=tag,
|
||||
)
|
||||
out_file = out_dir / f"page-{page_num}.html"
|
||||
out_file.write_text(html, encoding="utf-8")
|
||||
|
||||
def render_index_pages(
|
||||
self,
|
||||
items: list[Content],
|
||||
out_dir: Path,
|
||||
section: str | None,
|
||||
tag: str | None = None,
|
||||
) -> None:
|
||||
out_dir.mkdir(parents=True, exist_ok=True)
|
||||
if not items:
|
||||
log.warning("No content, generating empty index page")
|
||||
self.render_single_index_page(out_dir, (), 1, 1, section, tag)
|
||||
else:
|
||||
num_pages = ceil(len(items) / self.items_per_page)
|
||||
for i, page_items in enumerate(batched(items, self.items_per_page)):
|
||||
self.render_single_index_page(
|
||||
out_dir, page_items, i + 1, num_pages, section, tag
|
||||
)
|
||||
index = out_dir / "index.html"
|
||||
if index.exists() or index.is_symlink():
|
||||
index.unlink()
|
||||
index.symlink_to("page-1.html")
|
||||
|
||||
def render_tags_page(self) -> None:
|
||||
template = self.jinja_env.get_template("tags.html")
|
||||
html = template.render(site=self, tags=self.tags_by_count)
|
||||
@@ -170,19 +129,6 @@ class Site:
|
||||
tags_dir.mkdir(parents=True, exist_ok=True)
|
||||
(tags_dir / "index.html").write_text(html, encoding="utf-8")
|
||||
|
||||
def render_indices(self) -> None:
|
||||
sorted_all = sorted(self.content, key=lambda c: c.created, reverse=True)
|
||||
self.render_index_pages(sorted_all, self.output_path, None)
|
||||
for section, items in self.by_section.items():
|
||||
sorted_items = sorted(items, key=lambda c: c.created, reverse=True)
|
||||
self.render_index_pages(sorted_items, self.output_path / section, section)
|
||||
for tag, items in self.by_tag.items():
|
||||
sorted_items = sorted(items, key=lambda c: c.created, reverse=True)
|
||||
self.render_index_pages(
|
||||
sorted_items, self.output_path / "tags" / tag, None, tag
|
||||
)
|
||||
self.render_tags_page()
|
||||
|
||||
def build(self) -> None:
|
||||
if not self.origin:
|
||||
log.warning(
|
||||
@@ -195,6 +141,6 @@ class Site:
|
||||
self.output_path.mkdir(parents=True, exist_ok=True)
|
||||
for content in self.content:
|
||||
content.write(self)
|
||||
self.render_indices()
|
||||
self.render_tags_page()
|
||||
for output in self.other_outputs:
|
||||
output.write(self)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{% extends "base.html" %}
|
||||
{% set prefix = (section + "/") if section else (("tags/" + tag + "/") if tag else "") %}
|
||||
{% set heading = section or tag or "" %}
|
||||
{% set prefix = (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 %}
|
||||
|
||||
@@ -10,16 +10,16 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block breadcrumb %}
|
||||
{% if section %}
|
||||
{% if kind == "section" %}
|
||||
<nav aria-label="breadcrumb">
|
||||
<a href="{{ site.base_dir }}">{{ site.name }}</a> ›
|
||||
{{ section }}
|
||||
{{ label }}
|
||||
</nav>
|
||||
{% elif tag %}
|
||||
{% elif kind == "tag" %}
|
||||
<nav aria-label="breadcrumb">
|
||||
<a href="{{ site.base_dir }}">{{ site.name }}</a> ›
|
||||
<a href="{{ site.base_dir }}tags/">Tags</a> ›
|
||||
{{ tag }}
|
||||
{{ label }}
|
||||
</nav>
|
||||
{% else %}
|
||||
{{ super() }}
|
||||
@@ -40,11 +40,11 @@
|
||||
{% if total_pages > 1 %}
|
||||
<nav>
|
||||
{% if page_num > 1 %}
|
||||
<a href="{{ site.base_dir }}{{ prefix }}page-{{ page_num - 1 }}.html">← Newer</a>
|
||||
<a href="{{ site.base_dir }}{{ prefix }}{{ "" if page_num - 1 == 1 else "page-" ~ (page_num - 1) ~ ".html" }}">← Newer</a>
|
||||
{% endif %}
|
||||
{% for p in range(1, total_pages + 1) %}
|
||||
{% if p == page_num %}<strong>{{ p }}</strong>
|
||||
{% else %}<a href="{{ site.base_dir }}{{ prefix }}page-{{ p }}.html">{{ p }}</a>
|
||||
{% else %}<a href="{{ site.base_dir }}{{ prefix }}{{ "" if p == 1 else "page-" ~ p ~ ".html" }}">{{ p }}</a>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% if page_num < total_pages %}
|
||||
|
||||
Reference in New Issue
Block a user