added packaged template resources

This commit is contained in:
Daniel Fichtinger 2025-06-27 21:48:23 -04:00
parent c4505502e8
commit ca58a0758c
13 changed files with 135 additions and 54 deletions

View file

@ -1,5 +1,6 @@
from rich import print from rich import print
from zona.models import Item, ItemType from zona.models import Item, ItemType
from zona.metadata import parse_metadata
from zona import markdown as zmd from zona import markdown as zmd
from zona.templates import Templater from zona.templates import Templater
from zona.layout import Layout, discover_layout from zona.layout import Layout, discover_layout
@ -38,7 +39,7 @@ class ZonaBuilder:
if path.name.endswith(".md") and not path.is_relative_to( if path.name.endswith(".md") and not path.is_relative_to(
layout.root / "content" / "static" layout.root / "content" / "static"
): ):
item.metadata, item.content = util.parse_metadata(path) item.metadata, item.content = parse_metadata(path)
item.type = ItemType.MARKDOWN item.type = ItemType.MARKDOWN
item.copy = False item.copy = False
name = destination.stem name = destination.stem

View file

@ -0,0 +1,34 @@
<!doctype html>
<html>
<head>
<title>{{ metadata.title }}</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta charset="UTF-8" />
<link
href="{{ metadata.style }}"
rel="stylesheet"
type="text/css"
media="all"
/>
</head>
<body>
<div id="container">
{% if header %}
<header id="header">
<center>
{{ header | safe }}
</center>
<hr>
</header>
{% endif %}
{% block content %}{% endblock %}
{% if footer %}
<footer id="footer">
<hr>
{{ footer | safe }}
</footer>
{% endif %}
</div>
</body>
</html>

View file

@ -0,0 +1 @@
The footer content.

View file

@ -0,0 +1,2 @@
- One
- Two

View file

@ -0,0 +1,6 @@
{% extends "base.html" %}
{% block content %}
<article>{{ content | safe }}</article>
{% endblock %}

View file

@ -1,6 +1,7 @@
from pathlib import Path from pathlib import Path
from dataclasses import dataclass, asdict from dataclasses import dataclass, asdict
from zona.config import ZonaConfig, find_config from zona.config import ZonaConfig, find_config
from zona import util
import yaml import yaml
@ -54,9 +55,19 @@ def initialize_site(root: Path | None = None):
raise FileExistsError(f"Config file already exists at {config}") raise FileExistsError(f"Config file already exists at {config}")
# create requires layout # create requires layout
layout = Layout.from_input(root=root, validate=False) layout = Layout.from_input(root=root, validate=False)
for dir in [layout.root, layout.content, layout.templates]: # load template resources
templates = util.get_resources("templates")
for dir, resources in [
(layout.root, None),
(layout.content, None),
(layout.templates, templates),
]:
if not dir.is_dir(): if not dir.is_dir():
dir.mkdir() dir.mkdir()
if resources is not None:
for r in resources:
Path(r.name).write_text(r.contents)
config_path = layout.root / "config.yml" config_path = layout.root / "config.yml"
config = ZonaConfig() config = ZonaConfig()
with open(config_path, "w") as f: with open(config_path, "w") as f:

View file

@ -14,8 +14,6 @@ from pygments.formatters import HtmlFormatter
from zona import util from zona import util
from zona.models import Item from zona.models import Item
# TODO: create Ashen pygments style (separate package probably)
class ZonaRenderer(HTMLRenderer): class ZonaRenderer(HTMLRenderer):
def __init__( def __init__(

51
src/zona/metadata.py Normal file
View file

@ -0,0 +1,51 @@
from dataclasses import dataclass
from pathlib import Path
from datetime import date
from dacite.config import Config
from dacite.core import from_dict
from dacite.exceptions import DaciteError
from yaml import YAMLError
import zona.util
import frontmatter
@dataclass
class Metadata:
title: str
date: date
description: str | None
style: str | None = "/static/style.css"
header: bool = True
footer: bool = True
template: str = "page.html"
def parse_metadata(path: Path) -> tuple[Metadata, str]:
"""
Parses a file and returns parsed Metadata and its content. Defaults
are applied for missing fields. If there is no metadata, a Metadata
with default values is returned.
Raises:
ValueError: If the metadata block is malformed in any way.
"""
try:
post = frontmatter.load(str(path))
except YAMLError as e:
raise ValueError(f"YAML frontmatter error in {path}: {e}")
raw_meta = post.metadata or {}
defaults = {
"title": zona.util.filename_to_title(path),
"date": date.fromtimestamp(path.stat().st_mtime),
}
meta = {**defaults, **raw_meta}
try:
metadata = from_dict(
data_class=Metadata,
data=meta,
config=Config(check_types=True, strict=True),
)
except DaciteError as e:
raise ValueError(f"Malformed metadata in {path}: {e}")
return metadata, post.content

View file

@ -1,20 +1,8 @@
from pathlib import Path from pathlib import Path
from enum import Enum from enum import Enum
from datetime import date from dataclasses import dataclass
from dataclasses import dataclass, field
from zona.layout import Layout from zona.metadata import Metadata
@dataclass
class Metadata:
title: str
date: date
description: str | None
style: str | None = "/static/style.css"
header: bool = True
footer: bool = True
template: str = "page.html"
class ItemType(Enum): class ItemType(Enum):

View file

@ -1,14 +1,28 @@
from datetime import date from typing import NamedTuple
from rich import print
from importlib import resources
import fnmatch import fnmatch
from pathlib import Path from pathlib import Path
from shutil import copy2 from shutil import copy2
import string import string
from dacite import Config, DaciteError, from_dict
import frontmatter
from yaml import YAMLError
from zona.models import Metadata class ZonaResource(NamedTuple):
name: str
contents: str
def get_resources(subdir: str) -> list[ZonaResource]:
"""Load the packaged resources in data/subdir"""
out: list[ZonaResource] = []
for resource in (
resources.files("zona").joinpath(f"data/{subdir}").iterdir()
):
out.append(
ZonaResource(f"{subdir}/{resource.name}", resource.read_text())
)
print(out)
return out
def ensure_parents(target: Path): def ensure_parents(target: Path):
@ -37,33 +51,3 @@ def normalize_url(url: str) -> str:
def should_ignore(path: Path, patterns: list[str], base: Path) -> bool: def should_ignore(path: Path, patterns: list[str], base: Path) -> bool:
rel_path = path.relative_to(base) rel_path = path.relative_to(base)
return any(fnmatch.fnmatch(str(rel_path), pattern) for pattern in patterns) return any(fnmatch.fnmatch(str(rel_path), pattern) for pattern in patterns)
def parse_metadata(path: Path) -> tuple[Metadata, str]:
"""
Parses a file and returns parsed Metadata and its content. Defaults
are applied for missing fields. If there is no metadata, a Metadata
with default values is returned.
Raises:
ValueError: If the metadata block is malformed in any way.
"""
try:
post = frontmatter.load(str(path))
except YAMLError as e:
raise ValueError(f"YAML frontmatter error in {path}: {e}")
raw_meta = post.metadata or {}
defaults = {
"title": filename_to_title(path),
"date": date.fromtimestamp(path.stat().st_mtime),
}
meta = {**defaults, **raw_meta}
try:
metadata = from_dict(
data_class=Metadata,
data=meta,
config=Config(check_types=True, strict=True),
)
except DaciteError as e:
raise ValueError(f"Malformed metadata in {path}: {e}")
return metadata, post.content

View file

@ -1,6 +1,6 @@
import pytest import pytest
from datetime import date from datetime import date
from zona.models import Metadata from zona.metadata import Metadata
from zona.builder import split_metadata, discover, build from zona.builder import split_metadata, discover, build
from pathlib import Path from pathlib import Path

View file

@ -7,3 +7,8 @@ def test_title(tmp_path: Path):
b = tmp_path / "Writing_emails_Post.md" b = tmp_path / "Writing_emails_Post.md"
assert util.filename_to_title(a) == "My First Post" assert util.filename_to_title(a) == "My First Post"
assert util.filename_to_title(b) == "Writing Emails Post" assert util.filename_to_title(b) == "Writing Emails Post"
def test_get_resourcees():
util.get_resources("templates")
assert True