create-awesome-python-app 0.2.1__py3-none-any.whl → 0.2.3__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.
@@ -1,3 +1,3 @@
1
1
  """Create Awesome Python App CLI."""
2
2
 
3
- __version__ = "0.2.1"
3
+ __version__ = "0.2.3"
@@ -16,18 +16,11 @@ 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 bold, color_category, dim
19
20
 
20
21
  console = Console(stderr=True)
21
22
 
22
23
  CUSTOM_TEMPLATE_SENTINEL = "__custom_template__"
23
- _ANSI_RESET = "\033[0m"
24
- _CATEGORY_PALETTE = (
25
- "\033[33m", # yellow
26
- "\033[32m", # green
27
- "\033[36m", # cyan
28
- "\033[35m", # magenta
29
- "\033[34m", # blue
30
- )
31
24
 
32
25
 
33
26
  @dataclass(frozen=True)
@@ -172,13 +165,6 @@ def short_category_label(category_name: str) -> str:
172
165
  return " ".join(words[:2]) or category_name
173
166
 
174
167
 
175
- def _color_category(slug: str, label: str) -> str:
176
- if os.environ.get("NO_COLOR"):
177
- return label
178
- idx = sum(ord(char) for char in slug) % len(_CATEGORY_PALETTE)
179
- return f"{_CATEGORY_PALETTE[idx]}{label}{_ANSI_RESET}"
180
-
181
-
182
168
  def _category_map(data: dict[str, Any]) -> dict[str, str]:
183
169
  return {
184
170
  str(category.get("slug", "")): str(category.get("name", ""))
@@ -253,12 +239,17 @@ def build_template_choices(data: dict[str, Any]) -> list[TemplateChoice]:
253
239
  labels = template.get("labels", [])
254
240
  label_suffix = ""
255
241
  if isinstance(labels, list) and labels:
256
- label_suffix = " · " + ", ".join(str(label) for label in labels[:3])
242
+ label_suffix = dim(" · " + ", ".join(str(label) for label in labels[:3]))
257
243
  description = str(template.get("description", "")).strip()
258
- description_suffix = f" {description}" if description else ""
244
+ # Keep slug + short description in the title so select(use_search_filter)
245
+ # can match them (filter scans Choice.title only).
246
+ description_suffix = dim(f" — {description}") if description else ""
247
+ name = str(template.get("name", slug))
248
+ # ANSI is OK here: questionary.select renders titles as terminal text.
249
+ # Do not pass these titles to autocomplete (HTML match highlighting).
259
250
  title = (
260
- f"{_color_category(category_slug, badge)} "
261
- f"{template.get('name', slug)} ({slug})"
251
+ f"{color_category(category_slug, badge)} "
252
+ f"{bold(name)} ({slug})"
262
253
  f"{label_suffix}{description_suffix}"
263
254
  )
264
255
  choices.append(
@@ -271,7 +262,7 @@ def build_template_choices(data: dict[str, Any]) -> list[TemplateChoice]:
271
262
 
272
263
  choices.append(
273
264
  TemplateChoice(
274
- title=" " * 12 + "Use my own template URL",
265
+ title=" " * 12 + dim("Use my own template URL"),
275
266
  value=CUSTOM_TEMPLATE_SENTINEL,
276
267
  search="custom own template url github file",
277
268
  )
@@ -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(message, default=initial).ask()
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
- choice_by_title = {
243
- choice.title: choice.value for choice in template_choices
244
- }
245
- selected_title = questionary.autocomplete(
246
- "Pick a template (type to search)",
247
- choices=list(choice_by_title),
248
- match_middle=True,
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,61 @@
1
+ """High-contrast questionary styles for CPA interactive prompts."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+
7
+ from questionary import Style
8
+
9
+ # Lighter brand blues/greens than docs hex so selected rows stay readable on
10
+ # dark terminals (slate backgrounds common in Arch/Ghostty/Alacritty).
11
+ CPA_PROMPT_STYLE = Style.from_dict(
12
+ {
13
+ "qmark": "fg:#60a5fa bold",
14
+ "question": "bold fg:#f8fafc",
15
+ "answer": "fg:#4ade80 bold",
16
+ "pointer": "fg:#60a5fa bold",
17
+ "highlighted": "fg:#0f172a bg:#60a5fa bold",
18
+ "selected": "fg:#4ade80 bold",
19
+ "separator": "fg:#94a3b8",
20
+ "instruction": "fg:#94a3b8",
21
+ "text": "fg:#e2e8f0",
22
+ "disabled": "fg:#64748b italic",
23
+ "checkbox": "fg:#60a5fa",
24
+ "checkbox-selected": "fg:#4ade80 bold",
25
+ }
26
+ )
27
+
28
+
29
+ def colors_enabled() -> bool:
30
+ return not os.environ.get("NO_COLOR")
31
+
32
+
33
+ def ansi(code: str, text: str) -> str:
34
+ """Wrap *text* in an ANSI SGR sequence when colors are enabled."""
35
+ if not colors_enabled():
36
+ return text
37
+ return f"\033[{code}m{text}\033[0m"
38
+
39
+
40
+ # Bold bright ANSI — readable on dark terminals; select() renders these safely
41
+ # (unlike autocomplete, which HTML-parses choice text).
42
+ _CATEGORY_PALETTE = (
43
+ "1;93", # bright yellow
44
+ "1;92", # bright green
45
+ "1;96", # bright cyan
46
+ "1;95", # bright magenta
47
+ "1;94", # bright blue
48
+ )
49
+
50
+
51
+ def color_category(slug: str, label: str) -> str:
52
+ idx = sum(ord(char) for char in slug) % len(_CATEGORY_PALETTE)
53
+ return ansi(_CATEGORY_PALETTE[idx], label)
54
+
55
+
56
+ def bold(text: str) -> str:
57
+ return ansi("1", text)
58
+
59
+
60
+ def dim(text: str) -> str:
61
+ return ansi("2", text)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: create-awesome-python-app
3
- Version: 0.2.1
3
+ Version: 0.2.3
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.1
11
+ Requires-Dist: create-python-app-core>=0.2.3
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=DPsQbeW1110h7DEbWLxV7pLEEDAUnc5WkLiBamNsjSU,60
2
+ create_awesome_python_app/cache.py,sha256=eHKUlunGexZKYJo0HDNoBoOVq0DjLyRYgEe_wUh4Fk0,11812
3
+ create_awesome_python_app/catalog.py,sha256=_x6TqYAVlURM1dbNsflB9I4g8ku_anZzuK_ggN_OvKs,16909
4
+ create_awesome_python_app/cli.py,sha256=6og82WI-RZxTqHETqrieA8relgppwCqFntOEmOFrIPk,21195
5
+ create_awesome_python_app/prompt_style.py,sha256=A1RQn2hX7khu21bNL5KfD1ZNAHUJfCzmxXIRMgin658,1670
6
+ create_awesome_python_app-0.2.3.dist-info/METADATA,sha256=Kvw2BT6Ff3YF3ZkH8DVJd5uSFyepcGGlMBqX6fgJAp4,1428
7
+ create_awesome_python_app-0.2.3.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
8
+ create_awesome_python_app-0.2.3.dist-info/entry_points.txt,sha256=rszFbUjY-lvMDZ96zX1lALJwgD1mI3CkUuNyF2qqjYo,81
9
+ create_awesome_python_app-0.2.3.dist-info/RECORD,,
@@ -1,8 +0,0 @@
1
- create_awesome_python_app/__init__.py,sha256=eQquCHWgguZ5YtCx7_9ETIahAVlqtvq0K8yjUmhD1Ho,60
2
- create_awesome_python_app/cache.py,sha256=eHKUlunGexZKYJo0HDNoBoOVq0DjLyRYgEe_wUh4Fk0,11812
3
- create_awesome_python_app/catalog.py,sha256=-VQOrXv7Qda0RgIwHktBLKIiC-W2XZ02k1961mPPD_Q,16898
4
- create_awesome_python_app/cli.py,sha256=aNDUiIgRiks6nV72wrAbLit4yxD6VpCCv8Msp9QDXa4,20453
5
- create_awesome_python_app-0.2.1.dist-info/METADATA,sha256=IiVoa-T5XrT7jStTTKXGO9dlrj3xEUZSxnZw5drODdE,1428
6
- create_awesome_python_app-0.2.1.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
7
- create_awesome_python_app-0.2.1.dist-info/entry_points.txt,sha256=rszFbUjY-lvMDZ96zX1lALJwgD1mI3CkUuNyF2qqjYo,81
8
- create_awesome_python_app-0.2.1.dist-info/RECORD,,