mirror of
https://github.com/zensical/zensical.git
synced 2026-10-08 13:51:22 +00:00
fix: handle symlinked files in watched dependencies (#1010)
Signed-off-by: Timothée Mazzucotelli <dev@pawamoy.fr>
This commit is contained in:
1 parent
8e96dd1038
commit
4924feab39
4 files changed
+193
-16
No files matched your search
+67
@@ -158,6 +158,73 @@ def test_build_with_config_in_watch(
|
||||
assert (tmp_path / "site" / "index.html").is_file()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("toml", [False, True], ids=["yaml", "toml"])
|
||||
@pytest.mark.parametrize(
|
||||
"watch_path", ["extras/page.md", "extras", "docs/index.md", "."]
|
||||
)
|
||||
@pytest.mark.parametrize("external_target", [False, True])
|
||||
def test_build_with_symlinked_page_in_watch(
|
||||
tmp_path: Path, toml: bool, watch_path: str, external_target: bool
|
||||
) -> None:
|
||||
project = tmp_path / "project"
|
||||
project.mkdir()
|
||||
if external_target and watch_path.startswith("extras"):
|
||||
watch_path = "../" + watch_path
|
||||
|
||||
config = (
|
||||
_make_toml_project(project, toml_extra=f'watch = ["{watch_path}"]')
|
||||
if toml
|
||||
else _make_yml_project(project, yml_extra=f'watch: ["{watch_path}"]')
|
||||
)
|
||||
extras = (tmp_path if external_target else project) / "extras"
|
||||
extras.mkdir()
|
||||
target = extras / "page.md"
|
||||
target.write_text("# Symlinked page\n", encoding="utf-8")
|
||||
|
||||
# The watched target is exposed through two independent docs paths.
|
||||
(project / "docs" / "index.md").unlink()
|
||||
for name in ["index.md", "alias.md"]:
|
||||
try:
|
||||
(project / "docs" / name).symlink_to(target)
|
||||
except OSError as error:
|
||||
pytest.skip(f"symbolic links unavailable: {error}")
|
||||
|
||||
_build(config)
|
||||
|
||||
for output in ["index.html", "alias/index.html"]:
|
||||
html = (project / "site" / output).read_text(encoding="utf-8")
|
||||
assert "Symlinked page" in html
|
||||
|
||||
|
||||
@pytest.mark.parametrize("toml", [False, True], ids=["yaml", "toml"])
|
||||
@pytest.mark.parametrize("dependencies", ["macros", "watch", "both"])
|
||||
def test_build_ignores_dangling_symlinks_in_watched_directories(
|
||||
tmp_path: Path, toml: bool, dependencies: str
|
||||
) -> None:
|
||||
if toml:
|
||||
options = 'watch = ["docs"]\n' if dependencies != "macros" else ""
|
||||
if dependencies != "watch":
|
||||
options += "[project.plugins.macros]\n"
|
||||
config = _make_toml_project(tmp_path, toml_extra=options)
|
||||
else:
|
||||
options = "watch: [docs]\n" if dependencies != "macros" else ""
|
||||
if dependencies != "watch":
|
||||
options += "plugins: [macros]\n"
|
||||
config = _make_yml_project(tmp_path, yml_extra=options)
|
||||
|
||||
# A dangling link is ignored while the existing page is still rendered.
|
||||
try:
|
||||
(tmp_path / "docs" / "broken.md").symlink_to("missing.md")
|
||||
except OSError as error:
|
||||
pytest.skip(f"symbolic links unavailable: {error}")
|
||||
|
||||
_build(config)
|
||||
|
||||
html = (tmp_path / "site" / "index.html").read_text(encoding="utf-8")
|
||||
assert "Hello" in html
|
||||
assert not (tmp_path / "site" / "broken").exists()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("config_format", ["yaml", "toml"])
|
||||
@pytest.mark.parametrize(
|
||||
("enabled", "version_selector", "expected"),
|
||||
|
||||
+94
@@ -29,6 +29,7 @@ import json
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
from textwrap import dedent
|
||||
from typing import TYPE_CHECKING
|
||||
from urllib.parse import parse_qs, urlsplit
|
||||
@@ -74,6 +75,99 @@ def _write_config(
|
||||
return config
|
||||
|
||||
|
||||
@pytest.mark.parametrize("toml", [False, True], ids=["yaml", "toml"])
|
||||
@pytest.mark.parametrize("target_location", ["docs", "project", "external"])
|
||||
def test_default_include_directory_builds_symlinked_pages(
|
||||
tmp_path: Path, toml: bool, target_location: str
|
||||
) -> None:
|
||||
project = tmp_path / "project"
|
||||
project.mkdir()
|
||||
config = _write_config(project, {"on_error_fail": True}, toml=toml)
|
||||
docs = project / "content"
|
||||
target_dir = {"docs": docs, "project": project, "external": tmp_path}
|
||||
target = target_dir[target_location] / "page.md"
|
||||
target.write_text("# Hello\n\nValue: {{ 1 + 1 }}\n", encoding="utf-8")
|
||||
|
||||
# Two aliases are discovered alongside the target without ending the build.
|
||||
for name in ["index.md", "alias.md"]:
|
||||
try:
|
||||
(docs / name).symlink_to(target)
|
||||
except OSError as error:
|
||||
pytest.skip(f"symbolic links unavailable: {error}")
|
||||
|
||||
zensical.build(str(config), {"clean": True, "strict": False})
|
||||
|
||||
for output in ["index.html", "alias/index.html"]:
|
||||
html = (project / "site" / output).read_text(encoding="utf-8")
|
||||
assert "Value: 2" in html
|
||||
|
||||
|
||||
@pytest.mark.parametrize("dependency", ["page", "include"])
|
||||
def test_serve_rebuilds_after_symlinked_macro_dependency_changes(
|
||||
tmp_path: Path, dependency: str
|
||||
) -> None:
|
||||
config = _write_config(tmp_path, {"on_error_fail": True})
|
||||
with config.open("a", encoding="utf-8") as stream:
|
||||
stream.write("dev_addr: 127.0.0.1:0\n")
|
||||
|
||||
docs = tmp_path / "content"
|
||||
target = tmp_path / ("page.md" if dependency == "page" else "fragment.j2")
|
||||
target.write_text("First value: {{ 1 + 1 }}\n", encoding="utf-8")
|
||||
alias = docs / ("index.md" if dependency == "page" else "fragment.j2")
|
||||
try:
|
||||
alias.symlink_to(target)
|
||||
except OSError as error:
|
||||
pytest.skip(f"symbolic links unavailable: {error}")
|
||||
|
||||
if dependency == "include":
|
||||
(docs / "index.md").write_text(
|
||||
'{% include "fragment.j2" %}\n', encoding="utf-8"
|
||||
)
|
||||
|
||||
output = tmp_path / "site" / "index.html"
|
||||
with (tmp_path / "serve.log").open("w+", encoding="utf-8") as log:
|
||||
process = subprocess.Popen( # noqa: S603
|
||||
[sys.executable, "-m", "zensical", "serve", "-f", str(config)],
|
||||
cwd=tmp_path,
|
||||
stdout=log,
|
||||
stderr=subprocess.STDOUT,
|
||||
)
|
||||
|
||||
def wait_for(text: str) -> None:
|
||||
deadline = time.monotonic() + 10
|
||||
while time.monotonic() < deadline:
|
||||
try:
|
||||
content = output.read_text(encoding="utf-8")
|
||||
except FileNotFoundError:
|
||||
content = ""
|
||||
if text in content:
|
||||
return
|
||||
if process.poll() is not None:
|
||||
break
|
||||
time.sleep(0.02)
|
||||
|
||||
log.seek(0)
|
||||
pytest.fail(f"serve did not rebuild: {log.read()}")
|
||||
|
||||
try:
|
||||
wait_for("First value: 2")
|
||||
|
||||
# Canonical targets are still watched after alias discovery.
|
||||
target.write_text("Updated value: {{ 1 + 1 }}\n", encoding="utf-8")
|
||||
timestamp = target.stat().st_mtime + 2
|
||||
os.utime(target, (timestamp, timestamp))
|
||||
|
||||
wait_for("Updated value: 2")
|
||||
assert process.poll() is None
|
||||
finally:
|
||||
process.terminate()
|
||||
try:
|
||||
process.wait(timeout=5)
|
||||
except subprocess.TimeoutExpired:
|
||||
process.kill()
|
||||
process.wait(timeout=5)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("toml", [False, True], ids=["yaml", "toml"])
|
||||
def test_force_render_paths_uses_docs_paths_and_page_metadata(
|
||||
tmp_path: Path, toml: bool
|
||||
|
||||
Reference in new issue
Block a user