Styling & Highlighting mit spring-shell-jline
Was die Bibliothek in Version 4.0.3 anbietet — entwickelt entlang der
Attachment-Ausgabe von ReadCommand.java aus tina-snow-cli.
ReadCommand färbt seine Ausgabe heute mit elf handgeschriebenen
ANSI-Escape-Konstanten. Das funktioniert — und lässt genau die Fähigkeiten liegen,
für die spring-shell-jline überhaupt als eigene Dependency
in der pom.xml steht. Dieser Report zeigt fünf Ausbaustufen,
jede als Before/After am gleichen Code.
Ausgangslage: elf Konstanten und ein String.format
Der aktuelle Stand ist ein Block privater Konstanten und ein String.format,
das Farbe und Inhalt in derselben Zeile verschränkt. Lesbar ist das nur, solange man die
Escape-Sequenzen im Kopf hat.
/** * Optionally for coloring the output in the terminal. ANSI escape codes are used for text formatting and coloring. */ private static final String RESET = "\u001B[0m"; private static final String BOLD = "\u001B[1m"; private static final String DIM = "\u001B[2m"; private static final String ITALIC = "\u001B[3m"; private static final String UNDERLINE = "\u001B[4m"; private static final String RED = "\u001B[31m"; private static final String GREEN = "\u001B[32m"; private static final String BLUE = "\u001B[34m"; private static final String MAGENTA = "\u001B[35m"; private static final String GRAY = "\u001B[90m"; private static final String DARK_BLUE_256 = "\u001B[38;5;18m";ReadCommand.java:24–38
sb.append(String.format(
"\n%s | %s | %s | %s | erstellt: %s | von: %s%n",
BOLD + DARK_BLUE_256 + fileName + RESET,
ITALIC + MAGENTA + reversedContentType + RESET,
GREEN + sizeBytes + GRAY + " bytes",
DIM + RED + sysId + RESET,
UNDERLINE + BLUE + createdOn + RESET,
BOLD + DARK_BLUE_256 + createdBy + RESET));
ReadCommand.java:63–72
Im Terminal sieht das so aus:
-
S1WartungDie Farbwahl ist einkompiliert. Eine Farbanpassung ist ein Code-Change plus Build plus Deployment.
-
S2Portabilität
\u001B[38;5;18msetzt ein 256-Farb-Terminal voraus. Auf einem 16-Farben-TERM wird das nicht heruntergerechnet, sondern ignoriert oder falsch dargestellt. -
S3Pipe-AusgabeDie Escapes werden bedingungslos geschrieben — auch wenn die Ausgabe in eine Datei oder durch
grepläuft. Genau das ist im nicht-interaktiven CI/CD-Modus dieses Projekts der Normalfall.
Warum das JAR überhaupt separat in der pom.xml steht
Ein Blick in die beiden JARs erklärt die Dependency-Zeile: In
spring-shell-core-4.0.3.jar liegt keine einzige
Style-, Theme- oder Table-Klasse. Das komplette Vokabular ist in 4.0.x nach
org.springframework.shell.jline.tui.* gewandert und wird nur
von spring-shell-jline ausgeliefert.
| Baustein | Voll qualifizierter Name (4.0.3) | JAR |
|---|---|---|
| Style-Auflösung | o.s.shell.jline.tui.style.ThemeResolver | jline |
| Style-Tags & Defaults | …style.StyleSettings | jline |
| Template-Rendering | …style.TemplateExecutor | jline |
| Theme-Definition | …style.Theme, ThemeSettings, ThemeRegistry | jline |
| Sonderzeichen-Tags | …style.FigureSettings | jline |
| Tabellen | …tui.table.TableBuilder, TableModelBuilder, BorderStyle | jline |
| Eingabe-Highlighting | o.s.shell.jline.CommandHighlighter | jline |
| Bean-Verdrahtung | o.s.shell.core.autoconfigure.ThemingAutoConfiguration | core-autoconfigure |
Die Verdrahtung nimmt einem Spring Boot ab. ThemingAutoConfiguration
stellt vier Beans bereit — man injiziert sie einfach in den Command:
// aus ThemingAutoConfiguration, per javap ausgelesen: public ThemeActive themeActive(); public ThemeRegistry themeRegistry(ObjectProvider<Theme> themes); public ThemeResolver shellThemeResolver(ThemeRegistry, SpringShellProperties, ThemeActive); public TemplateExecutor templateExecutor(ThemeResolver);
themeRegistry sammelt seine Themes per
ObjectProvider<Theme> ein. Ein eigenes Theme zu registrieren
heißt deshalb: ein @Bean Theme deklarieren. Mehr nicht.
Stufe 1 — AttributedString statt Escape-Literalen
Die unterste Stufe tauscht nur die Schreibweise: AttributedStyle
aus JLine beschreibt den Stil als Objekt, AttributedStringBuilder
setzt Text und Stil zusammen. Der entscheidende Gewinn ist nicht Schönheit, sondern dass
JLine beim Rendern weiß, was das Zielterminal kann.
BOLD + DARK_BLUE_256 + fileName + RESET
AttributedStyle.DEFAULT
.bold()
.foreground(AttributedStyle.BLUE)
import org.jline.utils.AttributedStringBuilder;
import org.jline.utils.AttributedStyle;
private static final AttributedStyle S_FILE = AttributedStyle.DEFAULT.bold().foreground(AttributedStyle.BLUE);
private static final AttributedStyle S_TYPE = AttributedStyle.DEFAULT.italic().foreground(AttributedStyle.MAGENTA);
private static final AttributedStyle S_SIZE = AttributedStyle.DEFAULT.foreground(AttributedStyle.GREEN);
private static final AttributedStyle S_UNIT = AttributedStyle.DEFAULT.faint();
private static final AttributedStyle S_SYSID = AttributedStyle.DEFAULT.faint().foreground(AttributedStyle.RED);
private static final AttributedStyle S_CREATED = AttributedStyle.DEFAULT.underline().foreground(AttributedStyle.BLUE);
AttributedStringBuilder asb = new AttributedStringBuilder();
asb.styled(S_FILE, fileName)
.append(" | ")
.styled(S_TYPE, reversedContentType)
.append(" | ")
.styled(S_SIZE, sizeBytes)
.styled(S_UNIT, " bytes")
.append(" | ")
.styled(S_SYSID, sysId)
.append(" | erstellt: ")
.styled(S_CREATED, createdOn)
.append(" | von: ")
.styled(S_FILE, createdBy);
return asb.toAnsi(); // NICHT toString() -- siehe Falle 1
-
FALLE 1scheitert lautlos
Ein
@Command-Rückgabewert wird vonMethodInvokerCommandAdaptermitPrintWriter.println(Object)ausgegeben — also überString.valueOf(…)und damittoString().AttributedString.toString()liefert den Klartext ohne jede Formatierung. Wer einAttributedStringdirekt zurückgibt, bekommt eine korrekte, aber völlig farblose Ausgabe — ohne Fehlermeldung, ohne Warnung. Richtig ist.toAnsi()oder, terminal-bewusst,.toAnsi(terminal).
Weitere Nützlichkeiten von AttributedStyle / AttributedStringBuilder
// echte RGB-Farbe statt 256er-Index -- JLine rechnet auf die Terminal-Palette herunter
AttributedStyle.DEFAULT.foreground(0x1a, 0x3a, 0x8f); // foreground(int r, int g, int b)
AttributedStyle.DEFAULT.foregroundRgb(0x1a3a8f); // dieselbe Farbe als ein int
// helle Variante einer Basisfarbe
AttributedStyle.DEFAULT.foreground(AttributedStyle.CYAN | AttributedStyle.BRIGHT);
// weitere Attribute
.bold() .faint() .italic() .underline() .blink() .inverse() .conceal() .crossedOut()
.background(int) .backgroundRgb(int) .foregroundDefault() .boldOff()
// Regex-basiertes nachtraegliches Faerben -- z.B. Suchtreffer in der Ausgabe markieren
asb.styleMatches(Pattern.compile("(?i)" + Pattern.quote(filter)),
AttributedStyle.DEFAULT.inverse());
// terminal-bewusstes Rendern: JLine kennt Farbtiefe und Palette des Ziels
return asb.toAnsi(terminal);
Stufe 2 — ThemeResolver und benannte Style-Tags
Stufe 1 hat die Escapes ersetzt, aber die Farbwahl steckt weiter im Code. Stufe 2 gibt ihr
einen Namen. StyleSettings definiert 18 Tags mit Default-Stil,
ThemeResolver löst einen Tag zum konkreten Stil auf.
Der Command sagt dann nicht mehr „fett und blau“, sondern „das ist ein
Listenwert“.
| Konstante | Tag-Name | Default-Stil |
|---|---|---|
TAG_TITLE | style-title | bold |
TAG_VALUE | style-value | fg:blue |
TAG_LIST_KEY | style-list-key | default |
TAG_LIST_VALUE | style-list-value | bold,fg:green |
TAG_LEVEL_INFO | style-level-info | fg:green |
TAG_LEVEL_WARN | style-level-warn | fg:yellow |
TAG_LEVEL_ERROR | style-level-error | fg:red |
TAG_ITEM_ENABLED | style-item-enabled | bold |
TAG_ITEM_DISABLED | style-item-disabled | faint |
TAG_ITEM_SELECTED | style-item-selected | fg:green |
TAG_ITEM_UNSELECTED | style-item-unselected | bold |
TAG_ITEM_SELECTOR | style-item-selector | bold,fg:bright-cyan |
TAG_HIGHLIGHT | style-highlight | bold |
TAG_BACKGROUND | style-background | default |
… dazu style-dialog-background, style-button-background, style-menubar-background, style-statusbar-background — nur für die TUI-Views relevant | ||
Die Stil-Sprache selbst ist die von JLine — kommagetrennte Attribute:
bold, faint, italic, underline, blink, inverse, conceal, crossed-out fg:red fg:bright-cyan bg:black default
import org.springframework.shell.jline.tui.style.StyleSettings;
import org.springframework.shell.jline.tui.style.ThemeResolver;
private final ThemeResolver themeResolver; // Bean aus ThemingAutoConfiguration
public ReadCommand(AuthService authService,
ServiceNowService serviceNowService,
ThemeResolver themeResolver) {
this.authService = authService;
this.serviceNowService = serviceNowService;
this.themeResolver = themeResolver;
}
// Tag -> Stil-Ausdruck ("bold,fg:green") -> AttributedStyle
AttributedStyle fileStyle = themeResolver.resolveStyle(
themeResolver.resolveStyleTag(StyleSettings.TAG_LIST_VALUE));
AttributedStringBuilder asb = new AttributedStringBuilder();
asb.styled(fileStyle, fileName);
Bequemer ist der Ausdrucks-Modus. evaluateExpression reicht intern
an JLines StyleExpression weiter — Syntax
@{stil text}:
AttributedString line = themeResolver.evaluateExpression(
"@{bold,fg:blue " + fileName + "} | " +
"@{italic,fg:magenta " + reversedContentType + "} | " +
"@{fg:green " + sizeBytes + "}@{faint bytes}");
return line.toAnsi();
evaluateExpression interpretiert @{…}
im gesamten String. Dateinamen aus ServiceNow sind Fremddaten — enthält einer eine
geschweifte Klammer, verschiebt sich die Auswertung. Für Werte aus dem Backend ist der
Tag-Weg (resolveStyle + styled(…))
der sichere, der Ausdrucks-Weg gehört in Gerüst-Text mit bekanntem Inhalt.
Stufe 3 — TemplateExecutor: Layout aus dem Code holen
TemplateExecutor rendert StringTemplate-4-Templates und
registriert dafür einen eigenen AttributeRenderer. Dessen
Trick: ein format, das mit style-
beginnt, wird über den ThemeResolver aufgelöst und der
Wert anschließend in @{stil wert} verpackt. Das Layout
wandert damit aus dem Java-Code in einen Template-String — und die Fremddaten laufen
durch den Renderer statt durch den Ausdrucks-Parser.
sb.append(String.format(
"\n%s | %s | %s | %s | erstellt: %s | von: %s%n",
BOLD + DARK_BLUE_256 + fileName + RESET, /* ... */ ));
import org.springframework.shell.jline.tui.style.TemplateExecutor;
private static final String ROW_TEMPLATE =
"<fileName; format=\"style-list-value\"> | " +
"<contentType; format=\"style-value\"> | " +
"<size; format=\"style-level-info\"> bytes | " +
"<sysId; format=\"style-item-disabled\"> | " +
"erstellt: <createdOn; format=\"style-value\"> | " +
"von: <createdBy; format=\"style-title\">";
private final TemplateExecutor templateExecutor; // Bean aus ThemingAutoConfiguration
Map<String, Object> attrs = Map.of(
"fileName", fileName,
"contentType", reversedContentType,
"size", sizeBytes,
"sysId", sysId,
"createdOn", createdOn,
"createdBy", createdBy);
return templateExecutor.render(ROW_TEMPLATE, attrs).toAnsi();
Zwei Extras des Renderers
Kürzen. Neben style-* kennt der Renderer
ein truncate--Format. Die Optionen werden mit
- getrennt, Schlüssel und Wert mit :,
erlaubt sind width und prefix (beides ganze Zahlen).
<fileName; format="truncate-width:24"> <sysId; format="truncate-width:12-prefix:6">
Sonderzeichen. renderGroup
erwartet eine ST-Gruppe mit einem Template namens main und stellt
zusätzlich das Wörterbuch figures bereit — die Tags
aus FigureSettings: tick,
info, warning,
error, checkbox-on/-off,
Pfeile in alle vier Richtungen und question-mark.
Stufe 4 — Eigenes Theme, umschaltbar per application.yaml
Erst hier zahlt sich das Tag-System aus: Solange der Code nur Tags nennt, lässt sich die
gesamte Farbgebung austauschen, ohne einen Command anzufassen.
ThemeRegistry sammelt jedes Theme-Bean
ein, spring.shell.theme.name wählt aus.
import org.springframework.shell.jline.tui.style.StyleSettings;
import org.springframework.shell.jline.tui.style.Theme;
import org.springframework.shell.jline.tui.style.ThemeSettings;
@Configuration
class ShellThemeConfig {
@Bean
Theme tinaTheme() {
StyleSettings styles = new StyleSettings() {
@Override public String listValue() { return "bold,fg:cyan"; }
@Override public String value() { return "fg:bright-blue"; }
@Override public String listLevelInfo() { return "fg:green"; }
@Override public String listLevelWarn() { return "bold,fg:yellow"; }
@Override public String listLevelError() { return "bold,fg:bright-red"; }
@Override public String itemDisabled() { return "faint"; }
};
return Theme.of("tina", new ThemeSettings(styles) { });
}
}
spring:
main:
web-application-type: none
shell:
interactive:
enabled: false # Nicht-interaktiver Modus fuer CI/CD-Nutzung
theme:
name: tina # -> ThemeRegistry.get("tina")
src/main/resources/application.yaml — ergänzt um den theme-Block
ThemeSettings und StyleSettings sind
abstract, aber ohne abstrakte Methoden — jede Methode hat einen Default. Man
überschreibt deshalb nur die Tags, die man wirklich anders will, der Rest bleibt beim
Standard. StyleSettings.dump() und
ThemeSettings.dump() liefern eine Variante, die statt der Stile die
Tag-Namen zurückgibt — praktisch beim Debuggen, um zu sehen, welcher Tag wo greift.
Stufe 5 — TableBuilder statt Pipe-Zeichen
Die Attachment-Ausgabe ist inhaltlich eine Tabelle, die derzeit mit
" | " nachgebaut wird — mit dem bekannten Effekt, dass
Spalten bei unterschiedlich langen Dateinamen ausfransen.
org.springframework.shell.jline.tui.table löst das inklusive
Spaltenbreiten, Umbruch und Rahmen.
import org.springframework.shell.jline.tui.table.*;
TableModelBuilder<String> model = new TableModelBuilder<>();
model.addRow().addValue("Datei").addValue("Typ").addValue("Groesse")
.addValue("sys_id").addValue("erstellt").addValue("von");
for (Map<String, Object> a : sortedAttachments) {
model.addRow()
.addValue((String) a.get("file_name"))
.addValue(serviceNowService.getExtensionForContentType((String) a.get("content_type")))
.addValue(String.valueOf(a.get("size_bytes")))
.addValue((String) a.get("sys_id"))
.addValue(String.valueOf(a.get("sys_created_on")))
.addValue(String.valueOf(a.get("sys_created_by")));
}
Table table = new TableBuilder(model.build())
.addHeaderBorder(BorderStyle.fancy_light)
.on(CellMatchers.column(2)).addAligner(SimpleHorizontalAligner.right).and()
.on(CellMatchers.column(0)).addSizer(new AbsoluteWidthSizeConstraints(32)).and()
.build();
return table.render(120); // 120 = verfuegbare Terminalbreite
| Bereich | Verfügbar in 4.0.3 |
|---|---|
Rahmenstile (BorderStyle) |
oldschool, air, fancy_light, fancy_heavy, fancy_double, dazu fancy_light und fancy_heavy je als _double_dash, _triple_dash, _quadruple_dash |
| Rahmen-Kurzformen | addOutlineBorder, addHeaderBorder, addFullBorder, addInnerBorder, addHeaderAndVerticalsBorders, paintBorder(style, int) |
Zellauswahl (CellMatchers) |
table(), row(int), column(int), at(int,int), ofType(Class) |
| Pro Zelle einstellbar | addFormatter, addSizer, addWrapper, addAligner |
| Größe & Umbruch | AutoSizeConstraints, AbsoluteWidthSizeConstraints, NoWrapSizeConstraints, KeyValueSizeConstraints, DelimiterTextWrapper, KeyValueTextWrapper |
| Modelle | TableModelBuilder, ArrayTableModel, BeanListTableModel |
Formatter darf pro Zelle einen bereits gefärbten String
liefern (themeResolver.evaluateExpression(…).toAnsi()).
Die Spaltenbreite berechnet Table dann allerdings inklusive der
Escape-Zeichen — bei gefärbten Zellen deshalb AbsoluteWidthSizeConstraints
setzen, statt die Auto-Breite raten zu lassen.
Bonus — Prompt und Eingabe-Highlighting
Zwei Stellen färbt spring-shell-jline nicht in der Ausgabe,
sondern in der Eingabezeile. Beide greifen ausschließlich im interaktiven Modus —
in diesem Projekt steht spring.shell.interactive.enabled: false,
sie sind hier also der Vollständigkeit halber genannt.
// eigener Prompt: ein Bean, das AttributedString liefert
@Bean
PromptProvider tinaPrompt(ThemeResolver themeResolver) {
return () -> themeResolver.evaluateExpression("@{bold,fg:cyan tina-snow}@{faint > }");
}
CommandHighlighter implementiert JLines
Highlighter und färbt beim Tippen den erkannten Command-Namen
gegen die CommandRegistry ein. Es ist bereits verdrahtet —
ersetzen lässt es sich durch ein eigenes Highlighter-Bean.
Entscheidungshilfe
| Stufe | Aufwand | Lohnt sich, wenn… | Verzichtbar, wenn… |
|---|---|---|---|
| 1 AttributedString | gering | … die Ausgabe auf verschiedenen Terminals landen kann. JLine rechnet Farben auf die Zielpalette herunter. | … nur ein bekanntes Terminal im Spiel ist. |
| 2 ThemeResolver | gering | … mehrere Commands entstehen und einheitlich aussehen sollen. | … es bei einem einzelnen Command bleibt. |
| 3 TemplateExecutor | mittel | … das Ausgabeformat sich öfter ändert als die Logik, oder Fremddaten sicher eingebettet werden müssen. | … das Format seit Monaten stabil ist. |
| 4 Eigenes Theme | mittel | … Farben pro Umgebung oder Nutzer umschaltbar sein sollen (dev/prod, hell/dunkel). | … niemand die Farbgebung je anfassen wird. Setzt Stufe 2 voraus. |
| 5 TableBuilder | gering | … wie hier mehrere gleichförmige Datensätze ausgegeben werden. Größter sichtbarer Gewinn pro Zeile Code. | … die Ausgabe eine einzelne Statusmeldung ist. |
-
E1EmpfehlungFür
ReadCommandkonkret: Stufe 5 plus Stufe 2. Die Attachment-Liste ist eine Tabelle, und einTableBuilderlöst das Spaltenproblem, das reine Farbgebung gar nicht adressiert. Die Stufen 3 und 4 sind erst dann dran, wenn ein zweiter Command dieselbe Formatierung braucht. -
E2offenKeine der fünf Stufen löst S3 aus der Ausgangslage von allein: Auch
toAnsi()ohne Terminal-Argument schreibt Escapes bedingungslos. Im nicht-interaktiven Modus dieses Projekts ist das der Regelfall. Die VariantetoAnsi(Terminal)behält die Terminal-Fähigkeiten im Blick — ob sie bei umgeleiteter Ausgabe vollständig auf Klartext schaltet, ist für diesen Report nicht verifiziert und gehört vor einer Umstellung gegen die CI-Pipeline getestet.
Anhang: Verifikations-Log
Jede Signatur, jeder Tag-Name und jeder Default-Stil in diesem Report stammt aus dem Bytecode der lokal aufgelösten JARs, nicht aus der Dokumentation. Zum Nachvollziehen:
M2=~/.m2/repository/org/springframework/shell
J=$M2/spring-shell-jline/4.0.3/spring-shell-jline-4.0.3.jar
# Welche Style-Klassen liefert das jline-JAR ueberhaupt?
unzip -l $J | grep "tui/style/"
# Tag-Namen als Konstantenwerte
javap -constants -classpath $J org.springframework.shell.jline.tui.style.StyleSettings
# Default-Stile ("bold", "fg:green", ...) stehen im Bytecode der Basisklasse
javap -c -p -classpath $J org.springframework.shell.jline.tui.style.StyleSettings
# Beweis, dass core KEINE Style-Klasse enthaelt
unzip -l $M2/spring-shell-core/4.0.3/spring-shell-core-4.0.3.jar | grep -Ei "style|theme|table"
# Die Beans, die das alles verdrahten
javap -classpath $M2/spring-shell-core-autoconfigure/4.0.3/spring-shell-core-autoconfigure-4.0.3.jar \
org.springframework.shell.core.autoconfigure.ThemingAutoConfiguration
# Falle 1: wie der Rueckgabewert eines @Command ausgegeben wird
javap -c -p org/springframework/shell/core/command/adapter/MethodInvokerCommandAdapter.class | grep PrintWriter
org.springframework.shell.jline.tui.style.*) unterscheiden sich von
Spring Shell 3.x, wo dieselben Klassen als org.springframework.shell.style.*
im core-Artefakt lagen. Snippets aus 3.x-Beispielen im Netz kompilieren hier nicht.
Die Snippets dieses Reports wurden gegen die JAR-Signaturen geprüft, aber nicht kompiliert.
Zielbild — dieselbe Liste, zwei business-taugliche Ausgaben
Die Mockups oben zeigen jede Stufe isoliert. So sieht die Attachment-Liste aus, wenn die
Bausteine mit Blick auf Lesbarkeit zusammengesetzt werden: die menschliche Größe
statt der Byte-Zahl, ein relatives Datum, die sys_id gekürzt
und zurückgenommen. Beide Varianten arbeiten auf denselben drei Datensätzen —
einmal als eine Zeile pro Datei (Geist von Stufe 1), einmal als gerahmte Tabelle
(Stufe 3 fürs Einfärben, Stufe 5 für den Rahmen).
import org.jline.utils.AttributedStringBuilder;
import org.jline.utils.AttributedStyle;
// Stil einmal als Objekt -- JLine rechnet beim Rendern auf die Terminal-Palette herunter
private static final AttributedStyle NAME = AttributedStyle.DEFAULT.bold().foreground(AttributedStyle.BLUE);
private static final AttributedStyle META = AttributedStyle.DEFAULT.faint();
private static final AttributedStyle SIZE = AttributedStyle.DEFAULT.foreground(AttributedStyle.GREEN);
AttributedStringBuilder asb = new AttributedStringBuilder();
asb.styled(META, "%s \u203a %d Anhaenge".formatted(articleNumber, rows.size())).append("\n");
for (Attachment a : rows) { // rows: bereits nach Datum sortiert
asb.styled(NAME, rpad(a.fileName(), 29))
.styled(META, rpad(a.type(), 5))
.styled(SIZE, lpad(humanSize(a.sizeBytes()), 7)).append(" ")
.styled(META, rpad(relativeDate(a.createdOn()), 13))
.append(rpad(a.createdBy(), 8))
.styled(META, "\u00b7" + a.sysId().substring(0, 6))
.append("\n");
}
return asb.toAnsi(terminal); // NICHT toString() -- Falle 1
rpad/lpad,
humanSize, relativeDate sind
Projekt-Helfer, keine Library-API. Die Farbe kommt ausschließlich aus
AttributedStyle — siehe Stufe 1.
import org.springframework.shell.jline.tui.table.*;
import org.springframework.shell.jline.tui.style.ThemeResolver;
private final ThemeResolver themeResolver; // Bean aus ThemingAutoConfiguration
TableModelBuilder<String> model = new TableModelBuilder<>();
model.addRow().addValue("Datei").addValue("Typ").addValue("Groesse")
.addValue("erstellt").addValue("von").addValue("sys_id");
for (Attachment a : rows) {
model.addRow()
.addValue(a.fileName())
.addValue(a.type())
.addValue(humanSize(a.sizeBytes())) // 184320 -> "180 KB"
.addValue(relativeDate(a.createdOn())) // -> "vor 13 Tagen"
.addValue(a.createdBy())
.addValue("\u00b7" + a.sysId().substring(0, 6));
}
// Rahmen dezent (fancy_light), Dateiname pro Zelle ueber den ThemeResolver gefaerbt -- Stufe 3
Formatter accent = v -> new String[]{
themeResolver.evaluateExpression("@{bold,fg:blue " + v + "}").toAnsi() };
Table table = new TableBuilder(model.build())
.addFullBorder(BorderStyle.fancy_light)
.on(CellMatchers.column(0)).addFormatter(accent)
.addSizer(new AbsoluteWidthSizeConstraints(29)).and() // Escapes zaehlen mit -> feste Breite
.on(CellMatchers.column(2)).addAligner(SimpleHorizontalAligner.right).and()
.build();
return table.render(84);
Der Rahmen ist bewusst gedimmt, damit die Daten vorn stehen. Die feste
Spaltenbreite (AbsoluteWidthSizeConstraints) ist Pflicht, sobald
eine Zelle ANSI-Escapes enthält — siehe die Karte KOMBINIERBAR bei
Stufe 5.