背景・目的
最近、CobraというCLIアプリケーションを作成するライブラリを知りました。
今回、整理し簡単に試してみたいと思います。
まとめ
下記に特徴を整理します。
特徴 | 説明 |
---|---|
Cobraとは | 強力な最新の CLI アプリケーションを作成するためのライブラリ Kubernetes、Hugo、GitHub CLI など、多くの Go プロジェクトで使用されている |
概要 | git や go ツールに似た強力な最新の CLI インターフェースを作成するためのシンプルなインターフェースを提供するライブラリ |
提供する機能 | ・簡単なサブコマンドベースの CLI: アプリサーバー、アプリフェッチなど ・完全にPOSIX準拠のフラグ(短いバージョンと長いバージョンを含む) ・ネストされたサブコマンド ・グローバル、ローカル、カスケードフラグ ・インテリジェントな提案 ・コマンドとフラグの自動ヘルプ生成 ・サブコマンドのヘルプのグループ化 ・-h、--help などのヘルプ フラグの自動認識 ・アプリケーション用のシェルのオートコンプリートを自動的に生成 ・コマンドエイリアスを使用すると、変更しても壊れることはない ・独自のヘルプや使用方法などを定義できる柔軟性 ・Twelve-Factor AppのViperとの統合 |
コンセプト | コマンド、引数、フラグの構造に基づいて構築されている |
ライセンス | Apache 2.0 |
概要
下記を基に整理します。
Cobra is a library for creating powerful modern CLI applications.
Cobra is used in many Go projects such as Kubernetes, Hugo, and GitHub CLI to name a few. This list contains a more extensive list of projects using Cobra.
- Cobra は、強力な最新の CLI アプリケーションを作成するためのライブラリ
- Cobra は、Kubernetes、Hugo、GitHub CLI など、多くの Go プロジェクトで使用されている
- 他のCobraを使用するプロジェクトはこちらのリストを参照
Overview
Cobra is a library providing a simple interface to create powerful modern CLI interfaces similar to git & go tools.
- git や go ツールに似た強力な最新の CLI インターフェースを作成するためのシンプルなインターフェースを提供するライブラリ
Cobra provides:
- Easy subcommand-based CLIs: app server, app fetch, etc.
- Fully POSIX-compliant flags (including short & long versions)
- Nested subcommands
- Global, local and cascading flags
- Intelligent suggestions (app srver... did you mean app server?)
- Automatic help generation for commands and flags
- Grouping help for subcommands
- Automatic help flag recognition of -h, --help, etc.
- Automatically generated shell autocomplete for your application (bash, zsh, fish, powershell)
- Automatically generated man pages for your application
- Command aliases so you can change things without breaking them
- The flexibility to define your own help, usage, etc.
- Optional seamless integration with viper for 12-factor apps
- Cobraが下記を提供している
- 簡単なサブコマンドベースの CLI: アプリサーバー、アプリフェッチなど
- 完全にPOSIX準拠のフラグ(短いバージョンと長いバージョンを含む)
- ネストされたサブコマンド
- グローバル、ローカル、カスケードフラグ
- インテリジェントな提案
- コマンドとフラグの自動ヘルプ生成
- サブコマンドのヘルプのグループ化
- -h、--help などのヘルプ フラグの自動認識
- アプリケーション用のシェルのオートコンプリートを自動的に生成
- コマンドエイリアスを使用すると、変更しても壊れることはない
- 独自のヘルプや使用方法などを定義できる柔軟性
- Twelve-Factor AppのViperとの統合
Concepts
Cobra is built on a structure of commands, arguments & flags.
Commands represent actions, Args are things and Flags are modifiers for those actions.
The best applications read like sentences when used, and as a result, users intuitively know how to interact with them.
The pattern to follow is APPNAME VERB NOUN --ADJECTIVE or APPNAME COMMAND ARG --FLAG.
A few good real world examples may better illustrate this point.
In the following example, 'server' is a command, and 'port' is a flag:
- Cobra は、コマンド、引数、フラグの構造に基づいて構築されている
- コマンドはアクションを表し、引数は物、フラグはそれらのアクションの修飾子
- 最高のアプリケーションは、使用時に文章のように読み取られ、その結果、ユーザーは直感的に操作方法を知ることができる
- 従うべきパターンは、APPNAME VERB NOUN --ADJECTIVE または APPNAME COMMAND ARG --FLAG
- 例):「server」はコマンド、「port」はフラグ
hugo server --port=1313
- 例):このコマンドでは、Git に URL をそのままクローンするように指示している
git clone URL --bare
Commands
Command is the central point of the application. Each interaction that the application supports will be contained in a Command. A command can have children commands and optionally run an action.
- コマンドはアプリケーションの中心点
- アプリケーションがサポートする各インタラクションはコマンドに含まれる
- コマンドには子コマンドを含めることができ、オプションでアクションを実行できる
Flags
A flag is a way to modify the behavior of a command. Cobra supports fully POSIX-compliant flags as well as the Go flag package. A Cobra command can define flags that persist through to children commands and flags that are only available to that command.
- フラグは、コマンドの動作を変更する方法
- Cobra は、Go フラグ パッケージだけでなく、POSIX に完全準拠したフラグもサポートしている
- Cobra コマンドでは、子コマンドにまで存続するフラグと、そのコマンドでのみ使用可能なフラグを定義できる
Flag functionality is provided by the pflag library, a fork of the flag standard library which maintains the same interface while adding POSIX compliance.
- フラグ機能は、POSIX 準拠を追加しながら同じインターフェースを維持するフラグ標準ライブラリのフォークである pflag ライブラリによって提供される
実践
前提
下記を前提としています。
- VSCode
- MacOS:Sonoma
- Go:1.23.0
セットアップ
プロジェクトの作成
- ディレクトリを作成します
% mkdir go-cli %
- 作成したディレクトリをVSCodeのワークスペースに追加します
- プロジェクトを初期化します
% cd go-cli % go mod init go-cli go: creating new go.mod: module go-cli % cat go.mod module go-cli go 1.23.0 %
Cobraのセットアップ
-
Cobraをインストールします
% go get -u github.com/spf13/cobra@latest go: downloading github.com/spf13/cobra v1.8.1 go: downloading github.com/inconshreveable/mousetrap v1.1.0 go: downloading github.com/spf13/pflag v1.0.5 go: added github.com/inconshreveable/mousetrap v1.1.0 go: added github.com/spf13/cobra v1.8.1 go: added github.com/spf13/pflag v1.0.5 % ls -ltr total 16 -rw-r--r-- 1 XXXX XXXX 181 10 21 22:32 go.mod -rw-r--r-- 1 XXXX XXXX 896 10 21 22:32 go.sum % cat go.mod module go-cli go 1.23.0 require ( github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/spf13/cobra v1.8.1 // indirect github.com/spf13/pflag v1.0.5 // indirect ) %
-
CLIアプリケーションのベースを自動生成するために、CLIもインストールします
% go install github.com/spf13/cobra-cli@latest go: downloading github.com/spf13/cobra-cli v1.3.0 go: downloading github.com/spf13/cobra v1.3.0 go: downloading github.com/spf13/viper v1.10.1 go: downloading github.com/spf13/jwalterweatherman v1.1.0 go: downloading github.com/magiconair/properties v1.8.5 go: downloading github.com/mitchellh/mapstructure v1.4.3 go: downloading github.com/spf13/afero v1.6.0 go: downloading github.com/fsnotify/fsnotify v1.5.1 go: downloading github.com/spf13/cast v1.4.1 go: downloading github.com/subosito/gotenv v1.2.0 go: downloading gopkg.in/ini.v1 v1.66.2 go: downloading github.com/hashicorp/hcl v1.0.0 go: downloading github.com/pelletier/go-toml v1.9.4 go: downloading gopkg.in/yaml.v2 v2.4.0 go: downloading golang.org/x/sys v0.0.0-20211210111614-af8b64212486 go: downloading golang.org/x/text v0.3.7 %
-
PATHを確認します
% go env GOPATH /Users/XXXX/go %
-
.zshrc
にPATHを追加しますexport PATH="$PATH:$(go env GOPATH)/bin"
-
読み込みます
source ~/.zshrc
-
実行できました
% cobra-cli -h Cobra is a CLI library for Go that empowers applications. This application is a tool to generate the needed files to quickly create a Cobra application. Usage: cobra-cli [command] Available Commands: add Add a command to a Cobra Application completion Generate the autocompletion script for the specified shell help Help about any command init Initialize a Cobra Application Flags: -a, --author string author name for copyright attribution (default "YOUR NAME") --config string config file (default is $HOME/.cobra.yaml) -h, --help help for cobra-cli -l, --license string name of license for the project --viper use Viper for configuration Use "cobra-cli [command] --help" for more information about a command. %
Cobraを使ったCLIアプリケーションの作成
プロジェクトの初期化
- 下記のコマンドを実行し、初期化します
% cobra-cli init Your Cobra application is ready at /Users/XXXX/XXXX/go-cli %
-
LICENSE
、cmd
ディレクトリ、main.go
オブジェクトが生成されました% ls -l total 24 -rw-r--r-- 1 XXX XXX 0 10 21 22:53 LICENSE drwxr-x--x 3 XXX XXX 96 10 21 22:53 cmd -rw-r--r-- 1 XXX XXX 181 10 21 22:32 go.mod -rw-r--r-- 1 XXX XXX 896 10 21 22:32 go.sum -rw-r--r-- 1 XXX XXX 117 10 21 22:53 main.go %
-
main.go
を確認してみます% cat main.go /* Copyright © 2024 NAME HERE <EMAIL ADDRESS> */ package main import "go-cli/cmd" func main() { cmd.Execute() } %
-
cmd/root.go
を確認してみます% cat cmd/root.go /* Copyright © 2024 NAME HERE <EMAIL ADDRESS> */ package cmd import ( "os" "github.com/spf13/cobra" ) // rootCmd represents the base command when called without any subcommands var rootCmd = &cobra.Command{ Use: "go-cli", Short: "A brief description of your application", Long: `A longer description that spans multiple lines and likely contains examples and usage of using your application. For example: Cobra is a CLI library for Go that empowers applications. This application is a tool to generate the needed files to quickly create a Cobra application.`, // Uncomment the following line if your bare application // has an action associated with it: // Run: func(cmd *cobra.Command, args []string) { }, } // 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() { // Here you will define your flags and configuration settings. // Cobra supports persistent flags, which, if defined here, // will be global for your application. // rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.go-cli.yaml)") // Cobra also supports local flags, which will only run // when this action is called directly. rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") } %
helloコマンドの追加
-
コマンドを追加します
% cobra-cli add hello hello created at /Users/XXXX/XXXX/go-cli %
-
hello
ファイルが追加されました% ls -l cmd total 16 -rw-r--r-- 1 XXXX XXXX 1072 10 21 23:00 hello.go -rw-r--r-- 1 XXXX XXXX 1438 10 21 22:53 root.go %
-
下記のように編集します
/* Copyright © 2024 NAME HERE <EMAIL ADDRESS> */ package cmd import ( "fmt" "github.com/spf13/cobra" ) var name string // helloCmd represents the hello command var helloCmd = &cobra.Command{ Use: "hello", Short: "Say hello to someone", Long: `This command allows you to say hello to someone by specifying their name.`, Run: func(cmd *cobra.Command, args []string) { fmt.Printf("Hello, %s!\n", name) }, } func init() { rootCmd.AddCommand(helloCmd) helloCmd.Flags().StringVarP(&name, "name", "n", "World", "Name of the person to greet") }
-
実行します。想定通りの結果です
% go run main.go hello --name Taro Hello, Taro! %
ビルド
バイナリファイルを生成し、実行します
-
go build
コマンドを実行します% go build -o hello
- 確認します
% ls -l total 10680 -rw-r--r-- 1 XXXX XXXX 0 10 21 22:53 LICENSE drwxr-x--x 4 XXXX XXXX 128 10 21 23:00 cmd -rw-r--r-- 1 XXXX XXXX 181 10 21 23:13 go.mod -rw-r--r-- 1 XXXX XXXX 896 10 21 22:32 go.sum -rwxr-xr-x 1 XXXX XXXX 5452224 10 21 23:21 hello -rw-r--r-- 1 XXXX XXXX 117 10 21 22:53 main.go %
- 実行できました
% ./hello hello --name Jiro Hello, Jiro! %
フラグをトップレベルコマンドに適用する
hello --name
の形式で実行できるようにします。
-
hello.goを削除します
% rm cmd/hello.go
-
root.goを修正します
/* Copyright © 2024 NAME HERE <EMAIL ADDRESS> */ package cmd import ( "fmt" "os" "github.com/spf13/cobra" ) var name string // rootCmd represents the base command when called without any subcommands var rootCmd = &cobra.Command{ Use: "hello", Short: "Say hello to someone", Long: `This application allows you to say hello to a person by specifying their name.`, Run: func(cmd *cobra.Command, args []string) { fmt.Printf("Hello, %s!\n", name) }, } func Execute() { err := rootCmd.Execute() if err != nil { fmt.Println(err) os.Exit(1) } } func init() { rootCmd.Flags().StringVarP(&name, "name", "n", "World", "Name of the person to greet") }
-
できました
% ls -l total 10680 -rw-r--r-- 1 XXXX XXXX 0 10 21 22:53 LICENSE drwxr-x--x 3 XXXX XXXX 96 10 21 23:36 cmd -rw-r--r-- 1 XXXX XXXX 181 10 21 23:13 go.mod -rw-r--r-- 1 XXXX XXXX 896 10 21 22:32 go.sum -rwxr-xr-x 1 XXXX XXXX 5452128 10 21 23:37 hello -rw-r--r-- 1 XXXX XXXX 117 10 21 22:53 main.go %
-
実行します。想定通りの結果です
% ./hello --name Jiro Hello, Jiro! %
考察
今回、Cobraの基本的な特徴を整理し、簡単な動作確認を行いました。シンプルなCLIツールを作成するところまで進めましたが、今後はサブコマンドを追加したり、より複雑な機能を持つCLIツールの実装を試したいと思います。
参考