// Package ausgabe rendert die CLI-Ausgabe im Aufbau der Vorgabe: Kopfzeile, Phasen, Abschlussbox. // Alle Texte kommen aus der Engine; hier wird nur gesetzt und gefärbt. package ausgabe import ( "fmt" "os" "strings" ) // Farbmodus: 24-bit, 256 Farben oder keine. type Farbmodus int const ( Aus Farbmodus = iota Farben256 Echtfarben ) // Die Markenfarben (siehe README, Abschnitt Branding). type rgb struct{ r, g, b uint8 } var ( paper = rgb{0xE6, 0xED, 0xF3} muted = rgb{0x8B, 0x98, 0xA5} teal = rgb{0x2D, 0xD4, 0xBF} amber = rgb{0xF5, 0xB8, 0x41} rot = rgb{0xF8, 0x71, 0x71} ) // Stil legt fest, wie gesetzt wird: Farben und ob nur ASCII-Symbole erlaubt sind. type Stil struct { Farbe Farbmodus ASCII bool } // FarbmodusErmitteln: aus bei NO_COLOR, --no-color oder ohne Terminal; sonst 24-bit, wenn das // Terminal es meldet (COLORTERM), sonst 256 Farben. func FarbmodusErmitteln(keineFarbe, istTerminal bool) Farbmodus { if keineFarbe || !istTerminal { return Aus } if _, gesetzt := os.LookupEnv("NO_COLOR"); gesetzt { return Aus } if os.Getenv("TERM") == "dumb" { return Aus } switch strings.ToLower(os.Getenv("COLORTERM")) { case "truecolor", "24bit": return Echtfarben } return Farben256 } func (s Stil) faerben(f rgb, fett bool, text string) string { if s.Farbe == Aus || text == "" { return text } var code string switch s.Farbe { case Echtfarben: code = fmt.Sprintf("38;2;%d;%d;%d", f.r, f.g, f.b) default: code = fmt.Sprintf("38;5;%d", xterm256(f)) } if fett { code = "1;" + code } return "\x1b[" + code + "m" + text + "\x1b[0m" } func (s Stil) Paper(t string) string { return s.faerben(paper, false, t) } func (s Stil) Fett(t string) string { return s.faerben(paper, true, t) } func (s Stil) Muted(t string) string { return s.faerben(muted, false, t) } func (s Stil) Teal(t string) string { return s.faerben(teal, false, t) } func (s Stil) TealFett(t string) string { return s.faerben(teal, true, t) } func (s Stil) Amber(t string) string { return s.faerben(amber, false, t) } func (s Stil) AmberFett(t string) string { return s.faerben(amber, true, t) } func (s Stil) Rot(t string) string { return s.faerben(rot, false, t) } // xterm256 wählt die nächste Farbe aus Würfel (16–231) oder Graustufen (232–255). func xterm256(f rgb) int { stufen := []int{0, 95, 135, 175, 215, 255} naechste := func(wert uint8) int { beste, abstand := 0, 1<<30 for i, s := range stufen { d := int(wert) - s if d*d < abstand { beste, abstand = i, d*d } } return beste } r, g, b := naechste(f.r), naechste(f.g), naechste(f.b) wuerfel := 16 + 36*r + 6*g + b dw := quadAbstand(f, stufen[r], stufen[g], stufen[b]) grau := (int(f.r) + int(f.g) + int(f.b)) / 3 gi := (grau - 8) / 10 if gi < 0 { gi = 0 } else if gi > 23 { gi = 23 } gw := 8 + gi*10 if quadAbstand(f, gw, gw, gw) < dw { return 232 + gi } return wuerfel } func quadAbstand(f rgb, r, g, b int) int { dr, dg, db := int(f.r)-r, int(f.g)-g, int(f.b)-b return dr*dr + dg*dg + db*db } // Symbole je Status, mit ASCII-Ersatz (--ascii). func (s Stil) symbol(status string) string { if s.ASCII { switch status { case "ERLEDIGT": return "[OK]" case "LAEUFT": return "[..]" case "FEHLER": return "[!!]" default: return "[--]" } } switch status { case "ERLEDIGT": return "✓" case "LAEUFT": return "◐" case "FEHLER": return "✗" default: return "○" } } // Spinner für die laufende Phase: ◐◓◑◒. var spinner = []string{"◐", "◓", "◑", "◒"}