console-window 2.1.1__tar.gz → 2.1.3__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.
- {console_window-2.1.1 → console_window-2.1.3}/PKG-INFO +55 -1
- {console_window-2.1.1 → console_window-2.1.3}/README.md +54 -0
- {console_window-2.1.1 → console_window-2.1.3}/SampleApp.py +26 -1
- {console_window-2.1.1 → console_window-2.1.3}/console_window/__init__.py +6 -2
- console_window-2.1.3/console_window/cli.py +145 -0
- console_window-2.1.3/console_window/glyphs.py +555 -0
- console_window-2.1.3/console_window/help.py +66 -0
- {console_window-2.1.1 → console_window-2.1.3}/console_window/opts.py +73 -3
- {console_window-2.1.1 → console_window-2.1.3}/console_window/screens.py +36 -0
- {console_window-2.1.1 → console_window-2.1.3}/console_window/spinner.py +2 -1
- {console_window-2.1.1 → console_window-2.1.3}/console_window/textwidth.py +31 -3
- {console_window-2.1.1 → console_window-2.1.3}/console_window/theme.py +87 -5
- {console_window-2.1.1 → console_window-2.1.3}/console_window/widgets.py +40 -8
- {console_window-2.1.1 → console_window-2.1.3}/console_window/window.py +162 -41
- console_window-2.1.3/docs/GLYPH-MIGRATION-PLAN.md +380 -0
- {console_window-2.1.1 → console_window-2.1.3}/tests/CHANGELOG.md +86 -0
- console_window-2.1.3/tests/conftest.py +23 -0
- console_window-2.1.3/tests/test_cli_display_args.py +131 -0
- console_window-2.1.3/tests/test_demo_mode.py +97 -0
- console_window-2.1.3/tests/test_glyph_policy.py +141 -0
- console_window-2.1.3/tests/test_glyph_rendering.py +124 -0
- console_window-2.1.3/tests/test_glyph_transliteration.py +107 -0
- console_window-2.1.3/tests/test_glyphs.py +121 -0
- {console_window-2.1.1 → console_window-2.1.3}/tests/test_interactive.py +11 -0
- console_window-2.1.3/tests/test_prompt_window.py +288 -0
- {console_window-2.1.1 → console_window-2.1.3}/tests/test_public_api.py +4 -1
- {console_window-2.1.1 → console_window-2.1.3}/tests/test_textwidth.py +34 -0
- console_window-2.1.3/tests/test_theme_picker.py +165 -0
- console_window-2.1.1/tests/test_demo_mode.py +0 -46
- console_window-2.1.1/tests/test_prompt_window.py +0 -152
- {console_window-2.1.1 → console_window-2.1.3}/.github/workflows/ci.yml +0 -0
- {console_window-2.1.1 → console_window-2.1.3}/.gitignore +0 -0
- {console_window-2.1.1 → console_window-2.1.3}/LICENSE +0 -0
- {console_window-2.1.1 → console_window-2.1.3}/console_window/backend.py +0 -0
- {console_window-2.1.1 → console_window-2.1.3}/console_window/context.py +0 -0
- {console_window-2.1.1 → console_window-2.1.3}/console_window/geometry.py +0 -0
- {console_window-2.1.1 → console_window-2.1.3}/console_window/header.py +0 -0
- {console_window-2.1.1 → console_window-2.1.3}/console_window/keys.py +0 -0
- {console_window-2.1.1 → console_window-2.1.3}/console_window/signals.py +0 -0
- {console_window-2.1.1 → console_window-2.1.3}/pyproject.toml +0 -0
- {console_window-2.1.1 → console_window-2.1.3}/runner +0 -0
- {console_window-2.1.1 → console_window-2.1.3}/tests/AUDIT.md +0 -0
- {console_window-2.1.1 → console_window-2.1.3}/tests/test_backend.py +0 -0
- {console_window-2.1.1 → console_window-2.1.3}/tests/test_context.py +0 -0
- {console_window-2.1.1 → console_window-2.1.3}/tests/test_dialogs.py +0 -0
- {console_window-2.1.1 → console_window-2.1.3}/tests/test_flash.py +0 -0
- {console_window-2.1.1 → console_window-2.1.3}/tests/test_geometry.py +0 -0
- {console_window-2.1.1 → console_window-2.1.3}/tests/test_header.py +0 -0
- {console_window-2.1.1 → console_window-2.1.3}/tests/test_low_colour.py +0 -0
- {console_window-2.1.1 → console_window-2.1.3}/tests/test_overflow.py +0 -0
- {console_window-2.1.1 → console_window-2.1.3}/tests/test_prompt_line.py +0 -0
- {console_window-2.1.1 → console_window-2.1.3}/tests/test_render_retry.py +0 -0
- {console_window-2.1.1 → console_window-2.1.3}/tests/test_sample_app.py +0 -0
- {console_window-2.1.1 → console_window-2.1.3}/tests/test_screens.py +0 -0
- {console_window-2.1.1 → console_window-2.1.3}/tests/test_signals.py +0 -0
- {console_window-2.1.1 → console_window-2.1.3}/tests/test_spinner.py +0 -0
- {console_window-2.1.1 → console_window-2.1.3}/tests/test_status.py +0 -0
- {console_window-2.1.1 → console_window-2.1.3}/tests/test_taken_nav_keys.py +0 -0
- {console_window-2.1.1 → console_window-2.1.3}/tests/test_theme.py +0 -0
- {console_window-2.1.1 → console_window-2.1.3}/tests/test_viewport_pad.py +0 -0
- {console_window-2.1.1 → console_window-2.1.3}/tests/test_widechars.py +0 -0
- {console_window-2.1.1 → console_window-2.1.3}/tests/test_widgets.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: console-window
|
|
3
|
-
Version: 2.1.
|
|
3
|
+
Version: 2.1.3
|
|
4
4
|
Summary: A robust curses wrapper for creating Text User Interfaces (TUIs).
|
|
5
5
|
Author-email: Joe Defen <joedef@duck.com>
|
|
6
6
|
Requires-Python: >=3.10
|
|
@@ -385,6 +385,60 @@ Mixing the two values raises `AssertionError` rather than silently changing what
|
|
|
385
385
|
teardown means. Use `singleton=False` in tests and in applications that create
|
|
386
386
|
independent windows (for example, one real window plus a headless one).
|
|
387
387
|
|
|
388
|
+
### Symbols on terminals that cannot draw them
|
|
389
|
+
|
|
390
|
+
The UI is built around a handful of non-ASCII symbols (`│`, `⮞`, `█`, `←`, `→`,
|
|
391
|
+
`·`, box drawing). A terminal that cannot encode them does not fail loudly: it
|
|
392
|
+
paints a box, or nothing, and the row quietly loses its meaning. `console-window`
|
|
393
|
+
therefore keeps every decorative symbol in one registry, in two spellings, and
|
|
394
|
+
degrades by itself:
|
|
395
|
+
|
|
396
|
+
* **No code change.** A window detects the terminal when it is constructed — a
|
|
397
|
+
non-UTF-8 output encoding or `TERM=dumb` selects the ASCII spellings — and
|
|
398
|
+
text handed to `add_header()` / `add_body()` / `draw()` / `status()` /
|
|
399
|
+
`flash()` is transliterated before it is measured, so widths and the
|
|
400
|
+
run-length attribute counts stay correct.
|
|
401
|
+
* **Only decoration is rewritten.** `Grüße` stays `Grüße`: the substitution table
|
|
402
|
+
contains registered glyphs, never arbitrary text. Set
|
|
403
|
+
`CONSOLE_WINDOW_GLYPH_AUDIT=1` to log each decorative code point you use but
|
|
404
|
+
did not register, with the exact `register()` call to add.
|
|
405
|
+
* **App vocabulary.** Register what only your app draws:
|
|
406
|
+
|
|
407
|
+
```python
|
|
408
|
+
Glyphs.register_many({'tick': ('✓', 'OK')}, probe=('tick',))
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
or pass it with the window: `ConsoleWindowOpts(glyphs={'tick': ('✓', 'OK')},
|
|
412
|
+
glyphs_probe=('tick',))`. Once registered, the glyph both degrades with
|
|
413
|
+
`Glyphs.get('tick')` and appears in the font check via `Glyphs.probe_text()`.
|
|
414
|
+
* **Two switches, no argparse boilerplate.** The bundled helper adds them:
|
|
415
|
+
|
|
416
|
+
```python
|
|
417
|
+
parser = cli.add_display_arguments(argparse.ArgumentParser())
|
|
418
|
+
opts = parser.parse_args()
|
|
419
|
+
cli.apply_display_arguments(opts, prog='myapp')
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
`--ascii` forces the fallback (`--ascii=no` forces the full set) and
|
|
423
|
+
`--glyphs {auto,ascii,unicode}` is its symmetric spelling; `--glyphs` wins if
|
|
424
|
+
both are given. `--theme` seeds the first window's colour theme. Apps without
|
|
425
|
+
argparse can use `CONSOLE_WINDOW_GLYPHS=auto|ascii|unicode`, which outranks
|
|
426
|
+
detection.
|
|
427
|
+
* **The mode is process-wide**, like the colour-pair table, because there is one
|
|
428
|
+
terminal. `ConsoleWindowOpts(ascii=...)` seeds it and `win.ascii_mode` reads
|
|
429
|
+
it; a window constructed later never reverts an explicit choice.
|
|
430
|
+
* **Help screens can explain it for you.** Return
|
|
431
|
+
`help.theme_paragraph()` from `Screen.extra_help_paragraphs()` and set
|
|
432
|
+
`show_display_notes = True` on a `BasicHelpScreen` subclass to append the font
|
|
433
|
+
check (Unicode mode) or the way back to the full set (ASCII mode). Both are
|
|
434
|
+
off by default, so an upgrading app's help output is unchanged.
|
|
435
|
+
|
|
436
|
+
Themes work the same way: `Theme.create_picker_screen(base, cycle_key=None,
|
|
437
|
+
store=None, note=None)` discovers the key you bound to `spin_theme` for its hint
|
|
438
|
+
line, remembers the choice through any object with `load()`/`save()`, and
|
|
439
|
+
`Theme.initial(cli_theme, store)` returns the theme a new process should start
|
|
440
|
+
on. The library never opens a file.
|
|
441
|
+
|
|
388
442
|
## Versioning and Deprecation
|
|
389
443
|
|
|
390
444
|
`console-window` follows semantic versioning: patch releases fix behaviour
|
|
@@ -366,6 +366,60 @@ Mixing the two values raises `AssertionError` rather than silently changing what
|
|
|
366
366
|
teardown means. Use `singleton=False` in tests and in applications that create
|
|
367
367
|
independent windows (for example, one real window plus a headless one).
|
|
368
368
|
|
|
369
|
+
### Symbols on terminals that cannot draw them
|
|
370
|
+
|
|
371
|
+
The UI is built around a handful of non-ASCII symbols (`│`, `⮞`, `█`, `←`, `→`,
|
|
372
|
+
`·`, box drawing). A terminal that cannot encode them does not fail loudly: it
|
|
373
|
+
paints a box, or nothing, and the row quietly loses its meaning. `console-window`
|
|
374
|
+
therefore keeps every decorative symbol in one registry, in two spellings, and
|
|
375
|
+
degrades by itself:
|
|
376
|
+
|
|
377
|
+
* **No code change.** A window detects the terminal when it is constructed — a
|
|
378
|
+
non-UTF-8 output encoding or `TERM=dumb` selects the ASCII spellings — and
|
|
379
|
+
text handed to `add_header()` / `add_body()` / `draw()` / `status()` /
|
|
380
|
+
`flash()` is transliterated before it is measured, so widths and the
|
|
381
|
+
run-length attribute counts stay correct.
|
|
382
|
+
* **Only decoration is rewritten.** `Grüße` stays `Grüße`: the substitution table
|
|
383
|
+
contains registered glyphs, never arbitrary text. Set
|
|
384
|
+
`CONSOLE_WINDOW_GLYPH_AUDIT=1` to log each decorative code point you use but
|
|
385
|
+
did not register, with the exact `register()` call to add.
|
|
386
|
+
* **App vocabulary.** Register what only your app draws:
|
|
387
|
+
|
|
388
|
+
```python
|
|
389
|
+
Glyphs.register_many({'tick': ('✓', 'OK')}, probe=('tick',))
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
or pass it with the window: `ConsoleWindowOpts(glyphs={'tick': ('✓', 'OK')},
|
|
393
|
+
glyphs_probe=('tick',))`. Once registered, the glyph both degrades with
|
|
394
|
+
`Glyphs.get('tick')` and appears in the font check via `Glyphs.probe_text()`.
|
|
395
|
+
* **Two switches, no argparse boilerplate.** The bundled helper adds them:
|
|
396
|
+
|
|
397
|
+
```python
|
|
398
|
+
parser = cli.add_display_arguments(argparse.ArgumentParser())
|
|
399
|
+
opts = parser.parse_args()
|
|
400
|
+
cli.apply_display_arguments(opts, prog='myapp')
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
`--ascii` forces the fallback (`--ascii=no` forces the full set) and
|
|
404
|
+
`--glyphs {auto,ascii,unicode}` is its symmetric spelling; `--glyphs` wins if
|
|
405
|
+
both are given. `--theme` seeds the first window's colour theme. Apps without
|
|
406
|
+
argparse can use `CONSOLE_WINDOW_GLYPHS=auto|ascii|unicode`, which outranks
|
|
407
|
+
detection.
|
|
408
|
+
* **The mode is process-wide**, like the colour-pair table, because there is one
|
|
409
|
+
terminal. `ConsoleWindowOpts(ascii=...)` seeds it and `win.ascii_mode` reads
|
|
410
|
+
it; a window constructed later never reverts an explicit choice.
|
|
411
|
+
* **Help screens can explain it for you.** Return
|
|
412
|
+
`help.theme_paragraph()` from `Screen.extra_help_paragraphs()` and set
|
|
413
|
+
`show_display_notes = True` on a `BasicHelpScreen` subclass to append the font
|
|
414
|
+
check (Unicode mode) or the way back to the full set (ASCII mode). Both are
|
|
415
|
+
off by default, so an upgrading app's help output is unchanged.
|
|
416
|
+
|
|
417
|
+
Themes work the same way: `Theme.create_picker_screen(base, cycle_key=None,
|
|
418
|
+
store=None, note=None)` discovers the key you bound to `spin_theme` for its hint
|
|
419
|
+
line, remembers the choice through any object with `load()`/`save()`, and
|
|
420
|
+
`Theme.initial(cli_theme, store)` returns the theme a new process should start
|
|
421
|
+
on. The library never opens a file.
|
|
422
|
+
|
|
369
423
|
## Versioning and Deprecation
|
|
370
424
|
|
|
371
425
|
`console-window` follows semantic versioning: patch releases fix behaviour
|
|
@@ -20,6 +20,13 @@ from console_window import (
|
|
|
20
20
|
ScreenStack,
|
|
21
21
|
Theme,
|
|
22
22
|
)
|
|
23
|
+
from console_window import help as help_text
|
|
24
|
+
|
|
25
|
+
#: The demo's own vocabulary: a glyph the library does not know about. It is
|
|
26
|
+
#: handed to the window (not registered globally) to show the option route; the
|
|
27
|
+
#: registered fallback means ``\u2713`` degrades to ``OK`` automatically.
|
|
28
|
+
DEMO_GLYPHS = {'tick': ('\u2713', 'OK')}
|
|
29
|
+
DEMO_PROBE = ('tick', 'cursor', 'choice', 'scroll_prev', 'scroll_next')
|
|
23
30
|
|
|
24
31
|
# Screen IDs
|
|
25
32
|
MAIN_ST = 0
|
|
@@ -67,6 +74,9 @@ class SampleApp:
|
|
|
67
74
|
# Configure console window
|
|
68
75
|
win_opts = ConsoleWindowOpts(
|
|
69
76
|
head_line=True,
|
|
77
|
+
# Symbol set: one app glyph plus the font check the HELP screen shows.
|
|
78
|
+
glyphs=DEMO_GLYPHS,
|
|
79
|
+
glyphs_probe=DEMO_PROBE,
|
|
70
80
|
# No row sizing needed: the pads are viewport-sized and rows live in
|
|
71
81
|
# the model, so the 'z' stress mode's 2000 filler rows need no
|
|
72
82
|
# option. max_rows is only a safety ceiling (32767 by default).
|
|
@@ -498,9 +508,15 @@ class MainScreen(DemoScreen):
|
|
|
498
508
|
|
|
499
509
|
|
|
500
510
|
class HelpScreen(DemoScreen, BasicHelpScreen):
|
|
511
|
+
# Show the library's font-check / ASCII-fallback note under our own text.
|
|
512
|
+
show_display_notes = True
|
|
513
|
+
|
|
501
514
|
def __init__(self, app):
|
|
502
515
|
super().__init__('HELP', app)
|
|
503
516
|
|
|
517
|
+
def extra_help_paragraphs(self):
|
|
518
|
+
return (help_text.theme_paragraph(open_key='T', cycle_key='t'),)
|
|
519
|
+
|
|
504
520
|
# def draw_screen(self):
|
|
505
521
|
# app = self.app
|
|
506
522
|
# app.win.set_pick_mode(False)
|
|
@@ -545,13 +561,22 @@ class HistoryScreen(DemoScreen):
|
|
|
545
561
|
self.text_screen.draw_screen()
|
|
546
562
|
|
|
547
563
|
|
|
548
|
-
class ThemeScreen(Theme.create_picker_screen(DemoScreen)):
|
|
564
|
+
class ThemeScreen(Theme.create_picker_screen(DemoScreen, cycle_key='t')):
|
|
549
565
|
"""THEMES screen supplied by Theme.create_picker_screen()."""
|
|
550
566
|
|
|
551
567
|
def __init__(self, app):
|
|
552
568
|
super().__init__('THEMES', app)
|
|
553
569
|
|
|
554
570
|
def main(backend=None, singleton=True):
|
|
571
|
+
# The same two lines every console-window app needs for --ascii/--theme.
|
|
572
|
+
import argparse
|
|
573
|
+
|
|
574
|
+
from console_window import cli
|
|
575
|
+
|
|
576
|
+
parser = cli.add_display_arguments(argparse.ArgumentParser(prog='SampleApp'))
|
|
577
|
+
opts = parser.parse_args()
|
|
578
|
+
cli.apply_display_arguments(opts, prog='SampleApp')
|
|
579
|
+
|
|
555
580
|
try:
|
|
556
581
|
app = SampleApp(backend=backend, singleton=singleton)
|
|
557
582
|
app.main_loop()
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
# Single source of truth for the package version: pyproject.toml declares
|
|
2
2
|
# ``dynamic = ["version"]``, so flit reads this value instead of duplicating it.
|
|
3
|
-
__version__ = "2.1.
|
|
3
|
+
__version__ = "2.1.3"
|
|
4
4
|
|
|
5
5
|
from .context import Context
|
|
6
|
+
from .glyphs import Glyph, Glyphs
|
|
6
7
|
from .header import HeaderLine, Span
|
|
7
8
|
from .keys import NAVIGATION_KEYS
|
|
8
9
|
from .opts import ConsoleWindowOpts
|
|
@@ -15,7 +16,7 @@ from .screens import (
|
|
|
15
16
|
)
|
|
16
17
|
from .signals import reset_ctrl_c
|
|
17
18
|
from .spinner import OptionSpinner
|
|
18
|
-
from .theme import Theme
|
|
19
|
+
from .theme import Theme, ThemeStore
|
|
19
20
|
from .widgets import IncrementalSearchBar, InlineConfirmation, PromptLine
|
|
20
21
|
from .window import ConsoleWindow, TerminalTooSmallError
|
|
21
22
|
|
|
@@ -27,6 +28,8 @@ __all__ = [
|
|
|
27
28
|
'ConsoleWindow',
|
|
28
29
|
'ConsoleWindowOpts',
|
|
29
30
|
'Context',
|
|
31
|
+
'Glyph',
|
|
32
|
+
'Glyphs',
|
|
30
33
|
'HeaderLine',
|
|
31
34
|
'IncrementalSearchBar',
|
|
32
35
|
'InlineConfirmation',
|
|
@@ -37,5 +40,6 @@ __all__ = [
|
|
|
37
40
|
'Span',
|
|
38
41
|
'TerminalTooSmallError',
|
|
39
42
|
'Theme',
|
|
43
|
+
'ThemeStore',
|
|
40
44
|
'reset_ctrl_c',
|
|
41
45
|
]
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"""Argument-parser boilerplate for the display options.
|
|
2
|
+
|
|
3
|
+
Every curses app ends up writing the same three switches: a way to force the
|
|
4
|
+
plain-ASCII symbol set, a way to pick a colour theme, and a way to print what
|
|
5
|
+
was detected. This module supplies them, so an app's ``main()`` gains two
|
|
6
|
+
lines instead of sixty:
|
|
7
|
+
|
|
8
|
+
parser = cli.add_display_arguments(parser)
|
|
9
|
+
...
|
|
10
|
+
cli.apply_display_arguments(opts, prog='myapp')
|
|
11
|
+
|
|
12
|
+
The two switches are deliberately symmetric. ``--ascii`` accepts an optional
|
|
13
|
+
boolean (``--ascii``, ``--ascii=no``) and ``--glyphs`` accepts a mode
|
|
14
|
+
(``auto``, ``ascii``, ``unicode``); ``--glyphs`` wins when both are given, and
|
|
15
|
+
"absent" everywhere means *detect*, never "assume Unicode".
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import argparse
|
|
21
|
+
import sys
|
|
22
|
+
from collections.abc import Callable, Mapping
|
|
23
|
+
from typing import TextIO
|
|
24
|
+
|
|
25
|
+
from . import glyphs
|
|
26
|
+
from .glyphs import TerminalReport
|
|
27
|
+
from .theme import Theme, ThemeStore
|
|
28
|
+
|
|
29
|
+
__all__ = ['add_display_arguments', 'apply_display_arguments', 'resolve_force_ascii']
|
|
30
|
+
|
|
31
|
+
_TRUE_WORDS = frozenset({'1', 'true', 'yes', 'y', 'on'})
|
|
32
|
+
_FALSE_WORDS = frozenset({'0', 'false', 'no', 'n', 'off'})
|
|
33
|
+
_MODES = ('auto', 'ascii', 'unicode')
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _bool_argument(value: str) -> bool:
|
|
37
|
+
"""argparse ``type=`` for ``--ascii=VALUE``; bare ``--ascii`` uses ``const``."""
|
|
38
|
+
lowered = value.strip().lower()
|
|
39
|
+
if lowered in _TRUE_WORDS:
|
|
40
|
+
return True
|
|
41
|
+
if lowered in _FALSE_WORDS:
|
|
42
|
+
return False
|
|
43
|
+
raise argparse.ArgumentTypeError(
|
|
44
|
+
f'expected a boolean (yes/no, true/false, on/off), got {value!r}'
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def add_display_arguments(
|
|
49
|
+
parser: argparse.ArgumentParser,
|
|
50
|
+
*,
|
|
51
|
+
theme_store: ThemeStore | None = None,
|
|
52
|
+
prog: str | None = None,
|
|
53
|
+
) -> argparse.ArgumentParser:
|
|
54
|
+
"""Add ``--ascii``, ``--glyphs`` and ``--theme`` to *parser*.
|
|
55
|
+
|
|
56
|
+
Returns *parser* so a call site can wrap its own construction. *theme_store*
|
|
57
|
+
is accepted for symmetry with :func:`apply_display_arguments` (an app that
|
|
58
|
+
passes one here is one that remembers the choice); it does not change the
|
|
59
|
+
switches. *prog* only shapes the help text.
|
|
60
|
+
"""
|
|
61
|
+
name = prog or parser.prog or 'this program'
|
|
62
|
+
remembered = ' (remembered for the next start)' if theme_store is not None else ''
|
|
63
|
+
parser.add_argument(
|
|
64
|
+
'--ascii',
|
|
65
|
+
nargs='?',
|
|
66
|
+
const=True,
|
|
67
|
+
default=None,
|
|
68
|
+
type=_bool_argument,
|
|
69
|
+
metavar='BOOL',
|
|
70
|
+
help=(
|
|
71
|
+
f'use plain-ASCII symbols; --ascii=no forces the Unicode set. '
|
|
72
|
+
f'Without the switch {name} detects the terminal'
|
|
73
|
+
),
|
|
74
|
+
)
|
|
75
|
+
parser.add_argument(
|
|
76
|
+
'--glyphs',
|
|
77
|
+
choices=_MODES,
|
|
78
|
+
default=None,
|
|
79
|
+
help=(
|
|
80
|
+
'symbol mode: auto detects the terminal, ascii forces the fallback, '
|
|
81
|
+
'unicode forces the full set (same thing as --ascii / --ascii=no)'
|
|
82
|
+
),
|
|
83
|
+
)
|
|
84
|
+
parser.add_argument(
|
|
85
|
+
'--theme',
|
|
86
|
+
choices=Theme.list_all(),
|
|
87
|
+
default=None,
|
|
88
|
+
help=f'color theme{remembered}',
|
|
89
|
+
)
|
|
90
|
+
return parser
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def resolve_force_ascii(args: argparse.Namespace) -> bool | None:
|
|
94
|
+
"""Map a parsed namespace to ``force_ascii``: True, False or None (detect)."""
|
|
95
|
+
mode = getattr(args, 'glyphs', None)
|
|
96
|
+
if mode == 'ascii':
|
|
97
|
+
return True
|
|
98
|
+
if mode == 'unicode':
|
|
99
|
+
return False
|
|
100
|
+
if mode == 'auto':
|
|
101
|
+
return None
|
|
102
|
+
flag = getattr(args, 'ascii', None)
|
|
103
|
+
if flag is None or isinstance(flag, bool):
|
|
104
|
+
return flag
|
|
105
|
+
raise TypeError(
|
|
106
|
+
f'args.ascii must be None or a bool (it is {flag!r}); parse it with '
|
|
107
|
+
'add_display_arguments() or pass a bool'
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def apply_display_arguments(
|
|
112
|
+
args: argparse.Namespace,
|
|
113
|
+
*,
|
|
114
|
+
printer: Callable[[str], object] = print,
|
|
115
|
+
prog: str = '',
|
|
116
|
+
environ: Mapping[str, str] | None = None,
|
|
117
|
+
stream: TextIO | None = None,
|
|
118
|
+
) -> TerminalReport:
|
|
119
|
+
"""Set the glyph mode, print the report's notes, seed the theme.
|
|
120
|
+
|
|
121
|
+
Call this once, *before* any window is created, so the mode is decided while
|
|
122
|
+
nothing has been drawn. Returns the :class:`~console_window.glyphs.
|
|
123
|
+
TerminalReport` so a caller can react to ``ascii_required`` (for example by
|
|
124
|
+
printing a warning of its own).
|
|
125
|
+
|
|
126
|
+
*printer* is a callable taking one string; pass ``sys.stderr.write`` style
|
|
127
|
+
wrappers through ``lambda line: print(line, file=...)`` if needed.
|
|
128
|
+
"""
|
|
129
|
+
report = glyphs.apply_policy(
|
|
130
|
+
resolve_force_ascii(args),
|
|
131
|
+
environ=environ,
|
|
132
|
+
stream=sys.stdout if stream is None else stream,
|
|
133
|
+
)
|
|
134
|
+
for note in report.notes:
|
|
135
|
+
printer(f'{prog}: {note}' if prog else note)
|
|
136
|
+
|
|
137
|
+
theme = getattr(args, 'theme', None)
|
|
138
|
+
if theme:
|
|
139
|
+
if theme in Theme.list_all():
|
|
140
|
+
# No window exists yet, so record the choice for the first one.
|
|
141
|
+
Theme.set_active(theme)
|
|
142
|
+
else:
|
|
143
|
+
message = f'unknown theme {theme!r}; keeping {Theme.resolve(None)!r}'
|
|
144
|
+
printer(f'{prog}: {message}' if prog else message)
|
|
145
|
+
return report
|