searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

cobra 开发一个git style 终端工具

2023-09-21 09:02:08
38
0

概念

cobra 构建中,有三块基石头:命令、参数和标志。要使用 Cobra 编写一个命令行程序,首先要明确这三个概念:

  • 命令(COMMAND):命令表示要执行的操作。
  • 参数(ARG):是命令的参数,一般用来表示操作的对象。
  • 标志(FLAG):是命令的修饰,可以调整操作的行为。

一个好的命令行程序在使用时读起来像句子,用户会自然的理解并知道如何使用该程序。比如:

git log master --oneline --graph

其中:

log 是命令,表明执行的操作是查看日志;

master 是参数, 指明log操作的对象是master分支;

--oneline是标志,用于限定log的行为,以简洁单行的方式显示;

--graph也是标志,用于限定log的行为,以图形化方式显示提交和分支关系。

 

要编写一个好的命令行程序,通常遵循的模式是:

 APPNAME VERB NOUN --ADJECTIVE 或 APPNAME COMMAND ARG --FLAG

在这里 VERB 代表动词,NOUN 代表名词,ADJECTIVE 代表形容词。

 

另一方面,在实际应用中,如果一个命令有多个参数,为了方便使用和控制,通常会用具名参数的形式,即参数选项化:

ssh -p 1000  smbody@192.168.1.1

hull  create “name” “email” “class”  -->  hull create  "name" -E "email" -c "class"

 

快速开始

要使用 cobra 创建命令行程序,需要先通过如下命令进行安装:

$ go get -u github.com/spf13/cobra/cobra

安装好后,就可以像其他 Go 语言库一样导入 cobra 包并使用了。

import "github.com/spf13/cobra"

创建一个命令

以官网例子,假设我们要创建的命令行程序叫作 hugo,可以编写如下代码创建一个命令:

hugo/cmd/root.go

var rootCmd = &cobra.Command{
  Use:   "hugo",
  Short: "Hugo is a very fast static site generator",
  Long: `A Fast and Flexible Static Site Generator built with
                love by spf13 and friends in Go.
                Complete documentation is available at gohugo.io`,
  Run: func(cmd *cobra.Command, args []string) {
    fmt.Println("run hugo...")
  },
}

func Execute() {
  if err := rootCmd.Execute(); err != nil {
    fmt.Println(err)
    os.Exit(1)
  }
}

cobra.Command 是一个结构体,代表一个命令,其各个属性含义如下:

Use 是命令的名称。

Short 代表当前命令的简短描述。

Long 表示当前命令的完整描述。

Run 属性是一个函数,当执行命令时会调用此函数。

rootCmd.Execute() 是命令的执行入口,其内部会解析 os.Args[1:] 参数列表(默认情况下是这样,也可以通过 Command.SetArgs 方法设置参数),然后遍历命令树,为命令找到合适的匹配项和对应的标志。

创建 main.go

按照编写 Go 程序的惯例,我们要为 hugo 程序编写一个 main.go 文件,作为程序的启动入口。

package main

import (
    "hugo/cmd"
)

func main() {
    cmd.Execute()
}

main.go 代码实现非常简单,只在 main 函数中调用了 cmd.Execute() 函数,来执行命令。

编译并运行命令

现在,我们就可以编译并运行这个命令行程序了。

go build -o hugo main.go
$ ./hugo --help
A Fast and Flexible Static Site Generator built with
                love by spf13 and friends in Go.
                Complete documentation is available at gohugo.io

Usage:
  hugo [flags]

Flags:
  -h, --help   help for hugo

命令与子命令

与定义 rootCmd 一样,我们可以使用 cobra.Command 定义其他命令,并通过 rootCmd.AddCommand() 方法将其添加为 rootCmd 的一个子命令。

hugo/cmd/version.go

var versionCmd = &cobra.Command{
    Use:   "version",
    Short: "Print the version number of Hugo",
    Long:  `All software has versions. This is Hugo's`,
    Run: func(cmd *cobra.Command, args []string) {
        fmt.Println("Hugo Static Site Generator v0.9 -- HEAD")
    },
}

func init() {
    rootCmd.AddCommand(versionCmd)
}

现在重新编译并运行命令行程序。

$ ./hugo -h
A Fast and Flexible Static Site Generator built with
                love by spf13 and friends in Go.
                Complete documentation is available at gohugo.io

Usage:
  hugo [flags]
  hugo [command]

Available Commands:
  completion  Generate the autocompletion script for the specified shell
  help        Help about any command
  version     Print the version number of Hugo

Flags:
  -h, --help   help for hugo

Use "hugo [command] --help" for more information about a command.

可以看见version命令已经被加进来了。运行version子命令:

$ ./hugo version                       
Hugo Static Site Generator v0.9 -- HEAD

 

当然,子命令可以继续添加子命令,从而实现多级命令嵌套。cobra 会自动进行命令树展开,并匹配对应的选项。

在设计多级命令时,通常按一下规则:

APPNAME  GROUP...  ACTION  ARGs [FLAGS] 

 

标志

标志配置和获取值

cobra.Command 提供了Flags() 接口及一系列选项配置方法,来为指定的命令设置选项。例如:

subCmd.Flags().StringVarP(&Source, "source", "s", "", "Source directory to read from")

为subCmd 配置一个 --source/-s 选项,选项的值是 字符串类型,默认值是“”;help 说明为“Source directory to read from”。

 

cobra.Command 绑定的run函数, 原型为:

func(command *cobra.Command, args []string) {
}

通常,实现的run函数中,会从 command 中获取相关的选项内容,用于逻辑控制:

	name, err := command.Flags().GetString("grep-name")
	if err != nil {
		ErrorExit("参数或选项解析错误:" + err.Error())
	}

 

持久标志

如果一个标志是持久的,则意味着该标志将可用于它所分配的命令以及该命令下的所有子命令。

对于全局标志,可以定义在根命令 rootCmd 上。

var Verbose bool
rootCmd.PersistentFlags().BoolVarP(&Verbose, "verbose", "v", false, "verbose output")

这样,所有命令都可以继承和使用该标志。

 

本地标志

标志也可以是本地的,这意味着它只适用于该指定命令。

var Source string
subCmd.Flags().StringVarP(&Source, "source", "s", "", "Source directory to read from")

 

必选标志

默认情况下,标志是可选的。我们可以将其标记为必选,如果没有提供,则会报错。

var Region string
subCmd.Flags().StringVarP(&Region, "region", "r", "", "AWS region (required)")
subCmd.MarkFlagRequired("region")

参数验证

在执行命令行程序时,我们可能需要对命令参数进行合法性验证,cobra.Command 的 Args 属性提供了此功能。

Args 属性类型为一个函数:func(cmd *Command, args []string) error,可以用来验证参数。

Cobra 内置了以下验证函数:

  • NoArgs:如果存在任何命令参数,该命令将报错。
  • ArbitraryArgs:该命令将接受任意参数。
  • OnlyValidArgs:如果有任何命令参数不在 Command 的 ValidArgs 字段中,该命令将报错。
  • MinimumNArgs(int):如果没有至少 N 个命令参数,该命令将报错。
  • MaximumNArgs(int):如果有超过 N 个命令参数,该命令将报错。
  • ExactArgs(int):如果命令参数个数不为 N,该命令将报错。
  • ExactValidArgs(int):如果命令参数个数不为 N,或者有任何命令参数不在 Command 的 ValidArgs 字段中,该命令将报错。
  • RangeArgs(min, max):如果命令参数的数量不在预期的最小数量 min 和最大数量 max 之间,该命令将报错。
  • 内置验证函数用法如下:
var versionCmd = &cobra.Command{
    Use:   "version",
    Run: func(cmd *cobra.Command, args []string) {
        fmt.Println("Hugo Static Site Generator v0.9 -- HEAD")
    },
    Args: cobra.MaximumNArgs(2), // 使用内置的验证函数,位置参数多于 2 个则报错
}

当然,我们也可以自定义验证函数:

var printCmd = &cobra.Command{
    Use: "print [OPTIONS] [COMMANDS]",
    Run: func(cmd *cobra.Command, args []string) {
        fmt.Println("run print...")
        // 命令行位置参数列表:例如执行 `hugo print a b c d` 将得到 [a b c d]
        fmt.Printf("args: %v\n", args)
    },
    // 使用自定义验证函数
    Args: func(cmd *cobra.Command, args []string) error {
        if len(args) < 1 {
            return errors.New("requires at least one arg")
        }
        if len(args) > 4 {
            return errors.New("the number of args cannot exceed 4")
        }
        if args[0] != "a" {
            return errors.New("first argument must be 'a'")
        }
        return nil
    },
}

Hooks

在执行 Run 函数前后,我么可以执行一些钩子函数,其作用和执行顺序如下:

  • PersistentPreRun:在 PreRun 函数执行之前执行,对此命令的子命令同样生效。
  • PreRun:在 Run 函数执行之前执行。
  • Run:执行命令时调用的函数,用来编写命令的业务逻辑。
  • PostRun:在 Run 函数执行之后执行。
  • PersistentPostRun:在 PostRun 函数执行之后执行,对此命令的子命令同样生效。

可以使用如下方式测试:

var rootCmd = &cobra.Command{
    Use:   "hugo",
    PersistentPreRun: func(cmd *cobra.Command, args []string) {
        fmt.Println("hugo PersistentPreRun")
    },
    PreRun: func(cmd *cobra.Command, args []string) {
        fmt.Println("hugo PreRun")
    },
    Run: func(cmd *cobra.Command, args []string) {
        fmt.Println("run hugo...")
    },
    PostRun: func(cmd *cobra.Command, args []string) {
        fmt.Println("hugo PostRun")
    },
    PersistentPostRun: func(cmd *cobra.Command, args []string) {
        fmt.Println("hugo PersistentPostRun")
    },
}

 

通常,可以在root command 中,指定PresistentPreRun 方法,用于执行一些公共处理逻辑(比如,检查登录状态等),达到初始化或者配置的目的。

 

自定义usage 或help

定义自己的 Help 命令

如果你对 obra 自动生成的帮助命令不满意,我们可以自定义帮助命令或模板。

cmd.SetHelpCommand(cmd *Command)
cmd.SetHelpFunc(f func(*Command, []string))
cmd.SetHelpTemplate(s string)

obra 提供了三个方法来实现自定义帮助命令,后两者也适用于任何子命令。

默认情况下,我们可以使用 hugo help command 语法查看子命令的帮助信息,也可以使用 hugo command -h/--help 查看。

使用 help 命令查看帮助信息:

$ ./hugo help version
hugo PersistentPreRunE
All software has versions. This is Hugo's

Usage:
  hugo version [flags]

Flags:
  -h, --help   help for version

Global Flags:
      --author string   Author name for copyright attribution (default "YOUR NAME")
  -v, --verbose         verbose output
hugo PersistentPostRun

使用 -h/--help 查看帮助信息:

$ ./hugo version -h  
All software has versions. This is Hugo's

Usage:
  hugo version [flags]

Flags:
  -h, --help   help for version

Global Flags:
      --author string   Author name for copyright attribution (default "YOUR NAME")
  -v, --verbose         verbose output

对比发现,使用 help [cmd]时,会执行命令的Hook 函数,而使用 [cmd] help 不会。

 

定义自己的 Usage Message

当用户提供无效标志或无效命令时,cobra 通过向用户显示 Usage 来提示用户如何正确的使用命令。

help 信息一样,我们也可以进行自定义。cobra 提供了如下两个方法,来控制输出:

cmd.SetUsageFunc(f func(*Command) error)
cmd.SetUsageTemplate(s string)

未知命令建议

 

在我们使用 git 命令时,有一个非常好用的功能,能够对用户输错的未知命令智能提示。

 
$ git statu
git: 'statu' is not a git command. See 'git --help'.

The most similar commands are
    status
    stage
    stash

这个功能非常实用,幸运的是,cobra 自带了此功能。

 

如果你想彻底关闭此功能,可以使用如下设置:

command.DisableSuggestions = true

或者使用如下设置调整字符串匹配的最小距离:

command.SuggestionsMinimumDistance = 1

SuggestionsMinimumDistance 是一个正整数,表示输错的命令与正确的命令最多有几个不匹配的字符(最小距离),才会给出建议。如当值为 1 时,用户输入 hugo versiox 会给出建议,而如果用户输入 hugo versixx 时,则不会给出建议,因为已经有两个字母不匹配 version 了。

Shell 补全

cobra默认提供 completion 子命令,可以为指定的 Shell 生成自动补全脚本,现在我们就来讲解它的用法。

直接执行 hugo completion 命令,我们可以查看它支持的几种 Shell 类型 bashfishpowershellzsh

首先,明确自己环境中所使用的Shell类型,可以用以下方式查看

$ echo $0   
/bin/zsh

然后,可以根据自己的shell类型,查看如何生产completion 脚本

./hugo completion zsh -h
Generate the autocompletion script for the zsh shell.

If shell completion is not already enabled in your environment you will need
to enable it.  You can execute the following once:

        echo "autoload -U compinit; compinit" >> ~/.zshrc

To load completions in your current shell session:

        source <(hugo completion zsh)

To load completions for every new session, execute once:

#### Linux:

        hugo completion zsh > "${fpath[1]}/_hugo"

#### macOS:

        hugo completion zsh > $(brew --prefix)/share/zsh/site-functions/_hugo

You will need to start a new shell for this setup to take effect.

Usage:
  hugo completion zsh [flags]

Flags:
  -h, --help              help for zsh
      --no-descriptions   disable completion descriptions

Global Flags:
      --author string   Author name for copyright attribution (default "YOUR NAME")
  -v, --verbose         verbose output

最后,执行对应的指令,生产completion 脚本,并配置 .profile 或者 .zshrc 或者.bashrc (根据自己的环境),加载completion脚本,即可实现命令自动补全。

 

最后

本打算附上官方地址,但无法过审。GH 上搜索cobra, 认准spf13/cobra.

0条评论
0 / 1000