click

package module
v0.0.0-...-f0f83ea Latest Latest
Warning

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

Go to latest
Published: Apr 21, 2026 License: MIT Imports: 6 Imported by: 0

README

go-click

ci

tiny cli plumbing for lightweight or personal go projects. go-click is a tiny helper for small projects. you should probably just copy it into your own repo, but i'm lazy. if you want something more standard or more fully featured, use cobra.

⇁ TOC

⇁ What

you can really only do these things with it:

  • shared root state
  • root flags
  • command flags
  • nested commands
  • passthrough args after --
  • injected stdout / stderr

that's the whole idea.

⇁ Installation

go get github.com/0xdsqr/go-click

⇁ Getting Started

package main

import (
	"context"
	"flag"
	"fmt"
	"os"

	click "github.com/0xdsqr/go-click"
)

type root struct {
	verbose bool
}

func main() {
	app := click.App[root]{
		Name: "demo",
		ConfigureRoot: func(cfg *root) {
			cfg.verbose = false
		},
		ConfigureRootFlags: func(fs *flag.FlagSet, cfg *root) {
			fs.BoolVar(&cfg.verbose, "v", false, "enable verbose output")
		},
		Commands: []click.Command[root]{
			{
				Name: "hello",
				Run: func(_ context.Context, env click.Env[root], _ []string, _ []string) error {
					if env.Root.verbose {
						fmt.Fprintln(env.Stderr, "verbose mode enabled")
					}
					fmt.Fprintln(env.Stdout, "hello from go-click")
					return nil
				},
			},
		},
	}

	if err := app.Run(context.Background(), os.Args[1:]); err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
}

⇁ API

docs:

  • App
  • Command
  • Env
  • https://pkg.go.dev/github.com/0xdsqr/go-click
basic command
app := click.App[struct{}]{
	Name: "demo",
	Commands: []click.Command[struct{}]{
		{
			Name: "hello",
			Run: func(_ context.Context, env click.Env[struct{}], _ []string, _ []string) error {
				fmt.Fprintln(env.Stdout, "hello")
				return nil
			},
		},
	},
}
shared root state
type root struct {
	host string
}

app := click.App[root]{
	Name: "demo",
	ConfigureRoot: func(cfg *root) {
		cfg.host = "local"
	},
	Commands: []click.Command[root]{
		{
			Name: "host",
			Run: func(_ context.Context, env click.Env[root], _ []string, _ []string) error {
				fmt.Fprintln(env.Stdout, env.Root.host)
				return nil
			},
		},
	},
}
root flags
type root struct {
	verbose bool
}

app := click.App[root]{
	Name: "demo",
	ConfigureRootFlags: func(fs *flag.FlagSet, cfg *root) {
		fs.BoolVar(&cfg.verbose, "v", false, "enable verbose output")
	},
	Commands: []click.Command[root]{
		{
			Name: "hello",
			Run: func(_ context.Context, env click.Env[root], _ []string, _ []string) error {
				if env.Root.verbose {
					fmt.Fprintln(env.Stderr, "verbose mode enabled")
				}
				fmt.Fprintln(env.Stdout, "hello")
				return nil
			},
		},
	},
}
nested commands
app := click.App[struct{}]{
	Name: "demo",
	Commands: []click.Command[struct{}]{
		{
			Name: "project",
			Commands: []click.Command[struct{}]{
				{
					Name: "list",
					Run: func(_ context.Context, env click.Env[struct{}], _ []string, _ []string) error {
						fmt.Fprintln(env.Stdout, "listing projects")
						return nil
					},
				},
			},
		},
	},
}

usage:

demo project list
command flags
var formatJSON bool

app := click.App[struct{}]{
	Name: "demo",
	Commands: []click.Command[struct{}]{
		{
			Name: "project",
			Commands: []click.Command[struct{}]{
				{
					Name: "list",
					ConfigureFlags: func(fs *flag.FlagSet) {
						fs.BoolVar(&formatJSON, "json", false, "output JSON")
					},
					Run: func(_ context.Context, env click.Env[struct{}], _ []string, _ []string) error {
						if formatJSON {
							fmt.Fprintln(env.Stdout, `["a","b"]`)
							return nil
						}
						fmt.Fprintln(env.Stdout, "a\nb")
						return nil
					},
				},
			},
		},
	},
}

usage:

demo project list --json
passthrough args with --
app := click.App[struct{}]{
	Name: "demo",
	Commands: []click.Command[struct{}]{
		{
			Name:        "exec",
			Passthrough: true,
			Run: func(_ context.Context, env click.Env[struct{}], args []string, pass []string) error {
				fmt.Fprintln(env.Stdout, "normal args:", args)
				fmt.Fprintln(env.Stdout, "passthrough args:", pass)
				return nil
			},
		},
	},
}

usage:

demo exec build target -- --watch --verbose

args will be ["build", "target"]

pass will be ["--watch", "--verbose"]

custom stdout / stderr
var stdout bytes.Buffer
var stderr bytes.Buffer

app := click.App[struct{}]{
	Name:   "demo",
	Stdout: &stdout,
	Stderr: &stderr,
}

⇁ Contributing

some people have started using this for whatever reason. i am probably not going to accept mrs.

clone it. use the flake. do whatever you want with it.

nix develop
nix flake check

⇁ MIT

do whatever you want with it.

Documentation

Overview

Package click provides tiny helpers for building nested CLIs with root flags and optional passthrough arguments.

The package intentionally exposes a small API centered on App, Command, and Env, while keeping traversal and help internals private.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type App

type App[T any] struct {
	// Name is used when constructing the root flag set.
	Name string
	// ConfigureRoot initializes shared root state before flags are parsed.
	ConfigureRoot func(*T)
	// ConfigureRootFlags registers flags shared across the whole command tree.
	ConfigureRootFlags func(*flag.FlagSet, *T)
	// Commands are the top-level commands available in the app.
	Commands []Command[T]
	// Stdout overrides the default command output writer.
	Stdout io.Writer
	// Stderr overrides the default error output writer.
	Stderr io.Writer
}
Example
package main

import (
	"context"
	"flag"
	"fmt"

	click "github.com/0xdsqr/go-click"
)

func main() {
	type rootFlags struct {
		Host    string
		Verbose bool
	}

	app := click.App[rootFlags]{
		Name: "demo",
		ConfigureRoot: func(root *rootFlags) {
			root.Host = "local"
		},
		ConfigureRootFlags: func(fs *flag.FlagSet, root *rootFlags) {
			fs.BoolVar(&root.Verbose, "v", false, "enable verbose output")
		},
		Commands: []click.Command[rootFlags]{
			{
				Name: "hello",
				ConfigureFlags: func(fs *flag.FlagSet) {
					fs.Bool("loud", false, "print loudly")
				},
				Run: func(_ context.Context, env click.Env[rootFlags], _ []string, _ []string) error {
					fmt.Fprintln(env.Stdout, "hello from go-click")
					return nil
				},
			},
		},
	}

	if err := app.Run(context.Background(), []string{"hello"}); err != nil {
		panic(err)
	}

}
Output:
hello from go-click

func (App[T]) Run

func (a App[T]) Run(ctx context.Context, args []string) error

Run parses root flags and dispatches the matching command.

type Command

type Command[T any] struct {
	// Name is the token users type to select this command.
	Name string
	// Description is a short help string shown in command listings.
	Description string
	// Usage overrides the default command path shown in help output.
	Usage string
	// Commands are this command's nested subcommands.
	Commands []Command[T]
	// ConfigureFlags registers flags for this specific command.
	ConfigureFlags func(*flag.FlagSet)
	// Passthrough allows args after `--` to be passed separately to Run.
	Passthrough bool
	// Run executes the command with normal and passthrough arguments.
	Run func(context.Context, Env[T], []string, []string) error
}

Command describes one CLI command in the tree.

type Env

type Env[T any] struct {
	Stdout io.Writer
	Stderr io.Writer
	Root   T
}

Env contains the values passed to a command handler at runtime.

Jump to

Keyboard shortcuts

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