clifford

package module
v0.0.0-...-8e9ba49 Latest Latest
Warning

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

Go to latest
Published: Dec 30, 2025 License: MIT Imports: 3 Imported by: 0

README

clifford

[!WARNING] clifford is still under early development. Expect breaking changes and incomplete features.

Clifford is a simple and lightweight library for building command-line interfaces in Go. It makes it easy to define flags, arguments, and commands without minimal boilerplate.


Features

  • Define positional arguments, short flags (-f), and long flags (--flag) via struct embedding and tags.
  • Support for required and optional arguments.
  • Automatic generation of help messages (--help) and version information (--version).
  • Extensible through embedding and custom struct types.

Installation

clifford is available on GitHub and can be installed using Go modules:

go get github.com/chriso345/clifford

Usage

Define your CLI argument structure using embedded clifford.Clifford and marker types for flags and descriptions:

package main

import (
	"fmt"
	"log"

	"github.com/chriso345/clifford"
)

func main() {
	target := struct {
		clifford.Clifford `name:"mytool"`   // Set the name of the CLI tool
		clifford.Version  `version:"1.2.3"` // Enable automatic version flag
		clifford.Help                       // Enable automatic help flags

		Name struct {
			Value             string
			clifford.Clifford `short:"n" long:"name" desc:"User name"`
		}
		Age struct {
			Value             string
			clifford.ShortTag // auto generates -a
			clifford.LongTag  // auto generates --age
			clifford.Desc     `desc:"Age of the user"`
		}
	}{}

	err := clifford.Parse(&target)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("Name: %s\n", target.Name.Value)
	fmt.Printf("Age: %s\n", target.Age.Value)
}
  • Passing -h or --help will print an automatically generated help message and exit.
  • Passing --version will print the version information and exit.

If a user mistypes a subcommand, clifford will return a helpful message with a suggested correction:

$ app srve
unknown subcommand: srve (did you mean "serve"?)

This suggestion is based on fuzzy matching and common transposition errors.

Notes:

  • Use the default tag on a field to provide a fallback value which will also be shown in help output.
  • Help can be exposed as a dedicated subcommand (e.g. app help server) when the Help embedding is tagged appropriately; subcommands may advertise that they accept help as a subcommand or flag.
  • For subcommands, running app <subcmd> help or app <subcmd> -h will show help scoped to that subcommand when enabled.

Public API

The public API of clifford is still under development. The following types and functions are available:

  • clifford.Parse(target any) error: Parses the command-line arguments and populates the target struct.
  • clifford.BuildHelp(target any) string: Generates a help message for the CLI defined by target.
  • clifford.BuildVersion(target any) string: Generates a version message for the CLI defined by target.
  • clifford.BuildHelpWithParent(parent any, subName string, subTarget any, long bool) (string, error): Helper to generate subcommand help that shows the parent application name alongside the subcommand.
Public Marker Types

clifford provides several marker types to define flags and descriptions in your CLI:

  • clifford.Clifford: Base type for all CLI definitions. Must be embedded in the target struct. clifford.Clifford can also be used as a marker for individual fields.
  • clifford.Help: Enables automatic help message generation.
  • clifford.Version: Enables automatic version message generation.
  • clifford.ShortTag: Marks a field as having a short flag (e.g., -f). If no short flag is specified, it defaults to the first letter of the field name.
  • clifford.LongTag: Marks a field as having a long flag (e.g., --flag). If no long flag is specified, it defaults to the field name in kebab-case.
  • clifford.Desc: Provides a description for the field, which is used in the help message. Requires a desc tag with the description text.
  • clifford.Required: Marks a field as required. If a required field is not provided, clifford.Parse will return an error.
  • clifford.Subcommand: Marks a sub-struct as a subcommand; subcommands can have their own flags/positionals and may opt-in to show help as a subcommand.

License

This project is licensed under the MIT License. See the LICENSE file for details.

Documentation

Overview

Package clifford is a CLI argument parsing library for Go that uses reflection and struct tags to define command-line interfaces declaratively.

It supports positional arguments, short and long flags, required arguments, and automatic generation of help and version output.

The library is designed to be easy to use and integrate into Go CLI tools, providing a clean API for defining and parsing command-line parameters.

Example (Defaults)

Example_defaults demonstrates default values are applied when flags are omitted.

package main

import (
	"fmt"
	"os"

	"github.com/chriso345/clifford"
)

func main() {
	os.Args = []string{"app"}
	target := struct {
		clifford.Clifford `name:"app" desc:"App showing defaults"`

		Port struct {
			Value             int `default:"8080"`
			clifford.Clifford `long:"port" desc:"Port to run the server on"`
		}
	}{}

	err := clifford.Parse(&target)
	if err != nil {
		panic(err)
	}
	fmt.Println("Port:", target.Port.Value)
}
Output:

Port: 8080
Example (Disable_shorthand)
package main

import (
	"fmt"

	"github.com/chriso345/clifford"
)

func main() {
	// Demonstrate disabling -h/-v short forms on the top-level command
	target := struct {
		clifford.Clifford `name:"no-shorts" version:"0.1.0" help_short:"false" version_short:"false"`
		clifford.Help
		clifford.Version
	}{}

	if _, err := clifford.BuildHelp(&target, false); err != nil {
		panic(err)
	}
	fmt.Println("version")
}
Output:

version
Example (Error_types)

Example_error_types demonstrates checking for specific error kinds with errors.Is and accessing details with errors.As.

package main

import (
	"fmt"
	"os"

	stderrors "errors"

	"github.com/chriso345/clifford"
	"github.com/chriso345/clifford/errors"
)

func main() {
	os.Args = []string{"app"}
	target := struct {
		clifford.Clifford `name:"app"`

		File struct {
			Value             string
			clifford.Required `required:"true"`
		}
	}{}

	err := clifford.Parse(&target)
	if err == nil {
		fmt.Println("no error")
		return
	}

	if stderrors.Is(err, errors.ErrMissingArg) {
		fmt.Println("missing argument detected")
	}

	var ma errors.MissingArgError
	if stderrors.As(err, &ma) {
		fmt.Println("missing field:", ma.Field)
	}

}
Output:

missing field: File
Example (Flag)
package main

import (
	"fmt"
	"os"

	"github.com/chriso345/clifford"
)

func main() {
	target := struct {
		clifford.Clifford `name:"sampleapp"`

		Flag struct {
			Value             bool
			clifford.Clifford `short:"f" long:"flag" desc:"A sample boolean flag"`
		}
	}{}

	// Simulate command line arguments
	os.Args = []string{"sampleapp", "-f"}

	err := clifford.Parse(&target)
	if err != nil {
		panic(err)
	}
	fmt.Println("Flag value:", target.Flag.Value)
}
Output:

Flag value: true
Example (Help_as_subcommand)

Example_help_as_subcommand demonstrates generating help for a subcommand while keeping parent name in usage.

package main

import (
	"fmt"
	"regexp"

	"github.com/chriso345/clifford"
)

func stripANSI(input string) string {
	re := regexp.MustCompile(`\x1b\[[0-9;]*[a-zA-Z]`)
	return re.ReplaceAllString(input, "")
}

func main() {
	parent := struct {
		clifford.Clifford `name:"app" desc:"Parent app"`
		clifford.Help

		Serve struct {
			clifford.Subcommand `name:"serve" desc:"Start server"`
			Port                struct {
				Value             int
				clifford.Clifford `long:"port" desc:"Port number"`
			}
		}
	}{}

	sub := parent.Serve
	help, err := clifford.BuildHelpWithParent(&parent, "serve", &sub, false)
	if err != nil {
		panic(err)
	}
	fmt.Println(stripANSI(help))
}
Output:

Usage: app serve [OPTIONS]

Options:
  --port [PORT]  Port number
Example (Help_output)

Example_help_output demonstrates building help for a parent with multiple subcommands.

package main

import (
	"fmt"
	"regexp"

	"github.com/chriso345/clifford"
)

func main() {
	// Parent help shows subcommands and their descriptions
	target := struct {
		clifford.Clifford `name:"app" desc:"App for help output"`
		clifford.Help

		Serve struct {
			clifford.Subcommand `name:"serve" desc:"Start the server"`
		}
		Status struct {
			clifford.Subcommand `name:"status" desc:"Show server status"`
		}
	}{}

	help, err := clifford.BuildHelp(&target, false)
	if err != nil {
		panic(err)
	}
	fmt.Println(stripANSI(help))
}

func stripANSI(input string) string {
	re := regexp.MustCompile(`\x1b\[[0-9;]*[a-zA-Z]`)
	return re.ReplaceAllString(input, "")
}
Example (Inline)
package main

import (
	"fmt"
	"os"

	"github.com/chriso345/clifford"
)

func main() {
	cli := struct {
		clifford.Clifford `name:"myapp"`

		Value string `long:"value" desc:"A simple value flag"`
		Color string `short:"c" long:"color" desc:"Color option"`
	}{}

	// Simulate command line arguments
	os.Args = []string{"myapp", "--value", "42", "-c", "red"}

	err := clifford.Parse(&cli)
	if err != nil {
		panic(err)
	}

	fmt.Println("Value:", cli.Value)
	fmt.Println("Color:", cli.Color)
}
Output:

Value: 42
Color: red
Example (List_subcommands)
package main

import (
	"fmt"

	"github.com/chriso345/clifford"
)

func main() {
	// Build help that lists multiple subcommands and print a marker line for the example.
	target := struct {
		clifford.Clifford `name:"app" desc:"Application with multiple subcommands"`
		clifford.Help

		Serve struct {
			clifford.Subcommand `name:"serve" desc:"Start the server"`
		}
		Status struct {
			clifford.Subcommand `name:"status" desc:"Show server status"`
		}
	}{}

	if _, err := clifford.BuildHelp(&target, false); err != nil {
		panic(err)
	}
	fmt.Println("Subcommands:")
}
Output:

Subcommands:
Example (Nested_subcommands)
package main

import (
	"fmt"
	"os"

	"github.com/chriso345/clifford"
)

func main() {
	// Demonstrate nested subcommands: app remote add --name foo
	os.Args = []string{"app", "remote", "add", "--name", "origin"}
	target := struct {
		clifford.Clifford `name:"app"`
		clifford.Help

		Remote struct {
			clifford.Subcommand `name:"remote" desc:"Remote operations"`
			Add                 struct {
				clifford.Subcommand `name:"add" desc:"Add a remote"`
				Name                struct {
					Value             string
					clifford.Clifford `long:"name" desc:"Name of the remote"`
				}
			}
		}
	}{}

	err := clifford.Parse(&target)
	if err != nil {
		panic(err)
	}
	fmt.Println("Remote add name:", target.Remote.Add.Name.Value)
}
Output:

Remote add name: origin
Example (Readme)
package main

import (
	"fmt"
	"os"

	"github.com/chriso345/clifford"
)

func main() {
	// Simulate command line arguments
	os.Args = []string{"mytool", "--name", "Alice", "-a", "30"}

	target := struct {
		clifford.Clifford `name:"mytool"`   // Set the name of the CLI tool
		clifford.Version  `version:"1.2.3"` // Enable automatic version flag
		clifford.Help                       // Enable automatic help flags

		Name struct {
			Value             string
			clifford.Clifford `short:"n" long:"name" desc:"User name"`
		}
		Age struct {
			Value             string
			clifford.ShortTag // auto generates -a
			clifford.LongTag  // auto generates --age
			clifford.Desc     `desc:"Age of the user"`
		}
	}{}

	err := clifford.Parse(&target)
	if err != nil {
		panic(err)
	}

	fmt.Printf("Name: %s\n", target.Name.Value)
	fmt.Printf("Age: %s\n", target.Age.Value)
}
Output:

Name: Alice
Age: 30
Example (Simple_cli)
package main

import (
	"fmt"
	"os"

	"github.com/chriso345/clifford"
)

func main() {
	// Simulate command line arguments
	os.Args = []string{"example_cli", "bob"}

	target := struct {
		clifford.Clifford `name:"example_cli"` // This is the name of the cli command
		clifford.Version  `version:"1.0.0"`
		clifford.Help

		Name struct {
			Value string
		}
	}{}

	err := clifford.Parse(&target)
	if err != nil {
		panic(err)
	}

	if target.Name.Value != "" {
		fmt.Println("Hello, " + target.Name.Value + "!")
	} else {
		fmt.Println("Hello, World!")
	}
}
Output:

Hello, bob!
Example (Subcommand)
package main

import (
	"fmt"
	"os"

	"github.com/chriso345/clifford"
)

func main() {
	// Demonstrate a single-level subcommand parse
	os.Args = []string{"app", "serve", "--port", "8080"}

	target := struct {
		clifford.Clifford `name:"app"`
		clifford.Help

		Serve struct {
			clifford.Subcommand `name:"serve" desc:"Start the server"`
			Port                struct {
				Value             int
				clifford.Clifford `long:"port" desc:"Port to run the server on"`
			}
		}

		Status struct {
			clifford.Subcommand `name:"status" desc:"Show server status"`
		}
	}{}

	err := clifford.Parse(&target)
	if err != nil {
		panic(err)
	}

	fmt.Println("Serve port:", target.Serve.Port.Value)
}
Output:

Serve port: 8080
Example (Subcommands_inline)
package main

import (
	"fmt"
	"os"

	"github.com/chriso345/clifford"
)

func main() {
	args := struct {
		clifford.Clifford `name:"tool"`
		clifford.Help

		Mode struct {
			clifford.Subcommand `short:"m" long:"mode" desc:"Mode to run in"`

			MaxItems int    `short:"n" long:"max-items" desc:"Max items to show"`
			LogLevel string `long:"log-level" desc:"Set log level"`
		}

		Other struct {
			Value             string
			clifford.Clifford `long:"other" desc:"Other mode"`
		}
	}{}

	// Simulate command line arguments
	os.Args = []string{"tool", "mode", "--max-items", "20", "--log-level", "debug"}

	err := clifford.Parse(&args)
	if err != nil {
		panic(err)
	}

	if args.Mode.Subcommand {
		fmt.Println("Mode: mode")
	}
	fmt.Println("MaxItems:", args.Mode.MaxItems)
	fmt.Println("LogLevel:", args.Mode.LogLevel)
	fmt.Println("Other:", args.Other.Value)
}
Output:

Mode: mode
MaxItems: 20
LogLevel: debug
Other:
Example (Unknown_subcommand)

Example_unknown_subcommand shows the parser returning a helpful suggestion for mistyped subcommands.

package main

import (
	"fmt"
	"os"

	"github.com/chriso345/clifford"
)

func main() {
	os.Args = []string{"app", "srve"}
	target := struct {
		clifford.Clifford `name:"app"`
		clifford.Help

		Serve struct {
			clifford.Subcommand `name:"serve" desc:"Start server"`
		}
	}{}

	err := clifford.Parse(&target)
	if err != nil {
		fmt.Println(err.Error())
	}
}
Output:

unknown subcommand: srve (did you mean "serve"?)

Index

Examples

Constants

This section is empty.

Variables

View Source
var BuildHelp = display.BuildHelp

BuildHelp generates and returns a formatted help message for a CLI tool defined by the given struct pointer. BuildHelp also takes in a boolean `long` parameter that, if set to true, will print a more detailed help message with the long_about description

The `target` must be a pointer to a struct that embeds a `Clifford` field with a `name` tag. This tag specifies the CLI tool's name and is displayed in the usage header.

The function inspects the struct to determine CLI arguments and options, including those marked as required. It outputs a help string that includes:

  • The usage line with the command name and expected arguments
  • A section for required arguments (based on `Required` tags)
  • A section for optional flags (based on `short` or `long` tags)

If no `name` tag is found on any embedded `Clifford` field, the function returns an error.

Example:

target := struct {
	clifford.Clifford `name:"mytool"`

	Filename struct {
		Value    string
		clifford.Required
		clifford.Desc `desc:"Input file path"`
	}

	Verbose struct {
		Value    bool
		clifford.Clifford `short:"v" long:"verbose" desc:"Enable verbose output"`
	}
}{}

helpText, err := clifford.BuildHelp(&target, false)
if err != nil {
	log.Fatal(err)
}
fmt.Println(helpText)
View Source
var BuildVersion = display.BuildVersion

BuildVersion returns a formatted version string for the CLI tool defined by the provided struct pointer.

The `target` must be a pointer to a struct that embeds a `Clifford` field with a `version` tag. This tag specifies the version number of the CLI tool. If no version tag is found, the function returns an error.

This function is automatically invoked by `Parse` if the CLI arguments include `--version`.

Example:

target := struct {
	clifford.Clifford `name:"mytool" version:"1.2.3"`
}{}

version, err := clifford.BuildVersion(&target)
if err != nil {
	log.Fatal(err)
}
fmt.Println(version) // Output: mytool v1.2.3
View Source
var Parse = core.Parse

Parse parses command-line arguments into the provided target struct.

The target must be a pointer to a struct where each field represents either a CLI argument or a group of options. Each sub-struct should contain a `Value` field to hold the parsed value, and may be annotated using `clifford` tags or helper types like `ShortTag`, `LongTag`, `Required`, and `Desc`.

If the first argument passed to the CLI is `-h` or `--help`, Parse will automatically call BuildHelp and exit the program.

Usage:

target := struct {
	clifford.Clifford `name:"mytool"`

	Name struct {
		Value    string
		clifford.Clifford `short:"n" long:"name" desc:"User name"`
	}

	Age struct {
		Value    string
		clifford.ShortTag // Auto-generates: -a
		clifford.LongTag  // Auto-generates: --age
		clifford.Desc     `desc:"Age of the user"`
	}
}{}

err := clifford.Parse(&target)
if err != nil {
	log.Fatal(err)
}

Functions

func BuildHelpWithParent

func BuildHelpWithParent(parent any, subName string, subTarget any, long bool) (string, error)

BuildHelpWithParent exposes the subcommand-aware help builder for callers/tests.

func ModuleVersion

func ModuleVersion() string

ModuleVersion returns the version of the clifford module, if available.

Types

type Clifford

type Clifford = core.Clifford

Clifford is the primary metadata marker for CLI definitions.

It can be embedded in the root struct to define metadata for the CLI tool itself, such as its name, version, or other global settings via struct tags.

It can also be embedded into sub-structs to provide additional annotations such as `short`, `long`, `desc`, and `required`, either directly or via helper types.

Usage:

Root-level CLI tool definition:

cli := struct {
    Clifford `name:"mytool" version:"1.0.0"`
    ...
}{}

Sub-struct flag definition using Clifford for metadata:

cli := struct {
    Clifford `name:"mytool"`

    Name struct {
        Value    string
        Clifford `short:"n" long:"name" desc:"User name"`
    }
}{}

type Desc

type Desc = core.Desc

Desc is a helper type that allows you to annotate a CLI option or argument with a description.

This description will be included in the generated help output.

Usage:

cli := struct {
    Name struct {
        Value string
        Desc  `desc:"Name of the user"`
    }
}{}

type Help

type Help = core.Help

Help is a marker type that enables the automatic `--help` and `-h` flag handling.

When embedded in the root struct, this allows the user to request usage help. If `-h` or `--help` is passed as the first CLI argument, Clifford will invoke BuildHelp automatically and exit gracefully.

Usage:

cli := struct {
    Clifford `name:"mytool"`
    Help
    ...
}{}

type LongTag

type LongTag = core.LongTag

LongTag is a helper type used to automatically generate a long flag (e.g. `--name`) for a CLI option based on the parent struct field name.

Usage:

cli := struct {
    Name struct {
        Value   string
        LongTag // Will auto-generate: --name
    }
}{}

type Required

type Required = core.Required

Required is a marker type that indicates the associated argument or flag is required.

If a required argument or flag is not provided in the CLI input, the parser will return an error.

Usage:

cli := struct {
    File struct {
        Value    string
        Required // Must be supplied on the command line
    }
}{}

type ShortTag

type ShortTag = core.ShortTag

ShortTag is a helper type used to automatically generate a short flag (e.g. `-n`) for a CLI option based on the parent struct field name.

You can embed it in a sub-struct to indicate that a short flag should be derived from the first letter of the field name, unless explicitly overridden.

Usage:

cli := struct {
    Name struct {
        Value    string
        ShortTag // Will auto-generate: -n
    }
}{}

type Subcommand

type Subcommand = core.Subcommand

Subcommand is a helper exported from core to mark fields as subcommands. Usage:

cli := struct {
    CommandA struct {
        Value      string
        Subcommand // Marks this field as a subcommand
    }
}{}

type Version

type Version = core.Version

Version is a marker type that indicates the CLI tool supports a `--version` flag.

When included in the root struct, `Version` enables version display logic. If the `version` struct tag is set, Clifford will use it directly. If left empty, Clifford may attempt to auto-detect or fallback to a default (implementation dependent).

Usage:

// Automatic detection or programmatic assignment

cli := struct {
    Clifford `name:"mytool"`
    Version
}{}

// Static version string via struct tag

cli := struct {
    Clifford `name:"mytool"`
    Version `version:"1.0.0"`
}{}

Directories

Path Synopsis
Package core contains the core logic for parsing command-line arguments into user-defined structs using reflection.
Package core contains the core logic for parsing command-line arguments into user-defined structs using reflection.
Package display provides utilities for generating user-facing CLI output, including help messages, version strings, and ANSI formatting.
Package display provides utilities for generating user-facing CLI output, including help messages, version strings, and ANSI formatting.
Package errors defines error types and utilities related to CLI parsing and validation within the clifford library.
Package errors defines error types and utilities related to CLI parsing and validation within the clifford library.
internal
common
Package common provides internal utility functions and helpers used across the clifford library.
Package common provides internal utility functions and helpers used across the clifford library.

Jump to

Keyboard shortcuts

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