cmdkit

package module
v1.0.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 9, 2025 License: MIT Imports: 11 Imported by: 0

README

cmdkit

Go Documentation Go Report Card Tests

Package cmdkit provides a simple, intuitive, and kinda fun command-line application framework that extends Go's flag package with support for subcommands, better help formatting, and a simple-to-use API.

The package features:

  • Built on Go's standard flag package.
  • No external dependencies.
  • Subcommand support with proper flag handling.
  • Unix-style short and long flags (-h, --help).
  • Automatic help text generation and formatting.
  • Before/After hooks for setup and cleanup.

The package also provide utilities that:

  • Add environment variable support for flags.
  • Add proper exit code handling.
  • Add support for repeatable flags.
  • Add smart terminal detection.
  • Add color output detection with NO_COLOR compliance.

Installation

To install cmdkit and use it in your project, run:

go get go.cipher.host/cmdkit@latest

Usage

Here's a simple example that creates a greeting application:

package main

import (
    "fmt"
    "os"

    "go.cipher.host/cmdkit"
)

func main() {
    // Create a new application
    app := cmdkit.New("greet", "A friendly greeting app", "1.0.0")

    // Add a "hello" command
    hello := cmdkit.NewCommand("hello", "Say hello to someone", "[--formal] <name>")
    
    var formal bool
    hello.Flags.BoolVar(&formal, "formal", false, "use formal greeting")
    
    hello.RunE = func(args []string) error {
        if len(args) < 1 {
            return fmt.Errorf("name is required")
        }

        if formal {
            fmt.Printf("Good day, %s!\n", args[0])
        } else {
            fmt.Printf("Hi, %s!\n", args[0])
        }

        return nil
    }

    app.AddCommand(hello)

    // Run the application
    cmdkit.Exit(app.Run(os.Args[1:]))
}

This will create a command-line application with the following interface:

$ greet --help
Usage: greet [global flags] command [command flags] [arguments...]

A friendly greeting app

Commands:
  hello         Say hello to someone

Global Flags:
  -h, --help    show help

Use "greet [command] --help" for more information about a command.

$ greet hello --help
Usage: greet hello [--formal] <name>

Say hello to someone

Flags:
  -h, --help     show help
  --formal       use formal greeting

$ greet hello Alice
Hi, Alice!

$ greet hello --formal Bob
Good day, Bob!

Contributing

Anyone can help make cmdkit better. Check out the contribution guidelines for more information.


The work in this repository complies with the REUSE specification (https://reuse.software/spec-3.3/). While the default license is MIT, individual files may be licensed differently.

Please see the individual files for details and the LICENSES directory for a full list of licenses used in this repository.

Documentation

Overview

Package cmdkit provides a very simple framework for building command-line applications that follow Unix command-line interface conventions.

The package tries to build upon Go's flag package to provide a more structured approach to CLI development while maintaining simplicity and familiarity.

Example
package main

import (
	"errors"
	"fmt"

	"go.cipher.host/cmdkit"
)

func main() {
	var (
		// Create a new application with explicit metadata. You can also leave
		// the name and version empty to have the application infer them from
		// os.Args[0] and Go module information.
		app = cmdkit.New("myapp", "A simple example application", "1.0.0")

		// Add a simple command to the application.
		greet = cmdkit.NewCommand("greet", "Greet someone by name", "[--formal] <name>")
	)

	var formal bool

	// Register your flags using flag.TypeVar-kind of methods, e.g.
	// flag.BoolVar. If you want to add a Unix-style short form, register both
	// the long and short forms and then use App.AddShorthand.
	greet.Flags.BoolVar(&formal, "formal", false, "use formal greeting")

	// Set the command's run function.
	greet.RunE = func(args []string) error {
		if len(args) < 1 {
			return errors.New("name is required")
		}

		if formal {
			fmt.Fprintf(greet.OutWriter(), "Good day, %s!\n", args[0])
		} else {
			fmt.Fprintf(greet.OutWriter(), "Hi, %s!\n", args[0])
		}

		return nil
	}

	// Add the command to the application.
	app.AddCommand(greet)

	// Run the application with example arguments. You'd normally use
	// os.Args[1:] instead.
	if err := app.Run([]string{"greet", "Alice"}); err != nil {
		app.Errorf("error: %v", err)

		cmdkit.Exit(err)
	}

}
Output:

Hi, Alice!

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func Exit

func Exit(err error)

Exit provides a consistent way to terminate a command-line application while respecting Unix exit code conventions.

It handles the following cases:

- nil error -> exit 0 (success) - flag.ErrHelp -> exit 0 (help display is not an error) - ExitError -> use provided exit code - other errors -> exit 1 (general failure)

This function should typically be called at the top level of main().

func InputOrFile

func InputOrFile(path, message string) (io.ReadCloser, error)

InputOrFile provides a flexible input mechanism that handles both file and stdin input transparently. It follows the Unix convention where "-" represents stdin, allowing commands to be part of pipelines.

The function adds user-friendly behavior by displaying an optional message when reading from an interactive terminal, helping users understand that the program is waiting for input.

func IsTerminal

func IsTerminal(fd uintptr) bool

IsTerminal detects whether a given file descriptor represents an interactive terminal.

func LoadEnv

func LoadEnv(fs *flag.FlagSet, prefix string) error

LoadEnv bridges environment variables to command-line flags. It follows the common Unix pattern of using uppercase environment variables with a prefix.

The function only sets flags that haven't been explicitly set via command-line arguments, maintaining the precedence: 1. Command-line flags. 2. Environment variables. 3. Default values.

For example, for an app "gravity" with flag "--character", "GRAVITY_CHARACTER=mabel" would set the flag if not provided on command line.

func WantColor

func WantColor() bool

WantColor determines whether the application should use ANSI color codes in its output. It implements a comprehensive color detection system following modern terminal conventions:

The function respects: - NO_COLOR environment variable. - Terminal capability detection. - Special terminal types (e.g., "dumb" terminals).

This provides a consistent and user-configurable color experience across different terminal environments.

Types

type App

type App struct {
	// OutWriter receives normal output. When initialized from New, defaults to
	// [os.Stdout].
	//
	// Configurable primarily for testing and special output handling.
	//
	// [os.Stdout]: https://pkg.go.dev/os#Stdout
	OutWriter io.Writer

	// ErrWriter receives error output and help text. When initialized from New,
	// defaults to [os.Stderr].
	//
	// Separated from OutWriter to follow Unix output conventions.
	//
	// [os.Stderr]: https://pkg.go.dev/os#Stderr
	ErrWriter io.Writer

	// Flags holds the global flag set. These flags apply to all subcommands and
	// are parsed before command-specific flags.
	Flags *flag.FlagSet

	// Before is called before any subcommand execution, allowing for global
	// setup like configuration loading or resource initialization.
	Before func() error

	// After is called after subcommand execution, regardless of success or
	// failure. Useful for cleanup and resource release.
	After func() error

	// Name is the application name. When initialized from New, if not provided,
	// defaults to [os.Args[0]].
	//
	// [os.Args[0]]: https://pkg.go.dev/os#Args
	Name string

	// Description explains the application's purpose. Shown in help text to
	// guide users.
	Description string

	// Version indicates the application version. When initialized from new, if
	// not provided, defaults to [debug.ReadBuildInfo].
	//
	// [debug.ReadBuildInfo]: https://pkg.go.dev/runtime/debug#ReadBuildInfo
	Version string

	// Examples provides usage examples shown in help text. Each example should
	// be a complete command line.
	Examples []string

	// Commands holds the registered subcommands.
	Commands []*Command
	// contains filtered or unexported fields
}

App represents a command-line application. It serves as the top-level container for application state and coordinates the execution of subcommands while managing global flags and configuration.

func New

func New(name, description, version string) *App

New creates a new App instance with sane defaults.

Name resolution:

  • Uses provided name if non-empty.
  • Falls back to base name of [os.Args[0]] if name is empty.

Version resolution:

  • Uses provided version if non-empty.
  • Attempts to read version from Go module information.
  • Falls back to "unknown" if no version information is available.

func (*App) AddCommand

func (a *App) AddCommand(cmd *Command)

AddCommand registers a new subcommand with the application. Commands are stored in registration order, which affects help text display.

func (*App) AddShorthand

func (a *App) AddShorthand(name, shorthand string)

AddShorthand creates a Unix-style short form for a flag. This enables the common pattern where flags have both long and short forms (e.g., "--help" and "-h").

You should register both the long and short forms with flag.TypeVar-kind of methods, e.g. flag.StringVar before using this method.

func (*App) Errorf

func (a *App) Errorf(format string, args ...any)

Errorf writes a formatted error message to the application's error output. It prefixes the message with the application name to provide context in error messages.

func (*App) Run

func (a *App) Run(args []string) error

Run executes the application with the provided arguments, typically [os.Args[1:]].

func (*App) ShowHelp

func (a *App) ShowHelp()

ShowHelp displays the application's help text.

func (*App) ShowVersion

func (a *App) ShowVersion()

ShowVersion displays the application's version.

type Command

type Command struct {
	// Flags holds command-specific flags that only apply to this command. These
	// are parsed after global flags but before command arguments.
	Flags *flag.FlagSet

	// RunE executes the command's logic. It receives the remaining arguments
	// after flag parsing.
	RunE func(args []string) error

	// Before runs before command execution, allowing for command-specific setup
	// like validating flags or preparing resources.
	Before func() error

	// After runs after command execution for cleanup. Executes even if the
	// command fails.
	After func() error

	// Name is the command name as used in the command line.
	Name string

	// Description explains the command's purpose. Shown in both parent app help
	// and command-specific help.
	Description string

	// Usage describes the command's argument pattern. E.g., "[flags] <input>
	// <output>".
	Usage string

	// Examples shows example invocations of this specific command.
	Examples []string
	// contains filtered or unexported fields
}

Command represents a single subcommand in a command-line application. It encapsulates all the behavior and flags specific to one function of the application, following the Unix principle of small, focused tools.

Commands are designed to be self-contained units that can be composed into larger applications. Each command manages its own flags and handles its own argument validation and processing.

func NewCommand

func NewCommand(name, description, usage string) *Command

NewCommand creates a new Command instance with the provided metadata.

For the best user experience, the usage string should follow Unix conventions: - Optional elements in square brackets: [--flag]. - Required elements in angle brackets: <file>. - Variable numbers of arguments with ellipsis: [file...].

func (*Command) AddShorthand

func (c *Command) AddShorthand(name, shorthand string)

AddShorthand creates a short form alias for a command-specific flag. This works the same way as App.AddShorthand but scoped to the command's flags.

func (*Command) ErrWriter

func (c *Command) ErrWriter() io.Writer

ErrWriter returns the application's error writer.

func (*Command) OutWriter

func (c *Command) OutWriter() io.Writer

OutWriter returns the application's output writer.

func (*Command) Run

func (c *Command) Run(args []string) error

Run executes the command with the provided arguments. It follows a similar flow to App.Run.

func (*Command) ShowHelp

func (c *Command) ShowHelp()

ShowHelp displays command-specific help text.

type Error

type Error string

Error is an immutable error type.

const ErrUnknownCommand Error = "unknown command"

ErrUnknownCommand is returned when a command is not found.

var ErrCommandRunMissing Error = "command has no run function"

ErrCommandRunMissing is returned when a command is created without the RunE field.

func (Error) Error

func (e Error) Error() string

Error implements the error interface for Error.

type ExitError

type ExitError struct {
	// Err is the underlying error being wrapped.
	Err error

	// No is the process exit code to use. E.g., 0 for success, 1-255 for
	// errors.
	No int
}

ExitError wraps an error with an exit code, allowing commands to control their process exit status in a Unix-compatible way similar to the errno standard in C.

func NewExitError

func NewExitError(err error, code int) *ExitError

NewExitError returns a new Error instance with the given error code.

func (*ExitError) Code

func (e *ExitError) Code() int

Code returns the error code.

func (*ExitError) Error

func (e *ExitError) Error() string

Error implements the error interface.

func (*ExitError) Unwrap

func (e *ExitError) Unwrap() error

Unwrap returns the underlying error.

type RepeatValue

type RepeatValue []string

RepeatValue implements flag.Value to support repeated flag options. This allows flags to be specified multiple times to build a list of values.

func (*RepeatValue) Set

func (r *RepeatValue) Set(value string) error

Set satisfies the flag.Value interface.

func (*RepeatValue) String

func (r *RepeatValue) String() string

String satisfies the flag.Value interface.

Directories

Path Synopsis
internal
term
Package term provides support functions for dealing with terminals, as commonly found on UNIX systems.
Package term provides support functions for dealing with terminals, as commonly found on UNIX systems.
xflag
Package xflag provides a extension to the flag package.
Package xflag provides a extension to the flag package.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL