skit-cli 0.0.1__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.
- skit/__init__.py +3 -0
- skit/analyzer.py +410 -0
- skit/argspec.py +528 -0
- skit/argstate.py +166 -0
- skit/atomic.py +101 -0
- skit/cli.py +1621 -0
- skit/config.py +255 -0
- skit/editor.py +77 -0
- skit/flows.py +596 -0
- skit/i18n.py +324 -0
- skit/inlineform.py +61 -0
- skit/launcher.py +326 -0
- skit/locales/zh_CN/LC_MESSAGES/skit.mo +0 -0
- skit/locales/zh_TW/LC_MESSAGES/skit.mo +0 -0
- skit/metawriter.py +267 -0
- skit/models.py +154 -0
- skit/paths.py +44 -0
- skit/pep723.py +292 -0
- skit/promptform.py +67 -0
- skit/reconcile.py +321 -0
- skit/shim.py +386 -0
- skit/store.py +566 -0
- skit/theme.py +123 -0
- skit/tokens.py +112 -0
- skit/tui.py +774 -0
- skit/tui_add.py +361 -0
- skit/tui_footer.py +38 -0
- skit/tui_form.py +586 -0
- skit/tui_health.py +150 -0
- skit/tui_prefs.py +175 -0
- skit/tui_settings.py +374 -0
- skit/uvman.py +288 -0
- skit_cli-0.0.1.dist-info/METADATA +199 -0
- skit_cli-0.0.1.dist-info/RECORD +37 -0
- skit_cli-0.0.1.dist-info/WHEEL +4 -0
- skit_cli-0.0.1.dist-info/entry_points.txt +3 -0
- skit_cli-0.0.1.dist-info/licenses/LICENSE +21 -0
skit/i18n.py
ADDED
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
"""i18n core (GNU gettext).
|
|
2
|
+
|
|
3
|
+
Design notes:
|
|
4
|
+
- Catalogs are standard gettext with **source-string msgids**: the English text is the msgid, so an
|
|
5
|
+
untranslated entry falls back to the English source automatically. English therefore ships no .mo
|
|
6
|
+
(it is the identity). Translations live at locales/<gettext_locale>/LC_MESSAGES/skit.mo — POSIX
|
|
7
|
+
underscore dirs (zh_CN), while the public tags stay hyphenated (zh-CN).
|
|
8
|
+
- Edit the .po sources, then recompile with `python scripts/i18n.py compile` (Babel). Runtime uses the
|
|
9
|
+
stdlib `gettext` module only — zero third-party i18n dependencies.
|
|
10
|
+
- Locale negotiation chain: SKIT_LANG > config.toml [language] > LC_ALL > LC_MESSAGES > LANG > system
|
|
11
|
+
locale > en. Each candidate expands into a fallback chain (zh-TW -> zh-Hant alias -> zh -> en),
|
|
12
|
+
wired into gettext via its language list so a missing translation falls back entry by entry and
|
|
13
|
+
always ends at the English source. The macrolanguage step is script-aware (_zh_family): a
|
|
14
|
+
Traditional tag's bare-"zh" fallback resolves back to zh-TW, not zh-CN, so a msgid missing from
|
|
15
|
+
zh_TW.mo surfaces in English rather than in Simplified glyphs — and symmetrically for Simplified.
|
|
16
|
+
- Pseudo-locale (x-pseudo): an ⟦bracket + stretch⟧ transform over English, used to visually catch
|
|
17
|
+
hard-coded / untranslated strings and truncation. Placeholders (%(name)s) are preserved so
|
|
18
|
+
%-substitution at the call site still works.
|
|
19
|
+
- Headless: no CLI/TUI dependency; store/launcher can import it safely.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
import gettext as _gt
|
|
25
|
+
import os
|
|
26
|
+
import re
|
|
27
|
+
import sys
|
|
28
|
+
import tomllib
|
|
29
|
+
from pathlib import Path
|
|
30
|
+
|
|
31
|
+
from .atomic import atomic_write_toml, load_toml_recoverable
|
|
32
|
+
from .paths import config_dir
|
|
33
|
+
|
|
34
|
+
_LOCALES_DIR = Path(__file__).parent / "locales"
|
|
35
|
+
_DOMAIN = "skit"
|
|
36
|
+
DEFAULT_LOCALE = "en"
|
|
37
|
+
PSEUDO_LOCALE = "x-pseudo"
|
|
38
|
+
|
|
39
|
+
# Alias -> the locale we actually ship.
|
|
40
|
+
_ALIASES = {
|
|
41
|
+
"zh": "zh-CN",
|
|
42
|
+
"zh-hans": "zh-CN",
|
|
43
|
+
"zh-sg": "zh-CN",
|
|
44
|
+
"zh-my": "zh-CN",
|
|
45
|
+
"zh-hant": "zh-TW",
|
|
46
|
+
"zh-hk": "zh-TW",
|
|
47
|
+
"zh-mo": "zh-TW",
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
# Script/region hints that identify which shipped Chinese locale a "zh-*" tag belongs to. Used by
|
|
51
|
+
# _zh_family() to make the bare-macrolanguage fallback step ("zh" on its own, reached by truncating
|
|
52
|
+
# e.g. zh-TW down to zh) resolve within the same script family instead of always defaulting to
|
|
53
|
+
# Simplified.
|
|
54
|
+
#
|
|
55
|
+
# Split into script vs. region tiers because they must be checked in that order: BCP-47 treats the
|
|
56
|
+
# script subtag as more specific than the region subtag, so a tag carrying both must let script
|
|
57
|
+
# decide when they disagree (zh-Hans-TW is Simplified despite the Traditional-associated "TW"
|
|
58
|
+
# region; zh-Hant-CN is Traditional despite the Simplified-associated "CN" region). Region hints
|
|
59
|
+
# are only consulted when the tag has no script subtag at all.
|
|
60
|
+
_HANT_SCRIPT = {"hant"}
|
|
61
|
+
_HANS_SCRIPT = {"hans"}
|
|
62
|
+
_HANT_REGION_HINTS = {"hk", "mo", "tw"}
|
|
63
|
+
_HANS_REGION_HINTS = {"cn", "sg", "my"}
|
|
64
|
+
|
|
65
|
+
_translations: _gt.NullTranslations | None = None
|
|
66
|
+
_active: str = DEFAULT_LOCALE
|
|
67
|
+
_pseudo: bool = False
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _gt_dir(tag: str) -> str:
|
|
71
|
+
"""Public hyphen tag -> gettext locale dir (POSIX underscore): 'zh-CN' -> 'zh_CN'."""
|
|
72
|
+
return tag.replace("-", "_")
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def available_locales() -> list[str]:
|
|
76
|
+
"""Locales we ship: English (identity) plus every dir with a compiled catalog."""
|
|
77
|
+
found = {DEFAULT_LOCALE}
|
|
78
|
+
if _LOCALES_DIR.is_dir():
|
|
79
|
+
for p in _LOCALES_DIR.iterdir():
|
|
80
|
+
if (p / "LC_MESSAGES" / f"{_DOMAIN}.mo").is_file(): # pragma: no mutate — fs-case
|
|
81
|
+
found.add(p.name.replace("_", "-"))
|
|
82
|
+
return sorted(found)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _normalize(tag: str) -> str:
|
|
86
|
+
"""'zh_TW.UTF-8' -> 'zh-TW'; normalizes casing."""
|
|
87
|
+
tag = tag.split(".", maxsplit=1)[0].split("@", maxsplit=1)[0] # pragma: no mutate
|
|
88
|
+
tag = tag.replace("_", "-").strip()
|
|
89
|
+
if not tag:
|
|
90
|
+
return ""
|
|
91
|
+
parts = tag.split("-")
|
|
92
|
+
out = [parts[0].lower()]
|
|
93
|
+
for p in parts[1:]:
|
|
94
|
+
if len(p) == 2:
|
|
95
|
+
out.append(p.upper())
|
|
96
|
+
elif len(p) == 4:
|
|
97
|
+
out.append(p.capitalize())
|
|
98
|
+
else:
|
|
99
|
+
out.append(p.lower())
|
|
100
|
+
return "-".join(out)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _zh_family(tag: str) -> str | None:
|
|
104
|
+
"""Which shipped Chinese locale a "zh-*" tag's script/region hints point to: "zh-TW"
|
|
105
|
+
(Traditional) or "zh-CN" (Simplified). None if tag isn't zh-rooted, or is the bare "zh"
|
|
106
|
+
macrolanguage itself with no script/region qualifier at all (that case is left to the plain
|
|
107
|
+
_ALIASES lookup, which defaults it to zh-CN — the conventional "zh" == Simplified reading).
|
|
108
|
+
|
|
109
|
+
Script subtag is authoritative over region when a tag carries both and they disagree (BCP-47
|
|
110
|
+
treats script as the more specific signal): zh-Hant-CN is Traditional despite the "CN" region,
|
|
111
|
+
and zh-Hans-TW is Simplified despite the "TW" region. Region hints are only consulted when no
|
|
112
|
+
script subtag is present.
|
|
113
|
+
|
|
114
|
+
Used by _expand_chain so that truncating a qualified tag (zh-TW, zh-HK, zh-Hant, zh-Hans, ...)
|
|
115
|
+
down to the bare macrolanguage subtag "zh" resolves within the *same* script family, rather
|
|
116
|
+
than unconditionally injecting zh-CN into a Traditional tag's fallback chain — which would
|
|
117
|
+
surface Simplified glyphs for any msgid untranslated in zh_TW.mo, instead of falling through to
|
|
118
|
+
the English source as the module docstring's negotiation chain intends.
|
|
119
|
+
"""
|
|
120
|
+
parts = [p.lower() for p in tag.split("-")]
|
|
121
|
+
if parts[0] != "zh" or len(parts) == 1:
|
|
122
|
+
return None
|
|
123
|
+
rest = parts[1:]
|
|
124
|
+
if any(p in _HANT_SCRIPT for p in rest):
|
|
125
|
+
return "zh-TW"
|
|
126
|
+
if any(p in _HANS_SCRIPT for p in rest):
|
|
127
|
+
return "zh-CN"
|
|
128
|
+
if any(p in _HANT_REGION_HINTS for p in rest):
|
|
129
|
+
return "zh-TW"
|
|
130
|
+
if any(p in _HANS_REGION_HINTS for p in rest):
|
|
131
|
+
return "zh-CN"
|
|
132
|
+
return None
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _expand_chain(tag: str) -> list[str]:
|
|
136
|
+
"""Expand a single tag into a fallback chain: zh-Hant-TW -> [zh-Hant-TW, zh-Hant, zh] + alias
|
|
137
|
+
resolution.
|
|
138
|
+
|
|
139
|
+
Script-aware: when truncation reaches the bare macrolanguage subtag "zh" for a tag that carried
|
|
140
|
+
a script/region hint (zh-TW, zh-HK, zh-Hant, zh-Hans, zh-CN, ...), that step's alias resolves
|
|
141
|
+
within the same script family (see _zh_family) instead of unconditionally defaulting to zh-CN —
|
|
142
|
+
otherwise every Traditional tag's chain would gain a Simplified candidate ahead of en. A
|
|
143
|
+
genuinely bare "zh" request (no hint at all) still defaults to zh-CN via _ALIASES.
|
|
144
|
+
"""
|
|
145
|
+
family = _zh_family(tag)
|
|
146
|
+
chain: list[str] = []
|
|
147
|
+
parts = tag.split("-")
|
|
148
|
+
while parts:
|
|
149
|
+
cand = "-".join(parts)
|
|
150
|
+
if family is not None and cand.lower() == "zh":
|
|
151
|
+
alias = family
|
|
152
|
+
else:
|
|
153
|
+
alias = _ALIASES.get(cand.lower(), cand)
|
|
154
|
+
for c in (cand, alias):
|
|
155
|
+
if c not in chain:
|
|
156
|
+
chain.append(c)
|
|
157
|
+
parts.pop()
|
|
158
|
+
return chain
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _config_language() -> str:
|
|
162
|
+
path = config_dir() / "config.toml" # pragma: no mutate — fs-case-dependent
|
|
163
|
+
if not path.is_file():
|
|
164
|
+
return ""
|
|
165
|
+
try:
|
|
166
|
+
with open(path, "rb") as f:
|
|
167
|
+
return str(tomllib.load(f).get("language", ""))
|
|
168
|
+
except (OSError, tomllib.TOMLDecodeError):
|
|
169
|
+
return ""
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def _env(name: str) -> str:
|
|
173
|
+
return os.environ.get(name, "") # pragma: no mutate — default only matters via truthiness
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def detect_locale() -> str:
|
|
177
|
+
"""Return the raw tag the user requested (un-negotiated).
|
|
178
|
+
|
|
179
|
+
An empty string means no preference was found.
|
|
180
|
+
"""
|
|
181
|
+
for source in (
|
|
182
|
+
_env("SKIT_LANG"),
|
|
183
|
+
_config_language(),
|
|
184
|
+
_env("LC_ALL"),
|
|
185
|
+
_env("LC_MESSAGES"),
|
|
186
|
+
_env("LANG"),
|
|
187
|
+
):
|
|
188
|
+
tag = _normalize(source) if source and source.lower() != "c" else ""
|
|
189
|
+
if tag:
|
|
190
|
+
return tag
|
|
191
|
+
try:
|
|
192
|
+
import locale as _locale
|
|
193
|
+
|
|
194
|
+
loc = _locale.getlocale()[0]
|
|
195
|
+
if loc:
|
|
196
|
+
return _normalize(loc)
|
|
197
|
+
except (ValueError, TypeError):
|
|
198
|
+
pass
|
|
199
|
+
return ""
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def is_supported(tag: str) -> bool:
|
|
203
|
+
"""Whether the tag (or any candidate on its fallback chain) maps to a shipped locale.
|
|
204
|
+
The pseudo-locale is always considered supported. Used to validate entry points like the CLI
|
|
205
|
+
`lang` command."""
|
|
206
|
+
normalized = _normalize(tag)
|
|
207
|
+
if not normalized:
|
|
208
|
+
return False
|
|
209
|
+
if normalized.lower() == PSEUDO_LOCALE:
|
|
210
|
+
return True
|
|
211
|
+
shipped = set(available_locales())
|
|
212
|
+
return any(c in shipped for c in _expand_chain(normalized))
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def negotiate(requested: str) -> tuple[str, list[str]]:
|
|
216
|
+
"""Negotiate (primary locale, full fallback chain ending in en)."""
|
|
217
|
+
shipped = set(available_locales())
|
|
218
|
+
chain: list[str] = []
|
|
219
|
+
if requested:
|
|
220
|
+
for cand in _expand_chain(requested):
|
|
221
|
+
if cand in shipped and cand not in chain:
|
|
222
|
+
chain.append(cand)
|
|
223
|
+
if DEFAULT_LOCALE not in chain:
|
|
224
|
+
chain.append(DEFAULT_LOCALE)
|
|
225
|
+
return chain[0], chain
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def init(lang: str | None = None) -> str:
|
|
229
|
+
"""Initialize (or reinitialize) the message system; return the effective primary locale."""
|
|
230
|
+
global _translations, _active, _pseudo
|
|
231
|
+
requested = _normalize(lang) if lang else detect_locale()
|
|
232
|
+
_pseudo = requested.lower() == PSEUDO_LOCALE
|
|
233
|
+
if _pseudo:
|
|
234
|
+
primary, chain = DEFAULT_LOCALE, [DEFAULT_LOCALE]
|
|
235
|
+
_active = PSEUDO_LOCALE
|
|
236
|
+
else:
|
|
237
|
+
primary, chain = negotiate(requested)
|
|
238
|
+
_active = primary
|
|
239
|
+
# English is the msgid itself (identity), so only non-en locales need a catalog; gettext chains
|
|
240
|
+
# them as fallbacks in order and returns the English source for anything still untranslated.
|
|
241
|
+
languages = [_gt_dir(c) for c in chain if c != DEFAULT_LOCALE]
|
|
242
|
+
_translations = _gt.translation(_DOMAIN, _LOCALES_DIR, languages=languages, fallback=True)
|
|
243
|
+
return _active
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def current_locale() -> str:
|
|
247
|
+
return _active
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
_PSEUDO_TABLE = str.maketrans("aeiouAEIOU", "àéîöûÀÉÎÖÛ")
|
|
251
|
+
_PLACEHOLDER = re.compile(r"%\([^)]*\)[sdr]|%[sdrifgeExXoc%]")
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def _pseudoize(text: str) -> str:
|
|
255
|
+
"""en -> pseudo: bracket markers + vowel transforms (~30% inflation), leaving %(...)s
|
|
256
|
+
placeholders untouched so call-site %-substitution still resolves."""
|
|
257
|
+
parts: list[str] = []
|
|
258
|
+
last = 0 # pragma: no mutate — text[None:x] == text[0:x]
|
|
259
|
+
for m in _PLACEHOLDER.finditer(text):
|
|
260
|
+
parts.append(text[last : m.start()].translate(_PSEUDO_TABLE))
|
|
261
|
+
parts.append(m.group())
|
|
262
|
+
last = m.end()
|
|
263
|
+
parts.append(text[last:].translate(_PSEUDO_TABLE))
|
|
264
|
+
return f"⟦{''.join(parts)}~~⟧"
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def gettext(message: str) -> str:
|
|
268
|
+
"""Translate a source-string message (falls back to the English source; never raises)."""
|
|
269
|
+
if _translations is None:
|
|
270
|
+
init()
|
|
271
|
+
if _translations is None: # pragma: no cover — init() guarantees it is set
|
|
272
|
+
raise RuntimeError("i18n init failed")
|
|
273
|
+
text = _translations.gettext(message)
|
|
274
|
+
return _pseudoize(text) if _pseudo else text
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def ngettext(singular: str, plural: str, n: int) -> str:
|
|
278
|
+
"""Translate a source-string message with plural selection (CLDR rules per the target locale)."""
|
|
279
|
+
if _translations is None:
|
|
280
|
+
init()
|
|
281
|
+
if _translations is None: # pragma: no cover — init() guarantees it is set
|
|
282
|
+
raise RuntimeError("i18n init failed")
|
|
283
|
+
text = _translations.ngettext(singular, plural, n)
|
|
284
|
+
return _pseudoize(text) if _pseudo else text
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def set_language(tag: str) -> str:
|
|
288
|
+
"""Write config.toml and take effect immediately. Returns the effective locale. An empty tag
|
|
289
|
+
clears the setting (back to auto-detection).
|
|
290
|
+
|
|
291
|
+
Uses atomic.load_toml_recoverable() for the read step, so a present-but-corrupt config.toml is
|
|
292
|
+
backed up to config.toml.bak (and the user warned on stderr) instead of being silently wiped —
|
|
293
|
+
matching config.save_editor()/save_mirror(). This module can't import config.py for the same
|
|
294
|
+
recovery helper those use (config.py imports gettext from here, so the reverse would cycle);
|
|
295
|
+
the neutral atomic module is what both safely share.
|
|
296
|
+
"""
|
|
297
|
+
path = config_dir() / "config.toml" # pragma: no mutate — fs-case-dependent
|
|
298
|
+
recovery = load_toml_recoverable(path)
|
|
299
|
+
doc = recovery.doc
|
|
300
|
+
if recovery.corrupt:
|
|
301
|
+
if recovery.backup_path is not None:
|
|
302
|
+
print(
|
|
303
|
+
gettext(
|
|
304
|
+
"%(path)s is corrupt and could not be parsed. It has been backed up to "
|
|
305
|
+
"%(backup)s before this change; recover any lost settings from that file."
|
|
306
|
+
)
|
|
307
|
+
% {"path": str(path), "backup": str(recovery.backup_path)},
|
|
308
|
+
file=sys.stderr,
|
|
309
|
+
)
|
|
310
|
+
else:
|
|
311
|
+
print(
|
|
312
|
+
gettext(
|
|
313
|
+
"%(path)s is corrupt and could not be parsed, and it could not be backed up "
|
|
314
|
+
"either; the settings it contained will be lost when this change is saved."
|
|
315
|
+
)
|
|
316
|
+
% {"path": str(path)},
|
|
317
|
+
file=sys.stderr,
|
|
318
|
+
)
|
|
319
|
+
if tag:
|
|
320
|
+
doc["language"] = _normalize(tag)
|
|
321
|
+
else:
|
|
322
|
+
doc.pop("language", None)
|
|
323
|
+
atomic_write_toml(path, doc)
|
|
324
|
+
return init(tag or None)
|
skit/inlineform.py
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""The CLI's inline mini-form: the run form opened in place, in the same terminal.
|
|
2
|
+
|
|
3
|
+
`skit run NAME` on a TTY (with `form = "tui"`, the default) opens the same RunFormScreen
|
|
4
|
+
the TUI uses — via Textual's inline mode, so there's no alternate screen and the
|
|
5
|
+
scrollback survives. Submit collapses the form and the script runs right below it;
|
|
6
|
+
`--plain` / `form = "plain"` / TERM=dumb fall back to line prompts instead.
|
|
7
|
+
|
|
8
|
+
The extra-arguments row is hidden here: on the CLI, passthrough args already arrived
|
|
9
|
+
via `skit run NAME -- <args>` (argv owns them; two sources would fight).
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from typing import TYPE_CHECKING, override
|
|
15
|
+
|
|
16
|
+
from textual.app import App
|
|
17
|
+
|
|
18
|
+
from . import flows, theme
|
|
19
|
+
from .theme import CLAUDE_THEME
|
|
20
|
+
from .tui_form import FormResult, RunFormScreen
|
|
21
|
+
|
|
22
|
+
if TYPE_CHECKING:
|
|
23
|
+
from .models import Entry
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class _InlineFormApp(App[FormResult]):
|
|
27
|
+
ENABLE_COMMAND_PALETTE = False
|
|
28
|
+
CSS = theme.CHROME_CSS
|
|
29
|
+
|
|
30
|
+
def __init__(self, entry: Entry, plan: flows.FormPlan, prefill: dict[str, str]) -> None:
|
|
31
|
+
super().__init__()
|
|
32
|
+
self._entry: Entry = entry
|
|
33
|
+
self._plan: flows.FormPlan = plan
|
|
34
|
+
self._prefill: dict[str, str] = prefill
|
|
35
|
+
|
|
36
|
+
@override
|
|
37
|
+
def get_css_variables(self) -> dict[str, str]:
|
|
38
|
+
# The first stylesheet parse runs before on_mount activates the theme; the
|
|
39
|
+
# screen CSS needs $skit-box-* resolvable from the very first frame.
|
|
40
|
+
return {**super().get_css_variables(), **theme.BOX_VARIABLES}
|
|
41
|
+
|
|
42
|
+
def on_mount(self) -> None:
|
|
43
|
+
self.register_theme(CLAUDE_THEME)
|
|
44
|
+
self.theme = "skit-claude"
|
|
45
|
+
|
|
46
|
+
def _done(result: FormResult) -> None:
|
|
47
|
+
self.exit(result)
|
|
48
|
+
|
|
49
|
+
self.push_screen(
|
|
50
|
+
RunFormScreen(self._entry, self._plan, self._prefill, include_extra=False), _done
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def collect(entry: Entry, plan: flows.FormPlan, prefill: dict[str, str]) -> dict[str, str] | None:
|
|
55
|
+
"""Run the inline form; returns raw values, or None when the user cancelled."""
|
|
56
|
+
app = _InlineFormApp(entry, plan, prefill)
|
|
57
|
+
result = app.run(inline=True)
|
|
58
|
+
if result is None:
|
|
59
|
+
return None
|
|
60
|
+
values, _extra = result
|
|
61
|
+
return values
|