From 2729af8e397a015eabe74139c2904b863d3785ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Corentin=20S=C3=A9chet?= Date: Tue, 21 May 2024 21:13:55 +0200 Subject: [PATCH] refactor: organize code in context & extensions --- .gitignore | 1 + jwebsite/cli.py | 4 +- jwebsite/content.py | 114 +++--------------- jwebsite/context.py | 97 +++++++++++++++ jwebsite/extensions/git.py | 31 +++++ jwebsite/extensions/markdown.py | 34 ++++++ jwebsite/extensions/yaml.py | 23 ++++ jwebsite/site.py | 64 ---------- noxfile.py | 32 +++++ pyproject.toml | 2 +- tests/extensions/test_markdown.py | 38 ++++++ .../test_markdown/src/test-load.html | 4 + .../test_markdown/src/test-metadata.html | 3 + tests/extensions/test_yaml.py | 27 +++++ tests/extensions/test_yaml/src/test-load.html | 3 + tests/test_content.py | 56 --------- tests/test_content/otters/otter_list.json | 1 - tests/test_content/otters/paths.yml | 2 - tests/test_content/otters/steven.yml | 2 - tests/test_content/page-fr.md | 6 - tests/test_content/page.md | 5 - tests/test_context.py | 53 ++++++++ tests/test_context/babel.cfg | 3 + tests/test_context/content/content.txt | 1 + tests/test_context/content/en/content.txt | 1 + .../content/en/not-localized-content.txt | 1 + tests/test_context/content/fr/content.txt | 1 + .../locale/fr/LC_MESSAGES/tests.po | 25 ++++ .../src/test-jinja-localization.html | 1 + .../test_context/src/test-load-localized.html | 3 + tests/test_context/src/test-load.html | 1 + tests/test_context/src/test-render.html | 1 + tests/test_context/src/test-write.html | 1 + tests/test_site.py | 32 ----- tests/test_site/content/assets/steven-avatar | 1 - tests/test_site/content/otters.yml | 1 - tests/test_site/src/index.j2 | 3 - tests/test_site/src/output-file.j2 | 2 - 38 files changed, 406 insertions(+), 274 deletions(-) create mode 100644 jwebsite/context.py create mode 100644 jwebsite/extensions/git.py create mode 100644 jwebsite/extensions/markdown.py create mode 100644 jwebsite/extensions/yaml.py delete mode 100644 jwebsite/site.py create mode 100644 tests/extensions/test_markdown.py create mode 100644 tests/extensions/test_markdown/src/test-load.html create mode 100644 tests/extensions/test_markdown/src/test-metadata.html create mode 100644 tests/extensions/test_yaml.py create mode 100644 tests/extensions/test_yaml/src/test-load.html delete mode 100644 tests/test_content.py delete mode 100644 tests/test_content/otters/otter_list.json delete mode 100644 tests/test_content/otters/paths.yml delete mode 100644 tests/test_content/otters/steven.yml delete mode 100644 tests/test_content/page-fr.md delete mode 100644 tests/test_content/page.md create mode 100644 tests/test_context.py create mode 100644 tests/test_context/babel.cfg create mode 100644 tests/test_context/content/content.txt create mode 100644 tests/test_context/content/en/content.txt create mode 100644 tests/test_context/content/en/not-localized-content.txt create mode 100644 tests/test_context/content/fr/content.txt create mode 100644 tests/test_context/locale/fr/LC_MESSAGES/tests.po create mode 100644 tests/test_context/src/test-jinja-localization.html create mode 100644 tests/test_context/src/test-load-localized.html create mode 100644 tests/test_context/src/test-load.html create mode 100644 tests/test_context/src/test-render.html create mode 100644 tests/test_context/src/test-write.html delete mode 100644 tests/test_site.py delete mode 100644 tests/test_site/content/assets/steven-avatar delete mode 100644 tests/test_site/content/otters.yml delete mode 100644 tests/test_site/src/index.j2 delete mode 100644 tests/test_site/src/output-file.j2 diff --git a/.gitignore b/.gitignore index fb90e13..9e73285 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ **/__pycache__ .coverage jean_website.egg-info +tests/**/*.mo diff --git a/jwebsite/cli.py b/jwebsite/cli.py index 98aa597..42d4659 100644 --- a/jwebsite/cli.py +++ b/jwebsite/cli.py @@ -2,7 +2,7 @@ from pathlib import Path from click import group -from jwebsite.site import Site +from jwebsite.context import Context @group @@ -13,4 +13,4 @@ def main() -> None: ... def build() -> None: cwd = Path.cwd() with open(cwd / "site.py", encoding="utf-8") as site_config: - exec(site_config.read(), {"site": Site(cwd)}) + exec(site_config.read(), {"site": Context(cwd)}) diff --git a/jwebsite/content.py b/jwebsite/content.py index f67b64c..4bcb1b3 100644 --- a/jwebsite/content.py +++ b/jwebsite/content.py @@ -1,114 +1,36 @@ -from functools import cache from pathlib import Path -from typing import Any, Iterable, Iterator - -from markdown import Markdown -from yaml import Loader, load +from typing import Any, Iterator class Content: - current_language: str | None = None - - def __init__(self, path: Path) -> None: + def __init__(self, path: Path, data: Any, 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 -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) + @property + def data(self) -> Any: + return self.__data def __str__(self) -> str: - return str(self.__value) + return str(self.__data) + +class ContentField(Content): def __getitem__(self, key: Any) -> Any: - return DataField(self.__file_path, self.__value.get(key)) + return ContentField(self.path, self.data.get(key), self.language) def __iter__(self) -> Iterator[Any]: - for it in self.__value: - yield DataField(self.__file_path, it) + for it in self.data: + yield ContentField(self.path, it, self.language) - -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 + def __str__(self) -> str: + return str(self.data) diff --git a/jwebsite/context.py b/jwebsite/context.py new file mode 100644 index 0000000..587dfd6 --- /dev/null +++ b/jwebsite/context.py @@ -0,0 +1,97 @@ +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 jwebsite.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) + + @property + def current_language(self) -> str | None: + return self.__current_language + + def add_filters(self, **filters: Any) -> None: + self.__environment.filters.update(**filters) + + 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: + 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("r", encoding="utf-8") as content_file: + return Content(localized_path, content_file.read(), current_language) + + def __write(self, content: Content) -> Path: + relative_path = content.path.relative_to(self.__content_directory) + output_path = self.__output_directory / relative_path + with output_path.open("w") 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) diff --git a/jwebsite/extensions/git.py b/jwebsite/extensions/git.py new file mode 100644 index 0000000..3382fc5 --- /dev/null +++ b/jwebsite/extensions/git.py @@ -0,0 +1,31 @@ +from datetime import datetime +from subprocess import check_output + +from jwebsite.content import Content +from jwebsite.context import Context + + +def load_extension(context: Context) -> None: + context.add_filters(git_creation_date=_git_creation_date) + + +def _git_creation_date(content: Content) -> 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]) diff --git a/jwebsite/extensions/markdown.py b/jwebsite/extensions/markdown.py new file mode 100644 index 0000000..c2306ac --- /dev/null +++ b/jwebsite/extensions/markdown.py @@ -0,0 +1,34 @@ +from typing import Any + +from markdown import Markdown + +from jwebsite.content import Content, ContentField +from jwebsite.context import Context + + +def load_extension(context: Context) -> None: + context.add_filters(markdown=_MarkdownDocument) + + +class _MarkdownDocument: + def __init__(self, content: Content) -> 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"]) + self.__html = self.__markdown.convert(data) + + @property + def html(self) -> str: + return self.__html + + @property + def meta(self) -> Any: + return ContentField(self.__content.path, self.__markdown.Meta, self.__content.language) # type: ignore diff --git a/jwebsite/extensions/yaml.py b/jwebsite/extensions/yaml.py new file mode 100644 index 0000000..6824fe5 --- /dev/null +++ b/jwebsite/extensions/yaml.py @@ -0,0 +1,23 @@ +from typing import Any + +from yaml import Loader, load + +from jwebsite.content import Content, ContentField +from jwebsite.context import Context + + +def load_extension(context: Context) -> None: + context.add_filters(yaml=_load_yaml) + + +def _load_yaml(content: Any) -> ContentField: + 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 ContentField(content.path, load(data, Loader), content.language) diff --git a/jwebsite/site.py b/jwebsite/site.py deleted file mode 100644 index 749b9ed..0000000 --- a/jwebsite/site.py +++ /dev/null @@ -1,64 +0,0 @@ -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}" diff --git a/noxfile.py b/noxfile.py index 9b90cf1..99b8b08 100644 --- a/noxfile.py +++ b/noxfile.py @@ -1,7 +1,12 @@ """Nox configuration file.""" +from pathlib import Path +from tempfile import TemporaryDirectory + from nox import Session, session +_LOCALIZED_TESTS = ["tests/test_context"] + @session() def lint(session: Session) -> None: @@ -16,10 +21,37 @@ def mypy(session: Session) -> None: 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"]) def unit_tests(session: Session) -> None: """Run unit tests.""" devenv(session) + session.install("babel", "jinja2") + for directory in _LOCALIZED_TESTS: + session.run("pybabel", "compile", "--domain=tests", f"--directory={directory}/locale", "--use-fuzzy") session.run("python", "-m", "pytest", "--cov=jwebsite", "--cov-report=html") diff --git a/pyproject.toml b/pyproject.toml index 892f75e..5b87fc0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,7 +36,7 @@ requires = ["setuptools>=45"] [tool.mypy] strict = true -files = "jwebsite/**/*.py,noxfile.py" +files = "jwebsite/**/*.py,tests/**/*.py,noxfile.py" [tool.ruff] line-length = 110 diff --git a/tests/extensions/test_markdown.py b/tests/extensions/test_markdown.py new file mode 100644 index 0000000..b8ff72b --- /dev/null +++ b/tests/extensions/test_markdown.py @@ -0,0 +1,38 @@ +from pathlib import Path + +from pytest import mark, raises + +from jwebsite.content import Content +from jwebsite.context import Context + + +@mark.parametrize("content", ["# Otters", b"# Otters"]) +def test_load(datadir: Path, content: bytes | str) -> None: + context = Context(datadir) + context.load_extensions("jwebsite.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() == "

Otters

" + + +def test_load_type_error(datadir: Path) -> None: + context = Context(datadir) + context.load_extensions("jwebsite.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("jwebsite.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" diff --git a/tests/extensions/test_markdown/src/test-load.html b/tests/extensions/test_markdown/src/test-load.html new file mode 100644 index 0000000..a0a3802 --- /dev/null +++ b/tests/extensions/test_markdown/src/test-load.html @@ -0,0 +1,4 @@ +{% with document = content | markdown %} + {{- document.html }} +{%- endwith -%} + diff --git a/tests/extensions/test_markdown/src/test-metadata.html b/tests/extensions/test_markdown/src/test-metadata.html new file mode 100644 index 0000000..c17106a --- /dev/null +++ b/tests/extensions/test_markdown/src/test-metadata.html @@ -0,0 +1,3 @@ +{% with document = content | markdown %} + {{- document.meta.title }} +{%- endwith -%} diff --git a/tests/extensions/test_yaml.py b/tests/extensions/test_yaml.py new file mode 100644 index 0000000..18ba789 --- /dev/null +++ b/tests/extensions/test_yaml.py @@ -0,0 +1,27 @@ +from pathlib import Path + +from pytest import mark, raises + +from jwebsite.content import Content +from jwebsite.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("jwebsite.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("jwebsite.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) diff --git a/tests/extensions/test_yaml/src/test-load.html b/tests/extensions/test_yaml/src/test-load.html new file mode 100644 index 0000000..c6f3c62 --- /dev/null +++ b/tests/extensions/test_yaml/src/test-load.html @@ -0,0 +1,3 @@ +{% for otter in content | yaml -%} + {{- otter }} +{%- endfor %} diff --git a/tests/test_content.py b/tests/test_content.py deleted file mode 100644 index 93ca223..0000000 --- a/tests/test_content.py +++ /dev/null @@ -1,56 +0,0 @@ -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 == "

Content

" - 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 == "

Contenu

" - assert str(page.meta["title"]) == "Titre" diff --git a/tests/test_content/otters/otter_list.json b/tests/test_content/otters/otter_list.json deleted file mode 100644 index f88e25c..0000000 --- a/tests/test_content/otters/otter_list.json +++ /dev/null @@ -1 +0,0 @@ -["steven", "peter"] diff --git a/tests/test_content/otters/paths.yml b/tests/test_content/otters/paths.yml deleted file mode 100644 index 405fdb8..0000000 --- a/tests/test_content/otters/paths.yml +++ /dev/null @@ -1,2 +0,0 @@ -steven: ./relative-steven - diff --git a/tests/test_content/otters/steven.yml b/tests/test_content/otters/steven.yml deleted file mode 100644 index 88de4af..0000000 --- a/tests/test_content/otters/steven.yml +++ /dev/null @@ -1,2 +0,0 @@ -name: Steven -mood: Angry diff --git a/tests/test_content/page-fr.md b/tests/test_content/page-fr.md deleted file mode 100644 index 3dce283..0000000 --- a/tests/test_content/page-fr.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: Titre ---- - -Contenu - diff --git a/tests/test_content/page.md b/tests/test_content/page.md deleted file mode 100644 index cdc66ee..0000000 --- a/tests/test_content/page.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Title ---- - -Content diff --git a/tests/test_context.py b/tests/test_context.py new file mode 100644 index 0000000..210d711 --- /dev/null +++ b/tests/test_context.py @@ -0,0 +1,53 @@ +from pathlib import Path + +from jwebsite.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" + + +def test_load(datadir: Path) -> None: + context = Context(datadir) + context.render("test-load.html", "output.html") + + 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" / "output.html", encoding="utf-8") as ouput_file: + assert ouput_file.read() == "/content.txt" diff --git a/tests/test_context/babel.cfg b/tests/test_context/babel.cfg new file mode 100644 index 0000000..46f41ee --- /dev/null +++ b/tests/test_context/babel.cfg @@ -0,0 +1,3 @@ +[jinja2: src/**.html] +encoding = utf-8 + diff --git a/tests/test_context/content/content.txt b/tests/test_context/content/content.txt new file mode 100644 index 0000000..53b6d96 --- /dev/null +++ b/tests/test_context/content/content.txt @@ -0,0 +1 @@ +Otters diff --git a/tests/test_context/content/en/content.txt b/tests/test_context/content/en/content.txt new file mode 100644 index 0000000..53b6d96 --- /dev/null +++ b/tests/test_context/content/en/content.txt @@ -0,0 +1 @@ +Otters diff --git a/tests/test_context/content/en/not-localized-content.txt b/tests/test_context/content/en/not-localized-content.txt new file mode 100644 index 0000000..ff46d79 --- /dev/null +++ b/tests/test_context/content/en/not-localized-content.txt @@ -0,0 +1 @@ +Caimans diff --git a/tests/test_context/content/fr/content.txt b/tests/test_context/content/fr/content.txt new file mode 100644 index 0000000..f859787 --- /dev/null +++ b/tests/test_context/content/fr/content.txt @@ -0,0 +1 @@ +Loutres diff --git a/tests/test_context/locale/fr/LC_MESSAGES/tests.po b/tests/test_context/locale/fr/LC_MESSAGES/tests.po new file mode 100644 index 0000000..8df3fab --- /dev/null +++ b/tests/test_context/locale/fr/LC_MESSAGES/tests.po @@ -0,0 +1,25 @@ +# French translations for PROJECT. +# Copyright (C) 2024 ORGANIZATION +# This file is distributed under the same license as the PROJECT project. +# FIRST AUTHOR , 2024. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PROJECT VERSION\n" +"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" +"POT-Creation-Date: 2024-05-21 21:13+0200\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: fr\n" +"Language-Team: fr \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" + diff --git a/tests/test_context/src/test-jinja-localization.html b/tests/test_context/src/test-jinja-localization.html new file mode 100644 index 0000000..f5d13b4 --- /dev/null +++ b/tests/test_context/src/test-jinja-localization.html @@ -0,0 +1 @@ +{{ gettext('Otters') }} diff --git a/tests/test_context/src/test-load-localized.html b/tests/test_context/src/test-load-localized.html new file mode 100644 index 0000000..708084e --- /dev/null +++ b/tests/test_context/src/test-load-localized.html @@ -0,0 +1,3 @@ +{{ 'content.txt' | load -}} +{{ 'not-localized-content.txt' | load -}} + diff --git a/tests/test_context/src/test-load.html b/tests/test_context/src/test-load.html new file mode 100644 index 0000000..961788a --- /dev/null +++ b/tests/test_context/src/test-load.html @@ -0,0 +1 @@ +{{ 'content.txt' | load }} diff --git a/tests/test_context/src/test-render.html b/tests/test_context/src/test-render.html new file mode 100644 index 0000000..5a21af4 --- /dev/null +++ b/tests/test_context/src/test-render.html @@ -0,0 +1 @@ +{{animal}} diff --git a/tests/test_context/src/test-write.html b/tests/test_context/src/test-write.html new file mode 100644 index 0000000..6b1f13c --- /dev/null +++ b/tests/test_context/src/test-write.html @@ -0,0 +1 @@ +{{ 'content.txt' | load | write }} diff --git a/tests/test_site.py b/tests/test_site.py deleted file mode 100644 index 3b75eb5..0000000 --- a/tests/test_site.py +++ /dev/null @@ -1,32 +0,0 @@ -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() == "

Peter

Steven

" - - -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" diff --git a/tests/test_site/content/assets/steven-avatar b/tests/test_site/content/assets/steven-avatar deleted file mode 100644 index 1c2028f..0000000 --- a/tests/test_site/content/assets/steven-avatar +++ /dev/null @@ -1 +0,0 @@ -Yipee diff --git a/tests/test_site/content/otters.yml b/tests/test_site/content/otters.yml deleted file mode 100644 index 3826f38..0000000 --- a/tests/test_site/content/otters.yml +++ /dev/null @@ -1 +0,0 @@ -[Peter, Steven] diff --git a/tests/test_site/src/index.j2 b/tests/test_site/src/index.j2 deleted file mode 100644 index 33f66bd..0000000 --- a/tests/test_site/src/index.j2 +++ /dev/null @@ -1,3 +0,0 @@ -{%- for otter in site.content.load('otters.yml') -%} -

{{ otter }}

-{%- endfor %} diff --git a/tests/test_site/src/output-file.j2 b/tests/test_site/src/output-file.j2 deleted file mode 100644 index 4117c47..0000000 --- a/tests/test_site/src/output-file.j2 +++ /dev/null @@ -1,2 +0,0 @@ -{{ "assets/steven-avatar" | output }} -