path-sync 0.3.0__tar.gz

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.
@@ -0,0 +1,39 @@
1
+ # path-sync copy -n python-template
2
+
3
+ # === DO_NOT_EDIT: path-sync gitignore ===
4
+ # Python bytecode
5
+ __pycache__/
6
+ *.pyc
7
+ *.pyo
8
+
9
+ # Virtual environments
10
+ .venv/
11
+ venv/
12
+
13
+ # Build artifacts
14
+ dist/
15
+ build/
16
+ *.egg-info/
17
+
18
+ # Coverage
19
+ .coverage
20
+ htmlcov/
21
+ coverage.xml
22
+
23
+ # Cache directories
24
+ .ruff_cache/
25
+ .pytest_cache/
26
+ .mypy_cache/
27
+
28
+ # IDE
29
+ .idea/
30
+ *.iml
31
+ .vscode/
32
+
33
+ # pkg-ext dev mode files
34
+ .groups-dev.yaml
35
+ CHANGELOG-dev.md
36
+
37
+ # MkDocs build output
38
+ site/
39
+ # === OK_EDIT: path-sync gitignore ===
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Your Name
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,277 @@
1
+ Metadata-Version: 2.4
2
+ Name: path-sync
3
+ Version: 0.3.0
4
+ Summary: Sync files from a source repo to multiple destination repos
5
+ Author-email: EspenAlbert <espen.albert1@gmail.com>
6
+ License-Expression: MIT
7
+ License-File: LICENSE
8
+ Keywords: cli,repository,sync,template
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Programming Language :: Python :: 3.13
11
+ Requires-Python: >=3.13
12
+ Requires-Dist: gitpython>=3.1.0
13
+ Requires-Dist: pydantic>=2.0
14
+ Requires-Dist: pyyaml>=6.0
15
+ Requires-Dist: typer>=0.16.0
16
+ Requires-Dist: zero-3rdparty>=0.100.0
17
+ Description-Content-Type: text/markdown
18
+
19
+ # path-sync
20
+
21
+ Sync files from a source repo to multiple destination repos.
22
+
23
+ ## Overview
24
+
25
+ **Problem**: You have shared config files (linter rules, CI templates, editor settings) that should be consistent across multiple repositories. Manual copying leads to drift.
26
+
27
+ **Solution**: path-sync provides one-way file syncing with clear ownership:
28
+
29
+ | Term | Definition |
30
+ |------|------------|
31
+ | **SRC** | Source repository containing the canonical files |
32
+ | **DEST** | Destination repository receiving synced files |
33
+ | **Header** | Comment added to synced files marking them as managed |
34
+ | **Section** | Marked region within a file for partial syncing |
35
+
36
+ **Key behaviors**:
37
+ - SRC owns synced content; DEST should not edit it
38
+ - Files with headers are updated on each sync
39
+ - Remove a header to opt-out (file becomes DEST-owned)
40
+ - Orphaned files (removed from SRC) are deleted in DEST
41
+
42
+ ## Installation
43
+
44
+ ```bash
45
+ # From PyPI
46
+ uvx path-sync --help
47
+
48
+ # Or install in project
49
+ uv pip install path-sync
50
+ ```
51
+
52
+ ## Quick Start
53
+
54
+ ### 1. Bootstrap a source config
55
+
56
+ ```bash
57
+ path-sync boot -n myconfig -d ../dest-repo1 -d ../dest-repo2 -p '.cursor/**/*.mdc'
58
+ ```
59
+
60
+ Creates `.github/myconfig.src.yaml` with auto-detected git remote and destinations.
61
+
62
+ ### 2. Copy files to destinations
63
+
64
+ ```bash
65
+ path-sync copy -n myconfig
66
+ ```
67
+
68
+ By default, prompts before each git operation. See [Usage Scenarios](#usage-scenarios) for common patterns.
69
+
70
+ | Flag | Description |
71
+ |------|-------------|
72
+ | `-d dest1,dest2` | Filter specific destinations |
73
+ | `--dry-run` | Preview without writing (requires existing repos) |
74
+ | `-y, --no-prompt` | Skip confirmations (for CI) |
75
+ | `--local` | No git ops after sync (no commit/push/PR) |
76
+ | `--no-checkout` | Skip branch switching (assumes already on correct branch) |
77
+ | `--checkout-from-default` | Reset to origin/default before sync |
78
+ | `--no-pr` | Push but skip PR creation |
79
+ | `--force-overwrite` | Overwrite files even if header removed (opted out) |
80
+ | `--detailed-exit-code` | Exit 0=no changes, 1=changes, 2=error |
81
+ | `--skip-orphan-cleanup` | Skip deletion of orphaned synced files |
82
+ | `--pr-title` | Override PR title (supports `{name}`, `{dest_name}`) |
83
+ | `--pr-labels` | Comma-separated PR labels |
84
+ | `--pr-reviewers` | Comma-separated PR reviewers |
85
+ | `--pr-assignees` | Comma-separated PR assignees |
86
+
87
+ ### 3. Validate (run in dest repo)
88
+
89
+ ```bash
90
+ uvx path-sync validate-no-changes -b main
91
+ ```
92
+
93
+ Options:
94
+ - `-b, --branch` - Default branch to compare against (default: main)
95
+ - `--skip-sections` - Comma-separated `path:section_id` pairs to skip (e.g., `justfile:coverage`)
96
+
97
+ ## Usage Scenarios
98
+
99
+ | Scenario | Command |
100
+ |----------|---------|
101
+ | Interactive sync | `copy -n cfg` |
102
+ | CI fresh sync | `copy -n cfg --checkout-from-default -y` |
103
+ | Local preview | `copy -n cfg --dry-run` |
104
+ | Local test files | `copy -n cfg --local` |
105
+ | Already on branch | `copy -n cfg --no-checkout` |
106
+ | Push, manual PR | `copy -n cfg --no-pr -y` |
107
+ | Force opted-out | `copy -n cfg --force-overwrite` |
108
+
109
+ **Interactive prompt behavior**: Declining the checkout prompt syncs files but skips commit/push/PR (same as `--local`). Use `--no-checkout` when you're already on the correct branch and want to proceed with git operations.
110
+
111
+ ## Section Markers
112
+
113
+ For partial file syncing (e.g., `justfile`, `pyproject.toml`), wrap sections with markers:
114
+
115
+ ```makefile
116
+ # === DO_NOT_EDIT: path-sync default ===
117
+ lint:
118
+ ruff check .
119
+ # === OK_EDIT ===
120
+ ```
121
+
122
+ - **`DO_NOT_EDIT: path-sync {id}`** - Start of managed section with identifier
123
+ - **`OK_EDIT`** - End marker (content below is editable)
124
+
125
+ During sync, only content within markers is replaced. Destination can have extra sections.
126
+
127
+ Use `skip_sections` in destination config to exclude specific sections from sync:
128
+
129
+ ```yaml
130
+ destinations:
131
+ - name: dest1
132
+ dest_path_relative: ../dest1
133
+ skip_sections:
134
+ justfile: [coverage] # keep local coverage recipe
135
+ ```
136
+ ## Config Reference
137
+
138
+ **Source config** (`.github/{name}.src.yaml`):
139
+
140
+ ```yaml
141
+ name: cursor
142
+ src_repo_url: https://github.com/user/src-repo
143
+ schedule: "0 6 * * *"
144
+ paths:
145
+ - src_path: .cursor/**/*.mdc
146
+ - src_path: templates/justfile
147
+ dest_path: justfile
148
+ destinations:
149
+ - name: dest1
150
+ repo_url: https://github.com/user/dest1
151
+ dest_path_relative: ../dest1
152
+ # copy_branch: sync/cursor # defaults to sync/{config_name}
153
+ default_branch: main
154
+ skip_sections:
155
+ justfile: [coverage]
156
+ ```
157
+
158
+ | Field | Description |
159
+ |-------|-------------|
160
+ | `name` | Config identifier |
161
+ | `src_repo_url` | Source repo URL (auto-detected from git remote) |
162
+ | `schedule` | Cron for scheduled sync workflow |
163
+ | `paths` | Files/globs to sync (`src_path` required, `dest_path` optional) |
164
+ | `destinations` | Target repos with sync settings |
165
+ | `header_config` | Comment style per extension (has defaults) |
166
+ | `pr_defaults` | PR title, labels, reviewers, assignees |
167
+
168
+ ## Header Format
169
+
170
+ Synced files have a header comment identifying the source config:
171
+
172
+ ```python
173
+ # path-sync copy -n myconfig
174
+ ```
175
+
176
+ Comment style is extension-aware:
177
+
178
+ | Extension | Format |
179
+ |-----------|--------|
180
+ | `.py`, `.sh`, `.yaml` | `# path-sync copy -n {name}` |
181
+ | `.go`, `.js`, `.ts` | `// path-sync copy -n {name}` |
182
+ | `.md`, `.mdc`, `.html` | `<!-- path-sync copy -n {name} -->` |
183
+
184
+ Remove this header to opt-out of future syncs for that file.
185
+
186
+ ## GitHub Actions
187
+
188
+ ### Source repo workflow
189
+
190
+ Create `.github/workflows/path_sync_copy.yaml`:
191
+
192
+ ```yaml
193
+ name: path-sync copy
194
+ on:
195
+ schedule:
196
+ - cron: "0 6 * * *"
197
+ workflow_dispatch:
198
+
199
+ jobs:
200
+ sync:
201
+ runs-on: ubuntu-latest
202
+ steps:
203
+ - uses: actions/checkout@v4
204
+ - uses: astral-sh/setup-uv@v5
205
+ - run: uvx path-sync copy -n myconfig --checkout-from-default -y
206
+ env:
207
+ GH_TOKEN: ${{ secrets.GH_PAT }}
208
+ ```
209
+
210
+ ### Destination repo validation
211
+
212
+ Create `.github/workflows/path_sync_validate.yaml`:
213
+
214
+ ```yaml
215
+ name: path-sync validate
216
+ on:
217
+ push:
218
+ branches-ignore:
219
+ - main
220
+ - sync/**
221
+ pull_request:
222
+ branches:
223
+ - main
224
+
225
+ jobs:
226
+ validate:
227
+ runs-on: ubuntu-latest
228
+ steps:
229
+ - uses: actions/checkout@v4
230
+ with:
231
+ fetch-depth: 0
232
+ - uses: astral-sh/setup-uv@v5
233
+ - run: uvx path-sync validate-no-changes -b main
234
+ ```
235
+
236
+ **Validation skips automatically when:**
237
+ - On a `sync/*` branch (path-sync uses `sync/{config_name}` by default)
238
+ - On the default branch (comparing against itself)
239
+
240
+ The workflow triggers exclude these branches too, reducing unnecessary CI runs.
241
+
242
+ ### PAT Requirements
243
+
244
+ Create a **Fine-grained PAT** at <https://github.com/settings/tokens?type=beta>
245
+
246
+ | Permission | Scope |
247
+ |------------|-------|
248
+ | Contents | Read/write (push branches) |
249
+ | Pull requests | Read/write (create PRs) |
250
+ | Workflows | Read/write (if syncing `.github/workflows/`) |
251
+ | Metadata | Read (always required) |
252
+
253
+ Add as repository secret: `GH_PAT`
254
+
255
+ ### Common Errors
256
+
257
+ | Error | Fix |
258
+ |-------|-----|
259
+ | `HTTP 404: Not Found` | Add repo to PAT's repository access |
260
+ | `HTTP 403: Resource not accessible` | Add Contents + Pull requests permissions |
261
+ | `GraphQL: Resource not accessible` | Use GH_PAT, not GITHUB_TOKEN |
262
+ | `HTTP 422: Required status check` | Exclude `sync/*` from branch protection |
263
+
264
+ ## Alternatives Considered
265
+
266
+ | Tool | Why Not |
267
+ |------|---------|
268
+ | [repo-file-sync-action](https://github.com/BetaHuhn/repo-file-sync-action) | No local CLI, no validation |
269
+ | [Copier](https://copier.readthedocs.io/) | Merge-based (conflicts), no multi-dest |
270
+ | [Cruft](https://cruft.github.io/cruft/) | Patch-based, single dest |
271
+
272
+ **Why path-sync:**
273
+ - One SRC to many DEST repos
274
+ - Local CLI + CI support
275
+ - Section-level sync for shared files
276
+ - Validation enforced across repos
277
+ - Clear ownership (no merge conflicts)
@@ -0,0 +1,259 @@
1
+ # path-sync
2
+
3
+ Sync files from a source repo to multiple destination repos.
4
+
5
+ ## Overview
6
+
7
+ **Problem**: You have shared config files (linter rules, CI templates, editor settings) that should be consistent across multiple repositories. Manual copying leads to drift.
8
+
9
+ **Solution**: path-sync provides one-way file syncing with clear ownership:
10
+
11
+ | Term | Definition |
12
+ |------|------------|
13
+ | **SRC** | Source repository containing the canonical files |
14
+ | **DEST** | Destination repository receiving synced files |
15
+ | **Header** | Comment added to synced files marking them as managed |
16
+ | **Section** | Marked region within a file for partial syncing |
17
+
18
+ **Key behaviors**:
19
+ - SRC owns synced content; DEST should not edit it
20
+ - Files with headers are updated on each sync
21
+ - Remove a header to opt-out (file becomes DEST-owned)
22
+ - Orphaned files (removed from SRC) are deleted in DEST
23
+
24
+ ## Installation
25
+
26
+ ```bash
27
+ # From PyPI
28
+ uvx path-sync --help
29
+
30
+ # Or install in project
31
+ uv pip install path-sync
32
+ ```
33
+
34
+ ## Quick Start
35
+
36
+ ### 1. Bootstrap a source config
37
+
38
+ ```bash
39
+ path-sync boot -n myconfig -d ../dest-repo1 -d ../dest-repo2 -p '.cursor/**/*.mdc'
40
+ ```
41
+
42
+ Creates `.github/myconfig.src.yaml` with auto-detected git remote and destinations.
43
+
44
+ ### 2. Copy files to destinations
45
+
46
+ ```bash
47
+ path-sync copy -n myconfig
48
+ ```
49
+
50
+ By default, prompts before each git operation. See [Usage Scenarios](#usage-scenarios) for common patterns.
51
+
52
+ | Flag | Description |
53
+ |------|-------------|
54
+ | `-d dest1,dest2` | Filter specific destinations |
55
+ | `--dry-run` | Preview without writing (requires existing repos) |
56
+ | `-y, --no-prompt` | Skip confirmations (for CI) |
57
+ | `--local` | No git ops after sync (no commit/push/PR) |
58
+ | `--no-checkout` | Skip branch switching (assumes already on correct branch) |
59
+ | `--checkout-from-default` | Reset to origin/default before sync |
60
+ | `--no-pr` | Push but skip PR creation |
61
+ | `--force-overwrite` | Overwrite files even if header removed (opted out) |
62
+ | `--detailed-exit-code` | Exit 0=no changes, 1=changes, 2=error |
63
+ | `--skip-orphan-cleanup` | Skip deletion of orphaned synced files |
64
+ | `--pr-title` | Override PR title (supports `{name}`, `{dest_name}`) |
65
+ | `--pr-labels` | Comma-separated PR labels |
66
+ | `--pr-reviewers` | Comma-separated PR reviewers |
67
+ | `--pr-assignees` | Comma-separated PR assignees |
68
+
69
+ ### 3. Validate (run in dest repo)
70
+
71
+ ```bash
72
+ uvx path-sync validate-no-changes -b main
73
+ ```
74
+
75
+ Options:
76
+ - `-b, --branch` - Default branch to compare against (default: main)
77
+ - `--skip-sections` - Comma-separated `path:section_id` pairs to skip (e.g., `justfile:coverage`)
78
+
79
+ ## Usage Scenarios
80
+
81
+ | Scenario | Command |
82
+ |----------|---------|
83
+ | Interactive sync | `copy -n cfg` |
84
+ | CI fresh sync | `copy -n cfg --checkout-from-default -y` |
85
+ | Local preview | `copy -n cfg --dry-run` |
86
+ | Local test files | `copy -n cfg --local` |
87
+ | Already on branch | `copy -n cfg --no-checkout` |
88
+ | Push, manual PR | `copy -n cfg --no-pr -y` |
89
+ | Force opted-out | `copy -n cfg --force-overwrite` |
90
+
91
+ **Interactive prompt behavior**: Declining the checkout prompt syncs files but skips commit/push/PR (same as `--local`). Use `--no-checkout` when you're already on the correct branch and want to proceed with git operations.
92
+
93
+ ## Section Markers
94
+
95
+ For partial file syncing (e.g., `justfile`, `pyproject.toml`), wrap sections with markers:
96
+
97
+ ```makefile
98
+ # === DO_NOT_EDIT: path-sync default ===
99
+ lint:
100
+ ruff check .
101
+ # === OK_EDIT ===
102
+ ```
103
+
104
+ - **`DO_NOT_EDIT: path-sync {id}`** - Start of managed section with identifier
105
+ - **`OK_EDIT`** - End marker (content below is editable)
106
+
107
+ During sync, only content within markers is replaced. Destination can have extra sections.
108
+
109
+ Use `skip_sections` in destination config to exclude specific sections from sync:
110
+
111
+ ```yaml
112
+ destinations:
113
+ - name: dest1
114
+ dest_path_relative: ../dest1
115
+ skip_sections:
116
+ justfile: [coverage] # keep local coverage recipe
117
+ ```
118
+ ## Config Reference
119
+
120
+ **Source config** (`.github/{name}.src.yaml`):
121
+
122
+ ```yaml
123
+ name: cursor
124
+ src_repo_url: https://github.com/user/src-repo
125
+ schedule: "0 6 * * *"
126
+ paths:
127
+ - src_path: .cursor/**/*.mdc
128
+ - src_path: templates/justfile
129
+ dest_path: justfile
130
+ destinations:
131
+ - name: dest1
132
+ repo_url: https://github.com/user/dest1
133
+ dest_path_relative: ../dest1
134
+ # copy_branch: sync/cursor # defaults to sync/{config_name}
135
+ default_branch: main
136
+ skip_sections:
137
+ justfile: [coverage]
138
+ ```
139
+
140
+ | Field | Description |
141
+ |-------|-------------|
142
+ | `name` | Config identifier |
143
+ | `src_repo_url` | Source repo URL (auto-detected from git remote) |
144
+ | `schedule` | Cron for scheduled sync workflow |
145
+ | `paths` | Files/globs to sync (`src_path` required, `dest_path` optional) |
146
+ | `destinations` | Target repos with sync settings |
147
+ | `header_config` | Comment style per extension (has defaults) |
148
+ | `pr_defaults` | PR title, labels, reviewers, assignees |
149
+
150
+ ## Header Format
151
+
152
+ Synced files have a header comment identifying the source config:
153
+
154
+ ```python
155
+ # path-sync copy -n myconfig
156
+ ```
157
+
158
+ Comment style is extension-aware:
159
+
160
+ | Extension | Format |
161
+ |-----------|--------|
162
+ | `.py`, `.sh`, `.yaml` | `# path-sync copy -n {name}` |
163
+ | `.go`, `.js`, `.ts` | `// path-sync copy -n {name}` |
164
+ | `.md`, `.mdc`, `.html` | `<!-- path-sync copy -n {name} -->` |
165
+
166
+ Remove this header to opt-out of future syncs for that file.
167
+
168
+ ## GitHub Actions
169
+
170
+ ### Source repo workflow
171
+
172
+ Create `.github/workflows/path_sync_copy.yaml`:
173
+
174
+ ```yaml
175
+ name: path-sync copy
176
+ on:
177
+ schedule:
178
+ - cron: "0 6 * * *"
179
+ workflow_dispatch:
180
+
181
+ jobs:
182
+ sync:
183
+ runs-on: ubuntu-latest
184
+ steps:
185
+ - uses: actions/checkout@v4
186
+ - uses: astral-sh/setup-uv@v5
187
+ - run: uvx path-sync copy -n myconfig --checkout-from-default -y
188
+ env:
189
+ GH_TOKEN: ${{ secrets.GH_PAT }}
190
+ ```
191
+
192
+ ### Destination repo validation
193
+
194
+ Create `.github/workflows/path_sync_validate.yaml`:
195
+
196
+ ```yaml
197
+ name: path-sync validate
198
+ on:
199
+ push:
200
+ branches-ignore:
201
+ - main
202
+ - sync/**
203
+ pull_request:
204
+ branches:
205
+ - main
206
+
207
+ jobs:
208
+ validate:
209
+ runs-on: ubuntu-latest
210
+ steps:
211
+ - uses: actions/checkout@v4
212
+ with:
213
+ fetch-depth: 0
214
+ - uses: astral-sh/setup-uv@v5
215
+ - run: uvx path-sync validate-no-changes -b main
216
+ ```
217
+
218
+ **Validation skips automatically when:**
219
+ - On a `sync/*` branch (path-sync uses `sync/{config_name}` by default)
220
+ - On the default branch (comparing against itself)
221
+
222
+ The workflow triggers exclude these branches too, reducing unnecessary CI runs.
223
+
224
+ ### PAT Requirements
225
+
226
+ Create a **Fine-grained PAT** at <https://github.com/settings/tokens?type=beta>
227
+
228
+ | Permission | Scope |
229
+ |------------|-------|
230
+ | Contents | Read/write (push branches) |
231
+ | Pull requests | Read/write (create PRs) |
232
+ | Workflows | Read/write (if syncing `.github/workflows/`) |
233
+ | Metadata | Read (always required) |
234
+
235
+ Add as repository secret: `GH_PAT`
236
+
237
+ ### Common Errors
238
+
239
+ | Error | Fix |
240
+ |-------|-----|
241
+ | `HTTP 404: Not Found` | Add repo to PAT's repository access |
242
+ | `HTTP 403: Resource not accessible` | Add Contents + Pull requests permissions |
243
+ | `GraphQL: Resource not accessible` | Use GH_PAT, not GITHUB_TOKEN |
244
+ | `HTTP 422: Required status check` | Exclude `sync/*` from branch protection |
245
+
246
+ ## Alternatives Considered
247
+
248
+ | Tool | Why Not |
249
+ |------|---------|
250
+ | [repo-file-sync-action](https://github.com/BetaHuhn/repo-file-sync-action) | No local CLI, no validation |
251
+ | [Copier](https://copier.readthedocs.io/) | Merge-based (conflicts), no multi-dest |
252
+ | [Cruft](https://cruft.github.io/cruft/) | Patch-based, single dest |
253
+
254
+ **Why path-sync:**
255
+ - One SRC to many DEST repos
256
+ - Local CLI + CI support
257
+ - Section-level sync for shared files
258
+ - Validation enforced across repos
259
+ - Clear ownership (no merge conflicts)
@@ -0,0 +1,10 @@
1
+ # Generated by pkg-ext
2
+ # flake8: noqa
3
+ from path_sync import copy
4
+ from path_sync import config
5
+
6
+ VERSION = "0.3.0"
7
+ __all__ = [
8
+ "copy",
9
+ "config",
10
+ ]
@@ -0,0 +1,14 @@
1
+ import logging
2
+
3
+ from path_sync._internal import cmd_boot, cmd_copy, cmd_validate # noqa: F401
4
+ from path_sync._internal.models import LOG_FORMAT
5
+ from path_sync._internal.typer_app import app
6
+
7
+
8
+ def main() -> None:
9
+ logging.basicConfig(level=logging.INFO, format=LOG_FORMAT)
10
+ app()
11
+
12
+
13
+ if __name__ == "__main__":
14
+ main()
@@ -0,0 +1,14 @@
1
+ # Generated by pkg-ext
2
+ from path_sync._internal.models import Destination as _Destination
3
+ from path_sync._internal.models import HeaderConfig as _HeaderConfig
4
+ from path_sync._internal.models import PathMapping as _PathMapping
5
+ from path_sync._internal.models import PRDefaults as _PRDefaults
6
+ from path_sync._internal.models import SrcConfig as _SrcConfig
7
+ from path_sync._internal.models import SyncMode as _SyncMode
8
+
9
+ Destination = _Destination
10
+ HeaderConfig = _HeaderConfig
11
+ PRDefaults = _PRDefaults
12
+ PathMapping = _PathMapping
13
+ SrcConfig = _SrcConfig
14
+ SyncMode = _SyncMode
@@ -0,0 +1,4 @@
1
+ # Generated by pkg-ext
2
+ from path_sync._internal.cmd_copy import CopyOptions as _CopyOptions
3
+
4
+ CopyOptions = _CopyOptions
@@ -0,0 +1,72 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+
5
+ from zero_3rdparty.sections import (
6
+ Section,
7
+ get_comment_config,
8
+ )
9
+ from zero_3rdparty.sections import (
10
+ compare_sections as _compare_sections,
11
+ )
12
+ from zero_3rdparty.sections import (
13
+ extract_sections as _extract_sections,
14
+ )
15
+ from zero_3rdparty.sections import (
16
+ has_sections as _has_sections,
17
+ )
18
+ from zero_3rdparty.sections import (
19
+ parse_sections as _parse_sections,
20
+ )
21
+ from zero_3rdparty.sections import (
22
+ replace_sections as _replace_sections,
23
+ )
24
+ from zero_3rdparty.sections import (
25
+ wrap_in_default_section as _wrap_in_default_section,
26
+ )
27
+
28
+ __all__ = [
29
+ "Section",
30
+ "compare_sections",
31
+ "extract_sections",
32
+ "has_sections",
33
+ "parse_sections",
34
+ "replace_sections",
35
+ "wrap_in_default_section",
36
+ ]
37
+
38
+ TOOL_NAME = "path-sync"
39
+
40
+
41
+ def has_sections(content: str, path: Path) -> bool:
42
+ return _has_sections(content, TOOL_NAME, get_comment_config(path))
43
+
44
+
45
+ def parse_sections(content: str, path: Path) -> list[Section]:
46
+ return _parse_sections(content, TOOL_NAME, get_comment_config(path))
47
+
48
+
49
+ def wrap_in_default_section(content: str, path: Path) -> str:
50
+ return _wrap_in_default_section(content, TOOL_NAME, get_comment_config(path))
51
+
52
+
53
+ def extract_sections(content: str, path: Path) -> dict[str, str]:
54
+ return _extract_sections(content, TOOL_NAME, get_comment_config(path))
55
+
56
+
57
+ def replace_sections(
58
+ dest_content: str,
59
+ src_sections: dict[str, str],
60
+ path: Path,
61
+ skip_sections: list[str] | None = None,
62
+ ) -> str:
63
+ return _replace_sections(dest_content, src_sections, TOOL_NAME, get_comment_config(path), skip_sections)
64
+
65
+
66
+ def compare_sections(
67
+ baseline_content: str,
68
+ current_content: str,
69
+ path: Path,
70
+ skip: set[str] | None = None,
71
+ ) -> list[str]:
72
+ return _compare_sections(baseline_content, current_content, TOOL_NAME, get_comment_config(path), skip)
@@ -0,0 +1,100 @@
1
+ # path-sync copy -n python-template
2
+
3
+ # === OK_EDIT: path-sync header ===
4
+ [project]
5
+ name = "path-sync"
6
+ version = "0.3.0"
7
+ description = "Sync files from a source repo to multiple destination repos"
8
+ requires-python = ">=3.13"
9
+ license = "MIT"
10
+ license-files = [
11
+ "LICENSE",
12
+ ]
13
+ readme = "README.md"
14
+ keywords = [
15
+ "sync",
16
+ "template",
17
+ "repository",
18
+ "cli",
19
+ ]
20
+ classifiers = [
21
+ "Development Status :: 4 - Beta",
22
+ "Programming Language :: Python :: 3.13",
23
+ ]
24
+ authors = [
25
+ { name = "EspenAlbert", email = "espen.albert1@gmail.com" },
26
+ ]
27
+ dependencies = [
28
+ "pydantic>=2.0",
29
+ "pyyaml>=6.0",
30
+ "typer>=0.16.0",
31
+ "gitpython>=3.1.0",
32
+ "zero-3rdparty>=0.100.0",
33
+ ]
34
+
35
+ [project.scripts]
36
+ path-sync = "path_sync.__main__:main"
37
+
38
+ [build-system]
39
+ requires = [
40
+ "hatchling",
41
+ ]
42
+ build-backend = "hatchling.build"
43
+
44
+ [tool.hatch.build.targets.sdist]
45
+ include = [
46
+ "path_sync/*.py",
47
+ ]
48
+ exclude = [
49
+ "*_test.py",
50
+ "conftest.py",
51
+ "test_*.json",
52
+ ]
53
+
54
+ [tool.pkg-ext]
55
+ tag_prefix = "v"
56
+
57
+ # === DO_NOT_EDIT: path-sync ruff ===
58
+ [tool.ruff]
59
+ line-length = 120
60
+ target-version = "py313"
61
+
62
+ [tool.ruff.lint]
63
+ # E501: line too long (handled by formatter)
64
+ # UP006: use `type` instead of `Type` from typing module
65
+ # UP007: use `X | Y` instead of `Union[X, Y]`
66
+ # UP035: use `typing` instead of `typing_extensions`
67
+ # UP040: use `type` keyword instead of `TypeAlias`
68
+ # UP046: use type parameters on functions instead of TypeVar
69
+ # UP047: use type parameters instead of `Generic[T]` subclass
70
+ extend-ignore = ["E501", "UP006", "UP007", "UP035", "UP040", "UP046", "UP047"]
71
+ extend-select = ["Q", "RUF100", "C90", "UP", "I", "T"]
72
+ # === OK_EDIT: path-sync ruff ===
73
+
74
+ # === DO_NOT_EDIT: path-sync pytest ===
75
+ [tool.pytest.ini_options]
76
+ addopts = "-s -vv --log-cli-level=INFO"
77
+ python_files = ["*_test.py", "test_*.py"]
78
+ # === OK_EDIT: path-sync pytest ===
79
+
80
+ # === DO_NOT_EDIT: path-sync coverage ===
81
+ [tool.coverage.report]
82
+ omit = ["*_test.py"]
83
+ exclude_lines = ["no cov", "if __name__ == .__main__.:"]
84
+ # === OK_EDIT: path-sync coverage ===
85
+
86
+ # === DO_NOT_EDIT: path-sync dev ===
87
+ [dependency-groups]
88
+ dev = [
89
+ "pyright>=1.1",
90
+ "pytest>=9.0",
91
+ "pytest-cov>=7.0",
92
+ "ruff>=0.14",
93
+ ]
94
+ # === OK_EDIT: path-sync dev ===
95
+
96
+ # === DO_NOT_EDIT: path-sync docs ===
97
+ docs = [
98
+ "mkdocs-material>=9.5",
99
+ ]
100
+ # === OK_EDIT: path-sync docs ===