create-awesome-python-app 0.2.2__py3-none-any.whl → 0.2.4__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.
- create_awesome_python_app/__init__.py +1 -1
- create_awesome_python_app/catalog.py +27 -15
- create_awesome_python_app/cli.py +27 -11
- create_awesome_python_app/prompt_style.py +113 -0
- {create_awesome_python_app-0.2.2.dist-info → create_awesome_python_app-0.2.4.dist-info}/METADATA +2 -2
- create_awesome_python_app-0.2.4.dist-info/RECORD +9 -0
- create_awesome_python_app-0.2.2.dist-info/RECORD +0 -8
- {create_awesome_python_app-0.2.2.dist-info → create_awesome_python_app-0.2.4.dist-info}/WHEEL +0 -0
- {create_awesome_python_app-0.2.2.dist-info → create_awesome_python_app-0.2.4.dist-info}/entry_points.txt +0 -0
|
@@ -16,6 +16,10 @@ from rich.console import Console
|
|
|
16
16
|
from rich.table import Table
|
|
17
17
|
|
|
18
18
|
from create_awesome_python_app import __version__
|
|
19
|
+
from create_awesome_python_app.prompt_style import (
|
|
20
|
+
custom_template_title,
|
|
21
|
+
template_title_tokens,
|
|
22
|
+
)
|
|
19
23
|
|
|
20
24
|
console = Console(stderr=True)
|
|
21
25
|
|
|
@@ -26,7 +30,7 @@ CUSTOM_TEMPLATE_SENTINEL = "__custom_template__"
|
|
|
26
30
|
class TemplateChoice:
|
|
27
31
|
"""Searchable interactive template choice."""
|
|
28
32
|
|
|
29
|
-
title:
|
|
33
|
+
title: Any
|
|
30
34
|
value: str
|
|
31
35
|
search: str
|
|
32
36
|
|
|
@@ -235,32 +239,40 @@ def build_template_choices(data: dict[str, Any]) -> list[TemplateChoice]:
|
|
|
235
239
|
category_name = categories.get(category_slug, category_slug)
|
|
236
240
|
badge = short_category_label(category_name).ljust(10)[:10]
|
|
237
241
|
slug = str(template.get("slug", ""))
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
+
labels_raw = template.get("labels", [])
|
|
243
|
+
labels = (
|
|
244
|
+
[str(label) for label in labels_raw[:3]]
|
|
245
|
+
if isinstance(labels_raw, list)
|
|
246
|
+
else []
|
|
247
|
+
)
|
|
242
248
|
description = str(template.get("description", "")).strip()
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
#
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
249
|
+
name = str(template.get("name", slug))
|
|
250
|
+
search = _search_text(template, category_name)
|
|
251
|
+
# FormattedText tokens (not raw ANSI): select() prints str titles
|
|
252
|
+
# literally, which showed ^[[1;94m… in terminals.
|
|
253
|
+
title = template_title_tokens(
|
|
254
|
+
category_slug=category_slug,
|
|
255
|
+
badge=badge,
|
|
256
|
+
name=name,
|
|
257
|
+
slug=slug,
|
|
258
|
+
labels=labels,
|
|
259
|
+
description=description,
|
|
260
|
+
search=search,
|
|
250
261
|
)
|
|
251
262
|
choices.append(
|
|
252
263
|
TemplateChoice(
|
|
253
264
|
title=title,
|
|
254
265
|
value=template_url,
|
|
255
|
-
search=
|
|
266
|
+
search=search,
|
|
256
267
|
)
|
|
257
268
|
)
|
|
258
269
|
|
|
270
|
+
custom_search = "custom own template url github file"
|
|
259
271
|
choices.append(
|
|
260
272
|
TemplateChoice(
|
|
261
|
-
title=
|
|
273
|
+
title=custom_template_title(custom_search),
|
|
262
274
|
value=CUSTOM_TEMPLATE_SENTINEL,
|
|
263
|
-
search=
|
|
275
|
+
search=custom_search,
|
|
264
276
|
)
|
|
265
277
|
)
|
|
266
278
|
return choices
|
create_awesome_python_app/cli.py
CHANGED
|
@@ -104,6 +104,8 @@ def _prompt_custom_options(
|
|
|
104
104
|
) -> dict[str, str]:
|
|
105
105
|
import questionary
|
|
106
106
|
|
|
107
|
+
from create_awesome_python_app.prompt_style import CPA_PROMPT_STYLE
|
|
108
|
+
|
|
107
109
|
source = resolve_source(template, cache_dir=cache_dir)
|
|
108
110
|
root = download_repository(
|
|
109
111
|
source,
|
|
@@ -138,9 +140,12 @@ def _prompt_custom_options(
|
|
|
138
140
|
answer = questionary.confirm(
|
|
139
141
|
message,
|
|
140
142
|
default=initial.lower() in {"1", "true", "yes", "on"},
|
|
143
|
+
style=CPA_PROMPT_STYLE,
|
|
141
144
|
).ask()
|
|
142
145
|
else:
|
|
143
|
-
answer = questionary.text(
|
|
146
|
+
answer = questionary.text(
|
|
147
|
+
message, default=initial, style=CPA_PROMPT_STYLE
|
|
148
|
+
).ask()
|
|
144
149
|
if answer is None:
|
|
145
150
|
raise typer.Exit(1)
|
|
146
151
|
answers[option.key] = _stringify_option_value(answer)
|
|
@@ -230,30 +235,38 @@ def scaffold(
|
|
|
230
235
|
if want_interactive and not template:
|
|
231
236
|
try:
|
|
232
237
|
import questionary
|
|
238
|
+
from questionary import Choice
|
|
233
239
|
|
|
234
240
|
from create_awesome_python_app.catalog import (
|
|
235
241
|
CUSTOM_TEMPLATE_SENTINEL,
|
|
236
242
|
build_template_choices,
|
|
237
243
|
get_catalog_data,
|
|
238
244
|
)
|
|
245
|
+
from create_awesome_python_app.prompt_style import CPA_PROMPT_STYLE
|
|
239
246
|
|
|
240
247
|
interactive_catalog = get_catalog_data()
|
|
241
248
|
template_choices = build_template_choices(interactive_catalog)
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
+
# select + type-to-filter: browseable list (CNA-style discovery)
|
|
250
|
+
# instead of autocomplete-only. use_jk_keys must be False with search.
|
|
251
|
+
selected_template = questionary.select(
|
|
252
|
+
"Pick a template",
|
|
253
|
+
choices=[
|
|
254
|
+
Choice(title=choice.title, value=choice.value)
|
|
255
|
+
for choice in template_choices
|
|
256
|
+
],
|
|
249
257
|
qmark="?",
|
|
258
|
+
pointer="❯",
|
|
259
|
+
style=CPA_PROMPT_STYLE,
|
|
260
|
+
use_search_filter=True,
|
|
261
|
+
use_jk_keys=False,
|
|
262
|
+
instruction="(↑↓ browse · type to filter · Enter)",
|
|
250
263
|
).ask()
|
|
251
|
-
selected_template = choice_by_title.get(str(selected_title), selected_title)
|
|
252
264
|
if selected_template == CUSTOM_TEMPLATE_SENTINEL:
|
|
253
265
|
selected_template = questionary.text(
|
|
254
266
|
"Template URL",
|
|
255
267
|
default="file://.",
|
|
256
268
|
validate=lambda value: bool(value) or "Template URL is required",
|
|
269
|
+
style=CPA_PROMPT_STYLE,
|
|
257
270
|
).ask()
|
|
258
271
|
template = selected_template
|
|
259
272
|
if not template:
|
|
@@ -296,6 +309,7 @@ def scaffold(
|
|
|
296
309
|
get_catalog_data,
|
|
297
310
|
group_extension_choices,
|
|
298
311
|
)
|
|
312
|
+
from create_awesome_python_app.prompt_style import CPA_PROMPT_STYLE
|
|
299
313
|
|
|
300
314
|
interactive_catalog = interactive_catalog or get_catalog_data()
|
|
301
315
|
extension_choices = build_extension_choices(interactive_catalog, template)
|
|
@@ -316,7 +330,8 @@ def scaffold(
|
|
|
316
330
|
"Which kinds of extensions do you need?",
|
|
317
331
|
choices=category_choices,
|
|
318
332
|
qmark="?",
|
|
319
|
-
pointer="
|
|
333
|
+
pointer="❯",
|
|
334
|
+
style=CPA_PROMPT_STYLE,
|
|
320
335
|
).ask()
|
|
321
336
|
selected_addons: list[str] = []
|
|
322
337
|
for category_slug in selected_categories or []:
|
|
@@ -330,7 +345,8 @@ def scaffold(
|
|
|
330
345
|
for choice in choices
|
|
331
346
|
],
|
|
332
347
|
qmark="?",
|
|
333
|
-
pointer="
|
|
348
|
+
pointer="❯",
|
|
349
|
+
style=CPA_PROMPT_STYLE,
|
|
334
350
|
).ask()
|
|
335
351
|
selected_addons.extend(str(item) for item in picked or [])
|
|
336
352
|
addons = selected_addons
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"""High-contrast questionary styles for CPA interactive prompts."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from questionary import Style
|
|
9
|
+
|
|
10
|
+
# Lighter brand blues/greens than docs hex so selected rows stay readable on
|
|
11
|
+
# dark terminals (slate backgrounds common in Arch/Ghostty/Alacritty).
|
|
12
|
+
CPA_PROMPT_STYLE = Style.from_dict(
|
|
13
|
+
{
|
|
14
|
+
"qmark": "fg:#60a5fa bold",
|
|
15
|
+
"question": "bold fg:#f8fafc",
|
|
16
|
+
"answer": "fg:#4ade80 bold",
|
|
17
|
+
"pointer": "fg:#60a5fa bold",
|
|
18
|
+
"highlighted": "fg:#0f172a bg:#60a5fa bold",
|
|
19
|
+
"selected": "fg:#4ade80 bold",
|
|
20
|
+
"separator": "fg:#94a3b8",
|
|
21
|
+
"instruction": "fg:#94a3b8",
|
|
22
|
+
"text": "fg:#e2e8f0",
|
|
23
|
+
"disabled": "fg:#64748b italic",
|
|
24
|
+
"checkbox": "fg:#60a5fa",
|
|
25
|
+
"checkbox-selected": "fg:#4ade80 bold",
|
|
26
|
+
}
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
# Bright prompt_toolkit style strings (not raw ANSI — select() prints str titles
|
|
30
|
+
# literally, so escapes show as ^[[...m unless titles are FormattedText tokens).
|
|
31
|
+
_CATEGORY_STYLES = (
|
|
32
|
+
"fg:#facc15 bold", # yellow
|
|
33
|
+
"fg:#4ade80 bold", # green
|
|
34
|
+
"fg:#22d3ee bold", # cyan
|
|
35
|
+
"fg:#e879f9 bold", # magenta
|
|
36
|
+
"fg:#60a5fa bold", # blue
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class SearchableFormattedText(list):
|
|
41
|
+
"""FormattedText tokens with ``.lower()`` for questionary search filter.
|
|
42
|
+
|
|
43
|
+
``select(use_search_filter=True)`` does ``needle in choice.title.lower()``.
|
|
44
|
+
A plain token list has no ``.lower()``; this keeps filter working while
|
|
45
|
+
titles render as styled FormattedText.
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
def __init__(self, tokens: list[tuple[str, str]], search: str) -> None:
|
|
49
|
+
super().__init__(tokens)
|
|
50
|
+
self._search = search
|
|
51
|
+
|
|
52
|
+
def lower(self) -> str:
|
|
53
|
+
return self._search.lower()
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def colors_enabled() -> bool:
|
|
57
|
+
return not os.environ.get("NO_COLOR")
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def category_style(slug: str) -> str:
|
|
61
|
+
idx = sum(ord(char) for char in slug) % len(_CATEGORY_STYLES)
|
|
62
|
+
return _CATEGORY_STYLES[idx]
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def plain_title_text(title: Any) -> str:
|
|
66
|
+
"""Join FormattedText token text (or return a plain string title)."""
|
|
67
|
+
if isinstance(title, list):
|
|
68
|
+
return "".join(str(token[1]) for token in title)
|
|
69
|
+
return str(title)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def template_title_tokens(
|
|
73
|
+
*,
|
|
74
|
+
category_slug: str,
|
|
75
|
+
badge: str,
|
|
76
|
+
name: str,
|
|
77
|
+
slug: str,
|
|
78
|
+
labels: list[str],
|
|
79
|
+
description: str,
|
|
80
|
+
search: str,
|
|
81
|
+
) -> SearchableFormattedText | str:
|
|
82
|
+
"""Build a select-safe title: FormattedText when colors on, else plain str."""
|
|
83
|
+
label_suffix = ""
|
|
84
|
+
if labels:
|
|
85
|
+
label_suffix = " · " + ", ".join(labels[:3])
|
|
86
|
+
description_suffix = f" — {description}" if description else ""
|
|
87
|
+
plain = f"{badge} {name} ({slug}){label_suffix}{description_suffix}"
|
|
88
|
+
|
|
89
|
+
if not colors_enabled():
|
|
90
|
+
return plain
|
|
91
|
+
|
|
92
|
+
tokens: list[tuple[str, str]] = [
|
|
93
|
+
(category_style(category_slug), badge),
|
|
94
|
+
("", " "),
|
|
95
|
+
("bold", name),
|
|
96
|
+
("class:instruction", f" ({slug})"),
|
|
97
|
+
]
|
|
98
|
+
if label_suffix:
|
|
99
|
+
tokens.append(("class:instruction", label_suffix))
|
|
100
|
+
if description_suffix:
|
|
101
|
+
tokens.append(("fg:#94a3b8", description_suffix))
|
|
102
|
+
return SearchableFormattedText(tokens, search=search or plain)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def custom_template_title(search: str) -> SearchableFormattedText | str:
|
|
106
|
+
label = "Use my own template URL"
|
|
107
|
+
plain = " " * 12 + label
|
|
108
|
+
if not colors_enabled():
|
|
109
|
+
return plain
|
|
110
|
+
return SearchableFormattedText(
|
|
111
|
+
[("", " " * 12), ("italic fg:#94a3b8", label)],
|
|
112
|
+
search=search or plain,
|
|
113
|
+
)
|
{create_awesome_python_app-0.2.2.dist-info → create_awesome_python_app-0.2.4.dist-info}/METADATA
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: create-awesome-python-app
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.4
|
|
4
4
|
Summary: Composable scaffolding CLI for production-ready Python apps
|
|
5
5
|
Project-URL: Homepage, https://github.com/Create-Python-App/create-python-app
|
|
6
6
|
Project-URL: Repository, https://github.com/Create-Python-App/create-python-app
|
|
@@ -8,7 +8,7 @@ Project-URL: Issues, https://github.com/Create-Python-App/create-python-app/issu
|
|
|
8
8
|
Project-URL: Changelog, https://github.com/Create-Python-App/create-python-app/blob/main/CHANGELOG.md
|
|
9
9
|
License-Expression: MIT
|
|
10
10
|
Requires-Python: >=3.12
|
|
11
|
-
Requires-Dist: create-python-app-core>=0.2.
|
|
11
|
+
Requires-Dist: create-python-app-core>=0.2.4
|
|
12
12
|
Requires-Dist: questionary>=2.1.1
|
|
13
13
|
Requires-Dist: rich>=15.0.0
|
|
14
14
|
Requires-Dist: typer>=0.27.0
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
create_awesome_python_app/__init__.py,sha256=D04aKhr6EFJUIRigne5PNis9DoItI8X-4SC6lK_VfJE,60
|
|
2
|
+
create_awesome_python_app/cache.py,sha256=eHKUlunGexZKYJo0HDNoBoOVq0DjLyRYgEe_wUh4Fk0,11812
|
|
3
|
+
create_awesome_python_app/catalog.py,sha256=w5GS_kQ6MM37AilutXbYZ9Hk5JGa_12crzI0LYzKfRg,16821
|
|
4
|
+
create_awesome_python_app/cli.py,sha256=6og82WI-RZxTqHETqrieA8relgppwCqFntOEmOFrIPk,21195
|
|
5
|
+
create_awesome_python_app/prompt_style.py,sha256=pERX1qPQSLZXeJvPVFC7z85dvte73ZV4iuENPQduheE,3463
|
|
6
|
+
create_awesome_python_app-0.2.4.dist-info/METADATA,sha256=KD7yBBBomrwpw4hfqJm9aIBGWnjBukcHl48bOqjt8_M,1428
|
|
7
|
+
create_awesome_python_app-0.2.4.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
|
|
8
|
+
create_awesome_python_app-0.2.4.dist-info/entry_points.txt,sha256=rszFbUjY-lvMDZ96zX1lALJwgD1mI3CkUuNyF2qqjYo,81
|
|
9
|
+
create_awesome_python_app-0.2.4.dist-info/RECORD,,
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
create_awesome_python_app/__init__.py,sha256=zyHa8jk_E_HCeu1vlupYAWdBZUI34ETHbMgMgjJEcPE,60
|
|
2
|
-
create_awesome_python_app/cache.py,sha256=eHKUlunGexZKYJo0HDNoBoOVq0DjLyRYgEe_wUh4Fk0,11812
|
|
3
|
-
create_awesome_python_app/catalog.py,sha256=ljpyTFhaUo7t43CbQ8y86D_GfLOo7hE7yQwNojRUTG0,16612
|
|
4
|
-
create_awesome_python_app/cli.py,sha256=aNDUiIgRiks6nV72wrAbLit4yxD6VpCCv8Msp9QDXa4,20453
|
|
5
|
-
create_awesome_python_app-0.2.2.dist-info/METADATA,sha256=rlAPeMlrpm37YAgoHeFZd0UPnE9L-4uU8v01VPG1hpM,1428
|
|
6
|
-
create_awesome_python_app-0.2.2.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
|
|
7
|
-
create_awesome_python_app-0.2.2.dist-info/entry_points.txt,sha256=rszFbUjY-lvMDZ96zX1lALJwgD1mI3CkUuNyF2qqjYo,81
|
|
8
|
-
create_awesome_python_app-0.2.2.dist-info/RECORD,,
|
{create_awesome_python_app-0.2.2.dist-info → create_awesome_python_app-0.2.4.dist-info}/WHEEL
RENAMED
|
File without changes
|
|
File without changes
|