Documentation
¶
Overview ¶
Package text is repo-owned CLI text machinery: sanitization, terminal cell width measurement, truncation, glyph-safe pluralization/conjugation. Not a general-purpose text library — every rule here exists for evident-output's own rendering paths.
Sanitization (this file): neutralizes untrusted text for terminal-safe display.
Width: terminal cell width measurement for display text.
Index ¶
- Constants
- Variables
- func Block(s string) string
- func Cells(s string) int
- func ConjugatePast(verb string) string
- func Dim(s string, color bool) string
- func PadLeft(s string, n int) string
- func PadRight(s string, n int) string
- func Pluralize(quantity int64, singular string) string
- func RuneCells(r rune) int
- func SpinnerFrames(profile GlyphProfile) []string
- func SpinnerGlyph(now time.Time, profile GlyphProfile) string
- func StripANSI(s string) string
- func Style(s, code string, color bool) string
- func StyleGlyph(glyph, code string, color bool) string
- func Text(s string) string
- func Truncate(s string, maxCells int) string
- func TruncateNames(names []string, visible int, profile ...GlyphProfile) string
- func TruncateUTF8(s string, max int, suffix string) string
- func TruncateVisible(s string, maxCells int) string
- func VisibleCells(s string) int
- type GlyphProfile
Constants ¶
const ( SGRReset = "\x1b[0m" SGRBold = "\x1b[1m" SGRDim = "\x1b[2m" SGRRed = "\x1b[31m" SGRGreen = "\x1b[32m" SGRYellow = "\x1b[33m" SGRCyan = "\x1b[36m" SGRBlue = "\x1b[34m" )
SGR styles for terminal projection (library-owned sequences only).
const DefaultVisibleNames = 3
DefaultVisibleNames is how many names TruncateNames keeps before summarizing.
const SpinnerPeriod = 80 * time.Millisecond
SpinnerPeriod is the wall-clock duration between spinner frame advances.
Variables ¶
var ( GlyphDone = glyphSpec{"✓", "[ok]"} GlyphFailedState = glyphSpec{"✗", "[x]"} GlyphBlockedState = glyphSpec{"⊘", "[blocked]"} GlyphWarningState = glyphSpec{"!", "[!]"} GlyphCancelled = glyphSpec{"■", "[cancel]"} GlyphNotStarted = glyphSpec{"-", "[-]"} GlyphPending = glyphSpec{"○", "[.]"} GlyphRunning = glyphSpec{"◐", "[~]"} GlyphHumanInput = glyphSpec{"?", "[?]"} // GlyphNextAction marks a follow-up command/label line. evo-rec.md's // tightened vocabulary table gives it its own row so the meaning does not // depend on the cyan color alone (rule: text/glyph carries meaning). GlyphNextAction = glyphSpec{"→", ">"} // GlyphEvidence is the tree connector for Detail/Cause rows under a // Problem — dim per "Color and style demotions" (subordinate evidence). GlyphEvidence = glyphSpec{"└─", "-"} // GlyphOverflow marks a truncated/omitted-count line ("… +N more"). GlyphOverflow = glyphSpec{"…", "..."} // GlyphUnclassified covers states with no distinct row in the vocabulary // table (e.g. Empty); it must stay visually distinct from Pending's "○". GlyphUnclassified = glyphSpec{"·", "."} )
State glyph table (evo-rec.md "Tightened glyph vocabulary"). A live interactive redraw drives Running from the spinner alphabet instead (writeLiveTaskLine overrides TaskGlyph's Running result with the current animated frame); every other consumer — a plain/non-interactive durable line, a residual dump, a snapshot — has no animation loop behind it, so TaskGlyph gives Running its own static face (GlyphRunning) rather than a spinner frame frozen mid-spin (beginner-8: "no spinner glyph in plain").
Functions ¶
func Block ¶
Block neutralizes control characters while preserving newlines for multi-line presentation fields (Problem.Detail, capture tails). CRLF/CR normalize to LF. ESC/CSI and other C0 controls (except TAB and LF) are still stripped.
func Cells ¶
Cells returns terminal cell width for s using a conservative East-Asian / emoji heuristic suitable for CLI layout (TXT-001…006).
func ConjugatePast ¶
ConjugatePast returns the past-tense spelling of an imperative mutation verb (delete -> deleted, push -> pushed, write -> wrote) so callers write one imperative verb and evo picks the tense for [changed] rows; [planned] rows keep the imperative verb as written. Hyphenated compound verbs (delete-remote, fetch-prune) fall back to conjugating their first segment and dropping the rest, unless compoundPastTense names a specific spelling — see that table's comment for why the general rule doesn't always apply.
func Pluralize ¶
Pluralize returns the plural spelling of singular when quantity != 1 (an irregular table for the common exceptions, else the regular English +s/+es/+ies rule), and singular unchanged when quantity == 1 — the object-pluralization counterpart to ConjugatePast's verb table, so a mutation call site stops writing its own singular/plural noun() switch.
A glob/path/symbol object ("stale origin/*", "*.tmp") renders unchanged at any quantity — see isPluralizableWord — instead of blindly gaining a trailing "s" ("stale origin/*s") that the +s/+es/+ies rule was never designed to produce.
func SpinnerFrames ¶
func SpinnerFrames(profile GlyphProfile) []string
SpinnerFrames returns the animation alphabet for profile.
func SpinnerGlyph ¶
func SpinnerGlyph(now time.Time, profile GlyphProfile) string
SpinnerGlyph picks a frame from the clock so a fixed clock freezes it in tests.
func Style ¶
Style wraps s in code (an SGR escape) when color is true and both s and code are non-empty, closing with SGRReset.
func StyleGlyph ¶
StyleGlyph applies Style to a rendered glyph face.
func Text ¶
Text neutralizes control characters and invalid UTF-8 for single-line fields. Newlines become spaces. ESC/CSI/OSC and C0 controls other than TAB are stripped or replaced. Prefer Block for multi-line evidence (diffs, capture tails).
func Truncate ¶
Truncate trims s to at most maxCells, never splitting a multi-cell rune, appending "…" when truncated. Combining sequences stay attached to base.
func TruncateNames ¶
func TruncateNames(names []string, visible int, profile ...GlyphProfile) string
TruncateNames joins names for a skip/kept-style summary. Empty names yields "". visible <= 0 uses DefaultVisibleNames. When more names remain than visible, appends the overflow glyph for profile (evo-rec.md's tightened vocabulary: "… +N more", ASCII "... +N more") instead of a bare ", +N" that carries no glyph at all. profile is variadic so the simplest call — TruncateNames(names, visible) — stays correct: an omitted profile renders the Unicode overflow glyph; a caller that has already resolved a GlyphProfile passes it explicitly.
func TruncateUTF8 ¶
TruncateUTF8 trims s to at most max bytes without splitting a multi-byte rune, then appends suffix when truncation occurs. max is a byte budget for s before suffix.
func TruncateVisible ¶
TruncateVisible trims styled terminal text to maxCells while retaining complete control sequences and closing any active presentation state.
func VisibleCells ¶
VisibleCells returns cell width after stripping ANSI CSI/OSC sequences so styled and unstyled visible widths match (TXT-013) and OSC 8 links count as zero cells (TXT-014).
Types ¶
type GlyphProfile ¶
type GlyphProfile int
GlyphProfile selects which glyph vocabulary state markers render in (evo-rec.md "Tightened glyph vocabulary", rule GLYPH-001: glyph selection via capability profile, cell-width measurement not rune counts).
The zero value, GlyphsAuto, keeps today's Unicode vocabulary off a TTY (a non-interactive stream can't show a human mojibake, so there is nothing to guard against) and on any TTY whose locale already advertises UTF-8. It downgrades to the ASCII vocabulary only on an interactive terminal without UTF-8 locale support — the one case where the status column would otherwise render as mojibake.
const ( // GlyphsAuto detects the vocabulary from locale and TTY interactivity. GlyphsAuto GlyphProfile = iota // GlyphsUnicode forces the Unicode vocabulary regardless of locale. GlyphsUnicode // GlyphsASCII forces the ASCII vocabulary regardless of locale. GlyphsASCII )