spawnio 1.0.5__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.
- spawn/__init__.py +6 -0
- spawn/cli/__init__.py +0 -0
- spawn/cli/app.py +218 -0
- spawn/cli/noninteractive.py +204 -0
- spawn/cli/prompts.py +267 -0
- spawn/core/__init__.py +0 -0
- spawn/core/exceptions.py +7 -0
- spawn/core/models.py +21 -0
- spawn/core/registry.py +152 -0
- spawn/generators/__init__.py +0 -0
- spawn/generators/custom_structure.py +508 -0
- spawn/generators/project_generator.py +115 -0
- spawn/github/__init__.py +0 -0
- spawn/github/exceptions.py +5 -0
- spawn/github/publisher.py +69 -0
- spawn/github/validators.py +23 -0
- spawn/templates/__init__.py +0 -0
- spawn/templates/agent/__init__.py +182 -0
- spawn/templates/agent/content.py +374 -0
- spawn/templates/automation/__init__.py +100 -0
- spawn/templates/automation/content.py +181 -0
- spawn/templates/backend_api/__init__.py +243 -0
- spawn/templates/backend_api/content.py +509 -0
- spawn/templates/base.py +58 -0
- spawn/templates/chatbot/__init__.py +243 -0
- spawn/templates/chatbot/content.py +668 -0
- spawn/templates/cli_application/__init__.py +211 -0
- spawn/templates/cli_application/content.py +512 -0
- spawn/templates/data_project/__init__.py +125 -0
- spawn/templates/data_project/analysis/__init__.py +74 -0
- spawn/templates/data_project/analysis/content.py +190 -0
- spawn/templates/data_project/dashboard/__init__.py +61 -0
- spawn/templates/data_project/dashboard/content.py +135 -0
- spawn/templates/data_project/etl/__init__.py +66 -0
- spawn/templates/data_project/etl/content.py +239 -0
- spawn/templates/data_project/ml/__init__.py +62 -0
- spawn/templates/data_project/ml/content.py +239 -0
- spawn/templates/mcp_server/__init__.py +83 -0
- spawn/templates/mcp_server/content.py +161 -0
- spawn/templates/rag/__init__.py +124 -0
- spawn/templates/rag/content.py +463 -0
- spawn/templates/shared_content.py +89 -0
- spawn/utils/__init__.py +0 -0
- spawn/utils/banner.py +41 -0
- spawn/utils/console.py +3 -0
- spawn/utils/doctor.py +594 -0
- spawn/utils/git.py +132 -0
- spawn/utils/success.py +47 -0
- spawn/utils/theme.py +25 -0
- spawn/utils/uv.py +61 -0
- spawn/utils/validators.py +15 -0
- spawnio-1.0.5.dist-info/METADATA +798 -0
- spawnio-1.0.5.dist-info/RECORD +56 -0
- spawnio-1.0.5.dist-info/WHEEL +4 -0
- spawnio-1.0.5.dist-info/entry_points.txt +2 -0
- spawnio-1.0.5.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
from spawn.templates.base import BaseTemplate
|
|
4
|
+
from spawn.templates.cli_application.content import (
|
|
5
|
+
INIT_CONTENT,
|
|
6
|
+
TYPER_MAIN_CONTENT,
|
|
7
|
+
TYPER_COMMANDS_INIT_CONTENT,
|
|
8
|
+
TYPER_UTILS_INIT_CONTENT,
|
|
9
|
+
TYPER_TEST_CONTENT,
|
|
10
|
+
TYPER_README_CONTENT,
|
|
11
|
+
CLICK_MAIN_CONTENT,
|
|
12
|
+
CLICK_TEST_CONTENT,
|
|
13
|
+
CLICK_README_CONTENT,
|
|
14
|
+
ARGPARSE_MAIN_CONTENT,
|
|
15
|
+
ARGPARSE_TEST_CONTENT,
|
|
16
|
+
ARGPARSE_README_CONTENT,
|
|
17
|
+
TYPER_INTERACTIVE_MAIN_CONTENT,
|
|
18
|
+
TYPER_INTERACTIVE_TEST_CONTENT,
|
|
19
|
+
CLICK_INTERACTIVE_MAIN_CONTENT,
|
|
20
|
+
CLICK_INTERACTIVE_TEST_CONTENT,
|
|
21
|
+
ARGPARSE_INTERACTIVE_MAIN_CONTENT,
|
|
22
|
+
ARGPARSE_INTERACTIVE_TEST_CONTENT,
|
|
23
|
+
CLI_GITHUB_ACTIONS_CI_BASE,
|
|
24
|
+
CLI_GITHUB_ACTIONS_CI_RUFF_STEP,
|
|
25
|
+
CLI_GITHUB_ACTIONS_CI_PYTEST_STEP,
|
|
26
|
+
TYPER_INTERACTIVE_README_CONTENT,
|
|
27
|
+
CLICK_INTERACTIVE_README_CONTENT,
|
|
28
|
+
ARGPARSE_INTERACTIVE_README_CONTENT,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
# ---------------------------------------------------------------------------
|
|
32
|
+
# Folder structures
|
|
33
|
+
# ---------------------------------------------------------------------------
|
|
34
|
+
|
|
35
|
+
UTILITY_FOLDERS = [
|
|
36
|
+
"src/commands",
|
|
37
|
+
"src/utils",
|
|
38
|
+
"tests",
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
INTERACTIVE_FOLDERS = [
|
|
42
|
+
"src/commands",
|
|
43
|
+
"src/prompts",
|
|
44
|
+
"src/ui",
|
|
45
|
+
"src/utils",
|
|
46
|
+
"tests",
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
# ---------------------------------------------------------------------------
|
|
50
|
+
# Utility starter file lists
|
|
51
|
+
# ---------------------------------------------------------------------------
|
|
52
|
+
|
|
53
|
+
TYPER_UTILITY_FILES = [
|
|
54
|
+
("src/__init__.py", INIT_CONTENT),
|
|
55
|
+
("src/commands/__init__.py", TYPER_COMMANDS_INIT_CONTENT),
|
|
56
|
+
("src/utils/__init__.py", TYPER_UTILS_INIT_CONTENT),
|
|
57
|
+
("src/main.py", TYPER_MAIN_CONTENT),
|
|
58
|
+
("tests/__init__.py", INIT_CONTENT),
|
|
59
|
+
("tests/test_cli.py", TYPER_TEST_CONTENT),
|
|
60
|
+
]
|
|
61
|
+
|
|
62
|
+
CLICK_UTILITY_FILES = [
|
|
63
|
+
("src/__init__.py", INIT_CONTENT),
|
|
64
|
+
("src/commands/__init__.py", INIT_CONTENT),
|
|
65
|
+
("src/utils/__init__.py", INIT_CONTENT),
|
|
66
|
+
("src/main.py", CLICK_MAIN_CONTENT),
|
|
67
|
+
("tests/__init__.py", INIT_CONTENT),
|
|
68
|
+
("tests/test_cli.py", CLICK_TEST_CONTENT),
|
|
69
|
+
]
|
|
70
|
+
|
|
71
|
+
ARGPARSE_UTILITY_FILES = [
|
|
72
|
+
("src/__init__.py", INIT_CONTENT),
|
|
73
|
+
("src/commands/__init__.py", INIT_CONTENT),
|
|
74
|
+
("src/utils/__init__.py", INIT_CONTENT),
|
|
75
|
+
("src/main.py", ARGPARSE_MAIN_CONTENT),
|
|
76
|
+
("tests/__init__.py", INIT_CONTENT),
|
|
77
|
+
("tests/test_cli.py", ARGPARSE_TEST_CONTENT),
|
|
78
|
+
]
|
|
79
|
+
|
|
80
|
+
# ---------------------------------------------------------------------------
|
|
81
|
+
# Interactive starter file lists
|
|
82
|
+
# ---------------------------------------------------------------------------
|
|
83
|
+
|
|
84
|
+
TYPER_INTERACTIVE_FILES = [
|
|
85
|
+
("src/__init__.py", INIT_CONTENT),
|
|
86
|
+
("src/commands/__init__.py", INIT_CONTENT),
|
|
87
|
+
("src/prompts/__init__.py", INIT_CONTENT),
|
|
88
|
+
("src/ui/__init__.py", INIT_CONTENT),
|
|
89
|
+
("src/utils/__init__.py", INIT_CONTENT),
|
|
90
|
+
("src/main.py", TYPER_INTERACTIVE_MAIN_CONTENT),
|
|
91
|
+
("tests/__init__.py", INIT_CONTENT),
|
|
92
|
+
("tests/test_cli.py", TYPER_INTERACTIVE_TEST_CONTENT),
|
|
93
|
+
]
|
|
94
|
+
|
|
95
|
+
CLICK_INTERACTIVE_FILES = [
|
|
96
|
+
("src/__init__.py", INIT_CONTENT),
|
|
97
|
+
("src/commands/__init__.py", INIT_CONTENT),
|
|
98
|
+
("src/prompts/__init__.py", INIT_CONTENT),
|
|
99
|
+
("src/ui/__init__.py", INIT_CONTENT),
|
|
100
|
+
("src/utils/__init__.py", INIT_CONTENT),
|
|
101
|
+
("src/main.py", CLICK_INTERACTIVE_MAIN_CONTENT),
|
|
102
|
+
("tests/__init__.py", INIT_CONTENT),
|
|
103
|
+
("tests/test_cli.py", CLICK_INTERACTIVE_TEST_CONTENT),
|
|
104
|
+
]
|
|
105
|
+
|
|
106
|
+
ARGPARSE_INTERACTIVE_FILES = [
|
|
107
|
+
("src/__init__.py", INIT_CONTENT),
|
|
108
|
+
("src/commands/__init__.py", INIT_CONTENT),
|
|
109
|
+
("src/prompts/__init__.py", INIT_CONTENT),
|
|
110
|
+
("src/ui/__init__.py", INIT_CONTENT),
|
|
111
|
+
("src/utils/__init__.py", INIT_CONTENT),
|
|
112
|
+
("src/main.py", ARGPARSE_INTERACTIVE_MAIN_CONTENT),
|
|
113
|
+
("tests/__init__.py", INIT_CONTENT),
|
|
114
|
+
("tests/test_cli.py", ARGPARSE_INTERACTIVE_TEST_CONTENT),
|
|
115
|
+
]
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class CLITemplate(BaseTemplate):
|
|
119
|
+
def __init__(
|
|
120
|
+
self,
|
|
121
|
+
framework: str | None = None,
|
|
122
|
+
extras: list[str] | None = None,
|
|
123
|
+
cli_type: str | None = None,
|
|
124
|
+
) -> None:
|
|
125
|
+
self.framework = framework or "typer"
|
|
126
|
+
self.extras = extras or []
|
|
127
|
+
self.cli_type = cli_type or "utility"
|
|
128
|
+
|
|
129
|
+
is_interactive = self.cli_type == "interactive"
|
|
130
|
+
|
|
131
|
+
if self.framework == "click":
|
|
132
|
+
folders = list(INTERACTIVE_FOLDERS if is_interactive else UTILITY_FOLDERS)
|
|
133
|
+
starter_files = list(
|
|
134
|
+
CLICK_INTERACTIVE_FILES if is_interactive else CLICK_UTILITY_FILES
|
|
135
|
+
)
|
|
136
|
+
elif self.framework == "argparse":
|
|
137
|
+
folders = list(INTERACTIVE_FOLDERS if is_interactive else UTILITY_FOLDERS)
|
|
138
|
+
starter_files = list(
|
|
139
|
+
ARGPARSE_INTERACTIVE_FILES if is_interactive else ARGPARSE_UTILITY_FILES
|
|
140
|
+
)
|
|
141
|
+
else:
|
|
142
|
+
folders = list(INTERACTIVE_FOLDERS if is_interactive else UTILITY_FOLDERS)
|
|
143
|
+
starter_files = list(
|
|
144
|
+
TYPER_INTERACTIVE_FILES if is_interactive else TYPER_UTILITY_FILES
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
super().__init__(
|
|
148
|
+
name="CLI Application",
|
|
149
|
+
folders=folders,
|
|
150
|
+
starter_files=starter_files,
|
|
151
|
+
next_steps=[
|
|
152
|
+
"cd {project_name}",
|
|
153
|
+
"uv run python -m src.main greet"
|
|
154
|
+
if is_interactive
|
|
155
|
+
else "uv run python -m src.main hello",
|
|
156
|
+
],
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
def get_readme_content(self, context: dict) -> str | None:
|
|
160
|
+
is_interactive = self.cli_type == "interactive"
|
|
161
|
+
if self.framework == "click":
|
|
162
|
+
template = CLICK_INTERACTIVE_README_CONTENT if is_interactive else CLICK_README_CONTENT
|
|
163
|
+
elif self.framework == "argparse":
|
|
164
|
+
template = ARGPARSE_INTERACTIVE_README_CONTENT if is_interactive else ARGPARSE_README_CONTENT
|
|
165
|
+
else:
|
|
166
|
+
template = TYPER_INTERACTIVE_README_CONTENT if is_interactive else TYPER_README_CONTENT
|
|
167
|
+
return template.format_map(context)
|
|
168
|
+
|
|
169
|
+
def get_dependencies(self) -> list[str]:
|
|
170
|
+
if self.framework == "click":
|
|
171
|
+
base: list[str] = ["click"]
|
|
172
|
+
elif self.framework == "argparse":
|
|
173
|
+
base = []
|
|
174
|
+
else:
|
|
175
|
+
base = ["typer"]
|
|
176
|
+
|
|
177
|
+
if "pytest" in self.extras:
|
|
178
|
+
base = base + ["pytest"]
|
|
179
|
+
if "ruff" in self.extras:
|
|
180
|
+
base = base + ["ruff"]
|
|
181
|
+
|
|
182
|
+
return base
|
|
183
|
+
|
|
184
|
+
def post_install(self, project_path: Path) -> None:
|
|
185
|
+
pyproject = project_path / "pyproject.toml"
|
|
186
|
+
current = pyproject.read_text(encoding="utf-8")
|
|
187
|
+
additions = ""
|
|
188
|
+
|
|
189
|
+
if "pytest" in self.extras:
|
|
190
|
+
additions += (
|
|
191
|
+
"\n[tool.pytest.ini_options]\n"
|
|
192
|
+
'testpaths = ["tests"]\n'
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
if "ruff" in self.extras:
|
|
196
|
+
additions += "\n[tool.ruff]\nline-length = 88\n"
|
|
197
|
+
|
|
198
|
+
if additions:
|
|
199
|
+
pyproject.write_text(current + additions, encoding="utf-8")
|
|
200
|
+
|
|
201
|
+
if "github-actions" in self.extras:
|
|
202
|
+
workflows_path = project_path / ".github" / "workflows"
|
|
203
|
+
workflows_path.mkdir(parents=True, exist_ok=True)
|
|
204
|
+
ci_content = CLI_GITHUB_ACTIONS_CI_BASE
|
|
205
|
+
if "ruff" in self.extras:
|
|
206
|
+
ci_content += CLI_GITHUB_ACTIONS_CI_RUFF_STEP
|
|
207
|
+
if "pytest" in self.extras:
|
|
208
|
+
ci_content += CLI_GITHUB_ACTIONS_CI_PYTEST_STEP
|
|
209
|
+
(workflows_path / "ci.yml").write_text(
|
|
210
|
+
ci_content, encoding="utf-8"
|
|
211
|
+
)
|
|
@@ -0,0 +1,512 @@
|
|
|
1
|
+
INIT_CONTENT = ""
|
|
2
|
+
|
|
3
|
+
TYPER_MAIN_CONTENT = """\
|
|
4
|
+
import typer
|
|
5
|
+
|
|
6
|
+
app = typer.Typer()
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@app.command()
|
|
10
|
+
def hello() -> None:
|
|
11
|
+
\"\"\"Say hello.\"\"\"
|
|
12
|
+
typer.echo("Hello from {project_name}!")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@app.command()
|
|
16
|
+
def version() -> None:
|
|
17
|
+
\"\"\"Show the version.\"\"\"
|
|
18
|
+
typer.echo("{project_name} v0.1.0")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def main() -> None:
|
|
22
|
+
app()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
if __name__ == "__main__":
|
|
26
|
+
main()
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
TYPER_COMMANDS_INIT_CONTENT = ""
|
|
30
|
+
|
|
31
|
+
TYPER_UTILS_INIT_CONTENT = ""
|
|
32
|
+
|
|
33
|
+
TYPER_TEST_CONTENT = """\
|
|
34
|
+
from typer.testing import CliRunner
|
|
35
|
+
|
|
36
|
+
from src.main import app
|
|
37
|
+
|
|
38
|
+
runner = CliRunner()
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def test_hello_command() -> None:
|
|
42
|
+
result = runner.invoke(app, ["hello"])
|
|
43
|
+
assert result.exit_code == 0
|
|
44
|
+
assert "Hello" in result.output
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def test_version_command() -> None:
|
|
48
|
+
result = runner.invoke(app, ["version"])
|
|
49
|
+
assert result.exit_code == 0
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
TYPER_README_CONTENT = """\
|
|
53
|
+
# {project_name}
|
|
54
|
+
|
|
55
|
+
A CLI application generated with Spawn.
|
|
56
|
+
|
|
57
|
+
## Getting Started
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
uv run python -m src.main hello
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Running Tests
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
uv run pytest
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Commands
|
|
70
|
+
|
|
71
|
+
| Command | Description |
|
|
72
|
+
|---------|-------------------------|
|
|
73
|
+
| hello | Prints a hello message |
|
|
74
|
+
| version | Shows the version |
|
|
75
|
+
|
|
76
|
+
## Development
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
uv run python -m src.main --help
|
|
80
|
+
```
|
|
81
|
+
"""
|
|
82
|
+
|
|
83
|
+
# ---------------------------------------------------------------------------
|
|
84
|
+
# Click
|
|
85
|
+
# ---------------------------------------------------------------------------
|
|
86
|
+
|
|
87
|
+
CLICK_MAIN_CONTENT = """\
|
|
88
|
+
import click
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@click.group()
|
|
92
|
+
def main() -> None:
|
|
93
|
+
\"\"\"CLI tool for {project_name}.\"\"\"
|
|
94
|
+
pass
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@main.command()
|
|
98
|
+
def hello() -> None:
|
|
99
|
+
\"\"\"Say hello.\"\"\"
|
|
100
|
+
click.echo("Hello from {project_name}!")
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
if __name__ == "__main__":
|
|
104
|
+
main()
|
|
105
|
+
"""
|
|
106
|
+
|
|
107
|
+
CLICK_TEST_CONTENT = """\
|
|
108
|
+
from click.testing import CliRunner
|
|
109
|
+
|
|
110
|
+
from src.main import main
|
|
111
|
+
|
|
112
|
+
runner = CliRunner()
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def test_hello_command() -> None:
|
|
116
|
+
result = runner.invoke(main, ["hello"])
|
|
117
|
+
assert result.exit_code == 0
|
|
118
|
+
assert "Hello" in result.output
|
|
119
|
+
"""
|
|
120
|
+
|
|
121
|
+
CLICK_README_CONTENT = """\
|
|
122
|
+
# {project_name}
|
|
123
|
+
|
|
124
|
+
A CLI application generated with Spawn.
|
|
125
|
+
|
|
126
|
+
## Getting Started
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
uv run python -m src.main hello
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Running Tests
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
uv run pytest
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## Commands
|
|
139
|
+
|
|
140
|
+
| Command | Description |
|
|
141
|
+
|---------|-------------------------|
|
|
142
|
+
| hello | Prints a hello message |
|
|
143
|
+
|
|
144
|
+
## Development
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
uv run python -m src.main --help
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Built with Click
|
|
151
|
+
"""
|
|
152
|
+
|
|
153
|
+
# ---------------------------------------------------------------------------
|
|
154
|
+
# Argparse
|
|
155
|
+
# ---------------------------------------------------------------------------
|
|
156
|
+
|
|
157
|
+
ARGPARSE_MAIN_CONTENT = """\
|
|
158
|
+
import argparse
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def hello(args: argparse.Namespace) -> None:
|
|
162
|
+
\"\"\"Say hello.\"\"\"
|
|
163
|
+
print("Hello from {project_name}!")
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def main() -> None:
|
|
167
|
+
parser = argparse.ArgumentParser(
|
|
168
|
+
prog="{project_name}",
|
|
169
|
+
description="{project_name} CLI",
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
subparsers = parser.add_subparsers(dest="command")
|
|
173
|
+
|
|
174
|
+
hello_parser = subparsers.add_parser("hello", help="Say hello")
|
|
175
|
+
hello_parser.set_defaults(func=hello)
|
|
176
|
+
|
|
177
|
+
args = parser.parse_args()
|
|
178
|
+
|
|
179
|
+
if hasattr(args, "func"):
|
|
180
|
+
args.func(args)
|
|
181
|
+
else:
|
|
182
|
+
parser.print_help()
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
if __name__ == "__main__":
|
|
186
|
+
main()
|
|
187
|
+
"""
|
|
188
|
+
|
|
189
|
+
ARGPARSE_TEST_CONTENT = """\
|
|
190
|
+
import subprocess
|
|
191
|
+
import sys
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def test_hello_command() -> None:
|
|
195
|
+
result = subprocess.run(
|
|
196
|
+
[sys.executable, "-m", "src.main", "hello"],
|
|
197
|
+
capture_output=True,
|
|
198
|
+
text=True,
|
|
199
|
+
)
|
|
200
|
+
assert result.returncode == 0
|
|
201
|
+
assert "Hello" in result.stdout
|
|
202
|
+
"""
|
|
203
|
+
|
|
204
|
+
ARGPARSE_README_CONTENT = """\
|
|
205
|
+
# {project_name}
|
|
206
|
+
|
|
207
|
+
A CLI application generated with Spawn.
|
|
208
|
+
|
|
209
|
+
## Getting Started
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
uv run python -m src.main hello
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
## Running Tests
|
|
216
|
+
|
|
217
|
+
```bash
|
|
218
|
+
uv run pytest
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
## Commands
|
|
222
|
+
|
|
223
|
+
| Command | Description |
|
|
224
|
+
|---------|-------------------------|
|
|
225
|
+
| hello | Prints a hello message |
|
|
226
|
+
|
|
227
|
+
## Development
|
|
228
|
+
|
|
229
|
+
```bash
|
|
230
|
+
uv run python -m src.main --help
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
Built with Python's standard argparse library. No external dependencies required.
|
|
234
|
+
"""
|
|
235
|
+
|
|
236
|
+
# ---------------------------------------------------------------------------
|
|
237
|
+
# Interactive — Typer
|
|
238
|
+
# ---------------------------------------------------------------------------
|
|
239
|
+
|
|
240
|
+
TYPER_INTERACTIVE_MAIN_CONTENT = """\
|
|
241
|
+
import typer
|
|
242
|
+
|
|
243
|
+
app = typer.Typer()
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
@app.command()
|
|
247
|
+
def greet() -> None:
|
|
248
|
+
\"\"\"Greet the user interactively.\"\"\"
|
|
249
|
+
name = typer.prompt("What is your name?")
|
|
250
|
+
typer.echo(f"Hello, {{name}}! Welcome to {project_name}.")
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
@app.command()
|
|
254
|
+
def version() -> None:
|
|
255
|
+
\"\"\"Show the version.\"\"\"
|
|
256
|
+
typer.echo("{project_name} v0.1.0")
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def main() -> None:
|
|
260
|
+
app()
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
if __name__ == "__main__":
|
|
264
|
+
main()
|
|
265
|
+
"""
|
|
266
|
+
|
|
267
|
+
TYPER_INTERACTIVE_TEST_CONTENT = """\
|
|
268
|
+
from typer.testing import CliRunner
|
|
269
|
+
|
|
270
|
+
from src.main import app
|
|
271
|
+
|
|
272
|
+
runner = CliRunner()
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def test_greet_command() -> None:
|
|
276
|
+
result = runner.invoke(app, ["greet"], input="Alice\\n")
|
|
277
|
+
assert result.exit_code == 0
|
|
278
|
+
assert "Hello, Alice" in result.output
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def test_version_command() -> None:
|
|
282
|
+
result = runner.invoke(app, ["version"])
|
|
283
|
+
assert result.exit_code == 0
|
|
284
|
+
"""
|
|
285
|
+
|
|
286
|
+
# ---------------------------------------------------------------------------
|
|
287
|
+
# Interactive — Click
|
|
288
|
+
# ---------------------------------------------------------------------------
|
|
289
|
+
|
|
290
|
+
CLICK_INTERACTIVE_MAIN_CONTENT = """\
|
|
291
|
+
import click
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
@click.group()
|
|
295
|
+
def main() -> None:
|
|
296
|
+
\"\"\"Interactive CLI for {project_name}.\"\"\"
|
|
297
|
+
pass
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
@main.command()
|
|
301
|
+
def greet() -> None:
|
|
302
|
+
\"\"\"Greet the user interactively.\"\"\"
|
|
303
|
+
name = click.prompt("What is your name?")
|
|
304
|
+
click.echo(f"Hello, {{name}}! Welcome to {project_name}.")
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
if __name__ == "__main__":
|
|
308
|
+
main()
|
|
309
|
+
"""
|
|
310
|
+
|
|
311
|
+
CLICK_INTERACTIVE_TEST_CONTENT = """\
|
|
312
|
+
from click.testing import CliRunner
|
|
313
|
+
|
|
314
|
+
from src.main import main
|
|
315
|
+
|
|
316
|
+
runner = CliRunner()
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
def test_greet_command() -> None:
|
|
320
|
+
result = runner.invoke(main, ["greet"], input="Alice\\n")
|
|
321
|
+
assert result.exit_code == 0
|
|
322
|
+
assert "Hello, Alice" in result.output
|
|
323
|
+
"""
|
|
324
|
+
|
|
325
|
+
# ---------------------------------------------------------------------------
|
|
326
|
+
# Interactive — Argparse
|
|
327
|
+
# ---------------------------------------------------------------------------
|
|
328
|
+
|
|
329
|
+
ARGPARSE_INTERACTIVE_MAIN_CONTENT = """\
|
|
330
|
+
import argparse
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
def greet(args: argparse.Namespace) -> None:
|
|
334
|
+
\"\"\"Greet the user interactively.\"\"\"
|
|
335
|
+
name = input("What is your name? ")
|
|
336
|
+
print(f"Hello, {{name}}! Welcome to {project_name}.")
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
def main() -> None:
|
|
340
|
+
parser = argparse.ArgumentParser(
|
|
341
|
+
prog="{project_name}",
|
|
342
|
+
description="{project_name} interactive CLI",
|
|
343
|
+
)
|
|
344
|
+
|
|
345
|
+
subparsers = parser.add_subparsers(dest="command")
|
|
346
|
+
|
|
347
|
+
greet_parser = subparsers.add_parser("greet", help="Greet the user")
|
|
348
|
+
greet_parser.set_defaults(func=greet)
|
|
349
|
+
|
|
350
|
+
args = parser.parse_args()
|
|
351
|
+
|
|
352
|
+
if hasattr(args, "func"):
|
|
353
|
+
args.func(args)
|
|
354
|
+
else:
|
|
355
|
+
parser.print_help()
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
if __name__ == "__main__":
|
|
359
|
+
main()
|
|
360
|
+
"""
|
|
361
|
+
|
|
362
|
+
ARGPARSE_INTERACTIVE_TEST_CONTENT = """\
|
|
363
|
+
import subprocess
|
|
364
|
+
import sys
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
def test_greet_command() -> None:
|
|
368
|
+
result = subprocess.run(
|
|
369
|
+
[sys.executable, "-m", "src.main", "greet"],
|
|
370
|
+
input="Alice\\n",
|
|
371
|
+
capture_output=True,
|
|
372
|
+
text=True,
|
|
373
|
+
)
|
|
374
|
+
assert result.returncode == 0
|
|
375
|
+
assert "Hello, Alice" in result.stdout
|
|
376
|
+
"""
|
|
377
|
+
|
|
378
|
+
# ---------------------------------------------------------------------------
|
|
379
|
+
# GitHub Actions CI
|
|
380
|
+
# ---------------------------------------------------------------------------
|
|
381
|
+
|
|
382
|
+
CLI_GITHUB_ACTIONS_CI_BASE = """\
|
|
383
|
+
name: CI
|
|
384
|
+
|
|
385
|
+
on:
|
|
386
|
+
push:
|
|
387
|
+
branches: [main]
|
|
388
|
+
pull_request:
|
|
389
|
+
branches: [main]
|
|
390
|
+
|
|
391
|
+
jobs:
|
|
392
|
+
ci:
|
|
393
|
+
runs-on: ubuntu-latest
|
|
394
|
+
|
|
395
|
+
steps:
|
|
396
|
+
- uses: actions/checkout@v4
|
|
397
|
+
|
|
398
|
+
- name: Install uv
|
|
399
|
+
uses: astral-sh/setup-uv@v4
|
|
400
|
+
|
|
401
|
+
- name: Install dependencies
|
|
402
|
+
run: uv sync
|
|
403
|
+
"""
|
|
404
|
+
|
|
405
|
+
CLI_GITHUB_ACTIONS_CI_RUFF_STEP = """\
|
|
406
|
+
- name: Run Ruff
|
|
407
|
+
run: uv run ruff check .
|
|
408
|
+
"""
|
|
409
|
+
|
|
410
|
+
CLI_GITHUB_ACTIONS_CI_PYTEST_STEP = """\
|
|
411
|
+
- name: Run Pytest
|
|
412
|
+
run: uv run pytest
|
|
413
|
+
"""
|
|
414
|
+
|
|
415
|
+
# ---------------------------------------------------------------------------
|
|
416
|
+
# Interactive READMEs
|
|
417
|
+
# ---------------------------------------------------------------------------
|
|
418
|
+
|
|
419
|
+
TYPER_INTERACTIVE_README_CONTENT = """\
|
|
420
|
+
# {project_name}
|
|
421
|
+
|
|
422
|
+
A CLI application generated with Spawn.
|
|
423
|
+
|
|
424
|
+
## Getting Started
|
|
425
|
+
|
|
426
|
+
```bash
|
|
427
|
+
uv run python -m src.main greet
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
## Running Tests
|
|
431
|
+
|
|
432
|
+
```bash
|
|
433
|
+
uv run pytest
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
## Commands
|
|
437
|
+
|
|
438
|
+
| Command | Description |
|
|
439
|
+
|---------|--------------------------------|
|
|
440
|
+
| greet | Greets the user interactively |
|
|
441
|
+
| version | Shows the version |
|
|
442
|
+
|
|
443
|
+
## Development
|
|
444
|
+
|
|
445
|
+
```bash
|
|
446
|
+
uv run python -m src.main --help
|
|
447
|
+
```
|
|
448
|
+
"""
|
|
449
|
+
|
|
450
|
+
CLICK_INTERACTIVE_README_CONTENT = """\
|
|
451
|
+
# {project_name}
|
|
452
|
+
|
|
453
|
+
A CLI application generated with Spawn.
|
|
454
|
+
|
|
455
|
+
## Getting Started
|
|
456
|
+
|
|
457
|
+
```bash
|
|
458
|
+
uv run python -m src.main greet
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
## Running Tests
|
|
462
|
+
|
|
463
|
+
```bash
|
|
464
|
+
uv run pytest
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
## Commands
|
|
468
|
+
|
|
469
|
+
| Command | Description |
|
|
470
|
+
|---------|--------------------------------|
|
|
471
|
+
| greet | Greets the user interactively |
|
|
472
|
+
|
|
473
|
+
## Development
|
|
474
|
+
|
|
475
|
+
```bash
|
|
476
|
+
uv run python -m src.main --help
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
Built with Click
|
|
480
|
+
"""
|
|
481
|
+
|
|
482
|
+
ARGPARSE_INTERACTIVE_README_CONTENT = """\
|
|
483
|
+
# {project_name}
|
|
484
|
+
|
|
485
|
+
A CLI application generated with Spawn.
|
|
486
|
+
|
|
487
|
+
## Getting Started
|
|
488
|
+
|
|
489
|
+
```bash
|
|
490
|
+
uv run python -m src.main greet
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
## Running Tests
|
|
494
|
+
|
|
495
|
+
```bash
|
|
496
|
+
uv run pytest
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
## Commands
|
|
500
|
+
|
|
501
|
+
| Command | Description |
|
|
502
|
+
|---------|--------------------------------|
|
|
503
|
+
| greet | Greets the user interactively |
|
|
504
|
+
|
|
505
|
+
## Development
|
|
506
|
+
|
|
507
|
+
```bash
|
|
508
|
+
uv run python -m src.main --help
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
Built with Python's standard argparse library. No external dependencies required.
|
|
512
|
+
"""
|