simplcli

package
v0.1.2 Latest Latest
Warning

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

Go to latest
Published: May 1, 2026 License: GPL-3.0 Imports: 11 Imported by: 0

Documentation

Overview

Package simplcli is a simple no-dependencies CLI implementation.

A SimplCLI is a collection of sub commands, which are just a function matching the Runner interface and a doc string.

greetRunner := func(ctx context.Context, stdout, stderr io.Writer, args []string) error {
	fmt.Fprintln(stdout, "hello!")
	return nil
}

cli := simplcli.SimplCLI{
	SubCmds: map[string]simplcli.SubCmd{
		"hello": {Runner: greetRunner, Doc: "say hello"},
	},
}

simplcli.Entrypoint(cli)

Nesting Subcommands

SimplCLI can be nested by using the SimplCLI.Run as the Runner in another SimplCLI's sub commands:

greetRunner := func(ctx context.Context, stdout, stderr io.Writer, args []string) error {
	fmt.Fprintln(stdout, "hello, world!")
	return nil
}

childCLI := simplcli.SimplCLI{
	SubCmds: map[string]simplcli.SubCmd{
		"greet": {Runner: greetRunner, Doc: "greet the world"},
	},
}

parentCLI := simplcli.SimplCLI{
	SubCmds: map[string]simplcli.SubCmd{
		"sub": {Runner: childCLI.Run, Doc: "a nested subcommand"},
	},
}
err := parentCLI.Run(ctx, os.Stdout, os.Stderr, []string{"sub", "greet"})

Middleware

To facilitate setup/teardown (e.g. database connection, logging, etc.pp) Middleware can be used. Middlewares set on a SimplCLI are executed in order for every sub command.

logMiddleware := func(ctx context.Context, stdout, stderr io.Writer, args []string, next Runner) error {
	fmt.Fprintln(stdout, "before command")
	err := next(ctx, stdout, stderr, args)
	fmt.Fprintln(stdout, "after command")
	return err
}

cliWithMiddleware := simplcli.SimplCLI{
	Middlewares: []simplcli.Middleware{logMiddleware},
	SubCmds: map[string]simplcli.SubCmd{
		"greet": {Runner: greetRunner, Doc: "greet someone"},
	},
}
err := cliWithMiddleware.Run(ctx, os.Stdout, os.Stderr, []string{"greet"})

Flag handling

Handling flags is not easy - the stdlib flag package is fantastic but it also has drawbacks.

Two approaches that work well with simplcli is either using package level flags and calling flag.Parse in a middleware or using flag.FlagSet.

flag.Parse with package level flags works well for small-ish cli applications that have many shared global values.

var myFlag = flag.String("flag", "default", "docs...")

cli := simplcli.SimplCLI{
	Middlewares: []simplcli.Middleware{
		func(ctx context.Context, stdout, stderr io.Writer, args []string, next Runner) error {
			flag.Parse()
			return next(ctx, stdout, stderr, flag.Args())
		},
	},
	SubCmds: map[string]simplcli.SubCmd{
		// ...
	},
}

simplcli.Entrypoint(cli)

flag.FlagSet has the same features as the package level flags but allows building the flagset for each sub command specifically.

A common pattern is to have a method RegisterFlags on structs, which take a flag.FlagSet and register their flags on it.

type Options struct {
	SomeAttribute string
}

func (g *Greeter) RegisterFlags(fs *flags.FlagSet) {
	fs.StringVar(&o.SomeAttribute, "", false, "Attribute content")
}

greetRunner := func(ctx context.Context, stdout, stderr io.Writer, args []string) error {
	fs := flag.NewFlagSet("", flag.ExitOnError)
	fs.SetOutput(stderr)
	fName := fs.String("name", "", "Name to greet")

	o := &Options{}
	o.RegisterFlags(fs)

	if err := fs.Parse(args); err != nil {
		return err
	}
	_, err := fmt.Fprintf(stdout, "hello %s!", *fName)
	return err
}

cli := simplcli.SimplCLI{
	SubCmds: map[string]simplcli.SubCmd{
		"greet": {Runner: greetRunner, Doc: "greet someone"},
	},
}
simplcli.Entrypoint(cli)

Index

Examples

Constants

View Source
const (
	// Help is the special "help" subcommand. By default simplcli prints
	// all available subcommands. If a SimplCLI has a custom help
	// subcommand set it will be executed instead.
	// The Help function is a [Runner] but only gets a valid stdout.
	// io.Discard is passed as stderr.
	Help = "help"
)

Variables

View Source
var (
	// ErrNoArgs is returned when no arguments are passed.
	ErrNoArgs = fmt.Errorf(`no arguments passed, pass %q as the first argument `+
		`to get the list of available subcommands`, Help)
)

Functions

func Entrypoint

func Entrypoint(simplcli SimplCLI)

Entrypoint runs SimplCLI with a signal-cancelled context.Context, os.Stdout, os.Stderr and os.Args. If an error is returned it is printed to os.Stderr and os.Exit is called.

func PrintDefaultHelp

func PrintDefaultHelp(out io.Writer, s SimplCLI) error

PrintDefaultHelp prints all available subcommands to out.

func WithSignal

func WithSignal(ctx context.Context, stderr io.Writer, signals ...os.Signal) (context.Context, context.CancelFunc)

WithSignal wraps a context.Context to be cancelled when the specified signals are received. This is virtually equivalent to signal.NotifyContext but prints a message to stderr. When no signals are passed os.Interrupt and syscal.SIGTERM are defaulted.

Types

type Middleware

type Middleware func(ctx context.Context, stdout, stderr io.Writer, args []string, next Runner) error

Middleware is a function that wraps Runner.

Example
logMiddleware := func(ctx context.Context, stdout, stderr io.Writer, args []string, next Runner) error {
	fmt.Fprintln(stdout, "before command")
	err := next(ctx, stdout, stderr, args)
	fmt.Fprintln(stdout, "after command")
	return err
}

greetRunner := func(_ context.Context, stdout, _ io.Writer, _ []string) error {
	fmt.Fprintln(stdout, "hello, world!")
	return nil
}

cliWithMiddleware := SimplCLI{
	Middlewares: []Middleware{logMiddleware},
	SubCmds: map[string]SubCmd{
		"greet": {Runner: greetRunner, Doc: "greet someone"},
	},
}

if err := cliWithMiddleware.Run(context.Background(), os.Stdout, os.Stderr, []string{"greet"}); err != nil {
	log.Fatal(err)
}
Output:
before command
hello, world!
after command

type Runner

type Runner func(ctx context.Context, stdout, stderr io.Writer, args []string) error

Runner is the interface expected for functions being executed as a subcommand.

type SimplCLI

type SimplCLI struct {
	// The [SubCmd]'s to execute.
	SubCmds map[string]SubCmd
	// A list of middlewares to be applied to all subcommands.
	Middlewares []Middleware
}

SimplCLI contains multiple SubCmd's.

Example
greetRunner := func(_ context.Context, stdout, _ io.Writer, _ []string) error {
	fmt.Fprintln(stdout, "hello!")
	return nil
}

cli := SimplCLI{
	SubCmds: map[string]SubCmd{
		"hello": {Runner: greetRunner, Doc: "say hello"},
	},
}

if err := cli.Run(context.Background(), os.Stdout, os.Stderr, []string{"hello"}); err != nil {
	log.Fatal(err)
}
Output:
hello!

func (SimplCLI) PrintHelp

func (s SimplCLI) PrintHelp(ctx context.Context, out io.Writer) error

PrintHelp prints the available subcommands to out.

Example
noop := func(_ context.Context, _, _ io.Writer, _ []string) error {
	return nil
}

cli := SimplCLI{
	SubCmds: map[string]SubCmd{
		"first":  {noop, "first subcommand"},
		"second": {noop, "second subcommand"},
	},
}

if err := cli.PrintHelp(context.Background(), os.Stdout); err != nil {
	log.Fatal(err)
}
Output:
Available subcommands:
   first   first subcommand
  second   second subcommand

func (SimplCLI) Run

func (s SimplCLI) Run(ctx context.Context, stdout, stderr io.Writer, args []string) error

Run runs subcommand indicated by the first argument. If the first argument is "help" all registered subcommands are printed to out.

Example

Nested SimplCLI

greetRunner := func(_ context.Context, stdout, _ io.Writer, _ []string) error {
	fmt.Fprintln(stdout, "hello, world!")
	return nil
}

childCLI := SimplCLI{
	SubCmds: map[string]SubCmd{
		"greet": {Runner: greetRunner, Doc: "greet the world"},
	},
}

parentCLI := SimplCLI{
	SubCmds: map[string]SubCmd{
		"sub": {Runner: childCLI.Run, Doc: "a nested subcommand"},
	},
}
if err := parentCLI.Run(context.Background(), os.Stdout, os.Stderr, []string{"sub", "greet"}); err != nil {
	log.Fatal(err)
}
Output:
hello, world!

type SubCmd

type SubCmd struct {
	// Runner is the function to be executed when this sub command is run.
	Runner Runner
	// Doc is the doc string used when printing the help output.
	Doc string
}

SubCmd is a CLI subcommand.

Jump to

Keyboard shortcuts

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