diff --git a/src/zona/builder.py b/src/zona/builder.py index 3f72992..e4891f2 100644 --- a/src/zona/builder.py +++ b/src/zona/builder.py @@ -1,5 +1,6 @@ from rich import print from zona.models import Item, ItemType +from zona.metadata import parse_metadata from zona import markdown as zmd from zona.templates import Templater from zona.layout import Layout, discover_layout @@ -38,7 +39,7 @@ class ZonaBuilder: if path.name.endswith(".md") and not path.is_relative_to( layout.root / "content" / "static" ): - item.metadata, item.content = util.parse_metadata(path) + item.metadata, item.content = parse_metadata(path) item.type = ItemType.MARKDOWN item.copy = False name = destination.stem diff --git a/src/zona/data/templates/base.html b/src/zona/data/templates/base.html index e69de29..87a8392 100644 --- a/src/zona/data/templates/base.html +++ b/src/zona/data/templates/base.html @@ -0,0 +1,34 @@ + + + + {{ metadata.title }} + + + + + +
+ {% if header %} + + {% endif %} + {% block content %}{% endblock %} + {% if footer %} + + {% endif %} +
+ + + diff --git a/src/zona/data/templates/footer.md b/src/zona/data/templates/footer.md new file mode 100644 index 0000000..653482d --- /dev/null +++ b/src/zona/data/templates/footer.md @@ -0,0 +1 @@ +The footer content. diff --git a/src/zona/data/templates/header.md b/src/zona/data/templates/header.md new file mode 100644 index 0000000..a0e3630 --- /dev/null +++ b/src/zona/data/templates/header.md @@ -0,0 +1,2 @@ +- One +- Two diff --git a/src/zona/data/templates/page.html b/src/zona/data/templates/page.html new file mode 100644 index 0000000..d568de1 --- /dev/null +++ b/src/zona/data/templates/page.html @@ -0,0 +1,6 @@ +{% extends "base.html" %} + +{% block content %} +
{{ content | safe }}
+{% endblock %} + diff --git a/src/zona/data/templates/post.html b/src/zona/data/templates/post.html deleted file mode 100644 index e69de29..0000000 diff --git a/src/zona/layout.py b/src/zona/layout.py index facce4b..f309dbd 100644 --- a/src/zona/layout.py +++ b/src/zona/layout.py @@ -1,6 +1,7 @@ from pathlib import Path from dataclasses import dataclass, asdict from zona.config import ZonaConfig, find_config +from zona import util import yaml @@ -54,9 +55,19 @@ def initialize_site(root: Path | None = None): raise FileExistsError(f"Config file already exists at {config}") # create requires layout 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(): 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 = ZonaConfig() with open(config_path, "w") as f: diff --git a/src/zona/markdown.py b/src/zona/markdown.py index e9d5cb1..87ea02a 100644 --- a/src/zona/markdown.py +++ b/src/zona/markdown.py @@ -14,8 +14,6 @@ from pygments.formatters import HtmlFormatter from zona import util from zona.models import Item -# TODO: create Ashen pygments style (separate package probably) - class ZonaRenderer(HTMLRenderer): def __init__( diff --git a/src/zona/metadata.py b/src/zona/metadata.py new file mode 100644 index 0000000..7b722dc --- /dev/null +++ b/src/zona/metadata.py @@ -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 diff --git a/src/zona/models.py b/src/zona/models.py index faf402d..5c6b0ab 100644 --- a/src/zona/models.py +++ b/src/zona/models.py @@ -1,20 +1,8 @@ from pathlib import Path from enum import Enum -from datetime import date -from dataclasses import dataclass, field +from dataclasses import dataclass -from zona.layout import Layout - - -@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" +from zona.metadata import Metadata class ItemType(Enum): diff --git a/src/zona/util.py b/src/zona/util.py index c67a92a..3244249 100644 --- a/src/zona/util.py +++ b/src/zona/util.py @@ -1,14 +1,28 @@ -from datetime import date +from typing import NamedTuple +from rich import print +from importlib import resources import fnmatch from pathlib import Path from shutil import copy2 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): @@ -37,33 +51,3 @@ def normalize_url(url: str) -> str: def should_ignore(path: Path, patterns: list[str], base: Path) -> bool: rel_path = path.relative_to(base) 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 diff --git a/tests/test_builder.py b/tests/test_builder.py index cce7cd7..61e148e 100644 --- a/tests/test_builder.py +++ b/tests/test_builder.py @@ -1,6 +1,6 @@ import pytest from datetime import date -from zona.models import Metadata +from zona.metadata import Metadata from zona.builder import split_metadata, discover, build from pathlib import Path diff --git a/tests/test_util.py b/tests/test_util.py index a32e99b..2837df4 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -7,3 +7,8 @@ def test_title(tmp_path: Path): b = tmp_path / "Writing_emails_Post.md" assert util.filename_to_title(a) == "My First Post" assert util.filename_to_title(b) == "Writing Emails Post" + + +def test_get_resourcees(): + util.get_resources("templates") + assert True