dockerでも使用されている「spf13/cobra」を用いてCLIを作成してみた

今回作成してみたのは新規作成ではなく、過去に投稿した「[任意の文字列や行をハイライトするツールをGo言語で作成してみた](https://qiita.com/x-color/items/bb4c4054083c1ef0b941)」で作成した、テキストをハイライトするコマンドを「[cspf13/cobra](https://github.com/spf13/cobra)」を用いて実装し直したものです。

実装に用いた「[cspf13/cobra](https://github.com/spf13/cobra)」は、CLI作成用パッケージです。これは、「[docker](https://www.docker.com/)」などでも用いられているとても便利なパッケージで簡単にCLIを作成することができます。


作成したCLIは指定した文字列を含む行や文字列を色付けしたり、太字や斜体にしたりして、テ
キストを見やすくすることを目的としています。

**指定した文字列を含む行をマーキング**

![line command](https://qiita-image-store.s3.amazonaws.com/0/279498/10cfba4c-e7c5-e429-1122-693fe94b46e5.gif)

**行番号を指定してマーキング**

![number command](https://qiita-image-store.s3.amazonaws.com/0/279498/c383421b-1b4f-9a29-0625-8abdec40e4b0.gif)


作成したCLIは https://github.com/x-color/mkr に置いてありますので、ご自由にお使いください。

以下では、作成時に詰まったフラグのソートや、前回使用した「[urfave/cli](https://github.com/urfave/cli)」よりも楽にできる設定ファイル関連の実装部分などを紹介していきます。

# フラグのソート機能のオフ

作成時に少し困ったのは、フラグのソート機能がデフォルトでオンになっている点でした。
これによって、フラグのグループに沿って表示順を決めることができません。

表示順を自分で決めたい場合、このソート機能をオフにする必要があります。
以下のようにすることでオフにすることができます。

```go
// サブコマンドにも継承されるフラグ（グローバルフラグ）のソートを無効にする
cmd.PersistentFlags().SortFlags = false
// 親コマンドから継承されたフラグ（グローバルフラグ）のソートを無効にする
cmd.InheritedFlags().SortFlags = false
// フラグの種類を混同したソートを無効にする
cmd.Flags().SortFlags = false
```

`cmd.Flags().SortFlags = false`がわかりにくいと思いますが、以下の例を見てください。

```go
// 継承するフラグ
cmd.PersistentFlags().StringP("C-global", "C", "", "")
cmd.PersistentFlags().StringP("B-global", "B", "", "")
cmd.PersistentFlags().StringP("A-global", "A", "", "")
// 継承しないフラグ
cmd.Flags().StringP("C-local", "c", "", "")
cmd.Flags().StringP("B-local", "b", "", "")
cmd.Flags().StringP("A-local", "a", "", "")
```

と用意した際に、

```go
cmd.Flags().SortFlags = true
```

と設定すると以下のように表示されます。

```:コマンドのヘルプメッセージ
Flags:
 --A-global
 --A-local
 --B-global
 --B-local
 --C-global
 --C-local
```

逆に

```go
cmd.Flags().SortFlags = false
```

と設定すると

```:コマンドのヘルプメッセージ
Flags:
 --A-global
 --B-global
 --C-global
 --A-local
 --B-local
 --C-local
```

のように表示されます。
`cmd.Flags().SortFlags = false`を設定することで、フラグの種類(継承するフラグかどうかなど)が混合することを防いでくれます。

# 設定ファイルを用いたフラグの初期値カスタム機能の作成

「spf13/cobra」のREADME内でも書かれていますが、設定ファイル関連のパッケージである「[spf13/viper](https://github.com/spf13/viper)」を用いることでとても簡単に実装することができます。
前に使用したパッケージだと結構苦戦したのですが、今回は用意されている設定ファイル用のパッケージが強力なのでしたいことが楽に実装できました。

```go
// エラー処理などは省略
func readCfgFile(cfgFile string) {
    viper.SetConfigFile(cfgFile)
    viper.ReadInConfig()    
}
```

以上のコードだけで、`cfgFile`内のデータを読み取り`viper`が管理してくれます。
読み取ったデータは、

```go
viper.GetString("data")
```

などで、読み出すことができます。

コマンドのフラグの初期値を外部の設定ファイルで指定可能にしたい場合は、

```go
// viper内で管理している'data'に紐付いたデータとコマンドの'flag'フラグを紐付ける
viper.BindPFlag("data", cmd.Flags().Lookup("flag"))
data := viper.Get("data")
```

のようにしておくと、実行時に以下のようになります。

```bash
$ cmd
# data = 'cfgFile内で設定された値' (フラグを指定しない場合)

$ cmd --flag test-data
# data = 'test-data' (フラグを指定した場合)
```

これで、フラグの初期値を外部ファイルでカスタムできるようになります。

# 参考にしたサイト

**コマンド作成全般**
- 「spf13/cobra」の[README](https://github.com/spf13/cobra)
- 「spf13/cobra」の[GoDoc](https://godoc.org/github.com/spf13/cobra)
- 「[Golangのコマンドライブラリcobraを使って少しうまく実装する](https://qiita.com/tkit/items/3cdeafcde2bd98612428)」

**設定ファイルに関して**
- 「spf13/viper」の[README](https://github.com/spf13/viper)
- 「spf13/viper」の[GoDoc](https://godoc.org/github.com/spf13/viper)

**ソートのオンオフに関して**
- 「spf13/pflag」の[GoDoc](https://godoc.org/github.com/spf13/pflag)