-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcopier.yml
More file actions
184 lines (168 loc) · 6.33 KB
/
Copy pathcopier.yml
File metadata and controls
184 lines (168 loc) · 6.33 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
# ---- Copier mechanics -------------------------------------------------------
_subdirectory: template
_answers_file: .copier-answers.yml
_min_copier_version: "9.6.0"
_envops:
keep_trailing_newline: true
# _exclude REPLACES Copier's default ignore list, so the defaults are repeated here
# plus LICENSE-APACHE.txt (vendored verbatim and pulled into LICENSE via {% include %},
# never emitted as its own file in the generated project).
_exclude:
- "copier.yml"
- "copier.yaml"
- "~*"
- "*.py[co]"
- "__pycache__"
- ".git"
- ".DS_Store"
- ".svn"
- "LICENSE-APACHE.txt"
_message_after_copy: |
"{{ project_name }}" is ready.
Next:
cd {{ _copier_conf.dst_path }}
just ci # everything should be green
# Run once on initial copy only (guarded by _copier_operation, requires Copier >= 9.6).
# These are UNSAFE features: `copier copy --trust` / `copier update --trust` required.
_tasks:
- command: git init --quiet
when: "{{ _copier_operation == 'copy' }}"
- command: uv lock
when: "{{ _copier_operation == 'copy' }}"
- command: uv sync
when: "{{ _copier_operation == 'copy' }}"
- command: uv run pre-commit install --install-hooks
when: "{{ _copier_operation == 'copy' and enable_precommit_install }}"
# _migrations run ONLY on update (never copy), version-gated to the release that
# introduced a breaking rename/restructure (they run when new >= declared > old).
# None are needed yet; add one when a variable is renamed or a file moves across
# releases — a bare overwrite would otherwise orphan or conflict the change. Example:
# _migrations:
# - version: v2.0.0
# command: ["{{ _copier_python }}", "-c", "import pathlib; pathlib.Path('old.txt').unlink(missing_ok=True)"]
# ---- Identity / metadata ----------------------------------------------------
project_name:
type: str
help: Human-readable project name.
# Free text, but must not be blank: an empty title renders a nameless AGENTS.md
# header and module docstring. Whitespace-only is rejected too. Mirrors the
# package_name validator: an empty render == valid. Control chars are rejected too:
# project_name reaches markdown headers and module docstrings, where a newline yields
# a malformed header / split docstring (cosmetic, but never intended).
validator: >-
{% if not (project_name | trim) %}
project_name must not be empty
{% elif project_name | regex_search('[\x00-\x1f]') %}
project_name must not contain control characters
{% endif %}
package_name:
type: str
help: Importable package name (lowercase, underscores).
default: "{{ project_name | lower | replace(' ', '_') | replace('-', '_') }}"
# The shape regex enforces a lowercase identifier but not keyword-safety: `class`,
# `import`, `as`, ... match yet render an unimportable `src/<kw>/` (`import class` is
# a SyntaxError) and break `--cov=<kw>`. Reject hard keywords too. Jinja has no
# `keyword` module, so the list is explicit; uppercase keywords can't reach here (the
# regex bars a leading capital) and soft keywords (match/case/type) stay legal.
# The control-char check MUST come first: Python's `$` matches before a trailing newline,
# so `^[a-z][a-z0-9_]*$` accepts "demo_project\n", which would render invalid TOML
# (`name = "demo_project<NL>"`) and a broken `src/<name><NL>/` dir at copy time.
validator: >-
{% if package_name | regex_search('[\x00-\x1f]') %}
package_name must not contain control characters
{% elif not (package_name | regex_search('^[a-z][a-z0-9_]*$')) %}
package_name must match ^[a-z][a-z0-9_]*$
{% elif package_name in ["and", "as", "assert", "async", "await", "break", "class", "continue", "def", "del", "elif", "else", "except", "finally", "for", "from", "global", "if", "import", "in", "is", "lambda", "nonlocal", "not", "or", "pass", "raise", "return", "try", "while", "with", "yield"] %}
package_name must not be a Python keyword
{% endif %}
author_name:
type: str
default: Your Name
# author_name/author_email/description are free text rendered into single-line TOML basic
# strings; a newline or other C0 control char breaks the TOML and aborts the copy-time
# `uv lock`. Unreachable via the single-line prompt, but a multi-line scalar in a --data /
# answers file would hit it — reject hard, mirroring the package_name validator.
validator: >-
{% if author_name | regex_search('[\x00-\x1f]') %}
author_name must not contain control characters
{% endif %}
author_email:
type: str
default: you@example.com
validator: >-
{% if author_email | regex_search('[\x00-\x1f]') %}
author_email must not contain control characters
{% endif %}
description:
type: str
default: A new Python project.
validator: >-
{% if description | regex_search('[\x00-\x1f]') %}
description must not contain control characters
{% endif %}
license:
type: str
default: MIT
choices:
- MIT
- Apache-2.0
- ISC
- proprietary
python_version:
type: str
default: "3.13"
choices:
- "3.13"
- "3.12"
- "3.11"
project_type:
type: str
default: library
choices:
library: library
application: application
# ---- Guardrail toggles / tuning ---------------------------------------------
ruff_ruleset:
type: str
default: all
choices:
all (every rule, version-coupled): all
curated (explicit allowlist): curated
coverage_floor:
type: int
default: 85
help: Minimum branch-coverage percentage (fail_under).
# Reject a floor that no-ops the gate (<= 0) or can never pass (> 100, coverage
# caps at 100). Mirrors the package_name validator: empty render == valid.
validator: >-
{% if coverage_floor < 1 or coverage_floor > 100 %}
coverage_floor must be between 1 and 100
{% endif %}
enable_property_tests:
type: bool
default: true
enable_mutation_tests:
type: bool
default: true
enable_policy_tests:
type: bool
default: true
enable_scanners:
type: bool
default: true
enable_dependency_audit:
type: bool
default: true
enable_renovate:
type: bool
default: true
enable_sha_pin_policy:
type: bool
default: true
# Hidden helper (never prompted). The generation-test harness passes this as False
# (see tests/conftest.py) to skip the slow pre-commit hook-install task; a real
# `copier copy` leaves it true so the downstream gets its hooks installed.
enable_precommit_install:
type: bool
default: true
when: false