diff --git a/src/zona/builder.py b/src/zona/builder.py index de41e15..8e48a69 100644 --- a/src/zona/builder.py +++ b/src/zona/builder.py @@ -2,6 +2,7 @@ from rich import print from zona.models import Item, Metadata, ItemType, BuildCtx from zona import markdown as zmd from zona.templates import Templater +from zona.layout import Layout from zona import util from pathlib import Path import frontmatter @@ -40,28 +41,21 @@ def split_metadata(path: Path) -> tuple[Metadata, str]: return metadata, post.content -def discover(root: Path, out_dir: Path) -> list[Item]: - required_dirs = {"content", "templates"} - found_dirs = {p.name for p in root.iterdir() if p.is_dir()} - missing = required_dirs - found_dirs - assert not missing, f"Missing required directories: {', '.join(missing)}" - +def discover(layout: Layout) -> list[Item]: items: list[Item] = [] - subdir = "content" - base = root / subdir + base = layout.root / layout.content for path in base.rglob("*"): if path.is_file(): - print(f"{subdir}: {path.relative_to(root)}") # we only parse markdown files not in static/ - destination = out_dir / path.relative_to(base) + destination = layout.output / path.relative_to(base) item = Item( source=path, destination=destination, - url=str(destination.relative_to(out_dir)), + url=str(destination.relative_to(layout.output)), ) if path.name.endswith(".md") and not path.is_relative_to( - root / "content" / "static" + layout.root / "content" / "static" ): item.metadata, item.content = split_metadata(path) item.type = ItemType.MARKDOWN @@ -73,17 +67,17 @@ def discover(root: Path, out_dir: Path) -> list[Item]: relative = path.relative_to(base).with_suffix("") name = relative.stem item.destination = ( - out_dir / relative.parent / name / "index.html" + layout.output / relative.parent / name / "index.html" ) - rel_url = item.destination.parent.relative_to(out_dir) + rel_url = item.destination.parent.relative_to(layout.output) item.url = "" if rel_url == Path(".") else rel_url.as_posix() items.append(item) return items -def build(root: Path, items: list[Item]): - ctx = BuildCtx(root=root, base=root / "content") - templater = Templater(root / "templates") +def build(layout: Layout, items: list[Item]): + ctx = BuildCtx(layout) + templater = Templater(layout.templates) ctx.item_map = {item.source.resolve(): item for item in items} # print(item_map) for item in ctx.item_map.values(): diff --git a/src/zona/cli.py b/src/zona/cli.py index ac483ea..ca88713 100644 --- a/src/zona/cli.py +++ b/src/zona/cli.py @@ -1,18 +1,16 @@ import typer from pathlib import Path from zona import builder, server +from zona.layout import Layout, discover_layout app = typer.Typer() @app.command() -def build(in_dir: str | None = None, out_dir: str | None = None): - if in_dir is None: - in_dir = "." - if out_dir is None: - out_dir = "public" - items = builder.discover(Path(in_dir), Path(out_dir)) - builder.build(Path(in_dir), items) +def build(root: Path | None = None, output: Path | None = None): + layout: Layout = discover_layout(root, output) + items = builder.discover(layout) + builder.build(layout, items) @app.command() diff --git a/src/zona/layout.py b/src/zona/layout.py new file mode 100644 index 0000000..9bd8c92 --- /dev/null +++ b/src/zona/layout.py @@ -0,0 +1,35 @@ +from pathlib import Path +from dataclasses import dataclass + + +@dataclass +class Layout: + root: Path + content: Path + templates: Path + output: Path + + @classmethod + def from_input(cls, root: Path, output: Path | None = None) -> "Layout": + layout = cls( + root=root.resolve(), + content=(root / "content").resolve(), + templates=(root / "templates").resolve(), + output=(root / "public").resolve() if not output else output, + ) + for path in [layout.content, layout.templates]: + if not path.is_dir(): + raise FileNotFoundError(f"Missing required directory: {path}") + return layout + + +def discover_layout( + cli_root: Path | None = None, cli_output: Path | None = None +) -> Layout: + if cli_root: + root = cli_root + else: + # TODO: config based discovery + # We just set cwd for now + root = Path(".") + return Layout.from_input(root, cli_output) diff --git a/src/zona/markdown.py b/src/zona/markdown.py index 38afbd6..d31a4ef 100644 --- a/src/zona/markdown.py +++ b/src/zona/markdown.py @@ -35,7 +35,9 @@ class ZonaRenderer(HTMLRenderer): _href = href if href.startswith("/"): # resolve relative to content root - resolved = (self.ctx.base / cur.relative_to("/")).resolve() + resolved = ( + self.ctx.layout.content / cur.relative_to("/") + ).resolve() else: # treat as relative link and try to resolve resolved = (self.source.parent / cur).resolve() diff --git a/src/zona/models.py b/src/zona/models.py index ee1fdff..4c703ed 100644 --- a/src/zona/models.py +++ b/src/zona/models.py @@ -3,6 +3,8 @@ from enum import Enum from datetime import date from dataclasses import dataclass, field +from zona.layout import Layout + @dataclass class Metadata: @@ -34,6 +36,5 @@ class Item: @dataclass class BuildCtx: - root: Path - base: Path + layout: Layout item_map: dict[Path, Item] = field(default_factory=dict)