add header/footer template

This commit is contained in:
Daniel Fichtinger 2025-06-21 22:57:23 -04:00
parent d8b491bc33
commit 070c23720b
6 changed files with 54 additions and 17 deletions

View file

@ -12,6 +12,7 @@ dependencies = [
"jinja2>=3.1.6", "jinja2>=3.1.6",
"marko[codehilite]>=2.1.4", "marko[codehilite]>=2.1.4",
"python-frontmatter>=1.1.0", "python-frontmatter>=1.1.0",
"rich>=14.0.0",
"starlette>=0.47.1", "starlette>=0.47.1",
"typer>=0.16.0", "typer>=0.16.0",
"uvicorn>=0.34.3", "uvicorn>=0.34.3",

View file

@ -1,6 +1,7 @@
from rich import print
from zona.models import Item, Metadata, ItemType from zona.models import Item, Metadata, ItemType
from zona import markdown as zmd from zona import markdown as zmd
from zona import templates as tmpl from zona.templates import Templater
from zona import util from zona import util
from pathlib import Path from pathlib import Path
import frontmatter import frontmatter
@ -74,21 +75,24 @@ def discover(root: Path, out_dir: Path) -> list[Item]:
item.destination = ( item.destination = (
out_dir / relative.parent / name / "index.html" out_dir / relative.parent / name / "index.html"
) )
rel_url = item.destination.parent.relative_to(out_dir)
item.url = "" if rel_url == Path(".") else rel_url.as_posix()
items.append(item) items.append(item)
return items return items
def build(root: Path, items: list[Item]): def build(root: Path, items: list[Item]):
env = tmpl.init_templates(root / "templates") templater = Templater(root / "templates")
for item in items: for item in items:
dst = item.destination dst = item.destination
print(item)
# create parent dirs if needed # create parent dirs if needed
if item.type == ItemType.MARKDOWN: if item.type == ItemType.MARKDOWN:
assert item.content is not None assert item.content is not None
# parse markdown and render as html # parse markdown and render as html
raw_html = zmd.md_to_html(item.content) raw_html = zmd.md_to_html(item.content)
# TODO: test this # TODO: test this
rendered = tmpl.render_item(item, raw_html, env) rendered = templater.render_item(item, raw_html)
util.ensure_parents(dst) util.ensure_parents(dst)
dst.write_text(rendered, encoding="utf-8") dst.write_text(rendered, encoding="utf-8")
else: else:

View file

@ -9,9 +9,10 @@ class Metadata:
title: str title: str
date: date date: date
description: str | None description: str | None
style: str | None = "static/style.css" style: str | None = "/static/style.css"
header: bool = True header: bool = True
footer: bool = True footer: bool = True
template: str = "page.html"
class ItemType(Enum): class ItemType(Enum):

View file

@ -1,19 +1,48 @@
from pathlib import Path from pathlib import Path
from jinja2 import Environment, FileSystemLoader, select_autoescape from jinja2 import Environment, FileSystemLoader, select_autoescape
from zona.models import Item from zona.models import Item
from zona.markdown import md_to_html
def init_templates(template_dir: Path) -> Environment: def get_header(template_dir: Path) -> str | None:
return Environment( md_header = template_dir / "header.md"
loader=FileSystemLoader(template_dir), html_header = template_dir / "header.html"
autoescape=select_autoescape(["html", "xml"]), if md_header.exists():
) return md_to_html(md_header.read_text())
elif html_header.exists():
return html_header.read_text()
def render_item(item: Item, content: str, env: Environment) -> str: def get_footer(template_dir: Path) -> str | None:
template = env.get_template("post.html") md_footer = template_dir / "footer.md"
meta = item.metadata html_footer = template_dir / "footer.html"
assert meta is not None if md_footer.exists():
return template.render( return md_to_html(md_footer.read_text())
title=meta.title, content=content, url=item.url, metadata=meta elif html_footer.exists():
) return html_footer.read_text()
class Templater:
def __init__(self, template_dir: Path):
self.env: Environment = Environment(
loader=FileSystemLoader(template_dir),
autoescape=select_autoescape(["html", "xml"]),
)
self.template_dir: Path = template_dir
self.header: str | None = get_header(template_dir)
self.footer: str | None = get_footer(template_dir)
def render_item(self, item: Item, content: str) -> str:
env = self.env
meta = item.metadata
assert meta is not None
template = env.get_template(meta.template)
header = self.header if self.header and meta.header else False
footer = self.footer if self.footer and meta.footer else False
return template.render(
content=content,
url=item.url,
metadata=meta,
header=header,
footer=footer,
)

View file

@ -147,7 +147,7 @@ p {
style.write_text(style_content) style.write_text(style_content)
items = discover(tmp_path, outd) items = discover(tmp_path, outd)
build(items) build(tmp_path, items)
html = (outd / "post" / "index.html").read_text() html = (outd / "post" / "index.html").read_text()
assert html.strip() == "<h1>Hello World</h1>" assert html.strip() == "<h1>Hello World</h1>"
s = (outd / "static" / "style.css").read_text() s = (outd / "static" / "style.css").read_text()

2
uv.lock generated
View file

@ -382,6 +382,7 @@ dependencies = [
{ name = "jinja2" }, { name = "jinja2" },
{ name = "marko", extra = ["codehilite"] }, { name = "marko", extra = ["codehilite"] },
{ name = "python-frontmatter" }, { name = "python-frontmatter" },
{ name = "rich" },
{ name = "starlette" }, { name = "starlette" },
{ name = "typer" }, { name = "typer" },
{ name = "uvicorn" }, { name = "uvicorn" },
@ -400,6 +401,7 @@ requires-dist = [
{ name = "jinja2", specifier = ">=3.1.6" }, { name = "jinja2", specifier = ">=3.1.6" },
{ name = "marko", extras = ["codehilite"], specifier = ">=2.1.4" }, { name = "marko", extras = ["codehilite"], specifier = ">=2.1.4" },
{ name = "python-frontmatter", specifier = ">=1.1.0" }, { name = "python-frontmatter", specifier = ">=1.1.0" },
{ name = "rich", specifier = ">=14.0.0" },
{ name = "starlette", specifier = ">=0.47.1" }, { name = "starlette", specifier = ">=0.47.1" },
{ name = "typer", specifier = ">=0.16.0" }, { name = "typer", specifier = ">=0.16.0" },
{ name = "uvicorn", specifier = ">=0.34.3" }, { name = "uvicorn", specifier = ">=0.34.3" },