feat: merge user templates with defaults
This commit is contained in:
parent
c489a6f076
commit
1f2b736815
5 changed files with 102 additions and 15 deletions
|
@ -30,6 +30,7 @@ class ZonaBuilder:
|
|||
self.config.build.include_drafts = True
|
||||
self.items: list[Item] = []
|
||||
self.item_map: dict[Path, Item] = {}
|
||||
self.fresh: bool = True
|
||||
|
||||
def _discover(self):
|
||||
layout = self.layout
|
||||
|
@ -48,7 +49,9 @@ class ZonaBuilder:
|
|||
destination=destination,
|
||||
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(
|
||||
layout.root / "content" / "static"
|
||||
):
|
||||
logger.debug(f"Parsing {path.name}.")
|
||||
|
@ -69,11 +72,13 @@ class ZonaBuilder:
|
|||
item.copy = False
|
||||
name = destination.stem
|
||||
if name == "index":
|
||||
item.destination = item.destination.with_suffix(
|
||||
".html"
|
||||
item.destination = (
|
||||
item.destination.with_suffix(".html")
|
||||
)
|
||||
else:
|
||||
relative = path.relative_to(base).with_suffix("")
|
||||
relative = path.relative_to(base).with_suffix(
|
||||
""
|
||||
)
|
||||
name = relative.stem
|
||||
item.destination = (
|
||||
layout.output
|
||||
|
@ -85,7 +90,9 @@ class ZonaBuilder:
|
|||
layout.output
|
||||
)
|
||||
item.url = (
|
||||
"" if rel_url == Path(".") else rel_url.as_posix()
|
||||
""
|
||||
if rel_url == Path(".")
|
||||
else rel_url.as_posix()
|
||||
)
|
||||
items.append(item)
|
||||
self.items = items
|
||||
|
@ -118,7 +125,9 @@ class ZonaBuilder:
|
|||
# write code highlighting stylesheet
|
||||
if self.config.markdown.syntax_highlighting.enabled:
|
||||
pygments_style = zmd.get_style_defs(self.config)
|
||||
pygments_path = self.layout.output / "static" / "pygments.css"
|
||||
pygments_path = (
|
||||
self.layout.output / "static" / "pygments.css"
|
||||
)
|
||||
util.ensure_parents(pygments_path)
|
||||
pygments_path.write_text(pygments_style)
|
||||
for item in self.item_map.values():
|
||||
|
@ -159,7 +168,10 @@ class ZonaBuilder:
|
|||
child.unlink()
|
||||
elif child.is_dir():
|
||||
shutil.rmtree(child)
|
||||
if not self.fresh:
|
||||
self.layout = self.layout.refresh()
|
||||
logger.debug("Discovering...")
|
||||
self._discover()
|
||||
logger.debug("Building...")
|
||||
self._build()
|
||||
self.fresh = False
|
||||
|
|
|
@ -16,6 +16,22 @@ class Layout:
|
|||
content: Path
|
||||
templates: Path
|
||||
output: Path
|
||||
shared_templates: util.TempDir | None
|
||||
_validate: bool
|
||||
|
||||
def refresh(self):
|
||||
logger.debug("Refreshing layout...")
|
||||
if (
|
||||
self.shared_templates
|
||||
and not self.shared_templates.removed
|
||||
):
|
||||
logger.debug("Removing stale templates tempdir...")
|
||||
self.shared_templates.remove()
|
||||
return self.__class__.from_input(
|
||||
root=self.root,
|
||||
output=self.output,
|
||||
validate=self._validate,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_input(
|
||||
|
@ -31,6 +47,8 @@ class Layout:
|
|||
output=(root / "public").resolve()
|
||||
if not output
|
||||
else output,
|
||||
shared_templates=None,
|
||||
_validate=validate,
|
||||
)
|
||||
if validate:
|
||||
logger.debug("Validating site layout...")
|
||||
|
@ -39,10 +57,29 @@ class Layout:
|
|||
raise FileNotFoundError(
|
||||
"Missing required content directory!"
|
||||
)
|
||||
if not layout.templates.is_dir():
|
||||
internal_templates = util.get_resource_dir("templates")
|
||||
user_templates = layout.templates
|
||||
if not user_templates.is_dir() or util.is_empty(
|
||||
user_templates
|
||||
):
|
||||
logger.debug("Using default template directory.")
|
||||
# use the included defaults
|
||||
layout.templates = util.get_resource_dir("templates")
|
||||
layout.templates = internal_templates
|
||||
else:
|
||||
seen: set[str] = set()
|
||||
temp = util.TempDir()
|
||||
logger.debug(
|
||||
f"Creating shared template directory at {temp}"
|
||||
)
|
||||
for f in user_templates.iterdir():
|
||||
if f.is_file():
|
||||
util.copy_static_file(f, temp.path)
|
||||
seen.add(f.name)
|
||||
for f in internal_templates.iterdir():
|
||||
if f.is_file() and f.name not in seen:
|
||||
util.copy_static_file(f, temp.path)
|
||||
layout.shared_templates = temp
|
||||
layout.templates = temp.path
|
||||
|
||||
return layout
|
||||
|
||||
|
|
|
@ -36,6 +36,7 @@ class Templater:
|
|||
template_dir: Path,
|
||||
post_list: list[Item],
|
||||
):
|
||||
# build temporary template dir
|
||||
self.env: Environment = Environment(
|
||||
loader=FileSystemLoader(template_dir),
|
||||
autoescape=select_autoescape(["html", "xml"]),
|
||||
|
@ -78,7 +79,9 @@ class Templater:
|
|||
header=header,
|
||||
footer=footer,
|
||||
is_post=item.post,
|
||||
next=util.normalize_url(item.next.url) if item.next else None,
|
||||
next=util.normalize_url(item.next.url)
|
||||
if item.next
|
||||
else None,
|
||||
previous=util.normalize_url(item.previous.url)
|
||||
if item.previous
|
||||
else None,
|
||||
|
|
|
@ -1,11 +1,36 @@
|
|||
import fnmatch
|
||||
import re
|
||||
import shutil
|
||||
import string
|
||||
import tempfile
|
||||
import weakref
|
||||
from importlib import resources
|
||||
from importlib.resources.abc import Traversable
|
||||
from pathlib import Path
|
||||
from shutil import copy2
|
||||
from typing import NamedTuple
|
||||
from typing import Any, NamedTuple, override
|
||||
|
||||
|
||||
class TempDir:
|
||||
"""Temporary directory that cleans up when it's garbage collected."""
|
||||
|
||||
def __init__(self):
|
||||
self._tempdir: str = tempfile.mkdtemp()
|
||||
self.path: Path = Path(self._tempdir)
|
||||
self._finalizer: weakref.finalize[Any, Any] = (
|
||||
weakref.finalize(self, shutil.rmtree, self._tempdir)
|
||||
)
|
||||
|
||||
def remove(self):
|
||||
self._finalizer()
|
||||
|
||||
@property
|
||||
def removed(self):
|
||||
return not self._finalizer.alive
|
||||
|
||||
@override
|
||||
def __repr__(self) -> str:
|
||||
return f"<TempDir {self.path}>"
|
||||
|
||||
|
||||
class ZonaResource(NamedTuple):
|
||||
|
@ -65,6 +90,15 @@ def copy_static_file(src: Path, dst: Path):
|
|||
copy2(src, dst)
|
||||
|
||||
|
||||
def is_empty(path: Path) -> bool:
|
||||
"""If given a file, check if it has any non-whitespace content.
|
||||
If given a directory, check if it has any children."""
|
||||
if path.is_file():
|
||||
return path.read_text().strip() == ""
|
||||
else:
|
||||
return not any(path.iterdir())
|
||||
|
||||
|
||||
def filename_to_title(path: Path) -> str:
|
||||
name = path.stem
|
||||
words = name.replace("-", " ").replace("_", " ")
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue