copier/tests/test_dynamic_file_structures.py
Kj 557c0d6144
feat: add dynamic file structures in loop using yield-tag (#1855)
Add jinja2 extension for yield tag, allow _render_path to generate multiple paths and contexts when yield tag is used.

The tag is only allowed in path render contexts. When rendering within a file, it doesn't make sense. Use the normal for tag there. If you use yield, you'll get an exception.

Fixes https://github.com/copier-org/copier/issues/1271
2025-01-18 08:24:32 +00:00

250 lines
7.5 KiB
Python

import warnings
import pytest
import copier
from copier.errors import MultipleYieldTagsError, YieldTagInFileError
from tests.helpers import build_file_tree
def test_folder_loop(tmp_path_factory: pytest.TempPathFactory) -> None:
src, dst = map(tmp_path_factory.mktemp, ("src", "dst"))
build_file_tree(
{
src / "copier.yml": "",
src
/ "folder_loop"
/ "{% yield item from strings %}{{ item }}{% endyield %}"
/ "{{ item }}.txt.jinja": "Hello {{ item }}",
}
)
with warnings.catch_warnings():
warnings.simplefilter("error")
copier.run_copy(
str(src),
dst,
data={
"strings": ["a", "b", "c"],
},
defaults=True,
overwrite=True,
)
expected_files = [dst / f"folder_loop/{i}/{i}.txt" for i in ["a", "b", "c"]]
for f in expected_files:
assert f.exists()
assert f.read_text() == f"Hello {f.parent.name}"
all_files = [p for p in dst.rglob("*") if p.is_file()]
unexpected_files = set(all_files) - set(expected_files)
assert not unexpected_files, f"Unexpected files found: {unexpected_files}"
def test_nested_folder_loop(tmp_path_factory: pytest.TempPathFactory) -> None:
src, dst = map(tmp_path_factory.mktemp, ("src", "dst"))
build_file_tree(
{
src / "copier.yml": "",
src
/ "nested_folder_loop"
/ "{% yield string_item from strings %}{{ string_item }}{% endyield %}"
/ "{% yield integer_item from integers %}{{ integer_item }}{% endyield %}"
/ "{{ string_item }}_{{ integer_item }}.txt.jinja": "Hello {{ string_item }} {{ integer_item }}",
}
)
with warnings.catch_warnings():
warnings.simplefilter("error")
copier.run_copy(
str(src),
dst,
data={
"strings": ["a", "b"],
"integers": [1, 2, 3],
},
defaults=True,
overwrite=True,
)
expected_files = [
dst / f"nested_folder_loop/{s}/{i}/{s}_{i}.txt"
for s in ["a", "b"]
for i in [1, 2, 3]
]
for f in expected_files:
assert f.exists()
assert f.read_text() == f"Hello {f.parent.parent.name} {f.parent.name}"
all_files = [p for p in dst.rglob("*") if p.is_file()]
unexpected_files = set(all_files) - set(expected_files)
assert not unexpected_files, f"Unexpected files found: {unexpected_files}"
def test_file_loop(tmp_path_factory: pytest.TempPathFactory) -> None:
src, dst = map(tmp_path_factory.mktemp, ("src", "dst"))
build_file_tree(
{
src / "copier.yml": "",
src
/ "file_loop"
/ "{% yield string_item from strings %}{{ string_item }}{% endyield %}.jinja": "Hello {{ string_item }}",
}
)
with warnings.catch_warnings():
warnings.simplefilter("error")
copier.run_copy(
str(src),
dst,
data={
"strings": [
"a.txt",
"b.txt",
"c.txt",
"",
], # if rendred as '.jinja', it will not be created
},
defaults=True,
overwrite=True,
)
expected_files = [dst / f"file_loop/{i}.txt" for i in ["a", "b", "c"]]
for f in expected_files:
assert f.exists()
assert f.read_text() == f"Hello {f.stem}.txt"
all_files = [p for p in dst.rglob("*") if p.is_file()]
unexpected_files = set(all_files) - set(expected_files)
assert not unexpected_files, f"Unexpected files found: {unexpected_files}"
def test_folder_loop_dict_items(tmp_path_factory: pytest.TempPathFactory) -> None:
src, dst = map(tmp_path_factory.mktemp, ("src", "dst"))
build_file_tree(
{
src / "copier.yml": "",
src
/ "folder_loop_dict_items"
/ "{% yield dict_item from dicts %}{{ dict_item.folder_name }}{% endyield %}"
/ "{{ dict_item.file_name }}.txt.jinja": "Hello {{ '-'.join(dict_item.content) }}",
}
)
dicts = [
{
"folder_name": "folder_a",
"file_name": "file_a",
"content": ["folder_a", "file_a"],
},
{
"folder_name": "folder_b",
"file_name": "file_b",
"content": ["folder_b", "file_b"],
},
{
"folder_name": "folder_c",
"file_name": "file_c",
"content": ["folder_c", "file_c"],
},
]
with warnings.catch_warnings():
warnings.simplefilter("error")
copier.run_copy(
str(src),
dst,
data={"dicts": dicts},
defaults=True,
overwrite=True,
)
expected_files = [
dst / f"folder_loop_dict_items/{d['folder_name']}/{d['file_name']}.txt"
for d in [
{"folder_name": "folder_a", "file_name": "file_a"},
{"folder_name": "folder_b", "file_name": "file_b"},
{"folder_name": "folder_c", "file_name": "file_c"},
]
]
for f in expected_files:
assert f.exists()
assert f.read_text() == f"Hello {'-'.join([f.parts[-2], f.stem])}"
all_files = [p for p in dst.rglob("*") if p.is_file()]
unexpected_files = set(all_files) - set(expected_files)
assert not unexpected_files, f"Unexpected files found: {unexpected_files}"
def test_raise_yield_tag_in_file(tmp_path_factory: pytest.TempPathFactory) -> None:
src, dst = map(tmp_path_factory.mktemp, ("src", "dst"))
build_file_tree(
{
src / "copier.yml": "",
src
/ "file.txt.jinja": "{% yield item from strings %}{{ item }}{% endyield %}",
}
)
with pytest.raises(YieldTagInFileError, match="file.txt.jinja"):
copier.run_copy(
str(src),
dst,
data={
"strings": ["a", "b", "c"],
},
defaults=True,
overwrite=True,
)
def test_raise_multiple_yield_tags(tmp_path_factory: pytest.TempPathFactory) -> None:
src, dst = map(tmp_path_factory.mktemp, ("src", "dst"))
# multiple yield tags, not nested
file_name = "{% yield item1 from strings %}{{ item1 }}{% endyield %}{% yield item2 from strings %}{{ item2 }}{% endyield %}"
build_file_tree(
{
src / "copier.yml": "",
src / file_name: "",
}
)
with pytest.raises(MultipleYieldTagsError, match="item"):
copier.run_copy(
str(src),
dst,
data={
"strings": ["a", "b", "c"],
},
defaults=True,
overwrite=True,
)
src, dst = map(tmp_path_factory.mktemp, ("src", "dst"))
# multiple yield tags, nested
file_name = "{% yield item1 from strings %}{% yield item2 from strings %}{{ item1 }}{{ item2 }}{% endyield %}{% endyield %}"
build_file_tree(
{
src / "copier.yml": "",
src / file_name: "",
}
)
with pytest.raises(MultipleYieldTagsError, match="item"):
copier.run_copy(
str(src),
dst,
data={
"strings": ["a", "b", "c"],
},
defaults=True,
overwrite=True,
)