diff --git a/src/rendercv/cli/copy_templates.py b/src/rendercv/cli/copy_templates.py index 92b9ed56..3541e580 100644 --- a/src/rendercv/cli/copy_templates.py +++ b/src/rendercv/cli/copy_templates.py @@ -1,5 +1,7 @@ +import os import pathlib import shutil +import stat from typing import Literal @@ -29,3 +31,23 @@ def copy_templates( copy_templates_to, ignore=shutil.ignore_patterns("__init__.py", "__pycache__"), ) + make_tree_writable(copy_templates_to) + + +def make_tree_writable(path: pathlib.Path) -> None: + """Add user-write permission to all files and directories in a tree. + + Why: + On immutable distributions like NixOS, package files are read-only. + shutil.copytree preserves source permissions, so copied files remain + read-only. Users need write access to customize templates. + + Args: + path: Root directory to make writable. + """ + for dirpath, _, filenames in os.walk(path): + dp = pathlib.Path(dirpath) + dp.chmod(dp.stat().st_mode | stat.S_IWUSR) + for filename in filenames: + fp = dp / filename + fp.chmod(fp.stat().st_mode | stat.S_IWUSR) diff --git a/tests/cli/test_copy_templates.py b/tests/cli/test_copy_templates.py index ccbf5d85..a501d8ed 100644 --- a/tests/cli/test_copy_templates.py +++ b/tests/cli/test_copy_templates.py @@ -1,6 +1,10 @@ +import os +import pathlib +import stat + import pytest -from rendercv.cli.copy_templates import copy_templates +from rendercv.cli.copy_templates import copy_templates, make_tree_writable @pytest.mark.parametrize("template_type", ["markdown", "typst"]) @@ -14,3 +18,42 @@ def test_copy_templates(template_type, tmp_path): assert not (destination / "__pycache__").exists() # Check that at least some files were copied assert any(destination.iterdir()) + + +def test_copy_templates_produces_writable_files(tmp_path): + destination = tmp_path / "templates" + + copy_templates("typst", destination) + + for dirpath, _, filenames in os.walk(destination): + dp = pathlib.Path(dirpath) + assert dp.stat().st_mode & stat.S_IWUSR, f"Directory {dp} is not user-writable" + for filename in filenames: + fp = dp / filename + assert fp.stat().st_mode & stat.S_IWUSR, f"File {fp} is not user-writable" + + +def test_make_tree_writable(tmp_path): + # Create a tree with read-only files and directories + subdir = tmp_path / "readonly_dir" + subdir.mkdir() + test_file = subdir / "test.txt" + test_file.write_text("content") + nested = subdir / "nested" + nested.mkdir() + nested_file = nested / "nested.txt" + nested_file.write_text("nested content") + + # Make everything read-only + nested_file.chmod(stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH) + nested.chmod(stat.S_IRUSR | stat.S_IXUSR | stat.S_IRGRP | stat.S_IROTH) + test_file.chmod(stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH) + subdir.chmod(stat.S_IRUSR | stat.S_IXUSR | stat.S_IRGRP | stat.S_IROTH) + + make_tree_writable(subdir) + + # All should be user-writable now + assert subdir.stat().st_mode & stat.S_IWUSR + assert test_file.stat().st_mode & stat.S_IWUSR + assert nested.stat().st_mode & stat.S_IWUSR + assert nested_file.stat().st_mode & stat.S_IWUSR