improved layout handling
This commit is contained in:
parent
c46f882191
commit
35ed79490a
5 changed files with 57 additions and 27 deletions
|
@ -2,6 +2,7 @@ from rich import print
|
||||||
from zona.models import Item, Metadata, ItemType, BuildCtx
|
from zona.models import Item, Metadata, ItemType, BuildCtx
|
||||||
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
|
||||||
from zona import util
|
from zona import util
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import frontmatter
|
import frontmatter
|
||||||
|
@ -40,28 +41,21 @@ def split_metadata(path: Path) -> tuple[Metadata, str]:
|
||||||
return metadata, post.content
|
return metadata, post.content
|
||||||
|
|
||||||
|
|
||||||
def discover(root: Path, out_dir: Path) -> list[Item]:
|
def discover(layout: Layout) -> 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)}"
|
|
||||||
|
|
||||||
items: list[Item] = []
|
items: list[Item] = []
|
||||||
|
|
||||||
subdir = "content"
|
base = layout.root / layout.content
|
||||||
base = root / subdir
|
|
||||||
for path in base.rglob("*"):
|
for path in base.rglob("*"):
|
||||||
if path.is_file():
|
if path.is_file():
|
||||||
print(f"{subdir}: {path.relative_to(root)}")
|
|
||||||
# we only parse markdown files not in static/
|
# we only parse markdown files not in static/
|
||||||
destination = out_dir / path.relative_to(base)
|
destination = layout.output / path.relative_to(base)
|
||||||
item = Item(
|
item = Item(
|
||||||
source=path,
|
source=path,
|
||||||
destination=destination,
|
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(
|
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.metadata, item.content = split_metadata(path)
|
||||||
item.type = ItemType.MARKDOWN
|
item.type = ItemType.MARKDOWN
|
||||||
|
@ -73,17 +67,17 @@ def discover(root: Path, out_dir: Path) -> list[Item]:
|
||||||
relative = path.relative_to(base).with_suffix("")
|
relative = path.relative_to(base).with_suffix("")
|
||||||
name = relative.stem
|
name = relative.stem
|
||||||
item.destination = (
|
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()
|
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(layout: Layout, items: list[Item]):
|
||||||
ctx = BuildCtx(root=root, base=root / "content")
|
ctx = BuildCtx(layout)
|
||||||
templater = Templater(root / "templates")
|
templater = Templater(layout.templates)
|
||||||
ctx.item_map = {item.source.resolve(): item for item in items}
|
ctx.item_map = {item.source.resolve(): item for item in items}
|
||||||
# print(item_map)
|
# print(item_map)
|
||||||
for item in ctx.item_map.values():
|
for item in ctx.item_map.values():
|
||||||
|
|
|
@ -1,18 +1,16 @@
|
||||||
import typer
|
import typer
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from zona import builder, server
|
from zona import builder, server
|
||||||
|
from zona.layout import Layout, discover_layout
|
||||||
|
|
||||||
app = typer.Typer()
|
app = typer.Typer()
|
||||||
|
|
||||||
|
|
||||||
@app.command()
|
@app.command()
|
||||||
def build(in_dir: str | None = None, out_dir: str | None = None):
|
def build(root: Path | None = None, output: Path | None = None):
|
||||||
if in_dir is None:
|
layout: Layout = discover_layout(root, output)
|
||||||
in_dir = "."
|
items = builder.discover(layout)
|
||||||
if out_dir is None:
|
builder.build(layout, items)
|
||||||
out_dir = "public"
|
|
||||||
items = builder.discover(Path(in_dir), Path(out_dir))
|
|
||||||
builder.build(Path(in_dir), items)
|
|
||||||
|
|
||||||
|
|
||||||
@app.command()
|
@app.command()
|
||||||
|
|
35
src/zona/layout.py
Normal file
35
src/zona/layout.py
Normal file
|
@ -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)
|
|
@ -35,7 +35,9 @@ class ZonaRenderer(HTMLRenderer):
|
||||||
_href = href
|
_href = href
|
||||||
if href.startswith("/"):
|
if href.startswith("/"):
|
||||||
# resolve relative to content root
|
# resolve relative to content root
|
||||||
resolved = (self.ctx.base / cur.relative_to("/")).resolve()
|
resolved = (
|
||||||
|
self.ctx.layout.content / cur.relative_to("/")
|
||||||
|
).resolve()
|
||||||
else:
|
else:
|
||||||
# treat as relative link and try to resolve
|
# treat as relative link and try to resolve
|
||||||
resolved = (self.source.parent / cur).resolve()
|
resolved = (self.source.parent / cur).resolve()
|
||||||
|
|
|
@ -3,6 +3,8 @@ from enum import Enum
|
||||||
from datetime import date
|
from datetime import date
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
|
|
||||||
|
from zona.layout import Layout
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Metadata:
|
class Metadata:
|
||||||
|
@ -34,6 +36,5 @@ class Item:
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class BuildCtx:
|
class BuildCtx:
|
||||||
root: Path
|
layout: Layout
|
||||||
base: Path
|
|
||||||
item_map: dict[Path, Item] = field(default_factory=dict)
|
item_map: dict[Path, Item] = field(default_factory=dict)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue