Skip to content

Commit 63de5dc

Browse files
committed
updated with new utility to display ascii generated tables
1 parent 700812f commit 63de5dc

1 file changed

Lines changed: 351 additions & 0 deletions

File tree

content/docs/aesh/table.md

Lines changed: 351 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,351 @@
1+
---
2+
date: '2026-02-11T14:00:00+01:00'
3+
draft: false
4+
title: 'Table Display'
5+
weight: 19
6+
---
7+
8+
The table display utility renders structured data as formatted text tables in the terminal. It supports multiple border styles, automatic column width calculation, numeric formatting, and multi-line headers.
9+
10+
## Quick Start
11+
12+
```java
13+
import org.aesh.util.table.Table;
14+
import org.aesh.util.table.TableStyle;
15+
16+
String output = Table.<User>builder()
17+
.maxWidth(80)
18+
.style(TableStyle.DUCKDB)
19+
.column("Name", u -> u.getName())
20+
.column("Email", u -> u.getEmail())
21+
.build()
22+
.render(userList);
23+
24+
invocation.println(output);
25+
```
26+
27+
Output:
28+
29+
```
30+
┌──────┬──────────────────┐
31+
│ Name │ Email │
32+
├──────┼──────────────────┤
33+
│ Alice│ alice@example.com│
34+
│ Bob │ bob@example.com │
35+
└──────┴──────────────────┘
36+
```
37+
38+
## Static API
39+
40+
For one-off table rendering, use the static `render` methods directly:
41+
42+
```java
43+
import org.aesh.util.table.Table;
44+
45+
import java.util.Arrays;
46+
import java.util.List;
47+
import java.util.function.Function;
48+
49+
List<String> headers = Arrays.asList("Name", "Age", "Score");
50+
List<Function<User, Object>> accessors = Arrays.asList(
51+
u -> u.getName(),
52+
u -> u.getAge(),
53+
u -> u.getScore()
54+
);
55+
56+
// Render with default DUCKDB style
57+
String output = Table.render(80, users, headers, accessors);
58+
59+
// Render with a specific style
60+
String output = Table.render(80, users, headers, accessors,
61+
TableStyle.SQLITE.characters());
62+
```
63+
64+
## Builder API
65+
66+
The builder API pairs each header with its accessor function, preventing mismatched list sizes:
67+
68+
```java
69+
Table<User> table = Table.<User>builder()
70+
.maxWidth(120)
71+
.style(TableStyle.DOUBLE)
72+
.column("Name", u -> u.getName())
73+
.column("Age", u -> u.getAge())
74+
.column("Score", u -> u.getScore())
75+
.build();
76+
77+
// The built Table instance is reusable
78+
String output1 = table.render(activeUsers);
79+
String output2 = table.render(inactiveUsers);
80+
```
81+
82+
You can also pass custom character maps to the builder instead of a predefined style:
83+
84+
```java
85+
Table.<User>builder()
86+
.characters(myCustomCharacterMap)
87+
.column("Name", u -> u.getName())
88+
.build();
89+
```
90+
91+
## Table Styles
92+
93+
Four predefined styles are available via `TableStyle`:
94+
95+
### POSTGRES
96+
97+
Simple ASCII without outside borders, similar to PostgreSQL's `psql` output:
98+
99+
```java
100+
Table.render(80, users, headers, accessors, TableStyle.POSTGRES.characters());
101+
```
102+
103+
```
104+
Name | Age | Score
105+
-------+-----+------
106+
Alice | 30 | 95.50
107+
Bob | 25 | 87.12
108+
```
109+
110+
### SQLITE
111+
112+
ASCII with full outside borders:
113+
114+
```java
115+
Table.render(80, users, headers, accessors, TableStyle.SQLITE.characters());
116+
```
117+
118+
```
119+
+-------+-----+-------+
120+
| Name | Age | Score |
121+
+-------+-----+-------+
122+
| Alice | 30 | 95.50 |
123+
| Bob | 25 | 87.12 |
124+
+-------+-----+-------+
125+
```
126+
127+
### DUCKDB
128+
129+
Unicode box-drawing characters (the default style):
130+
131+
```java
132+
Table.render(80, users, headers, accessors, TableStyle.DUCKDB.characters());
133+
```
134+
135+
```
136+
┌───────┬─────┬───────┐
137+
│ Name │ Age │ Score │
138+
├───────┼─────┼───────┤
139+
│ Alice │ 30 │ 95.50 │
140+
│ Bob │ 25 │ 87.12 │
141+
└───────┴─────┴───────┘
142+
```
143+
144+
### DOUBLE
145+
146+
Double-line box-drawing with row separators between data rows:
147+
148+
```java
149+
Table.render(80, users, headers, accessors, TableStyle.DOUBLE.characters());
150+
```
151+
152+
```
153+
╔═══════╦═════╦═══════╗
154+
║ Name ║ Age ║ Score ║
155+
╠═══════╬═════╬═══════╣
156+
║ Alice ║ 30 ║ 95.50 ║
157+
╟───────╫─────╫───────╣
158+
║ Bob ║ 25 ║ 87.12 ║
159+
╚═══════╩═════╩═══════╝
160+
```
161+
162+
## Numeric Formatting
163+
164+
Column types are detected automatically from the data:
165+
166+
| Value Type | Alignment | Formatting |
167+
|------------|-----------|------------|
168+
| `String` | Left-aligned | As-is |
169+
| `Integer`, `Long` | Right-aligned | `%d` format |
170+
| `Float`, `Double` | Right-aligned | 2 decimal places |
171+
| `null` | Left-aligned | Rendered as `"null"` |
172+
173+
```java
174+
Table.<Item>builder()
175+
.column("Product", i -> i.name) // left-aligned string
176+
.column("Quantity", i -> i.quantity) // right-aligned integer
177+
.column("Price", i -> i.price) // right-aligned, 2 decimals
178+
.build()
179+
.render(items);
180+
```
181+
182+
## Multi-line Headers
183+
184+
Headers can span multiple lines using `System.lineSeparator()`:
185+
186+
```java
187+
List<String> headers = Arrays.asList(
188+
"First" + System.lineSeparator() + "Name",
189+
"Email"
190+
);
191+
```
192+
193+
```
194+
┌───────┬──────────────────┐
195+
│ First │ │
196+
│ Name │ Email │
197+
├───────┼──────────────────┤
198+
│ Alice │ alice@example.com│
199+
└───────┴──────────────────┘
200+
```
201+
202+
Headers are center-aligned within their column, and shorter headers are padded with empty lines to match the tallest header.
203+
204+
## Custom Border Characters
205+
206+
### Using Templates
207+
208+
Define custom borders with a visual 5-character-wide template. The template is a multi-line string where each line represents a part of the table border:
209+
210+
```java
211+
import org.aesh.util.table.TableCharacters;
212+
213+
// 6-line template: top border, header, header-body separator, body, row separator, bottom border
214+
String template =
215+
"╔═╦═╗" + System.lineSeparator() +
216+
"║h║h║" + System.lineSeparator() +
217+
"╠═╬═╣" + System.lineSeparator() +
218+
"║v║v║" + System.lineSeparator() +
219+
"╟─╫─╢" + System.lineSeparator() +
220+
"╚═╩═╝";
221+
222+
Map<String, String> chars = TableCharacters.templateToMap(template,
223+
TableStyle.DUCKDB.characters());
224+
```
225+
226+
Templates with 3, 5, or 6 lines are supported, providing increasing levels of border detail.
227+
228+
### Using Shorthand Expansion
229+
230+
Define minimal character sets and expand them to all positions:
231+
232+
```java
233+
Map<String, String> shorthand = new HashMap<>();
234+
shorthand.put(TableCharacters.VERTICAL, "");
235+
shorthand.put(TableCharacters.HORIZONTAL, "");
236+
shorthand.put(TableCharacters.INTERSECT, "");
237+
238+
// Expand to all border positions (second parameter enables outside borders)
239+
Map<String, String> full = TableCharacters.convertToFullNames(shorthand, true);
240+
```
241+
242+
## ANSI Color Support
243+
244+
Add ANSI color to table borders using `TableCharacters.prefix()`:
245+
246+
```java
247+
import org.aesh.terminal.utils.ANSI;
248+
import org.aesh.util.table.TableCharacters;
249+
250+
Map<String, String> colored = TableCharacters.prefix(
251+
ANSI.CYAN_TEXT, TableStyle.DUCKDB.characters());
252+
253+
String output = Table.render(80, users, headers, accessors, colored);
254+
```
255+
256+
This wraps each border character with the ANSI color prefix and `ANSI.RESET` suffix, so data values remain unaffected.
257+
258+
## Using Tables in Commands
259+
260+
A complete command that renders tabular output:
261+
262+
```java
263+
import org.aesh.command.Command;
264+
import org.aesh.command.CommandDefinition;
265+
import org.aesh.command.CommandResult;
266+
import org.aesh.command.invocation.CommandInvocation;
267+
import org.aesh.util.table.Table;
268+
import org.aesh.util.table.TableStyle;
269+
270+
@CommandDefinition(name = "users", description = "List all users")
271+
public class ListUsersCommand implements Command<CommandInvocation> {
272+
273+
@Override
274+
public CommandResult execute(CommandInvocation invocation) {
275+
List<User> users = loadUsers();
276+
277+
String table = Table.<User>builder()
278+
.maxWidth(invocation.getShell().size().getWidth())
279+
.style(TableStyle.DUCKDB)
280+
.column("Name", u -> u.getName())
281+
.column("Email", u -> u.getEmail())
282+
.column("Role", u -> u.getRole())
283+
.build()
284+
.render(users);
285+
286+
invocation.getShell().writeln(table);
287+
return CommandResult.SUCCESS;
288+
}
289+
}
290+
```
291+
292+
Using `invocation.getShell().size().getWidth()` adapts the table to the current terminal width.
293+
294+
## Validation Helpers
295+
296+
`TableCharacters` provides methods to inspect character maps:
297+
298+
```java
299+
// Check if the map defines a complete outside border
300+
boolean bordered = TableCharacters.hasOutsideBorder(characters);
301+
302+
// Check if the map defines row separators between data rows
303+
boolean separated = TableCharacters.hasRowSeparator(characters);
304+
305+
// Check if the map has the minimum required entries for rendering
306+
boolean valid = TableCharacters.isValid(characters);
307+
```
308+
309+
If an invalid character map is passed to `Table.render()`, it falls back to the `SQLITE` style automatically.
310+
311+
## API Reference
312+
313+
### Table
314+
315+
| Method | Description |
316+
|--------|-------------|
317+
| `Table.render(maxWidth, values, headers, accessors)` | Render with default DUCKDB style |
318+
| `Table.render(maxWidth, values, headers, accessors, characters)` | Render with custom border characters |
319+
| `Table.builder()` | Create a builder for fluent configuration |
320+
| `table.render(values)` | Render using a pre-built Table instance |
321+
322+
### Table.Builder
323+
324+
| Method | Description |
325+
|--------|-------------|
326+
| `maxWidth(int)` | Set maximum table width (default: 80) |
327+
| `style(TableStyle)` | Set a predefined border style |
328+
| `characters(Map)` | Set a custom character map |
329+
| `column(header, accessor)` | Add a column with header and value accessor |
330+
| `build()` | Build an immutable `Table` instance |
331+
332+
### TableCharacters
333+
334+
| Method | Description |
335+
|--------|-------------|
336+
| `convertToFullNames(map, border)` | Expand shorthand V/H/X to full position names |
337+
| `templateToMap(template, defaultMap)` | Parse a visual template into a character map |
338+
| `prefix(ansiPrefix, map)` | Wrap border characters with ANSI color codes |
339+
| `hasOutsideBorder(map)` | Check for complete outside border definition |
340+
| `hasRowSeparator(map)` | Check for row separator definition |
341+
| `isValid(map)` | Check for minimum required entries |
342+
| `lineSplit(headers)` | Split multi-line headers into aligned rows |
343+
344+
### TableStyle
345+
346+
| Style | Borders | Characters | Row Separators |
347+
|-------|---------|------------|----------------|
348+
| `POSTGRES` | None | ASCII (`\|`, `-`, `+`) | No |
349+
| `SQLITE` | Full | ASCII (`\|`, `-`, `+`) | No |
350+
| `DUCKDB` | Full | Unicode box-drawing | No |
351+
| `DOUBLE` | Full | Double-line box-drawing | Yes |

0 commit comments

Comments
 (0)