Terminal: Das prompt, dprompt and dbanner (0.9.1)
build / build (push) Successful in 38m26s

- das-shell 0.1.0: the Das two-line bash prompt by default (replaces the
  PS1 of Void's skel .bashrc once), dprompt (Textual prompt builder with
  presets and live preview) and dbanner (SSH login banner editor: canvas
  tools, fields with values, widgets, das-banner service for cached ones,
  Das OS logo and tryzub pictures). Python tests in make test.
- das-server 0.3.0 depends on das-shell; the server installer mentions
  dprompt and dbanner; the desktop image includes it.
- run-qemu.sh forwards the guest's SSH to localhost:2222.
- Hardware fixes: Ventoy menu icon, boot menu titles "Das OS <version>".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
das
2026-10-21 09:17:10 +03:00
co-authored by Claude Opus 5.5
parent 66ea614e0d
commit a2bffc7fe9
72 changed files with 5506 additions and 23 deletions
+21
View File
@@ -0,0 +1,21 @@
"""Points das_shell at the source tree and a scratch /etc before it is imported."""
import os
import sys
import tempfile
HERE = os.path.dirname(os.path.abspath(__file__))
FILES = os.path.dirname(HERE)
SCRATCH = tempfile.mkdtemp(prefix="das-shell-test-")
os.environ.update({
"DAS_SHELL_DATA": os.path.join(FILES, "share"),
"DAS_SHELL_LIB": FILES,
"DAS_SHELL_ETC": os.path.join(SCRATCH, "etc"),
"DAS_SHELL_CACHE": os.path.join(SCRATCH, "cache"),
"XDG_CONFIG_HOME": os.path.join(SCRATCH, "config"),
"LANG": "en_US.UTF-8",
})
os.environ.pop("LC_ALL", None)
os.environ.pop("LC_MESSAGES", None)
sys.path.insert(0, os.path.join(FILES, "python"))
@@ -0,0 +1,108 @@
import os
import time
import unittest
from tests.common import SCRATCH
from das_shell import banner, widgets # noqa: E402
class GridTest(unittest.TestCase):
def test_round_trip(self):
g = banner.Grid(10, 3)
g.box(0, 0, 9, 2, "double", g.style_key("cyan", None, True))
g.text(2, 1, "Дас", g.style_key("yellow"))
d = g.to_design({"name": "x"})
g2 = banner.Grid.from_design(d)
self.assertEqual(banner.grid_lines(g2, color=False), ["╔════════╗", "║ Дас ║", "╚════════╝"])
self.assertEqual(len(d["styles"]), 2)
def test_stamp_merges_styles(self):
a = banner.Grid(4, 1)
a.text(0, 0, "ab", a.style_key("red"))
b = banner.Grid(6, 1)
b.text(0, 0, "x", b.style_key("blue"))
b.stamp(2, 0, a)
self.assertEqual(banner.grid_lines(b, color=False), ["x ab"])
self.assertEqual(b.style(b.get(2, 0)[1])[0], "red")
def test_region_and_resize(self):
g = banner.Grid(5, 5)
g.fill(1, 1, 3, 3, "#", " ")
r = g.region(1, 1, 2, 2)
self.assertEqual((r.width, r.height), (2, 2))
g.resize(2, 2)
self.assertEqual(banner.grid_lines(g, color=False), ["", " #"])
class RenderTest(unittest.TestCase):
def test_fields_keep_the_frame(self):
d = banner.load("das-classic")
values = widgets.Values({"title": "T", "url": "https://example.org/"})
values["ip"] = "10.0.0.123"
lines = banner.render(dict(d, widgets=[]), values, [], color=False).splitlines()
self.assertEqual(len({len(line) for line in lines}), 1, lines)
self.assertIn("10.0.0.123", lines[2])
def test_long_value_is_cut(self):
self.assertEqual(banner.fit("abcdef", 4, "left"), "abc…")
self.assertEqual(banner.fit("ab", 4, "center"), " ab ")
def test_languages_and_empty_values(self):
values = widgets.Values({"url": ""})
self.assertEqual(banner.expand("[[Сайт|Site]]: {url}", values), "Site: ")
f = {"x": 0, "y": 0, "width": 20, "text": "{x} · {url}"}
self.assertEqual(banner.field_lines(f, widgets.Values({"x": "host", "url": ""}))[0].rstrip(), "host")
self.assertEqual(banner.expand("{unknown}", values), "{unknown}")
def test_widgets_right(self):
d = {"lines": ["AB", "CD"], "widgets_position": "right"}
out = banner.render(d, widgets.Values(), [["w1", "w2", "w3"]], color=False)
self.assertEqual(out.splitlines(), ["AB w1", "CD w2", " w3"])
def test_every_preset_renders(self):
for name, _ in banner.list_presets():
d = banner.load(name)
out = banner.render(d, widgets.Values(d["vars"]), [], color=True)
self.assertTrue(out.strip(), name)
def test_art_pictures(self):
names = {n for n, _, _ in banner.list_art()}
self.assertTrue({"das-logo", "das-logo-small", "trident", "trident-small", "trident-shield"} <= names)
class WidgetTest(unittest.TestCase):
def test_slow_widget_does_not_hold_the_login(self):
d = {"widgets": [{"type": "command", "cmd": "sleep 10"}, {"type": "command", "cmd": "echo hi"}]}
start = time.monotonic()
blocks = widgets.blocks(d, widgets.Values())
self.assertLess(time.monotonic() - start, 4)
self.assertEqual(blocks, [[], ["hi"]])
def test_cache(self):
w = {"type": "command", "cmd": "echo cached", "mode": "cached", "interval": 1000}
d = {"widgets": [w]}
self.assertEqual(widgets.blocks(d, widgets.Values()), [[]])
widgets.refresh(d)
self.assertEqual(widgets.blocks(d, widgets.Values()), [["cached"]])
def test_file_widget(self):
path = os.path.join(SCRATCH, "out.txt")
with open(path, "w") as f:
f.write("a\nb\n")
self.assertEqual(widgets.run({"type": "file", "path": path}, widgets.Values()), ["a", "b"])
def test_builtins(self):
values = widgets.Values()
for name in widgets.BUILTINS:
widgets.run({"type": "builtin", "name": name}, values)
self.assertTrue(values.get("hostname"))
self.assertNotEqual(values.get("uptime"), "?")
def test_vars_reach_scripts(self):
d = {"vars": {"city": "Kyiv"}, "widgets": [{"type": "command", "cmd": "echo $DBANNER_CITY"}]}
self.assertEqual(widgets.blocks(d, widgets.Values())[0], ["Kyiv"])
if __name__ == "__main__":
unittest.main()
@@ -0,0 +1,86 @@
import datetime
import os
import shutil
import subprocess
import unittest
from tests.common import FILES, SCRATCH
from das_shell import prompt # noqa: E402
NOW = datetime.datetime(2026, 10, 21, 8, 12)
def text(design, **state):
return prompt.preview_text(design, prompt.sample_state(now=NOW, **state))
class PresetTest(unittest.TestCase):
def test_presets_load(self):
names = [n for n, _ in prompt.list_presets()]
self.assertEqual(names[0], "das")
self.assertTrue({"das", "das-git", "classic", "minimal", "server"} <= set(names))
def test_das_matches_the_authors_prompt(self):
d = prompt.load_preset("das")
self.assertEqual(text(d), "┌─[das@dasos]─[2026-10-21]─[08:12]─[~/Projects/web]\n└─╼ $ ")
self.assertEqual(text(d, rc=1), "┌─[✗]─[das@dasos]─[2026-10-21]─[08:12]─[~/Projects/web]\n└─╼ $ ")
self.assertEqual(text(d, root=True), "┌─[root@dasos]─[2026-10-21]─[08:12]─[/etc/sv]\n└─╼ # ")
def test_classic_is_void(self):
self.assertEqual(text(prompt.load_preset("classic")), "[das@dasos web]$ ")
def test_hidden_parts_take_their_separator(self):
d = prompt.load_preset("das-git")
self.assertNotIn("──", text(d))
self.assertIn("─[main*]─[.venv]─[jobs:2]", text(d, git="main*", venv=".venv", jobs=2))
def test_unknown_segments_are_dropped(self):
d = prompt.normalize({"segments": [{"type": "nope"}, {"type": "cwd"}]})
self.assertEqual([s["type"] for s in d["segments"]], ["cwd"])
@unittest.skipUnless(shutil.which("bash"), "needs bash")
class BashTest(unittest.TestCase):
"""The generated script in a real bash, after Void's skel .bashrc."""
def run_prompt(self, preset, before="true", term="xterm-256color"):
path = os.path.join(SCRATCH, preset + ".sh")
with open(path, "w", encoding="utf-8") as f:
f.write(prompt.script(prompt.load_preset(preset)))
code = (f". {FILES}/prompt-lib.sh; . {path}; PS1='[\\u@\\h \\W]\\$ '; "
f"{before}; __das_prompt_hook; printf '%s' \"${{PS1@P}}\"")
out = subprocess.run(["bash", "--norc", "--noprofile", "-c", code], capture_output=True, text=True,
env=dict(os.environ, TERM=term, HOME="/home/das"), cwd="/").stdout
for seq in ("\x01", "\x02"):
out = out.replace(seq, "")
import re
return re.sub(r"\x1b\[[0-9;]*m|\x1b\][^\a]*\a", "", out)
def test_script_is_valid(self):
for name, _ in prompt.list_presets():
path = os.path.join(SCRATCH, name + ".sh")
with open(path, "w", encoding="utf-8") as f:
f.write(prompt.script(prompt.load_preset(name)))
subprocess.run(["bash", "-n", path], check=True)
def test_skel_ps1_is_replaced(self):
out = self.run_prompt("das")
self.assertTrue(out.startswith("┌─["), out)
self.assertNotIn("[✗]", out)
def test_error_mark(self):
self.assertIn("┌─[✗]─[", self.run_prompt("das", "false"))
def test_console_glyphs(self):
out = self.run_prompt("das", "false", term="linux")
self.assertIn("[x]", out)
self.assertIn("└─> ", out)
def test_plain_terminal(self):
out = self.run_prompt("das", term="dumb")
self.assertTrue(out.startswith("┌─["), out)
if __name__ == "__main__":
unittest.main()