LoginSignup
18
8

More than 3 years have passed since last update.

どうでもいいからささっとGoでCLIつくりたいとき

Last updated at Posted at 2018-12-06

体裁はどうでもいいからちゃちゃっとCLIツールをGoで作りたいときにどうしているかというお話。

この記事はパーソルキャリア Advent Calendar 2018の6日目です。

CLIのフレームワークは spf13/cobra を使います。
数年前からあってそれなりにメンテナンスされていて、デファクトスタンダード化しつつあるし楽なので

ディレクトリ構成

パッケージのディレクトリ構成はこんな感じです。
git-ltsがかつて用いていた構成を参考にされてもらってます。
実行ファイルと実際のコマンドを分けています。
これはビルドせずにgo runで実行したときに、main.goだけ指定すればいいようにするためです

構成
.
├── main.go
└── commands
    ├── commands.go
    └── commands_xxx.go

ファイル構成

main.go

コマンドを実行する処理を呼んでいるだけです。定形。

main.go
package main

import "github.com/yourname/package_name/commands"

func main() {
    commands.Run()
}

commands.go

コマンドのメイン処理になります。
foobar が、コマンド名になるので、適当な名称に差し替えてください。
それ以外はこのままで使えます。

Exitは、os.Exitをwrapした処理です。
os.Exitを使う場合、os.Exitはエラーメッセージを標準出力できないので、os.Exitの前にfmtlogを使って出力する必要があります。
log.Fatalを使う手もあるのですが、log.Fatalはエラーコードが1固定なので、指定したいときに問題です。

commands.go
package commands

import (
    "fmt"
    "os"

    "github.com/spf13/cobra"
)

var (
    // RootCmd defines root command
    RootCmd = &cobra.Command{
        Use: "foobar",
        Run: func(cmd *cobra.Command, args []string) {
            cmd.Usage()
        },
    }
)

// Run runs command.
func Run() {
    RootCmd.Execute()
}

// Exit finishes a runnning action.
func Exit(err error, codes ...int) {
    var code int
    if len(codes) > 0 {
        code = codes[0]
    } else {
        code = 2
    }
    if err != nil {
        fmt.Println(err)
    }
    os.Exit(code)
}

commands_xxx.go

ファイル名とコード内のxxxはサブコマンド名です。適当な名称に差し替えてください。
サブコマンドはコマンド単位でファイルを切ります。
コマンドの役割と、内容を明確にするためです。
varとinit()で自動的にサブコマンドがコマンドに登録されるようにしています。
実際の実行はxxxCommandが、
処理内容はxxxAction
担保するようにします。
これによって、テストを書きたいときにxxxActionが、きちんとテストできていれば問題ないようにします。
またxxxActionの内容をxxxCommandで書いてしまうと、xxxCommandはエラー時にos.Exitを返さないければいけないので、テスト時に複雑性が上がります。

xxxCommandでエラーコードを指定したい場合は、
エラー毎に定義をして、switch-case文でふりわけるか、xxxActionでエラーコードもあわせて返すようにします。

commands_xxx.go
package commands

import (
    "github.com/spf13/cobra"
)

var (
    xxxCmd = &cobra.Command{
        Use: "xxx",
        Run: xxxCommand,
    }
)

func xxxCommand(cmd *cobra.Command, args []string) {
    if err := xxxAction(); err != nil {
        Exit(err, 1)
    }
}

func xxxAction() (err error) {
    // 実行したい内容
}

func init() {
    RootCmd.AddCommand(xxxCmd)
}
18
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
18
8