Urfave CLI - Golang learning step 3-1
- 公開日
- カテゴリ:BuildingCLIs
- タグ:Golang,roadmap.sh,学習メモ
roadmap.sh > Go > Building CLIs > Urfave CLI の学習を進めていきます。
※ 学習メモとしての記録ですが、後にこのセクションを学ぶ道しるべとなるよう、ですます調で記載しています。
contents
開発環境
- チップ: Apple M2 Pro
- OS: macOS Sonoma
- go version: go1.23.2 darwin/arm64
参考 URL
- [OpenSource] Urfave CLI
- [article] Urfave Website
- [article] How to Build cli in Go
- [article] Building CLI using urfave cli
- [feed] Explore top posts about CLI
Urfave CLI
Urfave CLI は、Go でコマンドラインインターフェース (CLI) アプリケーションを作成するための人気ライブラリです。軽量で直感的な設計が特徴で、小規模から大規模な CLI アプリケーションの開発に適しています。
特徴
- コマンドとサブコマンドの管理
- 複数のコマンドやサブコマンドを簡単に定義・管理できます。コマンド間で引数やオプションを柔軟に設定可能。
- 使いやすい API
- シンプルな API により、初心者でも簡単に CLI を構築できる。
- カスタマイズ性
- ヘルプメッセージやエラーハンドリングなど、アプリケーションの挙動を柔軟にカスタマイズ可能。
- 軽量で高速
- 必要最低限の依存関係で動作するため、Go のエコシステムに馴染みやすい設計。
インストール
Urfave CLI をインストールするには、以下のコマンドを実行します。
go get github.com/urfave/cli/v2
CLI アプリケーションの実装
1, 最小構成のアプリケーション
以下の最小限のコードでコマンドを実行できます。
package main
import (
"os"
"github.com/urfave/cli/v2"
)
func main() {
(&cli.App{}).Run(os.Args)
}
上記プログラムをコンパイルし、実行します。動作しますが、何もアクションを実装していないため、ヘルプメッセージが表示されます。
コンパイル:
# hello という名前でコンパイル
go build -o hello
実行:
./hello
2. Action - アクションの実装
次に、ここにメッセージを出力するアクションを実装し、hello world を出力できるようにします。
package main
import (
"fmt"
"log"
"os"
"github.com/urfave/cli/v2"
)
func main() {
app := &cli.App{
Name: "hello",
Usage: "挨拶します",
Action: func(*cli.Context) error {
fmt.Println("hello world")
return nil
},
}
if err := app.Run(os.Args); err != nil {
log.Fatal(err)
}
}
コンパイルし、実行すると、hello world
と表示されます。
# コンパイル
go build -o hello
# 実行
./hello
# => hello world
3. Args - 引数の追加
コマンド実行時に引数を渡して、それをメッセージに表示するようにします。
アクションの関数に引数 Context に任意の変数名(ここではctx
)を設定すると、ctx.Args()
で引数を受け取れます。
func main() {
app := &cli.App{
Name: "hello",
Usage: "挨拶します",
Action: func(ctx *cli.Context) error {
fmt.Printf("Hello %s!!\n", ctx.Args().Get(0))
return nil
},
}
if err := app.Run(os.Args); err != nil {
log.Fatal(err)
}
}
# コンパイル
go build -o hello
# 実行
./hello rito
# => Hello rito!!
4. Flags - フラグ
引数では、シンプルにコマンドの後ろに値を入力しました。次にフラグを渡して渡す値をわかりやすくします。
フラグとは、コマンドの --lang
や --name
のような形式のオプションを指します。一般的に 「フラグ」(flags) または 「オプション引数」(optional arguments) と呼ばれます。
以下は、--lang
や --name
のフラグを追加しています。
func main() {
app := &cli.App{
Name: "hello",
Usage: "挨拶します",
Flags: []cli.Flag{
// 1 つ目のフラグ name を追加
&cli.StringFlag{
Name: "name", // name フラグ
Value: "you", // デフォルト値は "you"
Usage: "相手の名前",
},
// 2 つ目のフラグ lang を追加
&cli.StringFlag{
Name: "lang", // lang フラグ
Value: "english", // デフォルト値は "english"
Usage: "挨拶の言語",
},
},
Action: func(ctx *cli.Context) error {
name := ctx.String("name") // name フラグの値を取得
var hello string
lang := ctx.String("lang") // lang フラグの値を取得
switch lang {
case "japanese":
hello = "こんにちは"
case "korean":
hello = "안녕하세요"
case "spanish":
hello = "Hola"
default:
hello = "Hello"
}
fmt.Printf("%s %s!!\n", hello, name)
return nil
},
}
if err := app.Run(os.Args); err != nil {
log.Fatal(err)
}
}
フラグにはデフォルト値を設定できます。Value
がそれに当たります。つまり、オプション引数を指定しない場合は、デフォルト値として Value
に指定した値が設定されます。
# オプション引数を指定した場合:
./hello --lang korean --name rito
# => 안녕하세요 rito!!
# オプション引数を指定しない場合:
./hello
# => Hello you!!
5. Commands - コマンド
Commands(コマンド)を使うと、複数のアクションを設定できます。これまで hello アクションを実装しましたが、ここに thank アクションも定義することで、両方どちらも使えるようになります。
func main() {
app := &cli.App{
Commands: []*cli.Command{
{
Name: "hello",
Usage: "挨拶します",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "name", // name フラグ
Value: "you", // デフォルト値は "you"
Usage: "相手の名前",
},
&cli.StringFlag{
Name: "lang", // lang フラグ
Value: "english", // デフォルト値は "english"
Usage: "挨拶の言語",
},
},
Action: func(ctx *cli.Context) error {
name := ctx.String("name") // Name フラグの値を取得
var hello string
lang := ctx.String("lang") // lang フラグの値を取得
switch lang {
case "japanese":
hello = "こんにちは"
case "korean":
hello = "안녕하세요"
case "spanish":
hello = "Hola"
default:
hello = "Hello"
}
fmt.Printf("%s %s!!\n", hello, name)
return nil
},
},
{
Name: "thank",
Usage: "感謝します",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "name", // name フラグ
Value: "you", // デフォルト値は "you"
Usage: "相手の名前",
},
&cli.StringFlag{
Name: "lang", // lang フラグ
Value: "english", // デフォルト値は "english"
Usage: "挨拶の言語",
},
},
Action: func(ctx *cli.Context) error {
name := ctx.String("name") // Name フラグの値を取得
var thank string
lang := ctx.String("lang") // lang フラグの値を取得
switch lang {
case "japanese":
thank = "ありがとう"
case "korean":
thank = "고마워요"
case "spanish":
thank = "Gracias"
default:
thank = "Thank you"
}
fmt.Printf("%s %s!!\n", thank, name)
return nil
},
},
},
}
if err := app.Run(os.Args); err != nil {
log.Fatal(err)
}
}
# コンパイル
go build -o greet
# hello アクションを実行
./greet hello --name rito
# => Hello rito!!
# thank アクションを実行
./greet thank --name rito
# => Thank you rito!!
ヘルプを見てみると、2 つのアクションが使えるようになっていることがわかります。
% ./greet help
NAME:
greet - A new cli application
USAGE:
greet [global options] command [command options]
COMMANDS:
hello 挨拶します
thank 感謝します
help, h Shows a list of commands or help for one command
6. Subcommands - サブコマンド
サブコマンドは以下のような場合に使用します。
- 機能のグループ化が必要な場合
例:ユーザー管理
app user create
app user list
app user delete
- 関連する操作をまとめる場合
例:データベース操作
app db migrate
app db rollback
app db seed
- リソース単位で操作を分類する場合
例:Kubernetes ライクな構造
app pod start
app pod stop
app service create
app service delete
このような階層構造により、コマンドが整理され、ヘルプも見やすくなります。
これまでの実装に、wake(寝ている人を起こす)アクションをサブコマンドを使って「優しく起こす」と「強めに起こす」の 2 パターンで実装してみます。
func main() {
app := &cli.App{
Commands: []*cli.Command{
{
Name: "hello",
.
.
},
{
Name: "thank",
.
.
},
{
Name: "wake",
Usage: "寝ている人を起こします",
Aliases: []string{"w"},
Subcommands: []*cli.Command{
{
Name: "gentle",
Aliases: []string{"g"},
Usage: "優しく起こします",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "name",
Aliases: []string{"n"},
Value: "you",
Usage: "起こす人の名前",
},
&cli.StringFlag{
Name: "time",
Aliases: []string{"t"},
Value: "",
Usage: "現在時刻(デフォルトは現在時刻)",
},
},
Action: func(ctx *cli.Context) error {
name := ctx.String("name")
timeStr := ctx.String("time")
currentTime := time.Now()
if timeStr != "" {
parsedTime, err := time.Parse("15:04", timeStr)
if err == nil {
currentTime = parsedTime
}
}
var message string
hour := currentTime.Hour()
switch {
case hour < 5:
message = fmt.Sprintf("(小声で)%sさん、まだ深夜ですが...必要があって起こしました。申し訳ありません。", name)
case hour < 12:
message = fmt.Sprintf("おはようございます、%sさん。素敵な朝をお迎えください。朝食の用意ができています。", name)
default:
message = fmt.Sprintf("%sさん、お昼を過ぎていますよ。ゆっくり起きましょう。", name)
}
fmt.Println(message)
return nil
},
},
{
Name: "strong",
Aliases: []string{"s"},
Usage: "強めに起こします",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "name",
Aliases: []string{"n"},
Value: "you",
Usage: "起こす人の名前",
},
&cli.StringFlag{
Name: "time",
Aliases: []string{"t"},
Value: "",
Usage: "現在時刻(デフォルトは現在時刻)",
},
},
Action: func(ctx *cli.Context) error {
name := ctx.String("name")
timeStr := ctx.String("time")
currentTime := time.Now()
if timeStr != "" {
parsedTime, err := time.Parse("15:04", timeStr)
if err == nil {
currentTime = parsedTime
}
}
var message string
hour := currentTime.Hour()
switch {
case hour < 5:
message = fmt.Sprintf("%sさん!!緊急事態です!!直ちに起きてください!!", name)
case hour < 12:
message = fmt.Sprintf("%sさん!もう朝ですよ!!遅刻しますよ!!急いで起きてください!!", name)
default:
message = fmt.Sprintf("%sさん!!こんな時間まで寝てるんですか!?早く起きてください!!", name)
}
fmt.Println(message)
return nil
},
},
},
Action: func(ctx *cli.Context) error {
fmt.Println("'gentle' または 'strong' サブコマンドを指定してください。")
fmt.Println("使用例:")
fmt.Println(" wake gentle --name 田中 --time 07:30")
fmt.Println(" wake strong --name 田中")
return nil
},
},
},
}
if err := app.Run(os.Args); err != nil {
log.Fatal(err)
}
}
ヘルプは以下のようになります。
% ./greet help
NAME:
greet - A new cli application
USAGE:
greet [global options] command [command options]
COMMANDS:
hello 挨拶します
thank 感謝します
wake, w 寝ている人を起こします
help, h Shows a list of commands or help for one command
% ./greet wake help
NAME:
greet wake - 寝ている人を起こします
USAGE:
greet wake command [command options]
COMMANDS:
gentle, g 優しく起こします
strong, s 強めに起こします
help, h Shows a list of commands or help for one command
OPTIONS:
--name value, -n value 起こす人の名前 (default: "you")
--time value, -t value 現在時刻(デフォルトは現在時刻)
--help, -h show help
実際に実行してみると以下のような結果になります。
# 優しく起こす
% ./greet wake gentle --name 田中 --time 07:30
おはようございます、田中さん。素敵な朝をお迎えください。朝食の用意ができています。
# 強めに起こす
% ./greet wake strong --name 田中
田中さん!!こんな時間まで寝てるんですか!?早く起きてください!!
また、サブコマンドを指定しないときの表示も最下部のアクションで実装しています。
# サブコマンドを指定せず実行
% ./greet wake
'gentle' または 'strong' サブコマンドを指定してください。
使用例:
wake gentle --name 田中 --time 07:30
wake strong --name 田中
まとめ
- Urfave CLI は、Go 言語で CLI アプリケーションを構築するための軽量で人気のライブラリ
- コマンドとサブコマンドを簡単に定義・管理できる高い柔軟性
- 軽量で必要最低限の依存関係による高速な動作
- シンプルな API による使いやすさと直感的な設計
- ヘルプメッセージやエラーハンドリングを自由にカスタマイズ可能
- 小規模から大規模なプロジェクトまで幅広く対応できる汎用性
[Next] Step 3-2: Cobra