@codebend3r/che 1.0.0 → 1.1.0
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.
- package/README.md +27 -1
- package/bin/art.py +121 -0
- package/bin/che.py +31 -11
- package/bin/install.py +6 -12
- package/bin/shellgen.py +8 -0
- package/package.json +1 -1
- package/shell/che.bash +1 -1
- package/shell/che.fish +1 -1
- package/shell/che.zsh +1 -1
- package/shell/completions/_che +1 -1
- package/shell/completions/che.bash +1 -1
- package/shell/completions/che.fish +1 -1
package/README.md
CHANGED
|
@@ -11,8 +11,33 @@ your shell, and browsable through one menu.**
|
|
|
11
11
|

|
|
12
12
|

|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
</div>
|
|
15
|
+
|
|
16
|
+
<!-- che:banner -->
|
|
17
|
+
```text
|
|
18
|
+
**#*-
|
|
19
|
+
#*-. .::---:::.
|
|
20
|
+
= .:+#%@@@@@@@@@%#*+-.
|
|
21
|
+
.-*%@@@@@@@@@*==----+*-
|
|
22
|
+
.=-====*%@@@@+:..:===--+:
|
|
23
|
+
. .-:. .=%@%+=-+:...=+==
|
|
24
|
+
.=:-.:=:.+#***+***+++**+. ██████╗██╗ ██╗███████╗
|
|
25
|
+
-.:++**+-#@%##%%####%##*- ██╔════╝██║ ██║██╔════╝
|
|
26
|
+
.=+**#%#-:-+=--+%%@@####+. ██║ ███████║█████╗
|
|
27
|
+
.+*#%@@*...=+::+##%%#***-. ██║ ██╔══██║██╔══╝
|
|
28
|
+
-*##%@*=:-#*=-:--==##*+:. ╚██████╗██║ ██║███████╗
|
|
29
|
+
.=+#+-::--::-:.:.: :++=:. ╚═════╝╚═╝ ╚═╝╚══════╝
|
|
30
|
+
:++...::::---==++:---...
|
|
31
|
+
:..=**+++-=*#%@%=:::..
|
|
32
|
+
.:-*%@@@@@@@%#*. :::
|
|
33
|
+
: . . -+**#*=-.
|
|
34
|
+
++-. .::
|
|
35
|
+
+*+=: .-===.
|
|
36
|
+
```
|
|
37
|
+
<!-- /che:banner -->
|
|
15
38
|
|
|
39
|
+
<div align="center">
|
|
40
|
+
<img src="menu.png" alt="The che menu: commands grouped by category on the left, details for the selected command on the right" width="850">
|
|
16
41
|
</div>
|
|
17
42
|
|
|
18
43
|
The scripts are Python, standard library only. The names you type
|
|
@@ -290,6 +315,7 @@ where you copy one machine's rc file to another.
|
|
|
290
315
|
|
|
291
316
|
```
|
|
292
317
|
bin/
|
|
318
|
+
art.py the wordmark and the face beside it, for every screen
|
|
293
319
|
che.py the dispatcher and the interactive menu
|
|
294
320
|
commands.py the command manifest: the source of truth for everything
|
|
295
321
|
install.py installer, first-run wizard, doctor, self-update
|
package/bin/art.py
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"""The che wordmark and the face it shares a name with, as text.
|
|
2
|
+
|
|
3
|
+
``LOGO`` is the block-letter wordmark that ``che install``, ``che doctor`` and
|
|
4
|
+
the first-run screen print. ``FACE`` is Che Guevara, rendered from a photograph
|
|
5
|
+
to 32 columns by 18 rows so the two fit side by side in an 80-column terminal.
|
|
6
|
+
Dense characters are the bright parts of the photo, which reads correctly on a
|
|
7
|
+
dark background.
|
|
8
|
+
|
|
9
|
+
``banner()`` lays the face beside whatever a screen wants next to it, normally
|
|
10
|
+
the wordmark with a subtitle underneath, centred vertically on the face. Lines
|
|
11
|
+
may carry colour escapes; widths are measured with ``tui.display_width`` so the
|
|
12
|
+
escapes do not push the right-hand column about.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from collections.abc import Sequence
|
|
18
|
+
|
|
19
|
+
import tui
|
|
20
|
+
|
|
21
|
+
LOGO = r"""
|
|
22
|
+
██████╗██╗ ██╗███████╗
|
|
23
|
+
██╔════╝██║ ██║██╔════╝
|
|
24
|
+
██║ ███████║█████╗
|
|
25
|
+
██║ ██╔══██║██╔══╝
|
|
26
|
+
╚██████╗██║ ██║███████╗
|
|
27
|
+
╚═════╝╚═╝ ╚═╝╚══════╝
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
FACE = """
|
|
31
|
+
**#*-
|
|
32
|
+
#*-. .::---:::.
|
|
33
|
+
= .:+#%@@@@@@@@@%#*+-.
|
|
34
|
+
.-*%@@@@@@@@@*==----+*-
|
|
35
|
+
.=-====*%@@@@+:..:===--+:
|
|
36
|
+
. .-:. .=%@%+=-+:...=+==
|
|
37
|
+
.=:-.:=:.+#***+***+++**+.
|
|
38
|
+
-.:++**+-#@%##%%####%##*-
|
|
39
|
+
.=+**#%#-:-+=--+%%@@####+.
|
|
40
|
+
.+*#%@@*...=+::+##%%#***-.
|
|
41
|
+
-*##%@*=:-#*=-:--==##*+:.
|
|
42
|
+
.=+#+-::--::-:.:.: :++=:.
|
|
43
|
+
:++...::::---==++:---...
|
|
44
|
+
:..=**+++-=*#%@%=:::..
|
|
45
|
+
.:-*%@@@@@@@%#*. :::
|
|
46
|
+
: . . -+**#*=-.
|
|
47
|
+
++-. .::
|
|
48
|
+
+*+=: .-===.
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
# What the face gets on its left, so it lines up with the two-space indent the
|
|
52
|
+
# wordmark and every subtitle carry.
|
|
53
|
+
INDENT = " "
|
|
54
|
+
# Columns between the face and the block beside it. The wordmark lines start
|
|
55
|
+
# with two spaces of their own, so the visible gap is wider than this.
|
|
56
|
+
GAP = 1
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def logo_lines() -> list[str]:
|
|
60
|
+
return LOGO.strip("\n").splitlines()
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def face_lines() -> list[str]:
|
|
64
|
+
return FACE.strip("\n").splitlines()
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
HEIGHT = len(face_lines())
|
|
68
|
+
WIDTH = len(INDENT) + max(len(line) for line in face_lines())
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def wordmark(colors: tui.Palette | None = None) -> list[str]:
|
|
72
|
+
"""The block letters, coloured the way every screen prints them."""
|
|
73
|
+
colors = colors or tui.palette()
|
|
74
|
+
return [f"{colors.bright_magenta}{line}{colors.reset}" for line in logo_lines()]
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def face(colors: tui.Palette | None = None) -> list[str]:
|
|
78
|
+
"""The face, indented to line up with the wordmark and in the same colour."""
|
|
79
|
+
colors = colors or tui.palette()
|
|
80
|
+
return [f"{colors.bright_magenta}{INDENT}{line}{colors.reset}" for line in face_lines()]
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def compose(left: Sequence[str], right: Sequence[str], *, gap: int = GAP) -> list[str]:
|
|
84
|
+
"""Lay ``right`` beside ``left``, the shorter block centred on the taller."""
|
|
85
|
+
left = list(left)
|
|
86
|
+
right = list(right)
|
|
87
|
+
left_width = max((tui.display_width(line) for line in left), default=0)
|
|
88
|
+
height = max(len(left), len(right))
|
|
89
|
+
left_top = (height - len(left)) // 2
|
|
90
|
+
right_top = (height - len(right)) // 2
|
|
91
|
+
|
|
92
|
+
rows = []
|
|
93
|
+
for index in range(height):
|
|
94
|
+
a = left[index - left_top] if 0 <= index - left_top < len(left) else ""
|
|
95
|
+
b = right[index - right_top] if 0 <= index - right_top < len(right) else ""
|
|
96
|
+
if b:
|
|
97
|
+
a += " " * (left_width + gap - tui.display_width(a))
|
|
98
|
+
rows.append((a + b).rstrip())
|
|
99
|
+
return rows
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def fits(right: Sequence[str], columns: int) -> bool:
|
|
103
|
+
"""Whether the face and ``right`` sit side by side in ``columns``."""
|
|
104
|
+
right_width = max((tui.display_width(line) for line in right), default=0)
|
|
105
|
+
return WIDTH + GAP + right_width <= columns
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def banner(
|
|
109
|
+
right: Sequence[str], *, columns: int | None = None, colors: tui.Palette | None = None
|
|
110
|
+
) -> list[str]:
|
|
111
|
+
"""The face with ``right`` beside it, or ``right`` alone when it would not fit.
|
|
112
|
+
|
|
113
|
+
``right`` is normally the wordmark plus whatever a screen wants under it (a
|
|
114
|
+
subtitle, the command count). It comes back untouched, indent and all, on a
|
|
115
|
+
terminal too narrow for both, so every caller keeps working at 40 columns.
|
|
116
|
+
"""
|
|
117
|
+
if columns is None:
|
|
118
|
+
columns = tui.size()[0]
|
|
119
|
+
if not fits(right, columns):
|
|
120
|
+
return list(right)
|
|
121
|
+
return compose(face(colors), right)
|
package/bin/che.py
CHANGED
|
@@ -30,6 +30,7 @@ from pathlib import Path
|
|
|
30
30
|
|
|
31
31
|
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
32
32
|
|
|
33
|
+
import art
|
|
33
34
|
import install as installer
|
|
34
35
|
import shellgen
|
|
35
36
|
import tui
|
|
@@ -427,33 +428,52 @@ def handle_key(state: Menu, key: str) -> str:
|
|
|
427
428
|
return ""
|
|
428
429
|
|
|
429
430
|
|
|
431
|
+
# The face heads the menu only while the list under it keeps at least this
|
|
432
|
+
# many rows. Below that the one-line header takes over, as it always did.
|
|
433
|
+
MIN_LIST_ROWS = 8
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
def banner_fits(screen: tui.Screen, right: list[str]) -> bool:
|
|
437
|
+
"""Whether the face and wordmark can head the menu and still leave a list."""
|
|
438
|
+
spare = screen.rows - (art.HEIGHT + 1) - 4
|
|
439
|
+
return spare >= MIN_LIST_ROWS and art.fits(right, screen.columns)
|
|
440
|
+
|
|
441
|
+
|
|
430
442
|
def draw(screen: tui.Screen, state: Menu) -> None:
|
|
431
443
|
colors = tui.palette()
|
|
432
444
|
width = screen.columns
|
|
433
445
|
|
|
434
446
|
total = len(all_commands())
|
|
435
447
|
shown = len(state.matches())
|
|
436
|
-
title = f"{colors.bright_magenta}{colors.bold}che{colors.reset}"
|
|
437
448
|
counter = f"{shown}/{total}" if state.query else str(total)
|
|
438
|
-
header = (
|
|
439
|
-
f" {title} {colors.grey}v{VERSION}{colors.reset} "
|
|
440
|
-
f"{colors.grey}{counter} commands{colors.reset}"
|
|
441
|
-
)
|
|
442
|
-
screen.line()
|
|
443
|
-
screen.line(header)
|
|
444
|
-
|
|
445
449
|
if state.searching or state.query:
|
|
446
450
|
caret = f"{colors.cyan}▏{colors.reset}" if state.searching else " "
|
|
447
|
-
|
|
451
|
+
prompt = f" {colors.grey}search{colors.reset} {caret}{state.query}"
|
|
452
|
+
else:
|
|
453
|
+
prompt = f" {colors.grey}type to search ↑↓ move ⏎ run ? keys{colors.reset}"
|
|
454
|
+
|
|
455
|
+
right = art.wordmark(colors)
|
|
456
|
+
right += ["", f" {colors.grey}v{VERSION} {counter} commands{colors.reset}", prompt]
|
|
457
|
+
if banner_fits(screen, right) and not state.show_help:
|
|
458
|
+
# The face beside the wordmark, with the version, the count and the
|
|
459
|
+
# search line tucked under the letters.
|
|
460
|
+
screen.lines(art.banner(right, columns=width, colors=colors))
|
|
448
461
|
else:
|
|
449
|
-
|
|
462
|
+
title = f"{colors.bright_magenta}{colors.bold}che{colors.reset}"
|
|
463
|
+
screen.line()
|
|
464
|
+
screen.line(
|
|
465
|
+
f" {title} {colors.grey}v{VERSION}{colors.reset} "
|
|
466
|
+
f"{colors.grey}{counter} commands{colors.reset}"
|
|
467
|
+
)
|
|
468
|
+
screen.line(prompt)
|
|
450
469
|
screen.line(f" {colors.grey}{'─' * max(0, width - 4)}{colors.reset}")
|
|
470
|
+
header_rows = screen.used
|
|
451
471
|
|
|
452
472
|
if state.show_help:
|
|
453
473
|
draw_help(screen)
|
|
454
474
|
return
|
|
455
475
|
|
|
456
|
-
body_rows = max(3, screen.rows -
|
|
476
|
+
body_rows = max(3, screen.rows - header_rows - 4)
|
|
457
477
|
rows = state.rows()
|
|
458
478
|
if not rows:
|
|
459
479
|
screen.line()
|
package/bin/install.py
CHANGED
|
@@ -39,6 +39,7 @@ from pathlib import Path
|
|
|
39
39
|
# `parent`, where the scripts under bin/<category>/ use `parent.parent`.
|
|
40
40
|
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
41
41
|
|
|
42
|
+
import art
|
|
42
43
|
import shellgen
|
|
43
44
|
from commands import PACKAGE_NAME, REPO, VERSION, all_commands, required_binaries
|
|
44
45
|
from tui import choose, confirm, is_interactive, palette
|
|
@@ -818,22 +819,15 @@ def _with_fish_path_line(block: str) -> str:
|
|
|
818
819
|
# The first-run wizard
|
|
819
820
|
# ---------------------------------------------------------------------------
|
|
820
821
|
|
|
821
|
-
LOGO = r"""
|
|
822
|
-
██████╗██╗ ██╗███████╗
|
|
823
|
-
██╔════╝██║ ██║██╔════╝
|
|
824
|
-
██║ ███████║█████╗
|
|
825
|
-
██║ ██╔══██║██╔══╝
|
|
826
|
-
╚██████╗██║ ██║███████╗
|
|
827
|
-
╚═════╝╚═╝ ╚═╝╚══════╝
|
|
828
|
-
"""
|
|
829
|
-
|
|
830
822
|
|
|
831
823
|
def print_logo(subtitle: str = "") -> None:
|
|
824
|
+
"""The face beside the wordmark, with ``subtitle`` under the letters."""
|
|
832
825
|
colors = palette()
|
|
833
|
-
|
|
834
|
-
print(f"{colors.bright_magenta}{line}{colors.reset}")
|
|
826
|
+
right = art.wordmark(colors)
|
|
835
827
|
if subtitle:
|
|
836
|
-
|
|
828
|
+
right.append(f" {colors.grey}{subtitle}{colors.reset}")
|
|
829
|
+
for line in art.banner(right, colors=colors):
|
|
830
|
+
print(line)
|
|
837
831
|
print()
|
|
838
832
|
|
|
839
833
|
|
package/bin/shellgen.py
CHANGED
|
@@ -20,6 +20,7 @@ from __future__ import annotations
|
|
|
20
20
|
import shlex
|
|
21
21
|
from pathlib import Path
|
|
22
22
|
|
|
23
|
+
import art
|
|
23
24
|
from commands import (
|
|
24
25
|
BUILTINS,
|
|
25
26
|
CATEGORIES,
|
|
@@ -751,8 +752,15 @@ def _tools_table() -> str:
|
|
|
751
752
|
return "\n".join(rows)
|
|
752
753
|
|
|
753
754
|
|
|
755
|
+
def _banner_island() -> str:
|
|
756
|
+
"""The face beside the wordmark, exactly as ``che`` prints them."""
|
|
757
|
+
rows = art.compose(art.face_lines(), art.logo_lines())
|
|
758
|
+
return "```text\n" + "\n".join(rows) + "\n```"
|
|
759
|
+
|
|
760
|
+
|
|
754
761
|
def readme_islands() -> dict[str, str]:
|
|
755
762
|
return {
|
|
763
|
+
"banner": _banner_island(),
|
|
756
764
|
"commands": _commands_table(),
|
|
757
765
|
"builtins": _builtins_table(),
|
|
758
766
|
"keys": _keys_table(),
|
package/package.json
CHANGED
package/shell/che.bash
CHANGED
package/shell/che.fish
CHANGED
package/shell/che.zsh
CHANGED
package/shell/completions/_che
CHANGED