redant

package module
v0.0.6 Latest Latest
Warning

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

Go to latest
Published: Mar 14, 2026 License: MIT Imports: 32 Imported by: 25

README

Redant 命令行框架

Redant 是一个用于构建大型 Go 命令行程序的框架,提供命令树、选项系统、中间件链、帮助系统与多格式参数解析能力。

文档导航

flowchart TD
    A[README 总览] --> B[docs/INDEX.md 文档索引]
    B --> C[docs/DESIGN.md 架构与执行设计]
    B --> D[docs/EVALUATION.md 质量评估与改进]
    B --> E[docs/CHANGELOG.md 版本变更]
    B --> F[example/args-test/README.md 参数解析示例]

术语使用请参考:docs/INDEX.md 的“术语约定”章节。

核心能力

  • 命令树与子命令继承(支持嵌套)
  • 选项多来源配置(命令行、环境变量、默认值)
  • 中间件链式编排
  • 自动帮助信息与全局标志
  • 多格式参数解析(位置参数、查询串、表单、JSON)
  • Busybox 风格 argv0 调度(软链接命令入口)

架构总览

flowchart LR
    U[用户输入] --> P[命令解析]
    P --> F[标志解析]
    F --> A[参数解析]
    A --> M[中间件链]
    M --> H[命令处理器]
    H --> O[输出与退出码]

快速开始

package main

import (
    "context"
    "fmt"
    "os"

    "github.com/pubgo/redant"
)

func main() {
    cmd := redant.Command{
        Use:   "echo <text>",
        Short: "输出传入文本",
        Handler: func(ctx context.Context, inv *redant.Invocation) error {
            if len(inv.Args) == 0 {
                return fmt.Errorf("缺少文本参数")
            }
            fmt.Fprintln(inv.Stdout, inv.Args[0])
            return nil
        },
    }

    if err := cmd.Invoke().WithOS().Run(); err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }
}

Busybox 风格命令入口

通过软链接将一个二进制映射为多个独立命令名,框架会根据 argv0 自动分发到对应子命令。

完整调用流程(构建到分发)
flowchart TD
    A[编译生成二进制: app] --> B[创建软连接: ln -sf app echo]
    B --> C[调用软连接: ./echo hello]
    C --> D[程序读取 argv0]
    D --> E{argv0 名称是否与命令/别名一致}
    E -- 是 --> F[选择对应子命令并执行]
    E -- 否 --> G[回退到显式参数解析或根命令]

流程说明:

  1. 先构建主二进制(例如 app)。
  2. 通过 ln -sf 创建软连接(例如 echo -> app)。
  3. 用户调用软连接名(例如 echo hello)。
  4. 框架读取 argv0(此时通常为 echo)。
  5. argv0 与命令名或别名匹配,则直接调用该子命令;否则按常规参数路径继续解析。
flowchart TD
    A[启动可执行文件] --> B{是否显式提供子命令}
    B -- 是 --> C[按参数分发]
    B -- 否 --> D[按 argv0 分发]
    C --> E[执行目标命令]
    D --> E

示例:

  • 显式调用:app echo hello
  • 软链接调用:echo hello

参数解析流程

框架在命令分发完成后,会进入统一参数解析阶段,支持位置参数、查询串、表单与 JSON。

flowchart TD
    A[接收命令行输入] --> B[完成命令分发]
    B --> C[解析全局/局部标志]
    C --> D[提取剩余参数]
    D --> E{参数形态判断}
    E -- 普通 token --> F[位置参数]
    E -- 包含 '=' 且含 '&' --> G[查询串参数]
    E -- 包含 '=' 且含空格 --> H[表单参数]
    E -- 以 '{' 或 '[' 开头 --> I[JSON 参数]
    F --> J[写入 ArgSet / inv.Args]
    G --> J
    H --> J
    I --> J
    J --> K[必填与类型校验]
    K --> L[进入中间件与 Handler]

参数解析落地示例见:example/args-test/README.md

参数解析优先级
flowchart TD
    A[输入命令行] --> B{是否命中显式子命令}
    B -- 是 --> C[按显式子命令执行]
    B -- 否 --> D{argv0 是否命中命令/别名}
    D -- 是 --> E[按 argv0 分发子命令]
    D -- 否 --> F[保留根命令路径]
    C --> G[解析标志]
    E --> G
    F --> G
    G --> H[解析剩余参数]
    H --> I[进入中间件与 Handler]

优先级顺序:

  1. 显式子命令(最高)
  2. argv0 命令/别名分发
  3. 根命令默认路径
  4. 标志解析与参数格式解析

全局标志

  • --help, -h:显示帮助
  • --list-commands:列出命令树
  • --list-flags:列出所有标志

示例目录

  • example/demo:综合示例
  • example/echo:最小命令示例
  • example/env-test:环境变量示例
  • example/globalflags:全局标志示例
  • example/args-test:参数格式解析示例

AI 协作:文档与 Changelog 维护

本仓库已提供面向 Copilot Chat 的文档与变更日志维护配置,便于在多人协作时保持文风一致、结构稳定、条目可追溯。

相关文件
  • 工作区总指引:.github/copilot-instructions.md
  • 文档专项规则:.github/instructions/documentation.instructions.md
  • Changelog 专项规则:.github/instructions/changelog.instructions.md
  • Changelog 维护提示词:.github/prompts/changelog-maintenance.prompt.md
  • Changelog 模板参考:docs/CHANGELOG_LLM_PROMPT.md
怎么使用
  1. 常规文档维护(README.mddocs/**example/**/README.md 等):
    • 直接在聊天中描述文档修改需求,文档专项规则会自动参与。
  2. 维护 docs/CHANGELOG.md(推荐):
    • 在聊天输入:/changelog-maintenance draft
    • 用于根据当前改动更新 Unreleased
  3. 发布前落版 CHANGELOG
    • 在聊天输入:/changelog-maintenance release
    • 由 agent 按 .version/VERSION 自动执行版本落版。
维护建议
  • 当前 changelog 流程采用 LLM/agent 维护,不再依赖本地脚本与 task 子命令。
  • 建议在合并前执行一次 /changelog-maintenance draft,发布前执行 /changelog-maintenance release

许可证

本项目采用 MIT 许可证,详见 LICENSE

Documentation

Index

Constants

This section is empty.

Variables

View Source
var DiscardValue discardValue

DiscardValue does nothing but implements the pflag.Value interface. It's useful in cases where you want to accept an option, but access the underlying value directly instead of through the Option methods.

Functions

func ParseFormArgs

func ParseFormArgs(form string) (map[string][]string, error)

ParseFormArgs parses form formatted arguments into a map Format: key1=value1 key2=value2 key3="value with spaces" Values containing spaces should be quoted with single or double quotes

func ParseJSONArgs

func ParseJSONArgs(jsonStr string) (map[string][]string, error)

ParseJSONArgs parses JSON formatted arguments into a map JSON can be either an object like {"name":"value","age":18} or an array like ["value1","value2"]

func ParseQueryArgs

func ParseQueryArgs(query string) (map[string][]string, error)

ParseQueryArgs parses query string formatted arguments into a map

func PrintCommands

func PrintCommands(cmd *Command)

PrintCommands prints all commands in a formatted list with full paths, using help formatting style

func PrintFlags

func PrintFlags(rootCmd *Command)

PrintFlags prints all flags for all commands, using help formatting style

func Version

func Version() string

Types

type Arg

type Arg struct {
	Name        string `json:"name,omitempty"`
	Description string `json:"description,omitempty"`
	// Required means this value must be set by some means.
	// If `Default` is set, then `Required` is ignored.
	Required bool `json:"required,omitempty"`

	// Default is the default value for this argument.
	Default string `json:"default,omitempty"`

	// Value includes the types listed in values.go.
	// Used for type determination and automatic parsing.
	Value pflag.Value `json:"value,omitempty"`
}

type ArgSet

type ArgSet []Arg

type Bool

type Bool bool

func BoolOf

func BoolOf(b *bool) *Bool

func (*Bool) NoOptDefValue

func (*Bool) NoOptDefValue() string

func (*Bool) Set

func (b *Bool) Set(s string) error

func (Bool) String

func (b Bool) String() string

func (Bool) Type

func (Bool) Type() string

func (Bool) Value

func (b Bool) Value() bool

type Command

type Command struct {

	// Children is a list of direct descendants.
	Children []*Command

	// Use is provided in form "command [flags] [args...]".
	Use string

	// Aliases is a list of alternative names for the command.
	Aliases []string

	// Short is a one-line description of the command.
	Short string

	// Hidden determines whether the command should be hidden from help.
	Hidden bool

	// Deprecated indicates whether this command is deprecated.
	// If empty, the command is not deprecated.
	// If set, the value is used as the deprecation message.
	Deprecated string `json:"deprecated,omitempty"`

	// RawArgs determines whether the command should receive unparsed arguments.
	// No flags are parsed when set, and the command is responsible for parsing
	// its own flags.
	RawArgs bool

	// Long is a detailed description of the command,
	// presented on its help page. It may contain examples.
	Long    string
	Options OptionSet
	Args    ArgSet

	// Middleware is called before the Handler.
	// Use Chain() to combine multiple middlewares.
	Middleware MiddlewareFunc
	Handler    HandlerFunc
	// contains filtered or unexported fields
}

Command describes an executable command.

func (*Command) FullName

func (c *Command) FullName() string

FullName returns the full invocation name of the command, as seen on the command line.

func (*Command) FullOptions

func (c *Command) FullOptions() OptionSet

FullOptions returns the options of the command and its parents.

func (*Command) FullUsage

func (c *Command) FullUsage() string

func (*Command) GetGlobalFlags

func (c *Command) GetGlobalFlags() OptionSet

GetGlobalFlags returns the global flags from the root command All non-hidden options in the root command are considered global flags

func (*Command) Invoke

func (c *Command) Invoke(args ...string) *Invocation

Invoke creates a new invocation of the command, with stdio discarded.

The returned invocation is not live until Run() is called.

func (*Command) Name

func (c *Command) Name() string

Name returns the first word in the Use string.

func (*Command) Parent added in v0.0.3

func (c *Command) Parent() *Command

Parent returns the parent command of this command.

func (*Command) Run added in v0.0.2

func (c *Command) Run(ctx context.Context) error

type Duration

type Duration time.Duration

func DurationOf

func DurationOf(d *time.Duration) *Duration

func (*Duration) MarshalYAML

func (d *Duration) MarshalYAML() (any, error)

func (*Duration) Set

func (d *Duration) Set(v string) error

func (*Duration) String

func (d *Duration) String() string

func (Duration) Type

func (Duration) Type() string

func (*Duration) UnmarshalYAML

func (d *Duration) UnmarshalYAML(n *yaml.Node) error

func (*Duration) Value

func (d *Duration) Value() time.Duration

type Enum

type Enum struct {
	Choices []string
	Value   *string
}

func EnumOf

func EnumOf(v *string, choices ...string) *Enum

func (*Enum) MarshalYAML

func (e *Enum) MarshalYAML() (any, error)

func (*Enum) Set

func (e *Enum) Set(v string) error

func (*Enum) String

func (e *Enum) String() string

func (*Enum) Type

func (e *Enum) Type() string

func (*Enum) UnmarshalYAML

func (e *Enum) UnmarshalYAML(n *yaml.Node) error

type EnumArray

type EnumArray struct {
	Choices []string
	Value   *[]string
}

func EnumArrayOf

func EnumArrayOf(v *[]string, choices ...string) *EnumArray

func (*EnumArray) Append

func (e *EnumArray) Append(s string) error

func (*EnumArray) GetSlice

func (e *EnumArray) GetSlice() []string

func (*EnumArray) Replace

func (e *EnumArray) Replace(ss []string) error

func (*EnumArray) Set

func (e *EnumArray) Set(v string) error

func (*EnumArray) String

func (e *EnumArray) String() string

func (*EnumArray) Type

func (e *EnumArray) Type() string

type Float64

type Float64 float64

func Float64Of

func Float64Of(f *float64) *Float64

func (*Float64) Set

func (f *Float64) Set(s string) error

func (Float64) String

func (f Float64) String() string

func (Float64) Type

func (Float64) Type() string

func (Float64) Value

func (f Float64) Value() float64

type HandlerFunc

type HandlerFunc func(ctx context.Context, inv *Invocation) error

HandlerFunc handles an Invocation of a command.

func DefaultHelpFn

func DefaultHelpFn() HandlerFunc

DefaultHelpFn returns a function that generates usage (help) output for a given command.

type HostPort

type HostPort struct {
	Host string
	Port string
}

HostPort is a host:port pair.

func (*HostPort) MarshalJSON

func (hp *HostPort) MarshalJSON() ([]byte, error)

func (*HostPort) MarshalYAML

func (hp *HostPort) MarshalYAML() (any, error)

func (*HostPort) Set

func (hp *HostPort) Set(v string) error

func (*HostPort) String

func (hp *HostPort) String() string

func (*HostPort) Type

func (*HostPort) Type() string

func (*HostPort) UnmarshalJSON

func (hp *HostPort) UnmarshalJSON(b []byte) error

func (*HostPort) UnmarshalYAML

func (hp *HostPort) UnmarshalYAML(n *yaml.Node) error

type Int64

type Int64 int64

func Int64Of

func Int64Of(i *int64) *Int64

func (*Int64) Set

func (i *Int64) Set(s string) error

func (Int64) String

func (i Int64) String() string

func (Int64) Type

func (Int64) Type() string

func (Int64) Value

func (i Int64) Value() int64

type Invocation

type Invocation struct {
	Command *Command
	Flags   *pflag.FlagSet

	// Args is reduced into the remaining arguments after parsing flags
	// during Run.
	Args []string

	// Arg0 is the executable name used to invoke the program (typically os.Args[0]).
	// It enables busybox-style dispatch where the binary name maps directly to
	// a subcommand.
	Arg0 string

	Stdout io.Writer
	Stderr io.Writer
	Stdin  io.Reader

	// Annotations is a map of arbitrary annotations to attach to the invocation.
	Annotations map[string]any
	// contains filtered or unexported fields
}

Invocation represents an instance of a command being executed.

func (*Invocation) Context

func (inv *Invocation) Context() context.Context

func (*Invocation) CurWords

func (inv *Invocation) CurWords() (prev, cur string)

func (*Invocation) ParsedFlags

func (inv *Invocation) ParsedFlags() *pflag.FlagSet

func (*Invocation) Run

func (inv *Invocation) Run() (err error)

Run executes the command. If two command share a flag name, the first command wins.

func (*Invocation) SignalNotifyContext

func (inv *Invocation) SignalNotifyContext(parent context.Context, signals ...os.Signal) (ctx context.Context, stop context.CancelFunc)

SignalNotifyContext is equivalent to signal.NotifyContext, but supports being overridden in tests.

func (*Invocation) WithArgv0 added in v0.0.6

func (inv *Invocation) WithArgv0(arg0 string) *Invocation

WithArgv0 overrides the executable name used for busybox-style dispatch. This is primarily useful for testing or when simulating invocation via symlinked binaries.

func (*Invocation) WithContext

func (inv *Invocation) WithContext(ctx context.Context) *Invocation

WithContext returns a copy of the Invocation with the given context.

func (*Invocation) WithOS

func (inv *Invocation) WithOS() *Invocation

WithOS returns the invocation as a main package, filling in the invocation's unset fields with OS defaults.

func (*Invocation) WithTestParsedFlags

func (inv *Invocation) WithTestParsedFlags(
	_ testing.TB,
	parsedFlags *pflag.FlagSet,
) *Invocation

func (*Invocation) WithTestSignalNotifyContext

func (inv *Invocation) WithTestSignalNotifyContext(
	_ testing.TB,
	f func(parent context.Context, signals ...os.Signal) (ctx context.Context, stop context.CancelFunc),
) *Invocation

WithTestSignalNotifyContext allows overriding the default implementation of SignalNotifyContext. This should only be used in testing.

type MiddlewareFunc

type MiddlewareFunc func(next HandlerFunc) HandlerFunc

MiddlewareFunc returns the next handler in the chain, or nil if there are no more.

func Chain

func Chain(ms ...MiddlewareFunc) MiddlewareFunc

Chain returns a Handler that first calls middleware in order.

func RequireNArgs

func RequireNArgs(want int) MiddlewareFunc

func RequireRangeArgs

func RequireRangeArgs(start, end int) MiddlewareFunc

RequireRangeArgs returns a Middleware that requires the number of arguments to be between start and end (inclusive). If end is -1, then the number of arguments must be at least start.

type NoOptDefValuer

type NoOptDefValuer interface {
	NoOptDefValue() string
}

NoOptDefValuer describes behavior when no option is passed into the flag.

This is useful for boolean or otherwise binary flags.

type Option

type Option struct {
	// Flag is the long name of the flag used to configure this option. If unset,
	// flag configuring is disabled. This also serves as the option's identifier.
	Flag string `json:"flag,omitempty"`

	Description string `json:"description,omitempty"`

	// Required means this value must be set by some means. It requires
	// `ValueSourceType != ValueSourceNone`
	// If `Default` is set, then `Required` is ignored.
	Required bool `json:"required,omitempty"`

	// Shorthand is the one-character shorthand for the flag. If unset, no
	// shorthand is used.
	Shorthand string `json:"shorthand,omitempty"`

	// Envs is a list of environment variables used to configure this option.
	// The first non-empty environment variable value will be used.
	// If unset, environment configuring is disabled.
	Envs []string `json:"env,omitempty"`

	// Default is parsed into Value if set.
	Default string `json:"default,omitempty"`

	// Value includes the types listed in values.go.
	Value pflag.Value `json:"value,omitempty"`

	Hidden bool `json:"hidden,omitempty"`

	Deprecated string

	Category string

	// Action is called after the flag is parsed and set.
	// It receives the flag value and can perform additional validation or side effects.
	// If Action returns an error, command execution will fail.
	Action func(val pflag.Value) error `json:"-"`
}

Option is a configuration option for a CLI application.

func (Option) Type added in v0.0.3

func (o Option) Type() string

Type returns the type of the option value

type OptionSet

type OptionSet []Option

OptionSet is a group of options that can be applied to a command.

func GlobalFlags

func GlobalFlags() OptionSet

GlobalFlags returns the default global flags that should be added to every command

func (*OptionSet) Add

func (optSet *OptionSet) Add(opts ...Option)

Add adds the given Options to the OptionSet.

func (*OptionSet) Filter

func (optSet *OptionSet) Filter(filter func(opt Option) bool) OptionSet

Filter will only return options that match the given filter. (return true)

func (*OptionSet) FlagSet

func (optSet *OptionSet) FlagSet(name string) *pflag.FlagSet

type Regexp

type Regexp regexp.Regexp

func (*Regexp) MarshalJSON

func (r *Regexp) MarshalJSON() ([]byte, error)

func (*Regexp) MarshalYAML

func (r *Regexp) MarshalYAML() (any, error)

func (*Regexp) Set

func (r *Regexp) Set(v string) error

func (Regexp) String

func (r Regexp) String() string

func (Regexp) Type

func (Regexp) Type() string

func (*Regexp) UnmarshalJSON

func (r *Regexp) UnmarshalJSON(data []byte) error

func (*Regexp) UnmarshalYAML

func (r *Regexp) UnmarshalYAML(n *yaml.Node) error

func (*Regexp) Value

func (r *Regexp) Value() *regexp.Regexp

type RunCommandError

type RunCommandError struct {
	Cmd *Command
	Err error
}

func (*RunCommandError) Error

func (e *RunCommandError) Error() string

func (*RunCommandError) Unwrap

func (e *RunCommandError) Unwrap() error

type String

type String string

func StringOf

func StringOf(s *string) *String

func (*String) NoOptDefValue

func (*String) NoOptDefValue() string

func (*String) Set

func (s *String) Set(v string) error

func (String) String

func (s String) String() string

func (String) Type

func (String) Type() string

func (String) Value

func (s String) Value() string

type StringArray

type StringArray []string

StringArray is a slice of strings that implements pflag.Value and pflag.SliceValue.

func StringArrayOf

func StringArrayOf(ss *[]string) *StringArray

func (*StringArray) Append

func (s *StringArray) Append(v string) error

func (*StringArray) GetSlice

func (s *StringArray) GetSlice() []string

func (*StringArray) Replace

func (s *StringArray) Replace(vals []string) error

func (*StringArray) Set

func (s *StringArray) Set(v string) error

func (StringArray) String

func (s StringArray) String() string

func (StringArray) Type

func (StringArray) Type() string

func (StringArray) Value

func (s StringArray) Value() []string

type Struct

type Struct[T any] struct {
	Value T
}

Struct is a special value type that encodes an arbitrary struct. It implements the flag.Value interface, but in general these values should only be accepted via config for ergonomics.

The string encoding type is YAML.

func (*Struct[T]) MarshalJSON

func (s *Struct[T]) MarshalJSON() ([]byte, error)

nolint:revive

func (*Struct[T]) MarshalYAML

func (s *Struct[T]) MarshalYAML() (any, error)

nolint:revive

func (*Struct[T]) Set

func (s *Struct[T]) Set(v string) error

func (*Struct[T]) String

func (s *Struct[T]) String() string

func (*Struct[T]) Type

func (s *Struct[T]) Type() string

func (*Struct[T]) UnmarshalJSON

func (s *Struct[T]) UnmarshalJSON(b []byte) error

nolint:revive

func (*Struct[T]) UnmarshalYAML

func (s *Struct[T]) UnmarshalYAML(n *yaml.Node) error

nolint:revive

type URL

type URL url.URL

func URLOf

func URLOf(u *url.URL) *URL

func (*URL) MarshalJSON

func (u *URL) MarshalJSON() ([]byte, error)

func (*URL) MarshalYAML

func (u *URL) MarshalYAML() (any, error)

func (*URL) Set

func (u *URL) Set(v string) error

func (*URL) String

func (u *URL) String() string

func (*URL) Type

func (*URL) Type() string

func (*URL) UnmarshalJSON

func (u *URL) UnmarshalJSON(b []byte) error

func (*URL) UnmarshalYAML

func (u *URL) UnmarshalYAML(n *yaml.Node) error

func (*URL) Value

func (u *URL) Value() *url.URL

type UnknownSubcommandError

type UnknownSubcommandError struct {
	Args []string
}

func (*UnknownSubcommandError) Error

func (e *UnknownSubcommandError) Error() string

type Validator

type Validator[T pflag.Value] struct {
	Value T
	// contains filtered or unexported fields
}

Validator is a wrapper around a pflag.Value that allows for validation of the value after or before it has been set.

func Validate

func Validate[T pflag.Value](opt T, validate func(value T) error) *Validator[T]

func (*Validator[T]) MarshalJSON

func (i *Validator[T]) MarshalJSON() ([]byte, error)

func (*Validator[T]) MarshalYAML

func (i *Validator[T]) MarshalYAML() (any, error)

func (*Validator[T]) Set

func (i *Validator[T]) Set(input string) error

func (*Validator[T]) String

func (i *Validator[T]) String() string

func (*Validator[T]) Type

func (i *Validator[T]) Type() string

func (*Validator[T]) Underlying

func (i *Validator[T]) Underlying() pflag.Value

func (*Validator[T]) UnmarshalJSON

func (i *Validator[T]) UnmarshalJSON(b []byte) error

func (*Validator[T]) UnmarshalYAML

func (i *Validator[T]) UnmarshalYAML(n *yaml.Node) error

Directories

Path Synopsis
cmds
example
args-test command
demo command
echo command
env-test command
fastcommit command
globalflags command
queryargs command
internal

Jump to

Keyboard shortcuts

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