Compare commits

..

No commits in common. "b028477371d815402979829d112916353000c7ec" and "8591ae53fa82d76cbf31321d5ce7409018b3637b" have entirely different histories.

45 changed files with 300 additions and 486 deletions

3
.gitignore vendored
View File

@ -1,4 +1,3 @@
**/__pycache__ **/__pycache__
.coverage .coverage
jean_web.egg-info jean_website.egg-info
tests/**/*.mo

View File

@ -1,28 +0,0 @@
from pathlib import Path
from typing import Generic, TypeVar
TData = TypeVar("TData")
class Content(Generic[TData]):
def __init__(self, path: Path, data: TData, language: str | None = None) -> None:
self.__path = path
self.__data = data
self.__language = language
@property
def path(self) -> Path:
return self.__path
@property
def language(self) -> str | None:
return self.__language
@property
def data(self) -> TData:
return self.__data
def __str__(self) -> str:
if isinstance(self.__data, bytes):
return self.__data.decode("utf-8")
return str(self.__data)

View File

@ -1,120 +0,0 @@
import gettext
from contextlib import contextmanager
from gettext import GNUTranslations, NullTranslations
from importlib import import_module
from pathlib import Path
from typing import Any, Iterator
from jinja2 import pass_context
from jinja2.environment import Environment
from jinja2.loaders import FileSystemLoader
from jinja2.runtime import Context as JinjaContext
from jweb.content import Content
_DEFAULT_LANGUAGE = "en"
class Context:
def __init__(self, root_directory: Path) -> None:
self.__root_directory = root_directory
self.__output_directory = root_directory / "build"
self.__content_directory = root_directory / "content"
self.__environment = Environment(loader=FileSystemLoader(searchpath=root_directory / "src"))
self.__translations: dict[str, GNUTranslations | NullTranslations] = {}
self.__current_language: str | None = None
self.add_filters(load=self.__load, write=self.__write, glob=self.__glob)
self.add_globals(load=self.__load, write=self.__write, glob=self.__glob)
@property
def current_language(self) -> str | None:
return self.__current_language
def add_filters(self, **filters: Any) -> None:
self.__environment.filters.update(**filters)
def add_globals(self, **globals: Any) -> None:
self.__environment.globals.update(**globals)
def load_extensions(self, *extensions: str) -> None:
for extension in extensions:
module = import_module(extension)
module.load_extension(self)
def load_translations(self, domain: str, locale_dir: str | Path, *languages: str) -> None:
locale_dir = str(locale_dir)
self.__environment.add_extension("jinja2.ext.i18n")
self.__translations[_DEFAULT_LANGUAGE] = NullTranslations()
for language in languages:
self.__translations[language] = gettext.translation(
domain, localedir=str(locale_dir), languages=[language]
)
@contextmanager
def set_language(self, language: str) -> Iterator[None]:
translation = self.__translations[language]
old_language = self.__current_language
self.__environment.install_gettext_translations(translation, newstyle=True) # type: ignore
self.__current_language = language
yield
self.__current_language = old_language
self.__environment.uninstall_gettext_translations(translation) # type: ignore
def render(self, source: str, output: str | Path, **context: Any) -> None:
if self.__translations:
for language in self.__translations:
with self.set_language(language):
self.__render(source, self.__output_directory / language / output, **context)
else:
self.__render(source, self.__output_directory / output, **context)
@pass_context
def __load(self, context: JinjaContext, path: str | Path) -> Content[bytes]:
current_language = self.current_language
if current_language:
localized_path = self.__content_directory / current_language / path
if not localized_path.exists():
localized_path = self.__content_directory / _DEFAULT_LANGUAGE / path
else:
localized_path = self.__content_directory / path
with localized_path.open("rb") as content_file:
return Content(localized_path, content_file.read(), current_language)
@pass_context
def __glob(
self, context: JinjaContext, pattern: str, include_base_language: bool = False
) -> Iterator[Content[bytes]]:
roots: list[Path] = []
current_language = self.current_language
if current_language is None:
roots.append(self.__content_directory)
else:
if include_base_language:
roots.append(self.__content_directory / _DEFAULT_LANGUAGE)
roots.append(self.__content_directory / current_language)
paths: set[Path] = set()
for root in roots:
paths = paths | set(it.relative_to(root) for it in root.glob(pattern))
for path in paths:
yield self.__load(context, path)
def __write(self, content: Content[bytes]) -> Path:
relative_path = content.path.relative_to(self.__content_directory)
output_path = self.__output_directory / relative_path
output_path.parent.mkdir(parents=True, exist_ok=True)
with output_path.open("wb") as output_file:
output_file.write(content.data)
return Path(f"/{relative_path}")
def __render(self, source: str, output_path: Path, **context: Any) -> None:
output_path.parent.mkdir(exist_ok=True, parents=True)
template = self.__environment.get_template(source)
content = template.render(**context)
with open(output_path, "w") as output_file:
output_file.write(content)

View File

@ -1,31 +0,0 @@
from datetime import datetime
from subprocess import check_output
from typing import Any
from jweb.context import Context
def load_extension(context: Context) -> None:
context.add_filters(git_creation_date=_git_creation_date)
def _git_creation_date(content: Any) -> datetime:
git_dir = check_output(["git", "rev-parse", "--show-toplevel"], encoding="utf-8")
git_dir = git_dir.strip()
log = check_output(
[
"git",
"log",
"--pretty=format:%ad",
"--date=iso-strict",
"--diff-filter=A",
"--",
str(content.path.relative_to(git_dir)),
],
encoding="utf-8",
)
log_lines = log.splitlines()
if len(log_lines) == 0:
return datetime.now()
return datetime.fromisoformat(log_lines[0])

View File

@ -1,30 +0,0 @@
from typing import Any
from markdown import Markdown
from jweb.content import Content
from jweb.context import Context
def load_extension(context: Context) -> None:
context.add_filters(markdown=_MarkdownDocument)
class _MarkdownDocument(Content[str]):
def __init__(self, content: Content[Any]) -> None:
if not isinstance(content, Content) or not isinstance(content.data, (str, bytes)):
raise ValueError("markdown filter can only accept byte or string content")
data = content.data
if isinstance(data, bytes):
data = data.decode("utf-8")
assert isinstance(data, str)
self.__content = content
self.__markdown = Markdown(extensions=["full_yaml_metadata"])
super().__init__(content.path, self.__markdown.convert(data), content.language)
@property
def meta(self) -> Any:
return self.__markdown.Meta # type: ignore

View File

@ -1,23 +0,0 @@
from typing import Any
from yaml import Loader, load
from jweb.content import Content
from jweb.context import Context
def load_extension(context: Context) -> None:
context.add_filters(yaml=_load_yaml)
def _load_yaml(content: Any) -> Any:
if not isinstance(content, Content) or not isinstance(content.data, (str, bytes)):
raise ValueError("yaml filter can only accept byte or string content")
data = content.data
if isinstance(data, bytes):
data = data.decode("utf-8")
assert isinstance(data, str)
return load(data, Loader)

View File

@ -2,7 +2,7 @@ from pathlib import Path
from click import group from click import group
from jweb.context import Context from jwebsite.site import Site
@group @group
@ -13,4 +13,4 @@ def main() -> None: ...
def build() -> None: def build() -> None:
cwd = Path.cwd() cwd = Path.cwd()
with open(cwd / "site.py", encoding="utf-8") as site_config: with open(cwd / "site.py", encoding="utf-8") as site_config:
exec(site_config.read(), {"site": Context(cwd)}) exec(site_config.read(), {"site": Site(cwd)})

114
jwebsite/content.py Normal file
View File

@ -0,0 +1,114 @@
from functools import cache
from pathlib import Path
from typing import Any, Iterable, Iterator
from markdown import Markdown
from yaml import Loader, load
class Content:
current_language: str | None = None
def __init__(self, path: Path) -> None:
self.__path = path
@property
def path(self) -> Path:
return self.__path
class ContentDirectory(Content):
def load(self, subpath: str | Path) -> Content:
subpath = Path(subpath)
current: Content = self
for part in subpath.parts:
if not isinstance(current, ContentDirectory):
raise NotADirectoryError(self.path)
current = current.__load_children(part)
return current
def glob(self, pattern: str) -> Iterable[Content]:
for item in self.path.glob(pattern):
yield self.load(str(item.relative_to(self.path)))
def __load_children(self, name: str) -> Content:
child_path = self.__get_localized_path(self.path / name)
return self.__load_path(child_path)
@cache # noqa: B019
def __load_path(self, child_path: Path) -> Content:
if not child_path.exists():
raise FileNotFoundError(child_path)
if child_path.is_dir():
return ContentDirectory(child_path)
if child_path.is_file():
if child_path.suffix in [".yml", ".yaml", ".json"]:
return DataFile(child_path)
if child_path.suffix == ".md":
return MarkdownFile(child_path)
raise NotImplementedError()
@staticmethod
def __get_localized_path(path: Path) -> Path:
if Content.current_language is None:
return path
localized_path = path.with_name(f"{path.stem}-{Content.current_language}{path.suffix}")
print(localized_path)
if not localized_path.exists():
return path
return localized_path
class DataField:
def __init__(self, file_path: Path, value: Any) -> None:
self.__file_path = file_path
self.__value = value
def as_path(self) -> Path:
return self.__file_path.parent / str(self.__value)
def __str__(self) -> str:
return str(self.__value)
def __getitem__(self, key: Any) -> Any:
return DataField(self.__file_path, self.__value.get(key))
def __iter__(self) -> Iterator[Any]:
for it in self.__value:
yield DataField(self.__file_path, it)
class DataFile(Content):
def __init__(self, path: Path) -> None:
super().__init__(path)
with path.open("r", encoding="utf-8") as data_file:
self.__data = load(data_file, Loader)
def __getitem__(self, key: Any) -> Any:
return DataField(self.path, self.__data.get(key))
def __iter__(self) -> Iterator[Any]:
for it in self.__data:
yield DataField(self.path, it)
class MarkdownFile(Content):
def __init__(self, path: Path) -> None:
super().__init__(path)
with path.open("r", encoding="utf-8") as markdown_file:
self.__markdown = Markdown(extensions=["full_yaml_metadata"])
self.__html = self.__markdown.convert(markdown_file.read())
@property
def html(self) -> str:
return self.__html
@property
def meta(self) -> Any:
return DataField(self.path, self.__markdown.Meta) # type: ignore

View File

@ -1,9 +1,10 @@
from datetime import datetime from datetime import datetime
from subprocess import check_output from subprocess import check_output
from typing import Any
from jwebsite.content import Content
def git_creation_date(content: Any) -> datetime: def git_creation_date(content: Content) -> datetime:
git_dir = check_output(["git", "rev-parse", "--show-toplevel"], encoding="utf-8") git_dir = check_output(["git", "rev-parse", "--show-toplevel"], encoding="utf-8")
git_dir = git_dir.strip() git_dir = git_dir.strip()
log = check_output( log = check_output(

64
jwebsite/site.py Normal file
View File

@ -0,0 +1,64 @@
import gettext
from gettext import GNUTranslations, NullTranslations
from pathlib import Path
from shutil import copy
from typing import Any
from jinja2.environment import Environment
from jinja2.loaders import FileSystemLoader
from jwebsite.content import Content, ContentDirectory
from jwebsite.git import git_creation_date
class Site:
def __init__(self, root_directory: Path) -> None:
self.__root_directory = root_directory
self.__output_directory = root_directory / "build"
self.__environment = Environment(loader=FileSystemLoader(searchpath=root_directory / "src"))
self.__environment.filters.update({"output": self.__output, "git_creation_date": git_creation_date})
self.__content = ContentDirectory(root_directory / "content")
self.__translations: dict[str, GNUTranslations | NullTranslations] = {}
@property
def content(self) -> ContentDirectory:
return self.__content
def set_translations(self, domain: str, locale_dir: str, languages: list[str]) -> None:
self.__environment.add_extension("jinja2.ext.i18n")
self.__translations["en"] = NullTranslations()
for language in languages:
self.__translations[language] = gettext.translation(
domain, localedir=str(locale_dir), languages=[language]
)
def render(self, source: str, output: str | Path, **context: Any) -> None:
if self.__translations:
for language, translation in self.__translations.items():
Content.current_language = language
self.__environment.install_gettext_translations(translation, newstyle=True) # type: ignore
self.__render(source, self.__output_directory / language / output, **context)
self.__environment.uninstall_gettext_translations(translation) # type: ignore
else:
self.__render(source, self.__output_directory / output, **context)
def __render(self, source: str, output_path: Path, **context: Any) -> None:
output_path.parent.mkdir(exist_ok=True, parents=True)
template = self.__environment.get_template(source)
content = template.render(site=self, **context)
with open(output_path, "w") as output_file:
output_file.write(content)
def __output(self, path: str | Path) -> str:
path = Path(path)
if path.is_absolute():
src_path = path
relative_src_path = src_path.relative_to(self.__content.path)
else:
src_path = self.__content.path / path
relative_src_path = path
dst_path = self.__output_directory / relative_src_path
dst_path.parent.mkdir(parents=True, exist_ok=True)
copy(src_path, dst_path, follow_symlinks=True)
return f"/{relative_src_path}"

View File

@ -1,12 +1,7 @@
"""Nox configuration file.""" """Nox configuration file."""
from pathlib import Path
from tempfile import TemporaryDirectory
from nox import Session, session from nox import Session, session
_LOCALIZED_TESTS = ["tests/test_context"]
@session() @session()
def lint(session: Session) -> None: def lint(session: Session) -> None:
@ -21,38 +16,11 @@ def mypy(session: Session) -> None:
session.run("mypy") session.run("mypy")
@session()
def update_messages(session: Session) -> None:
session.install("babel", "jinja2")
for directory in _LOCALIZED_TESTS:
with TemporaryDirectory() as tmp_dir:
messages_file = Path(tmp_dir) / "messages.po"
session.run(
"pybabel",
"extract",
"--mapping",
f"{directory}/babel.cfg",
f"--output-file={messages_file}",
str(directory),
)
session.run(
"pybabel",
"update",
"--domain=tests",
f"--input-file={messages_file}",
f"--output-dir={directory}/locale",
)
@session(python=["3.10", "3.11"]) @session(python=["3.10", "3.11"])
def unit_tests(session: Session) -> None: def unit_tests(session: Session) -> None:
"""Run unit tests.""" """Run unit tests."""
devenv(session) devenv(session)
session.install("babel", "jinja2") session.run("python", "-m", "pytest", "--cov=jwebsite", "--cov-report=html")
for directory in _LOCALIZED_TESTS:
session.run("pybabel", "compile", "--domain=tests", f"--directory={directory}/locale", "--use-fuzzy")
session.run("python", "-m", "pytest", "--cov=jweb", "--cov-report=html")
@session() @session()

View File

@ -1,8 +1,8 @@
[project] [project]
name = "jean-web" name = "jean-website"
version = "0.0.1" version = "0.0.1"
authors = [ authors = [
{name = "Jean-Web", email ="team@collectivit.org"} {name = "Jean-Website", email ="team@collectivit.org"}
] ]
description = "Static site generator" description = "Static site generator"
@ -29,14 +29,14 @@ dev = [
] ]
[project.scripts] [project.scripts]
jweb = "jweb.cli:main" jwebsite = "jwebsite.cli:main"
[build-system] [build-system]
requires = ["setuptools>=45"] requires = ["setuptools>=45"]
[tool.mypy] [tool.mypy]
strict = true strict = true
files = "jweb/**/*.py,tests/**/*.py,noxfile.py" files = "jwebsite/**/*.py,noxfile.py"
[tool.ruff] [tool.ruff]
line-length = 110 line-length = 110

View File

@ -1,38 +0,0 @@
from pathlib import Path
from pytest import mark, raises
from jweb.content import Content
from jweb.context import Context
@mark.parametrize("content", ["# Otters", b"# Otters"])
def test_load(datadir: Path, content: bytes | str) -> None:
context = Context(datadir)
context.load_extensions("jweb.extensions.markdown")
context.render("test-load.html", "output.html", content=Content(Path("content.yml"), content, None))
with open(datadir / "build" / "output.html", encoding="utf-8") as ouput_file:
assert ouput_file.read() == "<h1>Otters</h1>"
def test_load_type_error(datadir: Path) -> None:
context = Context(datadir)
context.load_extensions("jweb.extensions.markdown")
with raises(ValueError):
context.render("test-load.html", "output.html", content=Content(Path("content.yml"), 10, None))
with raises(ValueError):
context.render("test-load.html", "output.html", content=10)
def test_metadata(datadir: Path) -> None:
context = Context(datadir)
context.load_extensions("jweb.extensions.markdown")
markdown = "---\n" "title: Steven\n" "---\n" "\n" "Content\n"
context.render("test-metadata.html", "output.html", content=Content(Path("content.yml"), markdown, None))
with open(datadir / "build" / "output.html", encoding="utf-8") as ouput_file:
assert ouput_file.read() == "Steven"

View File

@ -1,4 +0,0 @@
{% with document = content | markdown %}
{{- document }}
{%- endwith -%}

View File

@ -1,3 +0,0 @@
{% with document = content | markdown %}
{{- document.meta.title }}
{%- endwith -%}

View File

@ -1,27 +0,0 @@
from pathlib import Path
from pytest import mark, raises
from jweb.content import Content
from jweb.context import Context
@mark.parametrize("content", ["[Peter, Steven]", b"[Peter, Steven]"])
def test_load(datadir: Path, content: bytes | str) -> None:
context = Context(datadir)
context.load_extensions("jweb.extensions.yaml")
context.render("test-load.html", "output.html", content=Content(Path("content.yml"), content, None))
with open(datadir / "build" / "output.html", encoding="utf-8") as ouput_file:
assert ouput_file.read() == "PeterSteven"
def test_load_type_error(datadir: Path) -> None:
context = Context(datadir)
context.load_extensions("jweb.extensions.yaml")
with raises(ValueError):
context.render("test-load.html", "output.html", content=Content(Path("content.yml"), 10, None))
with raises(ValueError):
context.render("test-load.html", "output.html", content=10)

View File

@ -1,3 +0,0 @@
{% for otter in content | yaml -%}
{{- otter }}
{%- endfor %}

56
tests/test_content.py Normal file
View File

@ -0,0 +1,56 @@
from pathlib import Path
from pytest import raises
from jwebsite.content import Content, ContentDirectory
def test_load_directory(datadir: Path) -> None:
content = ContentDirectory(datadir)
otters = content.load(Path("otters"))
assert isinstance(otters, ContentDirectory)
def test_load_errors(datadir: Path) -> None:
directory = ContentDirectory(datadir)
with raises(FileNotFoundError):
directory.load(Path("otters/i-dont-exist"))
with raises(NotADirectoryError):
directory.load(Path("otters/steven.yml/child"))
def test_load_data(datadir: Path) -> None:
content = ContentDirectory(datadir)
steven = content.load(Path("otters/steven.yml"))
assert str(steven["name"]) == "Steven"
assert str(steven["mood"]) == "Angry"
otter_list = content.load(Path("otters/otter_list.json"))
assert [str(it) for it in otter_list] == ["steven", "peter"]
def test_data_as_path(datadir: Path) -> None:
content = ContentDirectory(datadir)
paths = content.load(Path("otters/paths.yml"))
assert paths["steven"].as_path() == datadir / "otters" / "relative-steven"
def test_load_markdown(datadir: Path) -> None:
content = ContentDirectory(datadir)
page = content.load(Path("page.md"))
assert page.html == "<p>Content</p>"
assert str(page.meta["title"]) == "Title"
def test_localized_content(datadir: Path) -> None:
content = ContentDirectory(datadir)
Content.current_language = "fr"
page = content.load(Path("page.md"))
assert page.html == "<p>Contenu</p>"
assert str(page.meta["title"]) == "Titre"

View File

@ -0,0 +1 @@
["steven", "peter"]

View File

@ -0,0 +1,2 @@
steven: ./relative-steven

View File

@ -0,0 +1,2 @@
name: Steven
mood: Angry

View File

@ -0,0 +1,6 @@
---
title: Titre
---
Contenu

View File

@ -0,0 +1,5 @@
---
title: Title
---
Content

View File

@ -1,88 +0,0 @@
from pathlib import Path
from pytest import mark
from jweb.context import Context
def test_render(datadir: Path) -> None:
context = Context(datadir)
context.render("test-render.html", "output.html", animal="Otters")
with open(datadir / "build" / "output.html", encoding="utf-8") as ouput_file:
assert ouput_file.read() == "Otters"
def test_jinja_localization(datadir: Path) -> None:
context = Context(datadir)
context.load_translations("tests", datadir / "locale", "fr")
context.render("test-jinja-localization.html", "output.html")
with open(datadir / "build" / "en" / "output.html", encoding="utf-8") as ouput_file:
assert ouput_file.read() == "Otters"
with open(datadir / "build" / "fr" / "output.html", encoding="utf-8") as ouput_file:
assert ouput_file.read() == "Loutres"
@mark.parametrize("path", ["content.txt", Path("content.txt")])
def test_load(datadir: Path, path: str | Path) -> None:
context = Context(datadir)
context.render("test-load.html", "output.html", path=path)
with open(datadir / "build" / "output.html", encoding="utf-8") as ouput_file:
assert ouput_file.read() == "Otters\n"
def test_load_localized(datadir: Path) -> None:
context = Context(datadir)
context.load_translations("tests", datadir / "locale", "fr")
context.render("test-load-localized.html", "output.html")
with open(datadir / "build" / "en" / "output.html", encoding="utf-8") as ouput_file:
assert ouput_file.read() == "Otters\nCaimans\n"
with open(datadir / "build" / "fr" / "output.html", encoding="utf-8") as ouput_file:
assert ouput_file.read() == "Loutres\nCaimans\n"
def test_write(datadir: Path) -> None:
context = Context(datadir)
context.render("test-write.html", "output.html")
with open(datadir / "build" / "content.txt", encoding="utf-8") as ouput_file:
assert ouput_file.read() == "Otters\n"
with open(datadir / "build/subdir/content.txt", encoding="utf-8") as ouput_file:
assert ouput_file.read() == "Weasel\n"
with open(datadir / "build" / "output.html", encoding="utf-8") as ouput_file:
assert ouput_file.read() == "/content.txt\n/subdir/content.txt"
def test_glob(datadir: Path) -> None:
context = Context(datadir)
context.render("test-glob.html", "output.html")
with open(datadir / "build" / "output.html", encoding="utf-8") as ouput_file:
assert ouput_file.read() == "Otters\nCaiman\n"
def test_glob_localized(datadir: Path) -> None:
context = Context(datadir)
context.load_translations("tests", datadir / "locale", "fr")
context.render("test-glob.html", "output.html")
with open(datadir / "build" / "en" / "output.html", encoding="utf-8") as ouput_file:
assert ouput_file.read() == "Otters\nCaimans\n"
with open(datadir / "build" / "fr" / "output.html", encoding="utf-8") as ouput_file:
assert ouput_file.read() == "Loutres\n"
def test_glob_localized_include_base_language(datadir: Path) -> None:
context = Context(datadir)
context.load_translations("tests", datadir / "locale", "fr")
context.render("test-glob-include-base-language.html", "output.html")
with open(datadir / "build" / "fr" / "output.html", encoding="utf-8") as ouput_file:
assert ouput_file.read() == "Caimans\nLoutres\n"

View File

@ -1,3 +0,0 @@
[jinja2: src/**.html]
encoding = utf-8

View File

@ -1 +0,0 @@
Otters

View File

@ -1 +0,0 @@
Otters

View File

@ -1 +0,0 @@
Loutres

View File

@ -1 +0,0 @@
Caiman

View File

@ -1 +0,0 @@
Weasel

View File

@ -1,25 +0,0 @@
# French translations for PROJECT.
# Copyright (C) 2024 ORGANIZATION
# This file is distributed under the same license as the PROJECT project.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2024.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2024-05-22 00:10+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language: fr\n"
"Language-Team: fr <LL@li.org>\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.15.0\n"
#: tests/test_context/src/test-jinja-localization.html:1
msgid "Otters"
msgstr "Loutres"

View File

@ -1,3 +0,0 @@
{% for content in glob("*.txt", include_base_language=True) | sort(attribute='path') -%}
{{ content -}}
{% endfor %}

View File

@ -1,3 +0,0 @@
{% for content in glob("*.txt") | sort(attribute='path') -%}
{{ content -}}
{% endfor %}

View File

@ -1 +0,0 @@
{{ gettext('Otters') }}

View File

@ -1,3 +0,0 @@
{{ 'content.txt' | load -}}
{{ 'not-localized-content.txt' | load -}}

View File

@ -1 +0,0 @@
{{ path | load }}

View File

@ -1 +0,0 @@
{{animal}}

View File

@ -1,2 +0,0 @@
{{ 'content.txt' | load | write }}
{{ 'subdir/content.txt' | load | write }}

32
tests/test_site.py Normal file
View File

@ -0,0 +1,32 @@
from pathlib import Path
from pyfakefs.fake_filesystem import FakeFilesystem
from pytest import fixture
from jwebsite.site import Site
@fixture
def site_dir(datadir: Path, fs: FakeFilesystem):
fs.add_real_directory(datadir)
yield fs
def test_render_page(datadir: Path, site_dir: FakeFilesystem):
site = Site(datadir)
site.render("index.j2", "index.html")
with open(datadir / "build" / "index.html", encoding="utf-8") as ouput_file:
assert ouput_file.read() == "<p>Peter</p><p>Steven</p>"
def test_output_file(datadir: Path, site_dir: FakeFilesystem):
site = Site(datadir)
build_dir = datadir / "build"
site.render("output-file.j2", "output-file.html")
with open(build_dir / "assets/steven-avatar", encoding="utf-8") as ouput_file:
assert ouput_file.read() == "Yipee\n"
with open(build_dir / "output-file.html", encoding="utf-8") as ouput_file:
assert ouput_file.read() == "/assets/steven-avatar\n"

View File

@ -0,0 +1 @@
Yipee

View File

@ -0,0 +1 @@
[Peter, Steven]

View File

@ -0,0 +1,3 @@
{%- for otter in site.content.load('otters.yml') -%}
<p>{{ otter }}</p>
{%- endfor %}

View File

@ -0,0 +1,2 @@
{{ "assets/steven-avatar" | output }}