golang支持复杂命令行解析与多格式配置解析插件库kong的使用

Golang 支持复杂命令行解析与多格式配置解析插件库 Kong 的使用

Kong 是一个用于 Go 的命令行解析器,支持复杂的命令行结构和多种配置格式解析。

简介

Kong 旨在以最少的开发工作量支持任意复杂的命令行结构。它通过 Go 类型表示命令行,使用结构体和标签来指导如何将命令行映射到结构体。

Kong Logo

基本示例

以下是一个简单的命令行示例,展示了如何使用 Kong 解析命令行参数:

package main

import "github.com/alecthomas/kong"

var CLI struct {
  Rm struct {
    Force     bool `help:"Force removal."`
    Recursive bool `help:"Recursively remove files."`

    Paths []string `arg:"" name:"path" help:"Paths to remove." type:"path"`
  } `cmd:"" help:"Remove files."`

  Ls struct {
    Paths []string `arg:"" optional:"" name:"path" help:"Paths to list." type:"path"`
  } `cmd:"" help:"List paths."`
}

func main() {
  ctx := kong.Parse(&CLI)
  switch ctx.Command() {
  case "rm <path>":
  case "ls":
  default:
    panic(ctx.Command())
  }
}

帮助系统

Kong 自动生成帮助文本,每个 Kong 应用程序都包含一个 --help 标志,可以显示自动生成的帮助信息。

查看帮助

$ shell --help
usage: shell <command>

A shell-like example app.

Flags:
  --help   Show context-sensitive help.
  --debug  Debug mode.

Commands:
  rm <path> ...
    Remove files.

  ls [<path> ...]
    List paths.

查看特定命令的帮助

$ shell --help rm
usage: shell rm <paths> ...

Remove files.

Arguments:
  <paths> ...  Paths to remove.

Flags:
      --debug        Debug mode.

  -f, --force        Force removal.
  -r, --recursive    Recursively remove files.

命令处理

Kong 提供了两种处理命令的方式:

1. 通过命令字符串切换

func main() {
  ctx := kong.Parse(&CLI)
  switch ctx.Command() {
  case "rm <path>":
  case "ls":
  default:
    panic(ctx.Command())
  }
}

2. 为每个命令附加 Run() 方法

type Context struct {
  Debug bool
}

type RmCmd struct {
  Force     bool `help:"Force removal."`
  Recursive bool `help:"Recursively remove files."`

  Paths []string `arg:"" name:"path" help:"Paths to remove." type:"path"`
}

func (r *RmCmd) Run(ctx *Context) error {
  fmt.Println("rm", r.Paths)
  return nil
}

type LsCmd struct {
  Paths []string `arg:"" optional:"" name:"path" help:"Paths to list." type:"path"`
}

func (l *LsCmd) Run(ctx *Context) error {
  fmt.Println("ls", l.Paths)
  return nil
}

var cli struct {
  Debug bool `help:"Enable debug mode."`

  Rm RmCmd `cmd:"" help:"Remove files."`
  Ls LsCmd `cmd:"" help:"List paths."`
}

func main() {
  ctx := kong.Parse(&cli)
  err := ctx.Run(&Context{Debug: cli.Debug})
  ctx.FatalIfErrorf(err)
}

钩子函数

Kong 支持以下钩子函数:

钩子 描述
BeforeReset 在值重置为默认值或零值之前调用
BeforeResolve 在解析器应用于节点之前调用
BeforeApply 在跟踪的命令行参数应用于语法之前调用
AfterApply 在命令行参数应用于语法并验证后调用

钩子函数示例:

type debugFlag bool

func (d debugFlag) BeforeApply(logger *log.Logger) error {
  logger.SetOutput(os.Stdout)
  return nil
}

var cli struct {
  Debug debugFlag `help:"Enable debug logging."`
}

func main() {
  logger := log.New(io.Discard, "", log.LstdFlags)
  ctx := kong.Parse(&cli, kong.Bind(logger))
  // ...
}

绑定选项

可以使用 Bind() 选项将值注入到 Run() 方法中:

type CLI struct {
  Debug bool `help:"Enable debug mode."`

  Rm RmCmd `cmd:"" help:"Remove files."`
  Ls LsCmd `cmd:"" help:"List paths."`
}

type AuthorName string

func (l *LsCmd) Run(cli *CLI) error {
  // 在这里使用 cli.Debug
  return nil
}

func (r *RmCmD) Run(author AuthorName) error {
  // 在这里使用绑定的 author
  return nil
}

func main() {
  var cli CLI
  ctx := kong.Parse(&cli, Bind(AuthorName("penguin")))
  err := ctx.Run()
}

标志(Flags)

任何未标记为 cmdarg 的字段都将被视为标志。标志默认是可选的。

type CLI struct {
  Flag string
}

命令和子命令

子命令通过在结构体字段上标记 cmd 来指定。Kong 支持任意嵌套的命令。

type CLI struct {
  Command struct {
    Flag string

    SubCommand struct {
    } `cmd`
  } `cmd`
}

分支位置参数

结构体也可以配置为分支位置参数:

var CLI struct {
  Rename struct {
    Name struct {
      Name string `arg` // <-- 注意:与封闭结构体字段名相同
      To struct {
        Name struct {
          Name string `arg`
        } `arg`
      } `cmd`
    } `arg`
  } `cmd`
}

位置参数

如果字段标记为 arg:"",它将被视为要解析的命令行的最终位置值。默认情况下,位置参数是必需的,但指定 optional:"" 可以改变这一点。

切片(Slices)

切片值会被特殊处理。输入首先根据 sep:"<rune>" 标签(默认为 ,)分割,然后每个元素由切片元素类型解析并附加到切片中。

var CLI struct {
  Ls struct {
    Files []string `arg:"" type:"existingfile"`
  } `cmd`
}

映射(Maps)

映射类似于切片,但每个值只能分配一个键/值对,sep 标签表示赋值字符,默认为 =

var CLI struct {
  Config struct {
    Set struct {
      Config map[string]float64 `arg:"" type:"file:"`
    } `cmd`
  } `cmd`
}

指针(Pointers)

指针的工作方式类似于底层类型,但可以区分零值和未提供值之间的差异。

var CLI struct {
	Foo *int
}

嵌套数据结构

Kong 支持带有 embed:"" 的嵌套数据结构:

var CLI struct {
  Logging struct {
    Level string `enum:"debug,info,warn,error" default:"info"`
    Type string `enum:"json,console" default:"console"`
  } `embed:"" prefix:"logging."`
}

自定义命名解码器

Kong 包含许多内置的自定义类型映射器,可以通过指定标签 type:"<type>" 来使用。

名称 描述
path 路径。应用 ~ 扩展。接受 - 表示 stdout
existingfile 现有文件。应用 ~ 扩展。接受 - 表示 stdin
existingdir 现有目录。应用 ~ 扩展
counter 递增数字字段。适用于 -vvv
filecontent 将路径处的文件内容读入字段

支持的字段类型

Kong 支持许多常见的 Go 类型:

类型 描述
time.Duration 使用 time.ParseDuration() 填充
time.Time 使用 time.Parse() 填充
*os.File 将打开的文件路径,或 - 表示 os.Stdin
*url.URL 使用 url.Parse() 填充

自定义解码器(映射器)

任何实现 encoding.TextUnmarshalerjson.Unmarshaler 的字段都将使用这些接口来解码值。

支持的标签

标签可以有两种形式:

  1. 标准 Go 语法,例如 kong:"required,name='foo'"
  2. 裸标签,例如 required:"" name:"foo"
标签 描述
cmd:"" 如果存在,结构体是一个命令
arg:"" 如果存在,字段是一个参数。默认必需
env:"X,Y,..." 指定用于默认值的环境变量
name:"X" 长名称,用于覆盖字段名称
help:"X" 帮助文本
type:"X" 指定命名类型
placeholder:"X" 占位符输入
default:"X" 默认值
short:"X" 短名称,如果是标志
aliases:"X,Y" 一个或多个别名
required:"" 如果存在,标志/参数是必需的
optional:"" 如果存在,标志/参数是可选的
hidden:"" 如果存在,命令或标志是隐藏的
negatable:"" 对于 bool 字段,支持前缀 --no- 来反转默认值
format:"X" 解析输入的格式
sep:"X" 序列的分隔符
mapsep:"X" 映射的分隔符
enum:"X,Y,..." 允许的有效值集合
group:"X" 标志或命令的逻辑组
xor:"X,Y,..." 标志的互斥 OR 组
and:"X,Y,..." 标志的 AND 组
prefix:"X" 所有子标志的前缀
envprefix:"X" 所有子标志的环境变量前缀
xorprefix:"X" XOR/AND 组中所有子标志的前缀
set:"K=V" 为子元素设置变量
embed:"" 如果存在,此字段的子项将嵌入到父项中
passthrough:"<mode>" 停止标志解析
- 忽略字段

插件

Kong CLI 可以通过嵌入 kong.Plugin 类型并用指向 Kong 注释结构体的指针填充它来扩展:

var pluginOne struct {
  PluginOneFlag string
}
var pluginTwo struct {
  PluginTwoFlag string
}
var cli struct {
  BaseFlag string
  kong.Plugins
}
cli.Plugins = kong.Plugins{&pluginOne, &pluginTwo}

动态命令

Kong 还支持通过 kong.DynamicCommand() 动态添加命令。

变量插值

Kong 支持将有限的变量插值到帮助字符串、占位符字符串、枚举列表和默认值中。

变量形式:

${<name>}
${<name>=<default>}
type cli struct {
  Config string `type:"path" default:"${config_file}"`
}

func main() {
  kong.Parse(&cli,
    kong.Vars{
      "config_file": "~/.app.conf",
    })
}

验证

Kong 对命令行的结构进行验证,但也支持可扩展的验证。树中的任何节点都可以实现以下接口之一:

type Validatable interface {
    Validate() error
 }

type Validatable interface {
    Validate(kctx *kong.Context) error
 }

修改 Kong 的行为

每个 Kong 解析器都可以通过传递给 New(cli any, options...Option) 的功能选项进行配置。

配置选项

  1. Name(help)Description(help) - 设置应用程序名称描述
  2. Configuration(loader, paths...) - 从配置文件加载默认值
  3. Resolver(...) - 支持从外部源获取默认值
  4. *Mapper(...) - 自定义命令行如何映射到 Go 值
  5. ConfigureHelp(HelpOptions)Help(HelpFunc) - 自定义帮助
  6. 注入值到 Run() 方法中

完整示例

以下是一个完整的示例,展示了如何使用 Kong 创建一个支持多个命令和复杂参数的 CLI 应用程序:

package main

import (
	"fmt"
	"log"
	"os"

	"github.com/alecthomas/kong"
)

var CLI struct {
	Debug bool `help:"Enable debug mode."`

	Add struct {
		User  string `arg:"" help:"User to add."`
		Group string `arg:"" help:"Group to add user to."`
	} `cmd:"" help:"Add a user to a group."`

	Remove struct {
		User  string `arg:"" help:"User to remove."`
		Group string `arg:"" help:"Group to remove user from."`
	} `cmd:"" help:"Remove a user from a group."`

	List struct {
		Group string `arg:"" optional:"" help:"Group to list users from."`
	} `cmd:"" help:"List users in a group."`

	Config struct {
		Set struct {
			Key   string `arg:"" help:"Config key to set."`
			Value string `arg:"" help:"Config value to set."`
		} `cmd:"" help:"Set a config value."`

		Get struct {
			Key string `arg:"" help:"Config key to get."`
		} `cmd:"" help:"Get a config value."`
	} `cmd:"" help:"Manage configuration."`
}

func main() {
	ctx := kong.Parse(&CLI,
		kong.Name("myapp"),
		kong.Description("A CLI application for managing users and groups."),
		kong.UsageOnError(),
		kong.ConfigureHelp(kong.HelpOptions{
			Compact: true,
		}),
		kong.Vars{
			"version": "0.1.0",
		},
	)

	switch ctx.Command() {
	case "add <user> <group>":
		fmt.Printf("Adding user %s to group %s\n", CLI.Add.User, CLI.Add.Group)
	case "remove <user> <group>":
		fmt.Printf("Removing user %s from group %s\n", CLI.Remove.User, CLI.Remove.Group)
	case "list":
		if CLI.List.Group != "" {
			fmt.Printf("Listing users in group %s\n", CLI.List.Group)
		} else {
			fmt.Println("Listing all users")
		}
	case "config set <key> <value>":
		fmt.Printf("Setting config %s=%s\n", CLI.Config.Set.Key, CLI.Config.Set.Value)
	case "config get <key>":
		fmt.Printf("Getting config %s\n", CLI.Config.Get.Key)
	default:
		log.Printf("Unknown command: %s\n", ctx.Command())
		os.Exit(1)
	}
}

这个示例展示了如何使用 Kong 创建一个具有多个命令和子命令的 CLI 应用程序,包括参数解析、帮助生成和命令路由。


更多关于golang支持复杂命令行解析与多格式配置解析插件库kong的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于golang支持复杂命令行解析与多格式配置解析插件库kong的使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


Golang 命令行解析与 Kong 库使用指南

Kong 是一个功能强大的 Go 语言库,用于解析命令行参数、配置文件和环境变量。它结合了 CLI 解析和配置管理,支持复杂的命令行应用开发。

基本安装

首先安装 Kong 库:

go get github.com/alecthomas/kong

基础使用示例

package main

import (
	"fmt"
	"os"
	
	"github.com/alecthomas/kong"
)

var cli struct {
	Debug   bool   `help:"Enable debug mode."`
	Port    int    `help:"Port to listen on." default:"8000"`
	Config  string `help:"Config file to load." type:"existingfile"`
	
	Run struct {
		Daemon bool `help:"Run as daemon."`
	} `cmd:"" help:"Run the server."`
	
	Migrate struct {
		Force bool `help:"Force migration."`
	} `cmd:"" help:"Migrate database."`
}

func main() {
	ctx := kong.Parse(&cli,
		kong.Name("myapp"),
		kong.Description("A CLI application"),
		kong.UsageOnError(),
		kong.ConfigureHelp(kong.HelpOptions{
			Compact: true,
		}),
	)
	
	switch ctx.Command() {
	case "run":
		fmt.Printf("Running server on port %d (debug=%v, daemon=%v)\n", 
			cli.Port, cli.Debug, cli.Run.Daemon)
	case "migrate":
		fmt.Printf("Migrating database (force=%v)\n", cli.Migrate.Force)
	default:
		ctx.PrintUsage(true)
		os.Exit(1)
	}
}

高级特性

1. 嵌套命令结构

Kong 支持复杂的嵌套命令结构:

var cli struct {
	Debug bool `help:"Enable debug mode."`
	
	DB struct {
		Init struct {
			Name string `arg:"" help:"Database name."`
		} `cmd:"" help:"Initialize database."`
		
		Backup struct {
			Output string `short:"o" help:"Output file." default:"backup.sql"`
		} `cmd:"" help:"Backup database."`
	} `cmd:"" help:"Database operations."`
}

2. 配置文件和环境变量

Kong 可以自动从配置文件和环境变量加载配置:

type Config struct {
	Host    string `env:"HOST" default:"localhost"`
	Port    int    `env:"PORT" default:"8080"`
	Timeout int    `yaml:"timeout" default:"30"`
}

func main() {
	var config Config
	_ = kong.Parse(&config,
		kong.Configuration(kong.JSON, "/etc/myapp/config.json", "~/.myapp.json"),
	)
	
	fmt.Printf("Host: %s, Port: %d, Timeout: %d\n", 
		config.Host, config.Port, config.Timeout)
}

3. 验证器

Kong 支持自定义验证器:

var cli struct {
	Age int `validate:"min=18,max=99"`
}

func main() {
	ctx := kong.Parse(&cli)
	// 如果年龄不在18-99之间,会自动报错
}

4. 钩子和中间件

var cli struct {
	Verbose bool `help:"Enable verbose logging."`
}

func main() {
	ctx := kong.Parse(&cli,
		kong.Bind(cli), // 将解析结果绑定到上下文
	)
	
	// 前置钩子
	ctx.BindTo(logger, (*Logger)(nil))
	
	// 执行命令
	err := ctx.Run()
	ctx.FatalIfErrorf(err)
}

实际应用示例

下面是一个完整的 CLI 应用示例:

package main

import (
	"fmt"
	"log"
	"os"
	
	"github.com/alecthomas/kong"
)

var cli struct {
	Version VersionCmd `cmd:"" help:"Show version."`
	Server  ServerCmd  `cmd:"" help:"Run server."`
	Client  ClientCmd  `cmd:"" help:"Run client."`
}

type VersionCmd struct{}
type ServerCmd struct {
	Port    int    `help:"Server port." default:"8080"`
	TLSCert string `help:"TLS certificate file." type:"existingfile"`
	TLSKey  string `help:"TLS key file." type:"existingfile"`
}
type ClientCmd struct {
	URL     string `help:"Server URL." default:"http://localhost:8080"`
	Timeout int    `help:"Request timeout in seconds." default:"10"`
}

func (v *VersionCmd) Run() error {
	fmt.Println("MyApp v1.0.0")
	return nil
}

func (s *ServerCmd) Run() error {
	log.Printf("Starting server on :%d (TLS=%v)", s.Port, s.TLSCert != "")
	// 实际服务器逻辑...
	return nil
}

func (c *ClientCmd) Run() error {
	log.Printf("Connecting to %s (timeout=%ds)", c.URL, c.Timeout)
	// 实际客户端逻辑...
	return nil
}

func main() {
	ctx := kong.Parse(&cli,
		kong.Name("myapp"),
		kong.Description("A complete CLI application"),
		kong.UsageOnError(),
		kong.Vars{
			"version": "v1.0.0",
		},
	)
	
	err := ctx.Run()
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
}

最佳实践

  1. 结构化命令:将相关命令组织到结构体中
  2. 提供默认值:为常用参数设置合理的默认值
  3. 添加帮助文本:为每个字段添加help标签
  4. 支持多种配置源:结合命令行、环境变量和配置文件
  5. 输入验证:使用validate标签或自定义验证器
  6. 错误处理:使用kong.UsageOnError()提供友好的错误信息

Kong 提供了比标准库 flag 包更强大的功能,特别适合构建复杂的 CLI 应用程序。它的设计哲学是通过结构体标签来定义 CLI 接口,减少了样板代码,同时提供了丰富的扩展点。

回到顶部