Add rendering of indices for the entire site and each section
This commit is contained in:
+77
-1
@@ -1,5 +1,8 @@
|
|||||||
import logging
|
import logging
|
||||||
from typing import Any, cast
|
from typing import Any, cast
|
||||||
|
from urllib.parse import urlparse
|
||||||
|
from itertools import batched
|
||||||
|
from math import ceil
|
||||||
|
|
||||||
from omicron.ssg.content import (
|
from omicron.ssg.content import (
|
||||||
Content,
|
Content,
|
||||||
@@ -11,7 +14,6 @@ from omicron.ssg.content import (
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import yaml
|
import yaml
|
||||||
from jinja2 import Environment, FileSystemLoader
|
from jinja2 import Environment, FileSystemLoader
|
||||||
from pygments.formatters import HtmlFormatter
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -31,6 +33,8 @@ class Site:
|
|||||||
config = self.read_config()
|
config = self.read_config()
|
||||||
self.name: str = config["name"]
|
self.name: str = config["name"]
|
||||||
self.template: str = config["template"]
|
self.template: str = config["template"]
|
||||||
|
self.items_per_page: int = int(config.get("items_per_page", 20))
|
||||||
|
self.origin, self.base_dir = Site.parse_base_uri(config.get("base_uri"))
|
||||||
self.template_path: Path = Site.resolve_template_path(self.template)
|
self.template_path: Path = Site.resolve_template_path(self.template)
|
||||||
self.output_path: Path = self.site_path / "output"
|
self.output_path: Path = self.site_path / "output"
|
||||||
self.jinja_env = Environment(loader=FileSystemLoader(self.template_path))
|
self.jinja_env = Environment(loader=FileSystemLoader(self.template_path))
|
||||||
@@ -48,6 +52,23 @@ class Site:
|
|||||||
raise ConfigError("template value missing from config")
|
raise ConfigError("template value missing from config")
|
||||||
return cast(dict[str, Any], config)
|
return cast(dict[str, Any], config)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def parse_base_uri(value: str | None) -> tuple[str, str]:
|
||||||
|
if not value:
|
||||||
|
return ("", "/")
|
||||||
|
parsed = urlparse(value)
|
||||||
|
if parsed.scheme and parsed.netloc:
|
||||||
|
origin = f"{parsed.scheme}://{parsed.netloc}"
|
||||||
|
elif parsed.netloc:
|
||||||
|
origin = f"//{parsed.netloc}"
|
||||||
|
else:
|
||||||
|
origin = ""
|
||||||
|
|
||||||
|
path = parsed.path
|
||||||
|
if not path.endswith("/"):
|
||||||
|
path += "/"
|
||||||
|
return (origin, path)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def resolve_template_path(template: str) -> Path:
|
def resolve_template_path(template: str) -> Path:
|
||||||
path = Path(__file__).parent / "templates" / template
|
path = Path(__file__).parent / "templates" / template
|
||||||
@@ -87,7 +108,61 @@ class Site:
|
|||||||
self.content.append(content)
|
self.content.append(content)
|
||||||
log.info("Discovered %d content items", len(self.content))
|
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,
|
||||||
|
) -> 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,
|
||||||
|
)
|
||||||
|
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,
|
||||||
|
) -> 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)
|
||||||
|
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
|
||||||
|
)
|
||||||
|
index = out_dir / "index.html"
|
||||||
|
if index.exists() or index.is_symlink():
|
||||||
|
index.unlink()
|
||||||
|
index.symlink_to("page-1.html")
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
def build(self) -> None:
|
def build(self) -> None:
|
||||||
|
if not self.origin:
|
||||||
|
log.warning(
|
||||||
|
"base_uri config value is missing a domain name, "
|
||||||
|
"can't add canonical uri to content"
|
||||||
|
)
|
||||||
|
if not self.base_dir.startswith("/"):
|
||||||
|
raise ConfigError("base_uri config value must be an absolute path")
|
||||||
self.discover()
|
self.discover()
|
||||||
self.output_path.mkdir(parents=True, exist_ok=True)
|
self.output_path.mkdir(parents=True, exist_ok=True)
|
||||||
(self.output_path / "pygments.css").write_text(
|
(self.output_path / "pygments.css").write_text(
|
||||||
@@ -95,3 +170,4 @@ class Site:
|
|||||||
)
|
)
|
||||||
for content in self.content:
|
for content in self.content:
|
||||||
content.render(self)
|
content.render(self)
|
||||||
|
self.render_indices()
|
||||||
|
|||||||
@@ -4,11 +4,11 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>{% block title %}{{ site.name }}{% endblock %}</title>
|
<title>{% block title %}{{ site.name }}{% endblock %}</title>
|
||||||
<link rel="stylesheet" href="/pygments.css">
|
<link rel="stylesheet" href="{{ site.base_dir }}pygments.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<header>
|
<header>
|
||||||
<a href="/">{{ site.name }}</a>
|
<a href="{{ site.base_dir }}">{{ site.name }}</a>
|
||||||
</header>
|
</header>
|
||||||
<main>
|
<main>
|
||||||
{% block main %}{% endblock %}
|
{% block main %}{% endblock %}
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block title %}{% if section %}{{ section }} — {% endif %}{{ site.name }}{% if page_num > 1 %} — Page {{ page_num }}{% endif %}{% endblock %}
|
||||||
|
|
||||||
|
{% block main %}
|
||||||
|
{% if section %}<h1>{{ section }}</h1>{% endif %}
|
||||||
|
{% for item in items %}
|
||||||
|
<article>
|
||||||
|
<header>
|
||||||
|
<h2><a href="{{ site.base_dir }}{{ item.uri }}">{{ item.title }}</a></h2>
|
||||||
|
<time datetime="{{ item.created }}">{{ item.created }}</time>
|
||||||
|
</header>
|
||||||
|
{% if item.summary %}<p>{{ item.summary }}</p>{% endif %}
|
||||||
|
</article>
|
||||||
|
{% endfor %}
|
||||||
|
{% if total_pages > 1 %}
|
||||||
|
<nav>
|
||||||
|
{% if page_num > 1 %}
|
||||||
|
<a href="{{ site.base_dir }}{% if section %}{{ section }}/{% endif %}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 }}{% if section %}{{ section }}/{% endif %}page-{{ p }}.html">{{ p }}</a>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
{% if page_num < total_pages %}
|
||||||
|
<a href="{{ site.base_dir }}{% if section %}{{ section }}/{% endif %}page-{{ page_num + 1 }}.html">Older →</a>
|
||||||
|
{% endif %}
|
||||||
|
</nav>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
Reference in New Issue
Block a user