mirror of
https://gitea.com/gitea/tea.git
synced 2026-10-26 19:11:25 +01:00
feat: fit list tables to the terminal and write TSV when piped
Lists had no header, wrapped every row once a title was longer than the terminal, and were space-padded when piped, so cut -f and awk -F'\t' could not split them and empty cells collapsed. terminal.Table now takes column specs and a Display. On a terminal it prints an uppercase header, aligns columns two spaces apart and shrinks flexible columns, widest first, to Streams.Width (the terminal, then $COLUMNS, then 80), cutting by display width with "…". States use the terminal's ANSI 16 colors, hex colors fall back to the 256-color palette unless COLORTERM advertises 24-bit, and OSC 8 links appear only on the first column, independently of color (TEA_HYPERLINKS=auto|always|never). Piped tables are tab-separated without header, color or links, and times are RFC3339 UTC instead of "about 2 hours ago". terminal.View lays out single resources (title, badges, metadata, fields, body, footer URL) and renders markdown bodies with glamour on a terminal only (TEA_MARKDOWN=raw keeps them raw). Shared formatters cover relative times, timestamps, sizes, durations and status icons. In the app, writeTable and writeView are the new list and view writers: a "Showing N of M …" context line, an empty-state message on stderr and the next-page hint. writeList, writeItem and batch keep working and now render through the new table: tab-separated when piped, truncated at the widest column on a terminal, with headers for issue list and pr list. Signed-off-by: bircni <bircni@icloud.com>
This commit is contained in:
17 files changed
+1253
-103
No files matched your search
@@ -0,0 +1,55 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package terminal
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Bytes formats a size in binary units: "13 B", "2.9 MiB".
|
||||
func Bytes(n int64) string {
|
||||
if n < 1024 {
|
||||
return fmt.Sprintf("%d B", n)
|
||||
}
|
||||
units := []string{"KiB", "MiB", "GiB", "TiB"}
|
||||
value, unit := float64(n)/1024, 0
|
||||
for value >= 1024 && unit < len(units)-1 {
|
||||
value /= 1024
|
||||
unit++
|
||||
}
|
||||
return fmt.Sprintf("%.1f %s", value, units[unit])
|
||||
}
|
||||
|
||||
// Duration formats an elapsed time by its two largest units: "1h 30m", "1m 12s", "45s".
|
||||
func Duration(d time.Duration) string {
|
||||
d = d.Round(time.Second)
|
||||
hours, minutes, seconds := int(d/time.Hour), int(d%time.Hour/time.Minute), int(d%time.Minute/time.Second)
|
||||
switch {
|
||||
case hours > 0:
|
||||
return fmt.Sprintf("%dh %dm", hours, minutes)
|
||||
case minutes > 0:
|
||||
return fmt.Sprintf("%dm %ds", minutes, seconds)
|
||||
default:
|
||||
return fmt.Sprintf("%ds", seconds)
|
||||
}
|
||||
}
|
||||
|
||||
// StatusIcon summarizes a check or run in one cell: ✓ succeeded, X failed, - skipped or canceled, and * still
|
||||
// pending. A conclusion, when present, takes precedence over the status.
|
||||
func StatusIcon(status, conclusion string) Cell {
|
||||
if conclusion != "" {
|
||||
status = conclusion
|
||||
}
|
||||
switch status {
|
||||
case "success":
|
||||
return Cell{Text: "✓", Color: "green"}
|
||||
case "failure", "error":
|
||||
return Cell{Text: "X", Color: "red"}
|
||||
case "cancelled", "canceled", "skipped": //nolint:misspell // Gitea API spelling.
|
||||
return Cell{Text: "-", Muted: true}
|
||||
default:
|
||||
return Cell{Text: "*", Color: "yellow"}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user