path-sync 0.2.1__py3-none-any.whl → 0.3.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- path_sync/__init__.py +10 -1
- path_sync/__main__.py +3 -3
- path_sync/_internal/__init__.py +1 -0
- path_sync/{cmd_boot.py → _internal/cmd_boot.py} +7 -11
- path_sync/{cmd_copy.py → _internal/cmd_copy.py} +96 -73
- path_sync/{cmd_validate.py → _internal/cmd_validate.py} +7 -13
- path_sync/{git_ops.py → _internal/git_ops.py} +3 -9
- path_sync/_internal/header.py +83 -0
- path_sync/{models.py → _internal/models.py} +15 -24
- path_sync/{validation.py → _internal/validation.py} +5 -23
- path_sync/config.py +14 -0
- path_sync/copy.py +4 -0
- path_sync/sections.py +61 -104
- {path_sync-0.2.1.dist-info → path_sync-0.3.1.dist-info}/METADATA +11 -5
- path_sync-0.3.1.dist-info/RECORD +21 -0
- path_sync-0.3.1.dist-info/licenses/LICENSE +21 -0
- path_sync/cmd_copy_test.py +0 -180
- path_sync/conftest.py +0 -15
- path_sync/header.py +0 -80
- path_sync/header_test.py +0 -41
- path_sync/models_test.py +0 -69
- path_sync/sections_test.py +0 -128
- path_sync/validation_test.py +0 -114
- path_sync-0.2.1.dist-info/RECORD +0 -23
- /path_sync/{file_utils.py → _internal/file_utils.py} +0 -0
- /path_sync/{typer_app.py → _internal/typer_app.py} +0 -0
- /path_sync/{yaml_utils.py → _internal/yaml_utils.py} +0 -0
- {path_sync-0.2.1.dist-info → path_sync-0.3.1.dist-info}/WHEEL +0 -0
- {path_sync-0.2.1.dist-info → path_sync-0.3.1.dist-info}/entry_points.txt +0 -0
path_sync/models_test.py
DELETED
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
from path_sync.models import (
|
|
2
|
-
Destination,
|
|
3
|
-
PathMapping,
|
|
4
|
-
PRDefaults,
|
|
5
|
-
SrcConfig,
|
|
6
|
-
find_repo_root,
|
|
7
|
-
resolve_config_path,
|
|
8
|
-
)
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
def test_path_mapping_resolved():
|
|
12
|
-
m1 = PathMapping(src_path="src/file.py")
|
|
13
|
-
assert m1.resolved_dest_path() == "src/file.py"
|
|
14
|
-
|
|
15
|
-
m2 = PathMapping(src_path="src/file.py", dest_path="dest/file.py")
|
|
16
|
-
assert m2.resolved_dest_path() == "dest/file.py"
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
def test_resolve_config_path(tmp_path):
|
|
20
|
-
src_path = resolve_config_path(tmp_path, "sdlc")
|
|
21
|
-
assert src_path == tmp_path / ".github" / "sdlc.src.yaml"
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
def test_find_repo_root(tmp_repo):
|
|
25
|
-
subdir = tmp_repo / "a" / "b"
|
|
26
|
-
subdir.mkdir(parents=True)
|
|
27
|
-
found = find_repo_root(subdir)
|
|
28
|
-
assert found == tmp_repo
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
def test_src_config_find_destination():
|
|
32
|
-
config = SrcConfig(
|
|
33
|
-
name="test",
|
|
34
|
-
destinations=[
|
|
35
|
-
Destination(name="repo1", dest_path_relative="../repo1"),
|
|
36
|
-
Destination(name="repo2", dest_path_relative="../repo2"),
|
|
37
|
-
],
|
|
38
|
-
)
|
|
39
|
-
dest = config.find_destination("repo1")
|
|
40
|
-
assert dest.name == "repo1"
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
def test_destination_skip_sections():
|
|
44
|
-
dest = Destination(
|
|
45
|
-
name="test",
|
|
46
|
-
dest_path_relative="../test",
|
|
47
|
-
skip_sections={"justfile": ["pkg-ext"], "pyproject.toml": ["coverage"]},
|
|
48
|
-
)
|
|
49
|
-
assert dest.skip_sections["justfile"] == ["pkg-ext"]
|
|
50
|
-
assert dest.skip_sections.get("unknown", []) == []
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
def test_pr_defaults_format_body():
|
|
54
|
-
pr = PRDefaults()
|
|
55
|
-
body = pr.format_body(
|
|
56
|
-
src_repo_url="https://github.com/user/my-repo",
|
|
57
|
-
src_sha="abc12345def67890",
|
|
58
|
-
sync_log="INFO Wrote: file.py",
|
|
59
|
-
dest_name="dest1",
|
|
60
|
-
)
|
|
61
|
-
assert "[my-repo](https://github.com/user/my-repo)" in body
|
|
62
|
-
assert "`abc12345`" in body
|
|
63
|
-
assert "INFO Wrote: file.py" in body
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
def test_pr_defaults_format_body_extracts_repo_name():
|
|
67
|
-
pr = PRDefaults(body_template="{src_repo_name}")
|
|
68
|
-
assert pr.format_body("https://github.com/u/repo", "sha", "", "") == "repo"
|
|
69
|
-
assert pr.format_body("https://github.com/u/repo.git", "sha", "", "") == "repo"
|
path_sync/sections_test.py
DELETED
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
import pytest
|
|
2
|
-
|
|
3
|
-
from path_sync import sections
|
|
4
|
-
|
|
5
|
-
JUSTFILE_CONTENT = """\
|
|
6
|
-
# path-sync copy -n python-template
|
|
7
|
-
|
|
8
|
-
# === OK_EDIT ===
|
|
9
|
-
# Custom variables
|
|
10
|
-
|
|
11
|
-
# === DO_NOT_EDIT: path-sync standard ===
|
|
12
|
-
pre-push: lint test
|
|
13
|
-
# === OK_EDIT ===
|
|
14
|
-
|
|
15
|
-
# === DO_NOT_EDIT: path-sync coverage ===
|
|
16
|
-
cov:
|
|
17
|
-
uv run pytest --cov
|
|
18
|
-
# === OK_EDIT ===
|
|
19
|
-
"""
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
def test_parse_sections():
|
|
23
|
-
result = sections.parse_sections(JUSTFILE_CONTENT)
|
|
24
|
-
assert len(result) == 2
|
|
25
|
-
assert result[0].id == "standard"
|
|
26
|
-
assert result[0].content == "pre-push: lint test"
|
|
27
|
-
assert result[1].id == "coverage"
|
|
28
|
-
assert "uv run pytest --cov" in result[1].content
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
def test_parse_sections_no_markers():
|
|
32
|
-
assert sections.parse_sections("just plain content\nno markers") == []
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
def test_parse_sections_nested_error():
|
|
36
|
-
content = """\
|
|
37
|
-
# === DO_NOT_EDIT: path-sync outer ===
|
|
38
|
-
# === DO_NOT_EDIT: path-sync inner ===
|
|
39
|
-
# === OK_EDIT ===
|
|
40
|
-
# === OK_EDIT ===
|
|
41
|
-
"""
|
|
42
|
-
with pytest.raises(ValueError, match="Nested section"):
|
|
43
|
-
sections.parse_sections(content)
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
def test_parse_sections_unclosed_error():
|
|
47
|
-
content = "# === DO_NOT_EDIT: path-sync test ===\nsome content"
|
|
48
|
-
with pytest.raises(ValueError, match="Unclosed section"):
|
|
49
|
-
sections.parse_sections(content)
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
def test_parse_sections_standalone_ok_edit():
|
|
53
|
-
content = "# === OK_EDIT ===\nsome content\n# === OK_EDIT ==="
|
|
54
|
-
assert (
|
|
55
|
-
sections.parse_sections(content) == []
|
|
56
|
-
) # standalone OK_EDIT is valid, ignored
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
def test_has_sections():
|
|
60
|
-
assert sections.has_sections(JUSTFILE_CONTENT)
|
|
61
|
-
assert not sections.has_sections("plain content")
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
def test_wrap_in_default_section():
|
|
65
|
-
result = sections.wrap_in_default_section("content here")
|
|
66
|
-
assert "DO_NOT_EDIT: path-sync default" in result
|
|
67
|
-
assert "content here" in result
|
|
68
|
-
assert result.endswith("# === OK_EDIT ===")
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
def test_extract_sections():
|
|
72
|
-
result = sections.extract_sections(JUSTFILE_CONTENT)
|
|
73
|
-
assert result == {
|
|
74
|
-
"standard": "pre-push: lint test",
|
|
75
|
-
"coverage": "cov:\n uv run pytest --cov",
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
def test_replace_sections_updates_content():
|
|
80
|
-
dest = """\
|
|
81
|
-
# === DO_NOT_EDIT: path-sync standard ===
|
|
82
|
-
old content
|
|
83
|
-
# === OK_EDIT ==="""
|
|
84
|
-
src_sections = {"standard": "new content"}
|
|
85
|
-
result = sections.replace_sections(dest, src_sections)
|
|
86
|
-
assert "new content" in result
|
|
87
|
-
assert "old content" not in result
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
def test_replace_sections_preserves_ok_edit():
|
|
91
|
-
dest = """\
|
|
92
|
-
# custom header
|
|
93
|
-
# === DO_NOT_EDIT: path-sync standard ===
|
|
94
|
-
old
|
|
95
|
-
# === OK_EDIT ===
|
|
96
|
-
# my custom stuff"""
|
|
97
|
-
result = sections.replace_sections(dest, {"standard": "new"})
|
|
98
|
-
assert "# custom header" in result
|
|
99
|
-
assert "# my custom stuff" in result
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
def test_replace_sections_skip():
|
|
103
|
-
dest = """\
|
|
104
|
-
# === DO_NOT_EDIT: path-sync standard ===
|
|
105
|
-
keep this
|
|
106
|
-
# === OK_EDIT ==="""
|
|
107
|
-
result = sections.replace_sections(
|
|
108
|
-
dest, {"standard": "replaced"}, skip_sections=["standard"]
|
|
109
|
-
)
|
|
110
|
-
assert "keep this" in result # skipped sections preserve dest content
|
|
111
|
-
assert "replaced" not in result
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
def test_replace_sections_adds_new():
|
|
115
|
-
dest = "# plain file"
|
|
116
|
-
result = sections.replace_sections(dest, {"newid": "new content"})
|
|
117
|
-
assert "DO_NOT_EDIT: path-sync newid" in result
|
|
118
|
-
assert "new content" in result
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
def test_replace_sections_keeps_dest_only():
|
|
122
|
-
dest = """\
|
|
123
|
-
# === DO_NOT_EDIT: path-sync custom ===
|
|
124
|
-
my custom section
|
|
125
|
-
# === OK_EDIT ==="""
|
|
126
|
-
result = sections.replace_sections(dest, {})
|
|
127
|
-
assert "my custom section" in result # dest-only sections preserved
|
|
128
|
-
assert "DO_NOT_EDIT: path-sync custom" in result
|
path_sync/validation_test.py
DELETED
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
from git import Repo
|
|
2
|
-
|
|
3
|
-
from path_sync import validation
|
|
4
|
-
from path_sync.header import get_header_line
|
|
5
|
-
|
|
6
|
-
HEADER = get_header_line(".py", "test-config")
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
def _setup_baseline(repo_path, filename: str, content: str) -> None:
|
|
10
|
-
"""Commit content as baseline on main and create origin/main ref."""
|
|
11
|
-
repo = Repo(repo_path)
|
|
12
|
-
file_path = repo_path / filename
|
|
13
|
-
file_path.write_text(content)
|
|
14
|
-
repo.index.add([filename])
|
|
15
|
-
repo.index.commit("baseline")
|
|
16
|
-
repo.create_head("origin/main", repo.head.commit)
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
def test_modify_ok_edit_passes(tmp_repo):
|
|
20
|
-
baseline = f"""{HEADER}
|
|
21
|
-
# === OK_EDIT ===
|
|
22
|
-
user content
|
|
23
|
-
# === DO_NOT_EDIT: path-sync standard ===
|
|
24
|
-
protected
|
|
25
|
-
# === OK_EDIT ===
|
|
26
|
-
"""
|
|
27
|
-
_setup_baseline(tmp_repo, "test.py", baseline)
|
|
28
|
-
|
|
29
|
-
current = f"""{HEADER}
|
|
30
|
-
# === OK_EDIT ===
|
|
31
|
-
modified user content
|
|
32
|
-
# === DO_NOT_EDIT: path-sync standard ===
|
|
33
|
-
protected
|
|
34
|
-
# === OK_EDIT ===
|
|
35
|
-
"""
|
|
36
|
-
(tmp_repo / "test.py").write_text(current)
|
|
37
|
-
|
|
38
|
-
result = validation.validate_no_unauthorized_changes(tmp_repo, "main")
|
|
39
|
-
assert result == []
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
def test_modify_do_not_edit_fails(tmp_repo):
|
|
43
|
-
baseline = f"""{HEADER}
|
|
44
|
-
# === DO_NOT_EDIT: path-sync standard ===
|
|
45
|
-
protected content
|
|
46
|
-
# === OK_EDIT ===
|
|
47
|
-
"""
|
|
48
|
-
_setup_baseline(tmp_repo, "test.py", baseline)
|
|
49
|
-
|
|
50
|
-
current = f"""{HEADER}
|
|
51
|
-
# === DO_NOT_EDIT: path-sync standard ===
|
|
52
|
-
MODIFIED protected content
|
|
53
|
-
# === OK_EDIT ===
|
|
54
|
-
"""
|
|
55
|
-
(tmp_repo / "test.py").write_text(current)
|
|
56
|
-
|
|
57
|
-
result = validation.validate_no_unauthorized_changes(tmp_repo, "main")
|
|
58
|
-
assert result == ["test.py:standard"]
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
def test_skip_section_passes(tmp_repo):
|
|
62
|
-
baseline = f"""{HEADER}
|
|
63
|
-
# === DO_NOT_EDIT: path-sync coverage ===
|
|
64
|
-
protected
|
|
65
|
-
# === OK_EDIT ===
|
|
66
|
-
"""
|
|
67
|
-
_setup_baseline(tmp_repo, "test.py", baseline)
|
|
68
|
-
|
|
69
|
-
current = f"""{HEADER}
|
|
70
|
-
# === DO_NOT_EDIT: path-sync coverage ===
|
|
71
|
-
MODIFIED
|
|
72
|
-
# === OK_EDIT ===
|
|
73
|
-
"""
|
|
74
|
-
(tmp_repo / "test.py").write_text(current)
|
|
75
|
-
|
|
76
|
-
result = validation.validate_no_unauthorized_changes(
|
|
77
|
-
tmp_repo, "main", skip_sections={"test.py": {"coverage"}}
|
|
78
|
-
)
|
|
79
|
-
assert result == []
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
def test_section_removed_fails(tmp_repo):
|
|
83
|
-
baseline = f"""{HEADER}
|
|
84
|
-
# === DO_NOT_EDIT: path-sync standard ===
|
|
85
|
-
protected
|
|
86
|
-
# === OK_EDIT ===
|
|
87
|
-
"""
|
|
88
|
-
_setup_baseline(tmp_repo, "test.py", baseline)
|
|
89
|
-
|
|
90
|
-
current = f"""{HEADER}
|
|
91
|
-
# no sections anymore
|
|
92
|
-
"""
|
|
93
|
-
(tmp_repo / "test.py").write_text(current)
|
|
94
|
-
|
|
95
|
-
result = validation.validate_no_unauthorized_changes(tmp_repo, "main")
|
|
96
|
-
assert result == ["test.py:standard"]
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
def test_no_sections_full_file_comparison(tmp_repo):
|
|
100
|
-
baseline = f"{HEADER}\noriginal content\n"
|
|
101
|
-
_setup_baseline(tmp_repo, "test.py", baseline)
|
|
102
|
-
|
|
103
|
-
(tmp_repo / "test.py").write_text(f"{HEADER}\nmodified content\n")
|
|
104
|
-
|
|
105
|
-
result = validation.validate_no_unauthorized_changes(tmp_repo, "main")
|
|
106
|
-
assert result == ["test.py"]
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
def test_parse_skip_sections():
|
|
110
|
-
result = validation.parse_skip_sections("justfile:coverage,pyproject.toml:default")
|
|
111
|
-
assert result == {"justfile": {"coverage"}, "pyproject.toml": {"default"}}
|
|
112
|
-
|
|
113
|
-
result = validation.parse_skip_sections("path:a,path:b")
|
|
114
|
-
assert result == {"path": {"a", "b"}}
|
path_sync-0.2.1.dist-info/RECORD
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
path_sync/__init__.py,sha256=fdUhqFKjflvMkVbUuA_RMMv4am652KWK8N3IVQzj6ok,63
|
|
2
|
-
path_sync/__main__.py,sha256=Hh0Na0BlS0EwBAerhWD4mnaB-oMo6jufFVQBuzNZk3g,296
|
|
3
|
-
path_sync/cmd_boot.py,sha256=hYSrMF9QHVXX5feO2UE3lFSJvV38tEsJaNl0sjU2gbw,2896
|
|
4
|
-
path_sync/cmd_copy.py,sha256=RcIv3s6HDiOHU3Z1Q5HYzVIOGDc_4cYqYGyj9Gpnjlk,15701
|
|
5
|
-
path_sync/cmd_copy_test.py,sha256=dcpRaFAmXuUavIvtA59_In4Fa58FKRklNcYmeGsEuVA,5049
|
|
6
|
-
path_sync/cmd_validate.py,sha256=wcMo_JR2jxFtqaNQt1mk_Mnwy_jPSpFHCXFuzNOphEU,1624
|
|
7
|
-
path_sync/conftest.py,sha256=-iu7W2Bh2aRzTR1x3aMnkWkrVnDMp6ezdh1AFgNcYUE,343
|
|
8
|
-
path_sync/file_utils.py,sha256=5C33qzKFQdwChi5YwUWBujj126t0P6dbGSU_5hWExpE,194
|
|
9
|
-
path_sync/git_ops.py,sha256=1ixXKtseZAWAJP9uJcAb78IGQOmRekriv-n_T1nx0mI,5945
|
|
10
|
-
path_sync/header.py,sha256=2YSCj7ainj5TPFINBHn8Uc2ECu591pZfd7NSOZMX5XA,2293
|
|
11
|
-
path_sync/header_test.py,sha256=R9jwOulSKR70HrFoxYBXUx3DGjdzfB2tZNu9sjL_3rA,1401
|
|
12
|
-
path_sync/models.py,sha256=GR2J8PRtAORltvnL73In6zjdNbkELtUomHdwPHfsWwU,3832
|
|
13
|
-
path_sync/models_test.py,sha256=m9kZbl3CGABrg58owNLx4Aiv5LPvKz0t61o0336J5x8,2052
|
|
14
|
-
path_sync/sections.py,sha256=jzzzt2e-umjY0Ab-d7W-29y7aVVBtf_vEX_abFEdZdo,3681
|
|
15
|
-
path_sync/sections_test.py,sha256=lsApYMe1BqGL7T0sYhbs28VCrDm9dmkq0G3JAvGOlPI,3546
|
|
16
|
-
path_sync/typer_app.py,sha256=lEGMRXql3Se3VbmwAohvpUaL2cbY-RwhPUq8kL7bPbc,177
|
|
17
|
-
path_sync/validation.py,sha256=RP9SWd69mGvB4MxGx4RFJiyIQ-X482Y5h7G7VYAOxiE,2766
|
|
18
|
-
path_sync/validation_test.py,sha256=NdK1JHAtjGIm7m7uVm0fM751ttJhKXrS21gt1eEDhP0,3071
|
|
19
|
-
path_sync/yaml_utils.py,sha256=yj6Bl54EltjLEcVKaiA5Ahb9byT6OUMh0xIEzTsrvnQ,498
|
|
20
|
-
path_sync-0.2.1.dist-info/METADATA,sha256=4-DnsGErtoA24qkvnBMXK4HuKm7YR-ZhX4FZ5h2zXXQ,7785
|
|
21
|
-
path_sync-0.2.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
22
|
-
path_sync-0.2.1.dist-info/entry_points.txt,sha256=jTsL0c-9gP-4_Jt3EPgihtpLcwQR0AFAf1AUpD50AlI,54
|
|
23
|
-
path_sync-0.2.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|