sprout-template 1.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.
- sprout_template-1.3.0/LICENSE +21 -0
- sprout_template-1.3.0/PKG-INFO +394 -0
- sprout_template-1.3.0/README.md +353 -0
- sprout_template-1.3.0/pyproject.toml +257 -0
- sprout_template-1.3.0/sprout/__init__.py +108 -0
- sprout_template-1.3.0/sprout/cli.py +1502 -0
- sprout_template-1.3.0/sprout/extensions.py +181 -0
- sprout_template-1.3.0/sprout/project/__init__.py +71 -0
- sprout_template-1.3.0/sprout/project/actions.py +409 -0
- sprout_template-1.3.0/sprout/project/github.py +112 -0
- sprout_template-1.3.0/sprout/project/licenses.py +136 -0
- sprout_template-1.3.0/sprout/project/validators.py +104 -0
- sprout_template-1.3.0/sprout/prompt.py +867 -0
- sprout_template-1.3.0/sprout/prompt_model.py +167 -0
- sprout_template-1.3.0/sprout/question.py +160 -0
- sprout_template-1.3.0/sprout/registry.py +175 -0
- sprout_template-1.3.0/sprout/scaffold.py +46 -0
- sprout_template-1.3.0/sprout/style.py +86 -0
- sprout_template-1.3.0/sprout/validators.py +67 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 zigai
|
|
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,394 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sprout-template
|
|
3
|
+
Version: 1.3.0
|
|
4
|
+
Summary: A Jinja2 project generator with Python-based configuration
|
|
5
|
+
Keywords: sprout
|
|
6
|
+
Author: zigai
|
|
7
|
+
Author-email: zigai <ziga.ivansek@gmail.com>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Environment :: Console
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Programming Language :: Python
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Topic :: Software Development
|
|
19
|
+
Classifier: Topic :: Software Development :: Code Generators
|
|
20
|
+
Classifier: Topic :: Utilities
|
|
21
|
+
Requires-Dist: interfacy>=0.6.7
|
|
22
|
+
Requires-Dist: jinja2>=3.0
|
|
23
|
+
Requires-Dist: prompt-toolkit>=3.0.52
|
|
24
|
+
Requires-Dist: rich>=13
|
|
25
|
+
Requires-Dist: pytest>=9.0.3 ; extra == 'dev'
|
|
26
|
+
Requires-Dist: coverage>=7.14.1 ; extra == 'dev'
|
|
27
|
+
Requires-Dist: ruff==0.15.16 ; extra == 'dev'
|
|
28
|
+
Requires-Dist: pre-commit>=4.6.0 ; extra == 'dev'
|
|
29
|
+
Requires-Dist: pyrefly>=1.0.0 ; extra == 'dev'
|
|
30
|
+
Requires-Dist: codespell>=2.4.2 ; extra == 'dev'
|
|
31
|
+
Requires-Dist: rattle-lint>=2.5.1 ; extra == 'dev'
|
|
32
|
+
Requires-Dist: pytest>=9.0.3 ; extra == 'test'
|
|
33
|
+
Requires-Dist: coverage>=7.14.1 ; extra == 'test'
|
|
34
|
+
Requires-Python: >=3.12
|
|
35
|
+
Project-URL: Repository, https://github.com/zigai/sprout
|
|
36
|
+
Project-URL: Issues, https://github.com/zigai/sprout/issues
|
|
37
|
+
Project-URL: Homepage, https://github.com/zigai/sprout
|
|
38
|
+
Provides-Extra: dev
|
|
39
|
+
Provides-Extra: test
|
|
40
|
+
Description-Content-Type: text/markdown
|
|
41
|
+
|
|
42
|
+
# sprout
|
|
43
|
+
|
|
44
|
+
Sprout is a Jinja2-based project generator with a Python manifest.
|
|
45
|
+
|
|
46
|
+
Instead of configuring prompts in
|
|
47
|
+
YAML, you write `sprout.py`:
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
from sprout import Question
|
|
51
|
+
|
|
52
|
+
questions = [
|
|
53
|
+
Question(key="project_name", prompt="Project name"),
|
|
54
|
+
]
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
That single manifest drives interactive prompts, CLI flags, validation and conditional questions.
|
|
58
|
+
|
|
59
|
+
Template files go in `template/`. `.jinja` files are rendered, everything else
|
|
60
|
+
is copied.
|
|
61
|
+
|
|
62
|
+
Works with local templates, Git repos, or `owner/repo` GitHub shorthand.
|
|
63
|
+
|
|
64
|
+
Every question becomes a CLI flag so you can script it too:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
sprout new <template-path> <project-path>
|
|
68
|
+
sprout new <template-path> <project-path> --project-name demo
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Install
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
uv tool install sprout-template
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Usage
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
sprout init [directory]
|
|
81
|
+
sprout add <template-source> [--name <trusted-name>]
|
|
82
|
+
sprout list
|
|
83
|
+
sprout new <template> <project-path> [--force] [--<question-flag> <value> ...]
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
`new` accepts a local template path, Git URL, `owner/repo` GitHub shorthand, or a trusted name added
|
|
87
|
+
with `sprout add`. Pass values for question flags to skip those prompts:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
sprout new <template-path> <project-path> --project-name demo
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Use `sprout new <template> --help` to show template-specific flags.
|
|
94
|
+
|
|
95
|
+
### Initialize a template
|
|
96
|
+
|
|
97
|
+
Create a minimal `sprout.py` and `template/README.md.jinja` scaffold in the current directory:
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
sprout init
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Pass a directory to initialize it elsewhere. Existing scaffold files are never overwritten.
|
|
104
|
+
|
|
105
|
+
### Trusted templates
|
|
106
|
+
|
|
107
|
+
Store a reusable name for any supported template source:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
sprout add zigai/python-project-template --name python
|
|
111
|
+
sprout new python ./my-project
|
|
112
|
+
sprout list
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Template structure
|
|
116
|
+
|
|
117
|
+
The source root must contain `sprout.py`.
|
|
118
|
+
|
|
119
|
+
The only required name is `questions`.
|
|
120
|
+
|
|
121
|
+
```python
|
|
122
|
+
from sprout import Question
|
|
123
|
+
|
|
124
|
+
questions = [
|
|
125
|
+
Question(key="project_name", prompt="Project name"),
|
|
126
|
+
]
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Optional names are `template_dir`, `style`, `extensions`, `title`, `cli_boolean_style`,
|
|
130
|
+
`should_skip_file(...)`, and `apply(context)`.
|
|
131
|
+
|
|
132
|
+
## Question model
|
|
133
|
+
|
|
134
|
+
Each `Question` describes one answer:
|
|
135
|
+
|
|
136
|
+
```python
|
|
137
|
+
from sprout import Question
|
|
138
|
+
|
|
139
|
+
Question(
|
|
140
|
+
key="project_name",
|
|
141
|
+
prompt="Project name",
|
|
142
|
+
help="Used for package metadata and generated paths",
|
|
143
|
+
default="demo",
|
|
144
|
+
)
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
`key` is the answer dictionary key. It also becomes the CLI flag name.
|
|
148
|
+
|
|
149
|
+
```text
|
|
150
|
+
project_name -> --project-name
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## Choices
|
|
154
|
+
|
|
155
|
+
Use choices when the answer should come from a closed list:
|
|
156
|
+
|
|
157
|
+
```python
|
|
158
|
+
from sprout import Question
|
|
159
|
+
|
|
160
|
+
questions = [
|
|
161
|
+
Question(
|
|
162
|
+
key="package_manager",
|
|
163
|
+
prompt="Package manager",
|
|
164
|
+
choices=[("uv", "uv"), ("pip", "pip")],
|
|
165
|
+
default="uv",
|
|
166
|
+
),
|
|
167
|
+
]
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## Multiselect
|
|
171
|
+
|
|
172
|
+
```python
|
|
173
|
+
from sprout import Question
|
|
174
|
+
|
|
175
|
+
questions = [
|
|
176
|
+
Question(
|
|
177
|
+
key="workflow",
|
|
178
|
+
prompt="Workflows",
|
|
179
|
+
choices=[("tests", "Tests"), ("lint", "Lint")],
|
|
180
|
+
multiselect=True,
|
|
181
|
+
),
|
|
182
|
+
]
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
From the CLI:
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
sprout new <template-path> <project-path> --workflow tests --workflow lint
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
## Booleans
|
|
192
|
+
|
|
193
|
+
Use the built-in yes/no helper:
|
|
194
|
+
|
|
195
|
+
```python
|
|
196
|
+
from sprout import Question
|
|
197
|
+
|
|
198
|
+
questions = [
|
|
199
|
+
Question.yes_no(
|
|
200
|
+
key="git_init",
|
|
201
|
+
prompt="Initialize Git?",
|
|
202
|
+
default=True,
|
|
203
|
+
),
|
|
204
|
+
]
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
By default, yes/no questions are exposed as Boolean CLI flags:
|
|
208
|
+
|
|
209
|
+
```bash
|
|
210
|
+
sprout new <template-path> <project-path> --git-init
|
|
211
|
+
sprout new <template-path> <project-path> --no-git-init
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
If a template should use explicit yes/no values instead, opt into that style in `sprout.py`:
|
|
215
|
+
|
|
216
|
+
```python
|
|
217
|
+
cli_boolean_style = "yes-no"
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
Then the CLI accepts values for yes/no questions:
|
|
221
|
+
|
|
222
|
+
```bash
|
|
223
|
+
sprout new <template-path> <project-path> --git-init yes
|
|
224
|
+
sprout new <template-path> <project-path> --git-init no
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
## Conditional flow
|
|
228
|
+
|
|
229
|
+
`when` can be a boolean or a callable that receives the answers collected so far:
|
|
230
|
+
|
|
231
|
+
```python
|
|
232
|
+
from sprout import Question
|
|
233
|
+
|
|
234
|
+
questions = [
|
|
235
|
+
Question.yes_no(
|
|
236
|
+
key="create_github_repo",
|
|
237
|
+
prompt="Create GitHub repository?",
|
|
238
|
+
default=False,
|
|
239
|
+
),
|
|
240
|
+
Question(
|
|
241
|
+
key="github_repo_visibility",
|
|
242
|
+
prompt="GitHub repository visibility",
|
|
243
|
+
choices=[("private", "Private"), ("public", "Public")],
|
|
244
|
+
default="private",
|
|
245
|
+
when=lambda answers: bool(answers.get("create_github_repo")),
|
|
246
|
+
),
|
|
247
|
+
]
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
## Defaults, parsers, and validators
|
|
251
|
+
|
|
252
|
+
Defaults can be static values or callables. Use `default=""` for a text prompt that should accept
|
|
253
|
+
a blank answer.
|
|
254
|
+
|
|
255
|
+
```python
|
|
256
|
+
from sprout import Question
|
|
257
|
+
|
|
258
|
+
questions = [
|
|
259
|
+
Question(
|
|
260
|
+
key="package_name",
|
|
261
|
+
prompt="Package name",
|
|
262
|
+
default=lambda answers: str(answers["project_name"]).replace("-", "_"),
|
|
263
|
+
),
|
|
264
|
+
]
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
Validators return `(valid, message)`:
|
|
268
|
+
|
|
269
|
+
```python
|
|
270
|
+
from sprout import Question, validate_repository_url
|
|
271
|
+
|
|
272
|
+
questions = [
|
|
273
|
+
Question(
|
|
274
|
+
key="repository_url",
|
|
275
|
+
prompt="Repository URL",
|
|
276
|
+
validators=[validate_repository_url],
|
|
277
|
+
),
|
|
278
|
+
]
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
Sprout includes validators for repository URLs, GitHub repository URLs, repository names, npm package
|
|
282
|
+
names, and semantic versions.
|
|
283
|
+
|
|
284
|
+
## Destination-aware questions
|
|
285
|
+
|
|
286
|
+
When question definitions need runtime context, make `questions` callable:
|
|
287
|
+
|
|
288
|
+
```python
|
|
289
|
+
from pathlib import Path
|
|
290
|
+
from jinja2 import Environment
|
|
291
|
+
from sprout import Question
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def questions(env: Environment, destination: Path) -> list[Question]:
|
|
295
|
+
return [
|
|
296
|
+
Question(
|
|
297
|
+
key="project_name",
|
|
298
|
+
prompt="Project name",
|
|
299
|
+
default=destination.name,
|
|
300
|
+
),
|
|
301
|
+
]
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
The callable must accept exactly two positional parameters: `env` and `destination`.
|
|
305
|
+
|
|
306
|
+
## Rendering
|
|
307
|
+
|
|
308
|
+
Default rendering uses `template_dir`, or `template` when no directory is declared.
|
|
309
|
+
|
|
310
|
+
```python
|
|
311
|
+
template_dir = "template"
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
## Skipping files
|
|
315
|
+
|
|
316
|
+
`should_skip_file` receives a path relative to `template_dir` and the final answers:
|
|
317
|
+
|
|
318
|
+
```python
|
|
319
|
+
from sprout import NO_LICENSE
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
def should_skip_file(relative_path: str, answers: dict[str, object]) -> bool:
|
|
323
|
+
return relative_path == "LICENSE.jinja" and answers.get("license") == NO_LICENSE
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
## Jinja environment
|
|
327
|
+
|
|
328
|
+
Set Jinja2 extension classes with `extensions`:
|
|
329
|
+
|
|
330
|
+
```python
|
|
331
|
+
from sprout import CurrentYearExtension, GitDefaultsExtension
|
|
332
|
+
|
|
333
|
+
extensions = [GitDefaultsExtension, CurrentYearExtension]
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
When `extensions` is omitted, the default environment includes Git defaults:
|
|
337
|
+
|
|
338
|
+
- `git_user_name`
|
|
339
|
+
- `git_user_email`
|
|
340
|
+
- `github_username`
|
|
341
|
+
|
|
342
|
+
Include `GitDefaultsExtension` explicitly when you provide a custom extension list and still want
|
|
343
|
+
those globals.
|
|
344
|
+
|
|
345
|
+
`CurrentYearExtension` exposes:
|
|
346
|
+
|
|
347
|
+
- `current_year`
|
|
348
|
+
|
|
349
|
+
## Prompt title and style
|
|
350
|
+
|
|
351
|
+
```python
|
|
352
|
+
title = "Generate a Python package"
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
```python
|
|
356
|
+
from sprout import ManifestContext
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
def title(context: ManifestContext) -> str | None:
|
|
360
|
+
return f"Generate project in {context.destination}"
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
The title is evaluated before answers are collected. For prompt appearance, assign `style` to a
|
|
364
|
+
`sprout.Style` instance.
|
|
365
|
+
|
|
366
|
+
## Custom generation
|
|
367
|
+
|
|
368
|
+
Most templates should use the default renderer, but you can define `apply(context)` when generation needs
|
|
369
|
+
custom file creation, post-processing, or post-generation actions.
|
|
370
|
+
|
|
371
|
+
```python
|
|
372
|
+
from sprout import ManifestContext, render_templates
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
def apply(context: ManifestContext):
|
|
376
|
+
return render_templates(
|
|
377
|
+
context.env,
|
|
378
|
+
context.template_dir,
|
|
379
|
+
context.destination,
|
|
380
|
+
context.answers,
|
|
381
|
+
render_paths=True,
|
|
382
|
+
)
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
`apply` must accept exactly one `context` parameter. It may return `None`, one path, or a sequence of
|
|
386
|
+
paths for the generated-files summary.
|
|
387
|
+
|
|
388
|
+
## Examples
|
|
389
|
+
|
|
390
|
+
- [python-project-template](https://github.com/zigai/python-project-template).
|
|
391
|
+
|
|
392
|
+
## License
|
|
393
|
+
|
|
394
|
+
[MIT License](https://github.com/zigai/sprout/blob/master/LICENSE)
|