016f93ab4f
Add a monorepo for small single-purpose CLI tools that share a common library, so tools reuse TUI primitives and Kubernetes client bootstrapping instead of duplicating them. - common/tui: direction markers, style palette, width-aware padding/layout - common/kube: in-cluster→kubeconfig client loading + core/metrics clients - podgap: live TUI comparing pod requests/limits vs actual usage, with up/down markers showing per-pod change since the last refresh - Makefile auto-discovers tool directories: make <tool> | all | install
87 lines
2.4 KiB
Go
87 lines
2.4 KiB
Go
// Package tui holds small, reusable terminal-UI primitives shared across the
|
|
// tools in benvin-utils: direction markers, a common style palette, and
|
|
// width-aware padding/truncation helpers that respect ANSI styling.
|
|
package tui
|
|
|
|
import (
|
|
"strings"
|
|
|
|
"github.com/charmbracelet/lipgloss"
|
|
)
|
|
|
|
// Shared style palette. Tools should prefer these so every benvin-utils TUI
|
|
// looks consistent.
|
|
var (
|
|
Header = lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("15")).Background(lipgloss.Color("240"))
|
|
Title = lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("87"))
|
|
Dim = lipgloss.NewStyle().Foreground(lipgloss.Color("244"))
|
|
Up = lipgloss.NewStyle().Foreground(lipgloss.Color("203")) // a rising value = red-ish
|
|
Down = lipgloss.NewStyle().Foreground(lipgloss.Color("84")) // a falling value = green
|
|
Hot = lipgloss.NewStyle().Foreground(lipgloss.Color("203")) // over threshold
|
|
Warn = lipgloss.NewStyle().Foreground(lipgloss.Color("214")) // approaching threshold
|
|
Err = lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("203"))
|
|
)
|
|
|
|
// Marker glyphs for direction-of-change indicators.
|
|
const (
|
|
GlyphUp = "▲"
|
|
GlyphDown = "▼"
|
|
GlyphFlat = "·"
|
|
)
|
|
|
|
// Arrow renders a direction marker: 1 = up (▲), -1 = down (▼), 0 = flat (·).
|
|
func Arrow(dir int) string {
|
|
switch {
|
|
case dir > 0:
|
|
return Up.Render(GlyphUp)
|
|
case dir < 0:
|
|
return Down.Render(GlyphDown)
|
|
default:
|
|
return Dim.Render(GlyphFlat)
|
|
}
|
|
}
|
|
|
|
// Sign reports whether d is positive (1), negative (-1), or zero (0). It is the
|
|
// usual companion to Arrow when comparing a new sample against a previous one.
|
|
func Sign[T int | int64 | float64](d T) int {
|
|
switch {
|
|
case d > 0:
|
|
return 1
|
|
case d < 0:
|
|
return -1
|
|
default:
|
|
return 0
|
|
}
|
|
}
|
|
|
|
// Pad right-pads plain (unstyled) text to width w, truncating if longer.
|
|
func Pad(s string, w int) string {
|
|
if len(s) > w {
|
|
s = s[:w]
|
|
}
|
|
return s + strings.Repeat(" ", w-len(s))
|
|
}
|
|
|
|
// PadVisible right-pads a possibly ANSI-styled cell to width w using its visible
|
|
// (rendered) width, so styled cells still align in a table.
|
|
func PadVisible(s string, w int) string {
|
|
if vis := lipgloss.Width(s); vis < w {
|
|
return s + strings.Repeat(" ", w-vis)
|
|
}
|
|
return s
|
|
}
|
|
|
|
// Truncate shortens s to at most n runes, adding an ellipsis when it cuts.
|
|
func Truncate(s string, n int) string {
|
|
if n <= 0 {
|
|
return ""
|
|
}
|
|
if len(s) <= n {
|
|
return s
|
|
}
|
|
if n == 1 {
|
|
return "…"
|
|
}
|
|
return s[:n-1] + "…"
|
|
}
|