Documentation
¶
Overview ¶
Package gotermstyle is the house terminal style for the Go command-line tools.
It is the Go counterpart to two things that already exist: pytermstyle (https://github.com/datapointchris/pytermstyle) for the Python tools, and ~/.local/shell/formatting.sh for the bash ones. The parallel is the point — a Go screen, a Python screen and a bash screen sitting next to each other on PATH should be indistinguishable, which is a property no third-party styling library can provide, because each brings its own aesthetic.
Scope ¶
The palette, the section header, the help grammar, and the rules about when any of it is emitted at all. Not domain logic, not configuration, not anything a single tool could own instead.
The membership test: it belongs here only if two tools would otherwise write it identically *and* writing it differently would be visible on screen. Everything else stays in the tool. menucore was named for one family and ended up holding the house style for all of them; the split that fixed it is the reason this package is named for the language rather than for a caller.
Dependencies ¶
None, and that is a constraint rather than a coincidence — the same one goselfupdate holds. Terminal detection is os.Stat plus os.ModeCharDevice, not golang.org/x/term, so a tool importing this downloads nothing and its own Go floor is unaffected.
Using it ¶
Resolve a Screen against the writer the output is going to, then render:
s := gotermstyle.New(cmd.OutOrStdout())
s.HelpHeader("menu labs", "Small experiments worth revisiting.")
s.HelpUsage("menu labs <verb>")
s.HelpSection("Commands")
s.HelpRow("menu labs list", "", "List every lab")
s.HelpRow("menu labs show", "<id>", "Show one lab")
s.HelpEnd()
HelpRow buffers rather than writing, so the flush sizes the description column from the longest row in the section. No call site passes a width or a color. Use HelpText rather than writing directly for prose between rows, so pending rows flush ahead of it.
For output that is not a help screen, Palette paints and Clip fits:
p := gotermstyle.PaletteFor(os.Stdout) fmt.Println(p.Cyan(name), p.Command(path))
Where the state lives, and why it differs from the Python ¶
pytermstyle and formatting.sh keep the buffered rows and the section counter in globals, which is affordable in a one-shot CLI and costs pytermstyle a reset helper its tests have to call. Go runs its tests in one process and its programs concurrently, so that state lives on a Screen here. The rendered screen is the same; only the state's owner differs.
The design, the measurements behind it, and the libraries that were considered and rejected are in .planning/design.md.
Index ¶
- Constants
- func Clip(text string, used int) string
- func Columns() int
- func Enabled(w io.Writer) bool
- func SectionColor(title string, index int) string
- type Palette
- func (p Palette) Blue(text string) string
- func (p Palette) Bold(text string) string
- func (p Palette) Code(code string) string
- func (p Palette) Command(text string) string
- func (p Palette) Cyan(text string) string
- func (p Palette) Green(text string) string
- func (p Palette) Magenta(text string) string
- func (p Palette) On() bool
- func (p Palette) Paint(code, text string) string
- func (p Palette) Red(text string) string
- func (p Palette) White(text string) string
- func (p Palette) Yellow(text string) string
- type Screen
- func (s *Screen) Header(title string)
- func (s *Screen) HelpEnd()
- func (s *Screen) HelpHeader(name, tagline string)
- func (s *Screen) HelpRow(name, args, description string)
- func (s *Screen) HelpSection(title string)
- func (s *Screen) HelpText(lines ...string)
- func (s *Screen) HelpUsage(lines ...string)
- func (s *Screen) Palette() Palette
Constants ¶
const ( CodeCyan = "\033[0;96m" CodeYellow = "\033[0;93m" CodeGreen = "\033[0;92m" CodeRed = "\033[0;91m" CodeBlue = "\033[0;94m" CodeMagenta = "\033[0;95m" CodeWhite = "\033[0;97m" // CodeCommand is the one non-bright entry. A command you would type reads // differently from a name in a listing, and that difference is the point. CodeCommand = "\033[0;36m" CodeBold = "\033[1m" CodeReset = "\033[0m" )
The house palette, as the escape sequences themselves. These are the codes `~/dotfiles/configs/common/.local/shell/colors.sh` exports and pytermstyle interpolates, so a Go screen, a Python screen and a bash screen sitting next to each other on PATH are the same product.
They are terminal-palette codes rather than absolute RGB, which is why no color-profile degradation is needed: a 16-color terminal renders them correctly, and TERM=dumb is handled by the gate instead.
There is deliberately no dim. See standards/cli-design.md § "Never use `[dim]`", which rules it out for being unreadable against half the terminal themes in use.
const Bar = "" /* 150-byte string literal not displayed */
Bar is the rule a header draws above and below its title.
const DefaultColumns = 80
DefaultColumns is the width assumed when nothing will say what it really is.
Variables ¶
This section is empty.
Functions ¶
func Clip ¶
Clip shortens text to fit the terminal alongside used other columns.
Callers pass the length of the uncolored surrounding text and color the result afterwards, so a clip can never land inside an escape sequence and leak a raw code onto the screen.
With no room to say anything the text is returned whole: truncating to a bare ellipsis loses more than it saves, and a row that overflows is more use than a row that says nothing.
Every character this clips around is assumed one column wide. That holds for the ↳ · … this family is built for and does not hold for CJK or emoji, which is the one thing a wide-rune library would buy and is a deliberate acceptance rather than an oversight.
func Columns ¶
func Columns() int
Columns is the terminal width. $COLUMNS is consulted first so a caller can override it and a test can set it, then the terminal itself, then DefaultColumns.
This is the order Python's shutil.get_terminal_size uses, which is what the counterpart library resolves through.
func Enabled ¶
Enabled reports whether escape sequences written to w will reach something that can render them.
NO_COLOR is the user saying they do not want color (no-color.org), so it wins outright. FORCE_COLOR answers a different question — "is this a terminal" — for a caller that knows the detection is wrong: a CI log that renders escapes, a pager invoked with -R, a test that needs the codes in order to assert on them. Preference beats detection, so NO_COLOR is first.
The terminal check is os.Stat plus os.ModeCharDevice rather than golang.org/x/term, which would cost this package the zero-dependency guarantee it is built around. It is the idiom goselfupdate/autoupdate uses.
func SectionColor ¶
SectionColor is the code a section heading renders in, by name first and by position second. Matching is case-insensitive because a heading is written for a reader rather than for this lookup.
Types ¶
type Palette ¶
type Palette struct {
// contains filtered or unexported fields
}
Palette decides whether the codes above are emitted. Resolve it once, against the stream the output is going to, and pass it around — re-deciding per call site is how one screen ends up half-colored.
func NewPalette ¶
NewPalette returns a palette that emits color, or does not.
func PaletteFor ¶
PaletteFor resolves the gate against w. This is the constructor to reach for; NewPalette is for a caller that has already decided, and for tests.
func (Palette) Code ¶
Code returns the sequence when color is on and the empty string when it is not, so an interpolating caller can use the constants directly.
func (Palette) On ¶
On reports whether this palette emits color. A caller building a string by interpolation rather than through Paint needs to ask.
type Screen ¶
type Screen struct {
// contains filtered or unexported fields
}
Screen renders one help screen to one writer.
It carries the buffered rows and the section counter that pytermstyle and formatting.sh both keep in globals. Go runs its tests in one process and its programs concurrently, so a value is the same code without the reset helper those two need, and makes concurrent use a non-question. It draws the screen those two draw; only where the state lives differs.
func NewWith ¶
NewWith returns a Screen using a palette the caller has already resolved. A tool that renders to several streams resolves once and shares it; a test asserts on the codes by passing NewPalette(true).
func (*Screen) Header ¶
Header draws a boxed section header: rule, title, rule, blank line. This is the standalone header a command prints over its own output, not the one that opens a help screen.
func (*Screen) HelpEnd ¶
func (s *Screen) HelpEnd()
HelpEnd closes the screen and resets it for the next one.
func (*Screen) HelpHeader ¶
HelpHeader opens a help screen. The blank line leads rather than trails, which is what separates the screen from the command that asked for it.
func (*Screen) HelpRow ¶
HelpRow buffers one row. Nothing is written until the section ends, because the description column is sized from the longest row in the section and no call site should have to pass a width.
A row with an empty name is a continuation of the one above it.
func (*Screen) HelpSection ¶
HelpSection opens a section, flushing whatever rows the previous one buffered.
func (*Screen) HelpText ¶
HelpText prints prose between rows. It flushes first, so a line written after a row cannot appear above it.