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
+101
View File
@@ -0,0 +1,101 @@
#!/usr/bin/env python3
"""Turns an SVG into a dbanner picture: half-block cells (▀ ▄ █) in ANSI colors.
tools/art2cells.py SVG WIDTH_PX HEIGHT_PX NAME "UK title" "EN title" \
"#rrggbb=ansi_name" ... [--mirror] > srcpkgs/das-shell/files/share/banner/art/NAME.json
Each output cell holds two square pixels, so HEIGHT_PX must be even. The SVG is
drawn 8 times larger by ImageMagick, and every pixel takes the color that
covers most of its 8x8 block (or stays empty). Colors are matched to the
nearest one given on the command line; --mirror copies the left half to the
right for symmetric pictures. Needs ImageMagick and Pillow; this is a
developer tool, the package ships only the JSON.
"""
import json
import os
import subprocess
import sys
import tempfile
from collections import Counter
from PIL import Image
SCALE = 8
def main():
args = sys.argv[1:]
mirror = "--mirror" in args
if mirror:
args.remove("--mirror")
svg, width, height, name, uk, en, *pairs = args
width, height = int(width), int(height)
palette = []
for pair in pairs:
hexcolor, ansi = pair.split("=")
palette.append((tuple(int(hexcolor[i:i + 2], 16) for i in (1, 3, 5)), ansi))
with tempfile.TemporaryDirectory() as tmp:
png = os.path.join(tmp, "art.png")
subprocess.run(["convert", "-background", "none", "-density", "600", svg,
"-resize", f"{width * SCALE}x{height * SCALE}!", png], check=True)
img = Image.open(png).convert("RGBA")
def nearest(rgb):
return min(palette, key=lambda p: sum((a - b) ** 2 for a, b in zip(p[0], rgb)))[1]
pixels = []
for py in range(height):
row = []
for px in range(width):
votes = Counter()
for y in range(py * SCALE, (py + 1) * SCALE):
for x in range(px * SCALE, (px + 1) * SCALE):
r, g, b, a = img.getpixel((x, y))
votes[nearest((r, g, b)) if a >= 128 else None] += 1
row.append(votes.most_common(1)[0][0])
if mirror:
# The source is symmetric; rounding is not, so copy the left half
for x in range(width // 2):
row[width - 1 - x] = row[x]
pixels.append(row)
styles, keys = {}, "abcdefghijklmnopqrstuvwxyz"
def key(fg, bg):
want = {"fg": fg, "bg": bg, "bold": False}
for k, v in styles.items():
if v == want:
return k
k = keys[len(styles)]
styles[k] = want
return k
lines, smap = [], []
for cy in range(height // 2):
line, keyline = "", ""
for x in range(width):
top, bottom = pixels[2 * cy][x], pixels[2 * cy + 1][x]
if top is None and bottom is None:
ch, k = " ", " "
elif top == bottom:
ch, k = "█", key(top, None)
elif bottom is None:
ch, k = "▀", key(top, None)
elif top is None:
ch, k = "▄", key(bottom, None)
else:
ch, k = "▀", key(top, bottom)
line += ch
keyline += k
lines.append(line.rstrip())
smap.append(keyline[:len(line.rstrip())])
design = {"name": name, "title": {"uk": uk, "en": en}, "styles": styles,
"lines": lines, "style_map": smap}
json.dump(design, sys.stdout, ensure_ascii=False, indent=2)
sys.stdout.write("\n")
if __name__ == "__main__":
main()