Skip to content

h3go/cli

Repository files navigation

CLI

Go Reference Go Report Card License: MIT

中文文档

A simple, lightweight Go CLI application framework built on the standard library flag package.

Features

  • 🚀 Simple to Use - Built on standard library flag package, low learning curve
  • 📦 Lightweight - Zero third-party dependencies, clean codebase
  • 🎯 Flexible Configuration - Support custom help and version commands
  • 🧩 Subcommand Support - Built-in subcommand routing and management
  • 🔧 Context Support - Native context.Context support for timeout control and cancellation
  • 📝 Auto Help Generation - Automatically generate formatted help information

Installation

go get github.com/h3go/cli

Quick Start

Basic Example

package main

import (
	"context"
	"fmt"
	"os"

	"github.com/h3go/cli"
)

func main() {
	// Create CLI application
	app := cli.NewProgram("myapp", "1.0.0")
	app.Usage = "A simple CLI application"

	// Create command
	initCmd := cli.NewCommand("init", "Initialize a new project")
	var path string
	initCmd.Flags = func(fs *flag.FlagSet) {
		fs.StringVar(&path, "path", ".", "Project path")
	}
	initCmd.Action = func(ctx context.Context, cmd *cli.Command) error {
		fmt.Printf("Initializing project at %s\n", path)
		return nil
	}

	// Register command
	app.Commands = []*cli.Command{initCmd}

	// Run application
	if err := app.Run(os.Args); err != nil {
		fmt.Fprintf(os.Stderr, "Error: %v\n", err)
		os.Exit(1)
	}
}

Run:

$ myapp init --path ./myproject
Initializing project at ./myproject

Complete Example

package main

import (
	"context"
	"fmt"
	"os"

	"github.com/h3go/cli"
)

func main() {
	app := cli.NewProgram("myapp", "1.0.0")
	app.Usage = "A feature-rich CLI application"
	app.Banner = `
 __  __                            
|  \/  |_   _  __ _ _ __  _ __    
| |\/| | | | |/ _' | '_ \| '_ \   
| |  | | |_| | (_| | |_) | |_) |  
|_|  |_|\__, |\__,_| .__/| .__/   
        |___/      |_|   |_|      
`

	// init command
	initCmd := cli.NewCommand("init", "Initialize a new project")
	initCmd.Description = "Create a new project with the specified configuration"
	var initPath string
	var verbose bool
	initCmd.Flags = func(fs *flag.FlagSet) {
		fs.StringVar(&initPath, "path", ".", "Project path")
		fs.BoolVar(&verbose, "verbose", false, "Verbose output")
	}
	initCmd.Action = func(ctx context.Context, cmd *cli.Command) error {
		if verbose {
			fmt.Printf("Initializing project at %s (verbose mode)\n", initPath)
		} else {
			fmt.Printf("Initializing project at %s\n", initPath)
		}
		return nil
	}

	// build command
	buildCmd := cli.NewCommand("build", "Build the project")
	buildCmd.Description = "Compile the project with specified options"
	var buildOutput string
	var optimize bool
	buildCmd.Flags = func(fs *flag.FlagSet) {
		fs.StringVar(&buildOutput, "output", "bin/app", "Output path")
		fs.BoolVar(&optimize, "optimize", false, "Enable optimization")
	}
	buildCmd.Action = func(ctx context.Context, cmd *cli.Command) error {
		fmt.Printf("Building project to %s (optimize=%v)\n", buildOutput, optimize)
		return nil
	}

	// deploy command
	deployCmd := cli.NewCommand("deploy", "Deploy the application")
	var env string
	deployCmd.Flags = func(fs *flag.FlagSet) {
		fs.StringVar(&env, "env", "production", "Environment (development/staging/production)")
	}
	deployCmd.Action = func(ctx context.Context, cmd *cli.Command) error {
		fmt.Printf("Deploying to %s environment\n", env)
		return nil
	}

	app.Commands = []*cli.Command{initCmd, buildCmd, deployCmd}

	if err := app.Run(os.Args); err != nil {
		fmt.Fprintf(os.Stderr, "Error: %v\n", err)
		os.Exit(1)
	}
}

Usage Guide

Create Application

app := cli.NewProgram("myapp", "1.0.0")
app.Usage = "Application description"
app.Banner = "ASCII art banner (optional)"

Create Command

cmd := cli.NewCommand("commandname", "Short description")
cmd.Description = "Long description (optional)"

Add Flags

Add flags using standard library flag package style:

var name string
var age int
var verbose bool

cmd.Flags = func(fs *flag.FlagSet) {
	fs.StringVar(&name, "name", "default", "User name")
	fs.IntVar(&age, "age", 0, "User age")
	fs.BoolVar(&verbose, "verbose", false, "Enable verbose output")
}

Define Command Action

cmd.Action = func(ctx context.Context, cmd *cli.Command) error {
	fmt.Printf("Name: %s, Age: %d\n", name, age)
	return nil
}

Get Flag Values

Besides using closures to capture variables, you can also use convenience methods to get flag values in Action:

cmd := cli.NewCommand("config", "Configure settings")
cmd.Flags = func(fs *flag.FlagSet) {
	fs.String("host", "localhost", "Server host")
	fs.Int("port", 8080, "Server port")
	fs.Bool("debug", false, "Enable debug mode")
	fs.Int64("max-size", 1024, "Maximum size in bytes")
	fs.Uint("workers", 4, "Number of workers")
	fs.Uint64("limit", 1000000, "Rate limit")
	fs.Float64("ratio", 0.5, "Compression ratio")
	fs.Duration("timeout", 30*time.Second, "Request timeout")
}

cmd.Action = func(ctx context.Context, cmd *cli.Command) error {
	// Use convenience methods to get flag values
	host := cmd.String("host")           // Get string
	port := cmd.Int("port")               // Get int
	debug := cmd.Bool("debug")            // Get bool
	maxSize := cmd.Int64("max-size")      // Get int64
	workers := cmd.Uint("workers")        // Get uint
	limit := cmd.Uint64("limit")          // Get uint64
	ratio := cmd.Float64("ratio")         // Get float64
	timeout := cmd.Duration("timeout")    // Get duration
	
	fmt.Printf("Server: %s:%d\n", host, port)
	fmt.Printf("Debug: %v\n", debug)
	fmt.Printf("Max Size: %d bytes\n", maxSize)
	fmt.Printf("Workers: %d\n", workers)
	fmt.Printf("Limit: %d\n", limit)
	fmt.Printf("Ratio: %.2f\n", ratio)
	fmt.Printf("Timeout: %v\n", timeout)
	
	return nil
}

Usage example:

$ myapp config --host example.com --port 3000 --debug --timeout 1m
Server: example.com:3000
Debug: true
Max Size: 1024 bytes
Workers: 4
Limit: 1000000
Ratio: 0.50
Timeout: 1m0s

Note: These convenience methods search from merged flags (including global flags and command flags), returning zero values if not found.

Register Commands

app.Commands = []*cli.Command{cmd1, cmd2, cmd3}

Run Application

if err := app.Run(os.Args); err != nil {
	fmt.Fprintf(os.Stderr, "Error: %v\n", err)
	os.Exit(1)
}

Context Support

Use RunContext for timeout and cancellation support:

ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()

if err := app.RunContext(ctx, os.Args); err != nil {
	fmt.Fprintf(os.Stderr, "Error: %v\n", err)
	os.Exit(1)
}

Check context in commands:

cmd.Action = func(ctx context.Context, cmd *cli.Command) error {
	select {
	case <-ctx.Done():
		return ctx.Err()
	default:
		// Execute command logic
	}
	return nil
}

Built-in Features

Automatic Help

The framework automatically provides help functionality:

# Show application help
$ myapp -h
$ myapp --help
$ myapp help

# Show command help
$ myapp init -h
$ myapp help init

Automatic Version

The framework automatically provides version information:

$ myapp -v
$ myapp --version
$ myapp version

Custom Help and Version

// Hide built-in help/version
app.HideHelpCommand = true
app.HideVersionCommand = true
app.HideHelpFlag = true
app.HideVersionFlag = true

// Custom help command
customHelp := cli.NewCommand("help", "Custom help")
customHelp.Action = func(ctx context.Context, cmd *cli.Command) error {
	fmt.Println("My custom help message")
	return nil
}
app.HelpCommand = customHelp

// Custom version command
customVersion := cli.NewCommand("version", "Custom version")
customVersion.Action = func(ctx context.Context, cmd *cli.Command) error {
	fmt.Println("My custom version info")
	return nil
}
app.VersionCommand = customVersion

Default Command

Set a default command to execute when users don't provide a command:

app := cli.NewProgram("myapp", "1.0.0")
app.DefaultCommand = "serve" // Set default command

serveCmd := cli.NewCommand("serve", "Start the server")
var port int
serveCmd.Flags = func(fs *flag.FlagSet) {
	fs.IntVar(&port, "port", 8080, "Port to listen on")
}
serveCmd.Action = func(ctx context.Context, cmd *cli.Command) error {
	fmt.Printf("Server listening on port %d\n", port)
	return nil
}

app.Commands = []*cli.Command{serveCmd}

Usage example:

# Execute default command serve when no command is provided
$ myapp
Server listening on port 8080

# Pass flags to default command
$ myapp --port 3000
Server listening on port 3000

# Explicitly specify other commands
$ myapp help
# Show help information

API Documentation

Program

type Program struct {
	Commands           []*Command // Command list
	Name               string     // Application name
	Usage              string     // Application description
	Version            string     // Application version
	Banner             string     // Application banner (ASCII art, etc.)
	DefaultCommand     string     // Default command name (used when no command is specified)
	HideHelpCommand    bool       // Hide help command
	HideVersionCommand bool       // Hide version command
	HideHelpFlag       bool       // Hide -h/--help flag
	HideVersionFlag    bool       // Hide -v/--version flag
	HelpCommand        *Command   // help command (customizable)
	VersionCommand     *Command   // version command (customizable)
}

func NewProgram(appName, version string) *Program
func (p *Program) Run(args []string) error
func (p *Program) RunContext(ctx context.Context, args []string) error
func (p *Program) Get(name string) *Command
func (p *Program) SetOutput(w io.Writer)
func (p *Program) Output() io.Writer
func (p *Program) PrintUsage() error

Command

type Command struct {
	Name         string              // Command name (e.g., "init", "migrate")
	Usage        string              // Short usage description (one line)
	Description  string              // Detailed description (multiple lines)
	Flags        func(*flag.FlagSet) // Command flag configuration function
	Action       ActionFunc          // Command execution function
	HideHelpFlag bool                // Hide -h help flag
}

func NewCommand(name, usage string) *Command
func DefaultHelpCommand() *Command
func DefaultVersionCommand() *Command
func (c *Command) Run(args []string) error
func (c *Command) RunContext(ctx context.Context, args []string) error
func (c *Command) SetOutput(w io.Writer)
func (c *Command) Output() io.Writer
func (c *Command) SetProgram(p *Program)
func (c *Command) Program() *Program
func (c *Command) PrintUsage() error
func (c *Command) String(name string) string
func (c *Command) Bool(name string) bool
func (c *Command) Int(name string) int
func (c *Command) Int64(name string) int64
func (c *Command) Uint(name string) uint
func (c *Command) Uint64(name string) uint64
func (c *Command) Float64(name string) float64
func (c *Command) Duration(name string) time.Duration

ActionFunc

type ActionFunc func(ctx context.Context, cmd *Command) error

Example Projects

Check the examples directory for more examples:

Comparison with Other Frameworks

Feature cli cobra urfave/cli
Dependencies 0 Multiple 0
Based on stdlib
Learning curve Low Medium Low
Feature richness Medium High High
Code size Minimal Large Medium

License

MIT License - see LICENSE

Contributing

Issues and Pull Requests are welcome!

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages