MyDocs

# cobra-cliを使う

cobra-cli を使う

go.mod を作成

$ go mod init sample
go: creating new go.mod: module sample

project の開始

init コマンドを実行。

$ go run -mod=mod github.com/spf13/cobra-cli@latest init

$ tree
tree
.
├── LICENSE
├── cmd
│   └── root.go
├── go.mod
├── go.sum
└── main.go

main.gocmd.Execute() を呼び出しているだけ。

/*
Copyright © 2022 NAME HERE <EMAIL ADDRESS>

*/
package main

import "sample/cmd"

func main() {
	cmd.Execute()
}

cmd/root.go を編集。実行したら Hello を出力する。

/*
Copyright © 2022 NAME HERE <EMAIL ADDRESS>

*/
package cmd

import (
	"fmt"
	"os"

	"github.com/spf13/cobra"
)

// rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{
	Use:   "sample",
	Short: "挨拶をします",
	RunE: func(cmd *cobra.Command, args []string) error {
		fmt.Println("Hello")
		return nil
	},
}

// Execute adds all child commands to the root command and sets flags appropriately.
// This is called by main.main(). It only needs to happen once to the rootCmd.
func Execute() {
	err := rootCmd.Execute()
	if err != nil {
		os.Exit(1)
	}
}

func init() {
}

実行。

$ go run main.go
Hello

help を見る。

$ go run main.go -h
挨拶をします

Usage:
  sample [flags]

Flags:
  -h, --help   help for sample

フラグを設定

グローバルフラグを設定

--night をつければ Good night を出力する。

/*
Copyright © 2022 NAME HERE <EMAIL ADDRESS>

*/
package cmd

import (
	"fmt"
	"os"

	"github.com/spf13/cobra"
)

var night bool

// rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{
	Use:   "sample",
	Short: "挨拶をします",
	RunE: func(cmd *cobra.Command, args []string) error {
		if night {
			fmt.Println("Good night")
			return nil
		}
		fmt.Println("Hello")
		return nil
	},
}

// Execute adds all child commands to the root command and sets flags appropriately.
// This is called by main.main(). It only needs to happen once to the rootCmd.
func Execute() {
	err := rootCmd.Execute()
	if err != nil {
		os.Exit(1)
	}
}

func init() {
	rootCmd.PersistentFlags().BoolVar(&night, "night", false, "night フラグの説明文です")
}

実行。

$ go run main.go -h
挨拶をします

Usage:
  sample [flags]

Flags:
  -h, --help    help for sample
      --night   night フラグの説明文です

$ go run main.go
Hello

$ go run main.go --night
Good night

shorthand で設定する

func init() {
	// rootCmd.PersistentFlags().BoolVar(&night, "night", false, "night フラグの説明文です")
	rootCmd.PersistentFlags().BoolVarP(&night, "night", "n", false, "night フラグの説明文です")
}

実行。

$ go run main.go -h
挨拶をします

Usage:
  sample [flags]

Flags:
  -h, --help    help for sample
  -n, --night   night フラグの説明文です

$ go run main.go -n
Good night

ローカルフラグを設定する

--name をつければ挨拶の後に名前を出力する。 今度は変数を設定しないでフラグの値を取得してみる。

/*
Copyright © 2022 NAME HERE <EMAIL ADDRESS>

*/
package cmd

import (
	"fmt"
	"os"

	"github.com/spf13/cobra"
)

var night bool

// rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{
	Use:   "sample",
	Short: "挨拶をします",
	RunE: func(cmd *cobra.Command, args []string) error {
		name, _ := cmd.Flags().GetString("name")
		if night {
			fmt.Println("Good night " + name)
			return nil
		}
		fmt.Println("Hello " + name)
		return nil
	},
}

// Execute adds all child commands to the root command and sets flags appropriately.
// This is called by main.main(). It only needs to happen once to the rootCmd.
func Execute() {
	err := rootCmd.Execute()
	if err != nil {
		os.Exit(1)
	}
}

func init() {
	rootCmd.PersistentFlags().BoolVarP(&night, "night", "n", false, "night フラグの説明文です")
	rootCmd.Flags().StringP("name", "a", "", "name フラグの説明文です")
}

実行。

$ go run main.go -h
挨拶をします

Usage:
  sample [flags]

Flags:
  -h, --help          help for sample
  -a, --name string   name フラグの説明文です
  -n, --night         night フラグの説明文です

$ go run main.go
Hello 
$ go run main.go -a aaa
Hello aaa

$ go run main.go -a aaa --night
Good night aaa

フラグの値の取得方法

~P がついている方は shorthand を設定できる。 ~Var がついている方は直接値を設定できる。

rootCmd.Flags().String("name", "", "name フラグの説明文です")
rootCmd.Flags().StringP("name", "a", "", "name フラグの説明文です")
rootCmd.Flags().StringVar(&name, "name", "", "name フラグの説明文です")
rootCmd.Flags().StringVarP(&name, "name", "a", "", "name フラグの説明文です")

必須フラグ

func init() {
	rootCmd.PersistentFlags().BoolVarP(&night, "night", "n", false, "night フラグの説明文です")
	rootCmd.Flags().StringP("name", "a", "", "name フラグの説明文です (required)")
	rootCmd.MarkFlagRequired("name")
}

実行。

$ go run main.go
Error: required flag(s) "name" not set
Usage:
  sample [flags]

Flags:
  -h, --help          help for sample
  -a, --name string   name フラグの説明文です (required)
  -n, --night         night フラグの説明文です

$ go run main.go -a aaa
Hello aaa

引数の設定

引数に数字を 2 つ与えるとその合計を出力する。

// rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{
	Use:   "sample",
	Short: "挨拶をします",
	RunE: func(cmd *cobra.Command, args []string) error {
		name, _ := cmd.Flags().GetString("name")

		if night {
			fmt.Println("Good night " + name)
			if len(args) > 0 {
				firstNum, _ := strconv.Atoi(args[0])
				secondNum, _ := strconv.Atoi(args[1])
				fmt.Printf("SUM: %v\n", firstNum+secondNum)
			}
			return nil
		}

		fmt.Println("Hello " + name)
		if len(args) > 0 {
			firstNum, _ := strconv.Atoi(args[0])
			secondNum, _ := strconv.Atoi(args[1])
			fmt.Printf("SUM: %v\n", firstNum+secondNum)
		}
		return nil
	},
}

実行。

$ go run main.go -a aaa
Hello aaa

$ go run main.go -a aaa 1 99
Hello aaa
SUM: 100

バリデーションチェック

これらのメソッドが用意されている。

メソッド機能
NoArgs引数が存在したらエラーを返す
OnlyValidArgsValidArgs に含まれていないとエラーを返す
ArbitraryArgsエラーを返さない
MinimumNArgs(n)少なくとも n 個の引数がないとエラーを返す
MaximumNArgs(n)n 個以上の引数があるとエラーを返す
ExactArgs(n)n 個の引数以外だとエラーを返す
ExactValidArgs(int)n 個の引数以外 or ValidArgs フィールドにない位置引数がある場合はエラーを返す
RangeArgs(min, max)範囲内の値でないとエラーを返す

こんな感じで設定できる。

Args:  cobra.ExactArgs(1),

複数のバリデーションをチェックする場合は MatchAll を使えば良さそう。

// MatchAll allows combining several PositionalArgs to work in concert.
func MatchAll(pargs ...PositionalArgs) PositionalArgs {
	return func(cmd *Command, args []string) error {
		for _, parg := range pargs {
			if err := parg(cmd, args); err != nil {
				return err
			}
		}
		return nil
	}
}

今回は 2 個の引数のみ受け付ける。かつ、値が数値であることのみ許可する。

var rootCmd = &cobra.Command{
	Use:   "sample",
	Short: "挨拶をします。2 つの int を渡すと計算もします",
	Args: func(cmd *cobra.Command, args []string) error {
		if len(args) == 0 {
			return nil
		} else {
			if len(args) != 2 {
				return errors.New("引数の数が不正です")
			}
			_, err1 := strconv.Atoi(args[0])
			_, err2 := strconv.Atoi(args[1])
			if err1 != nil || err2 != nil {
				return errors.New("引数の型が不正です")
			}
		}
		return nil
	},
	RunE: func(cmd *cobra.Command, args []string) error {
		name, _ := cmd.Flags().GetString("name")

		if night {
			fmt.Println("Good night " + name)
			if len(args) > 0 {
				firstNum, _ := strconv.Atoi(args[0])
				secondNum, _ := strconv.Atoi(args[1])
				fmt.Printf("SUM: %v\n", firstNum+secondNum)
			}
			return nil
		}

		fmt.Println("Hello " + name)
		if len(args) > 0 {
			firstNum, _ := strconv.Atoi(args[0])
			secondNum, _ := strconv.Atoi(args[1])
			fmt.Printf("SUM: %v\n", firstNum+secondNum)
		}
		return nil
	},
}

実行。

$ go run main.go -a aaa
Hello aaa

$ go run main.go -a aaa 1 99
Hello aaa
SUM: 100

$ go run main.go -a aaa 1 abc
Error: 引数の型が不正です
Usage:
  sample [flags]

Flags:
  -h, --help          help for sample
  -a, --name string   name フラグの説明文です (required)
  -n, --night         night フラグの説明文です

PreRun / PostRun Hooks

実行前と実行後の処理を設定できる。

var rootCmd = &cobra.Command{
	Use:   "sample",
	Short: "挨拶をします。2 つの int を渡すと計算もします",
	Args: func(cmd *cobra.Command, args []string) error {
		if len(args) == 0 {
			return nil
		} else {
			if len(args) != 2 {
				return errors.New("引数の数が不正です")
			}
			_, err1 := strconv.Atoi(args[0])
			_, err2 := strconv.Atoi(args[1])
			if err1 != nil || err2 != nil {
				return errors.New("引数の型が不正です")
			}
		}
		return nil
	},
	PreRun: func(cmd *cobra.Command, args []string) {
		fmt.Println("=== PreRun ===")
	},
	RunE: func(cmd *cobra.Command, args []string) error {
		name, _ := cmd.Flags().GetString("name")

		if night {
			fmt.Println("Good night " + name)
			if len(args) > 0 {
				firstNum, _ := strconv.Atoi(args[0])
				secondNum, _ := strconv.Atoi(args[1])
				fmt.Printf("SUM: %v\n", firstNum+secondNum)
			}
			return nil
		}

		fmt.Println("Hello " + name)
		if len(args) > 0 {
			firstNum, _ := strconv.Atoi(args[0])
			secondNum, _ := strconv.Atoi(args[1])
			fmt.Printf("SUM: %v\n", firstNum+secondNum)
		}
		return nil
	},
	PostRun: func(cmd *cobra.Command, args []string) {
		fmt.Println("=== PostRun ===")
	},
}

実行。

$ go run main.go -a aaa
=== PreRun ===
Hello aaa
=== PostRun ===

subcommand

$ go run -mod=mod github.com/spf13/cobra-cli@latest add server

$ tree
.
├── LICENSE
├── cmd
│   ├── root.go
│   └── server.go
├── go.mod
├── go.sum
└── main.go

cmd 配下にファイルが生成されている。

/*
Copyright © 2022 NAME HERE <EMAIL ADDRESS>

*/
package cmd

import (
	"fmt"

	"github.com/spf13/cobra"
)

// serverCmd represents the server command
var serverCmd = &cobra.Command{
	Use:   "server",
	Short: "server を起動します",
	Run: func(cmd *cobra.Command, args []string) {
		fmt.Println("server called")
	},
}

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

実行。

$ go run main.go -h
挨拶をします。2 つの int を渡すと計算もします

Usage:
  sample [flags]
  sample [command]

Available Commands:
  completion  Generate the autocompletion script for the specified shell
  help        Help about any command
  server      A brief description of your command

Flags:
  -h, --help          help for sample
  -a, --name string   name フラグの説明文です (required)
  -n, --night         night フラグの説明文です

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

server のヘルプをみる。

$ go run main.go server -h
server を起動します

Usage:
  sample server [flags]

Flags:
  -h, --help   help for server

Global Flags:
  -n, --night   night フラグの説明文です
$ go run main.go server
server called

subcommand も簡単に実装できそう。