Documentation
¶
Overview ¶
Package clihelp provides a declarative, lightweight CLI application framework and width-aware, colorized help text formatter for Go applications.
Package clihelp provides a declarative, lightweight CLI application framework and width-aware, colorized help text formatter for Go applications.
Core Concepts ¶
clihelp combines declarative command and flag definitions with robust execution lifecycles, pflag-backed option parsing, positional argument validation, shell completion, and GitHub-friendly Markdown generation.
An application is defined using App, which contains a hierarchy of Command nodes, persistent/local Option flags, and lifecycle hooks.
Quick Start ¶
A minimal CLI application:
package main
import (
"fmt"
"os"
"github.com/sarielhp/clihelp"
)
func main() {
var verbose bool
var output string
app := &clihelp.App{
Name: "demo",
Description: "Demonstration command-line tool",
Version: "1.0.0",
Pager: true,
Commands: []clihelp.Command{
{
Name: "build",
Description: "Compile the target package",
UsageLine: "demo build [options] <target>",
Args: clihelp.ExactArgs(1),
Options: []clihelp.Option{
clihelp.String(&output, "-o, --output PATH", "dist", "Output directory"),
clihelp.Bool(&verbose, "-v, --verbose", false, "Enable verbose logging"),
},
Run: func(ctx *clihelp.Context) error {
target := ctx.Args[0]
fmt.Fprintf(ctx.Stdout, "Building %s to %s (verbose=%v)\n", target, output, verbose)
return nil
},
},
},
}
if err := app.Execute(os.Args[1:]); err != nil {
clihelp.PrintError(err)
os.Exit(1)
}
}
Execution Lifecycle ¶
When App.Execute or App.ExecuteContext is called, the execution follows a deterministic multi-stage pipeline:
- Completion Check: If the first argument is "__complete", shell completion runs.
- Version Check: If "--version", "-v", or "version" is passed without a custom version command, the version string is printed to stdout.
- Command Resolution: Subcommands and aliases are resolved hierarchically. Unknown command tokens trigger typo suggestions via Levenshtein distance. Built-in "help <subcommand>" is automatically routed.
- Flag Binding & Parsing: App.PersistentOptions, ancestor persistent options, and target command options are bound to a pflag.FlagSet and parsed against remaining arguments. Help flags (-h, --help) are automatically registered.
- Argument Validation: The command's ArgsValidator validates remaining positional arguments.
- Lifecycle Hooks: App.BeforeRun -> Command.PreRun -> Command.Run (or App.Run) -> Command.PostRun -> App.AfterRun
If any hook or validator returns a non-nil error, execution halts immediately and the error is returned.
Flag Specifications ¶
Options are configured using typed helper constructors such as String, Int, Bool, BoolToggle, Duration, StringSlice, Enum, and Var.
Flag specification strings support rich syntax:
- Short and long flags: "-o, --output PATH"
- Multiple aliases: "-p, -P, --port, --listen-port <port>"
- Boolean toggle pairs: "--[no-]cache" (registers both --cache and --no-cache)
- Value hints / placeholders: "<file>", "PATH", "[value]"
Caution: Do not manually register "-h" or "--help" flags in your Options slices. App.Execute automatically manages help flag registration and help rendering.
Positional Argument Validators ¶
Positional arguments are validated after flag parsing. Built-in validators include:
- NoArgs: rejects any positional arguments
- ExactArgs(n): requires exactly n positional arguments
- MinimumNArgs(n): requires at least n positional arguments
- MaximumNArgs(n): requires at most n positional arguments
- RangeArgs(min, max): requires between min and max positional arguments
Validated arguments are available via Context.Args.
Shell Autocompletion ¶
clihelp includes generators for Bash and Zsh autocompletion:
Dynamic completion is supported by setting the Option.Complete callback.
Markdown Help Generation ¶
Call RenderMarkdown to generate a GitHub-friendly markdown help site from the command hierarchy. The generator uses SHA-256 content hashing to avoid unnecessary disk writes.
Index ¶
- Constants
- func DisplayName(c Command) string
- func DisplayNameWithArgs(c Command) string
- func FirstSentence(s string) string
- func GenBashCompletion(app *App, w io.Writer) error
- func GenZshCompletion(app *App, w io.Writer) error
- func Inline(s string) string
- func NoArgs(args []string) error
- func RenderMarkdown(a *App, o MarkdownOptions) (changed bool, err error)
- type App
- func (a *App) Execute(args []string) error
- func (a *App) ExecuteContext(ctx context.Context, args []string) error
- func (a *App) LookupCommand(path ...string) *Command
- func (a *App) PrintError(err error)
- func (a *App) Render(o Options, path ...string) bool
- func (a *App) RenderCommand(o Options, path ...string) bool
- func (a *App) RenderGlobal(o Options)
- func (a *App) RenderTree(o Options)
- type ArgsValidator
- type Command
- type Context
- type Example
- type MarkdownOptions
- type Note
- type Option
- func Bool(target *bool, flags string, defaultVal bool, usage string) Option
- func BoolToggle(target *bool, flags string, defaultVal bool, usage string) Option
- func Duration(target *time.Duration, flags string, defaultVal time.Duration, usage string) Option
- func Enum(target *string, flags string, allowed []string, defaultVal string, ...) Option
- func Int(target *int, flags string, defaultVal int, usage string) Option
- func String(target *string, flags string, defaultVal string, usage string) Option
- func StringSlice(target *[]string, flags string, defaultVal []string, usage string) Option
- func Var(target pflag.Value, flags string, usage string) Option
- type Options
- type Param
- type Theme
Examples ¶
Constants ¶
const DefaultMaxColIndent = 24
DefaultMaxColIndent defines the standard column threshold for description text alignment in two-column command and option listings (GNU standard: 24).
Variables ¶
This section is empty.
Functions ¶
func DisplayName ¶
DisplayName renders a command name followed by its aliases in parentheses.
func DisplayNameWithArgs ¶
DisplayNameWithArgs renders a command name with aliases and positional argument signature.
func FirstSentence ¶
FirstSentence returns the first sentence of s, or the first line/paragraph if shorter.
func GenBashCompletion ¶
GenBashCompletion writes a Bash tab-completion script to w.
func GenZshCompletion ¶
GenZshCompletion writes a Zsh tab-completion script to w.
func Inline ¶
Inline renders inline markdown in s to a string with ANSI/OSC8 sequences. It is the exported form of the internal inline helper used by the renderer.
func RenderMarkdown ¶
func RenderMarkdown(a *App, o MarkdownOptions) (changed bool, err error)
RenderMarkdown generates GitHub-friendly markdown help pages for a. It owns exactly the directory Dir: it writes one .md file per command plus an index.md, prunes orphaned .md files it produced earlier (safe, it is the sole owner of that directory), and never touches files outside it.
Generation is gated so a deployed binary (which never sets the CLIHELP_GEN environment variable) stays silent. The on-disk hash file both enables generation and records the last generated state; when the usage tree is unchanged the pass is a no-op. changed reports whether any generation pass ran. A suggestion for committing the pages is printed to stderr only when changed is true.
Additive helper: the markdown materialized in Dir is not tracked by git (the per-app dotfile is gitignored), so committing and pushing the generated pages to the repository is a separate, ordinary `git add`/`commit`/`push` step.
Types ¶
type App ¶
type App struct {
Name string
Description string
Version string
GlobalNote string
UsageLine string
PersistentOptions []Option
Commands []Command
BeforeRun func(ctx *Context) error
AfterRun func(ctx *Context) error
Run func(ctx *Context) error
// AbbrevCommands enables prefix-based command matching. When true, a unique
// prefix of a command name (or alias) is accepted as a match. When the prefix
// is ambiguous, an error listing the candidates is returned.
AbbrevCommands bool
// Pager enables automatic paging through $PAGER when output exceeds
// the terminal height. When true, help output is buffered and piped through
// the pager only when it doesn't fit on one screen.
Pager bool
// Presentation overrides
Theme *Theme
GlobalFlags []Option
Shortcuts []Command
ConfigPath string
// I/O overrides for testing and custom redirection
Stdout io.Writer
Stderr io.Writer
}
App represents the root CLI application.
func (*App) Execute ¶
Execute runs the application using os.Args[1:] and context.Background().
Example ¶
package main
import (
"fmt"
"os"
"github.com/sarielhp/clihelp"
)
func main() {
var verbose bool
var output string
app := &clihelp.App{
Name: "demo",
Description: "Demonstration CLI tool",
Version: "1.0.0",
Pager: true,
Commands: []clihelp.Command{
{
Name: "build",
Description: "Compile the binary target",
UsageLine: "demo build [options] <target>",
Args: clihelp.ExactArgs(1),
Options: []clihelp.Option{
clihelp.String(&output, "-o, --output PATH", "dist/app", "Output binary path"),
clihelp.Bool(&verbose, "-v, --verbose", false, "Enable verbose logging"),
},
Run: func(ctx *clihelp.Context) error {
fmt.Printf("building %s -> %s (verbose: %v)\n", ctx.Args[0], output, verbose)
return nil
},
},
},
}
// Run with build command arguments
if err := app.Execute([]string{"build", "-o", "bin/demo", "-v", "main.go"}); err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
}
}
Output: building main.go -> bin/demo (verbose: true)
func (*App) ExecuteContext ¶
ExecuteContext runs the application using the given context and argument slice.
Example ¶
package main
import (
"context"
"fmt"
"github.com/sarielhp/clihelp"
)
func main() {
app := &clihelp.App{
Name: "runner",
Pager: true,
Commands: []clihelp.Command{
{
Name: "run",
Run: func(ctx *clihelp.Context) error {
select {
case <-ctx.Context.Done():
return ctx.Context.Err()
default:
fmt.Println("job completed successfully")
return nil
}
},
},
},
}
ctx := context.Background()
_ = app.ExecuteContext(ctx, []string{"run"})
}
Output: job completed successfully
func (*App) LookupCommand ¶
LookupCommand traverses the command hierarchy and returns the matching Command pointer, or nil if not found. Matches both Name and Aliases.
func (*App) PrintError ¶
PrintError prints a formatted error message to the App's stderr with colored prefix.
func (*App) Render ¶
Render writes global help when path is empty, or command help for a path.
Example ¶
package main
import (
"fmt"
"strings"
"github.com/sarielhp/clihelp"
)
func main() {
var buf strings.Builder
app := &clihelp.App{
Name: "webcli",
Description: "[webcli](https://example.com) — Modern web utility tool with `fast` execution.",
Pager: true,
Commands: []clihelp.Command{
{
Name: "ping",
Description: "Ping remote server",
},
},
}
// Render global help to buffer
app.RenderGlobal(clihelp.Options{Writer: &buf, Width: 80})
// Output is formatted with colors and OSC 8 hyperlinks in supported terminals
fmt.Println(strings.Contains(buf.String(), "webcli"))
}
Output: true
func (*App) RenderCommand ¶
RenderCommand writes help for the command at path (e.g. "config" "set"), rendering any of these present sections in order: Usage, Description, Subcommands, Parameters, Flags, Examples, Notes. Returns true if the path exists.
func (*App) RenderGlobal ¶
RenderGlobal writes the top-level application overview: a command-line usage template, description, command list with aliases, shortcut commands, global flags, and help footer.
func (*App) RenderTree ¶
RenderTree writes a tree view of the command hierarchy to w.
Example ¶
package main
import (
"fmt"
"strings"
"github.com/sarielhp/clihelp"
)
func main() {
var buf strings.Builder
app := &clihelp.App{
Name: "gitcli",
Pager: true,
Commands: []clihelp.Command{
{
Name: "remote",
Description: "Manage set of tracked repositories",
Subcommands: []clihelp.Command{
{Name: "add", Description: "Add a remote"},
{Name: "remove", Description: "Remove a remote"},
},
},
{
Name: "status",
Description: "Show working tree status",
},
},
}
app.RenderTree(clihelp.Options{Writer: &buf, Width: 80})
fmt.Println(strings.Contains(buf.String(), "remote"))
fmt.Println(strings.Contains(buf.String(), "add"))
fmt.Println(strings.Contains(buf.String(), "status"))
}
Output: true true true
type ArgsValidator ¶
ArgsValidator validates positional arguments after flag parsing.
func ExactArgs ¶
func ExactArgs(n int) ArgsValidator
ExactArgs returns an ArgsValidator that ensures exactly n arguments are provided.
Example ¶
package main
import (
"fmt"
"github.com/sarielhp/clihelp"
)
func main() {
app := &clihelp.App{
Name: "tagger",
Pager: true,
Commands: []clihelp.Command{
{
Name: "tag",
Args: clihelp.ExactArgs(2),
Run: func(ctx *clihelp.Context) error {
fmt.Printf("tagging %s with %s\n", ctx.Args[0], ctx.Args[1])
return nil
},
},
},
}
_ = app.Execute([]string{"tag", "file.txt", "v1.0"})
}
Output: tagging file.txt with v1.0
func MaximumNArgs ¶
func MaximumNArgs(n int) ArgsValidator
MaximumNArgs returns an ArgsValidator that ensures at most n arguments are provided.
func MinimumNArgs ¶
func MinimumNArgs(n int) ArgsValidator
MinimumNArgs returns an ArgsValidator that ensures at least n arguments are provided.
func RangeArgs ¶
func RangeArgs(min, max int) ArgsValidator
RangeArgs returns an ArgsValidator that ensures between min and max arguments are provided.
type Command ¶
type Command struct {
Name string
Aliases []string
Description string
UsageLine string
Group string
Hidden bool
PersistentOptions []Option
Options []Option
Subcommands []Command
Examples []Example
Args ArgsValidator
PreRun func(ctx *Context) error
Run func(ctx *Context) error
PostRun func(ctx *Context) error
// Presentation / legacy fields
Title string
Parameters []Param
SubcommandEntries []Param
Notes []Note
}
Command represents an executable command or category node.
type Context ¶
type Context struct {
Context context.Context
App *App
Command *Command
Args []string
RawArgs []string
Stdout io.Writer
Stderr io.Writer
}
Context encapsulates execution state passed to command handlers and lifecycle hooks.
type MarkdownOptions ¶
type MarkdownOptions struct {
// Dir is the directory that receives the generated pages. When empty it
// defaults to "docs/clihelp".
Dir string
}
MarkdownOptions controls markdown help-page generation.
type Note ¶
Note carries an optional heading (rendered as a section label) and a body of prose that is reflowed to the available width.
type Option ¶
type Option struct {
Flags string // e.g. "-p, --podcast <name>"
Description string // e.g. "Podcast title, index, or ID"
DefaultText string // Custom display override for default value
Hidden bool // Hidden from help and completion output
Deprecated string // Deprecation notice
Complete func(toComplete string) []string // Dynamic shell tab-completion callback
Binder func(fs *pflag.FlagSet) error // Registers the flag on fs; returns an error on duplicate/help-flag conflicts
}
Option represents a command-line flag or option definition.
func BoolToggle ¶
BoolToggle binds a boolean toggle pair (e.g. --[no-]check-new).
Example ¶
package main
import (
"fmt"
"github.com/sarielhp/clihelp"
)
func main() {
var normalize bool
app := &clihelp.App{
Name: "soundctl",
Pager: true,
Commands: []clihelp.Command{
{
Name: "process",
Options: []clihelp.Option{
clihelp.BoolToggle(&normalize, "--[no-]normalize", true, "Apply audio normalization"),
},
Run: func(ctx *clihelp.Context) error {
fmt.Printf("normalize: %v\n", normalize)
return nil
},
},
},
}
_ = app.Execute([]string{"process", "--no-normalize"})
}
Output: normalize: false
func Duration ¶
Duration binds a time.Duration flag to target.
Example ¶
package main
import (
"fmt"
"time"
"github.com/sarielhp/clihelp"
)
func main() {
var timeout time.Duration
app := &clihelp.App{
Name: "fetcher",
Pager: true,
Commands: []clihelp.Command{
{
Name: "fetch",
Options: []clihelp.Option{
clihelp.Duration(&timeout, "-t, --timeout DURATION", 10*time.Second, "Request timeout"),
},
Run: func(ctx *clihelp.Context) error {
fmt.Printf("timeout: %v\n", timeout)
return nil
},
},
},
}
_ = app.Execute([]string{"fetch", "--timeout", "45s"})
}
Output: timeout: 45s
func Enum ¶
Enum restricts input to an enumerated list of valid strings.
Example ¶
package main
import (
"fmt"
"github.com/sarielhp/clihelp"
)
func main() {
var env string
app := &clihelp.App{
Name: "deployer",
Pager: true,
Commands: []clihelp.Command{
{
Name: "deploy",
Options: []clihelp.Option{
clihelp.Enum(&env, "-e, --env ENV", []string{"dev", "staging", "prod"}, "dev", "Target deployment environment"),
},
Run: func(ctx *clihelp.Context) error {
fmt.Printf("deploying to: %s\n", env)
return nil
},
},
},
}
_ = app.Execute([]string{"deploy", "--env", "staging"})
}
Output: deploying to: staging
func StringSlice ¶
StringSlice binds a repeatable or comma-separated string slice flag to target.
Example ¶
package main
import (
"fmt"
"github.com/sarielhp/clihelp"
)
func main() {
var tags []string
app := &clihelp.App{
Name: "builder",
Pager: true,
Commands: []clihelp.Command{
{
Name: "build",
Options: []clihelp.Option{
clihelp.StringSlice(&tags, "-t, --tag TAG", []string{"latest"}, "Image tag (repeatable)"),
},
Run: func(ctx *clihelp.Context) error {
fmt.Printf("tags: %v\n", tags)
return nil
},
},
},
}
_ = app.Execute([]string{"build", "-t", "v1.0", "-t", "release"})
}
Output: tags: [v1.0 release]
type Options ¶
type Options struct {
// Writer is the output destination. When nil, os.Stdout is used.
Writer io.Writer
// Width is the target terminal width in columns. When zero it is
// auto-detected with a 70-column fallback for non-terminal output.
Width int
// MaxContentWidth caps the wrap width to indent+MaxContentWidth columns.
// When zero it defaults to 80. Set to a larger value to allow content to
// use more horizontal space than the default 80-column body.
MaxContentWidth int
// Theme overrides the App.Theme and the package default. When nil the
// App's theme (or the default) applies.
Theme *Theme
// Pager enables automatic paging through $PAGER when output exceeds
// the terminal height. When true, output is buffered and piped through
// the pager only when it doesn't fit on one screen.
Pager bool
}
Options controls a single render operation.
type Param ¶
Param describes a positional argument (or a listable subcommand/flag) with a display name and a free-form description.
type Theme ¶
type Theme struct {
// Hdr colors section labels (e.g. "Description:", "Usage:").
Hdr *color.Color
// Body colors description/usage prose.
Body *color.Color
// Accent colors the help header line, separators, and global command groups.
Accent *color.Color
// Subcommand colors subcommand names.
Subcommand *color.Color
// Separator toggles the horizontal rule drawn around the header block.
Separator bool
// TitlePrefix is prepended to the command help header line
// (e.g. "Detailed Usage: ").
TitlePrefix string
}
Theme controls the colors, separators, and header wording used by the renderer. A Theme's zero value is safe: any nil color field falls back to the default mail_cli palette when applied via App.Theme or Options.Theme.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package main demonstrates how to integrate the 'clihelp' package into a production Go command-line application with declarative routing and flag parsing.
|
Package main demonstrates how to integrate the 'clihelp' package into a production Go command-line application with declarative routing and flag parsing. |
|
mail_cli_fake
command
Command mail_cli_fake regenerates the complete mail_cli usage/help interface using clihelp's data model and unified renderer.
|
Command mail_cli_fake regenerates the complete mail_cli usage/help interface using clihelp's data model and unified renderer. |