「urfave/cli」を用いて「hlt」というCLIを作成した際に、設定ファイルを用いてコマンドのオプションの初期値をカスタマイズする方法を調べたので書いておく。
何がしたかったのか
CLIのオプションの初期値を設定ファイルを用いて外部から設定可能にしたい。
number: 9
word: message
上記のような設定ファイルを用意した場合、以下を実行しただけで、number = 9
, word = "message"
となって欲しい。
# CLIを実行
$ cli
# number = 9
# word = "message"
解決策
「urfave/cli」のREADMEにも書いてあるが、「urfave/cli/altsrc」を用いるとできる。
しかし、READMEに書いてあるサンプルコードは、オプションによって設定ファイルを指定して動作するものになっていたので、今回のように最初から設定ファイルが決まっている場合向けではなかった。
また、サンプルコードをそのままコピペして実行してみても、型の不一致で実行できなかった...
それらの解決方法を説明する前に、先に解決した際のコードを載せておく。
package main
import (
"fmt"
"log"
"os"
"gopkg.in/urfave/cli.v1" // "github.com/urfave/cli"ではうまくいかない
"github.com/urfave/cli/altsrc" // 設定ファイルを用いるためのパッケージ
)
func main() {
app := cli.NewApp()
// オプションの用意。初期値をファイルで決めるオプションは`altsrc.NewXXXFlag()`を用いて定義する
flags := []cli.Flag{
altsrc.NewIntFlag(cli.IntFlag{Name: "number"}),
altsrc.NewStringFlag(cli.StringFlag{Name: "word"}),
}
app.Action = func(c *cli.Context) error {
fmt.Println("number:", c.Int("number"))
fmt.Println("word:", c.String("word"))
return nil
}
// 実行時にファイルを読み込み、オプションの初期値を設定させる
app.Before = altsrc.InitInputSource(flags, func() (altsrc.InputSourceContext, error) {
return altsrc.NewYamlSourceFromFile("config.yaml")
})
app.Flags = flags
err := app.Run(os.Args)
if err != nil {
log.Fatal(err)
}
}
決まった設定ファイルを読み込む
サンプルコードでは以下のコードが用いられており、これは、load
オプションに設定されたファイルから読み込むプログラムとなっている。
altsrc.InitInputSourceWithContext(flags, altsrc.NewYamlSourceFromFlagFunc("load"))
ファイルを直接指定して読み込むには、以下のコードを用いると良い。
altsrc.NewYamlSourceFromFile("config.yaml")
これならば、config.yaml
を直接読み込ませることができる。
しかし、altsrc.NewYamlSourceFromFlagFunc()
をaltsrc.NewYamlSourceFromFile()
にしただけでは、うまくいかない。前者は返り値が関数なのに対して、後者は関数ではないため、単純に型が合わない。
なので、altsrc.InitInputSourceWithContext()
の代わりにaltsrc.InitInputSource()
を用い、さらに、altsrc.NewYamlSourceFromFile()
を無名関数でラップする。
ラップする理由は、altsrc.InitInputSource()
の第二引数がfunc() (altsrc.InputSourceContext, error)
を要求しているのに対して、altsrc.NewYamlSourceFromFile()
がaltsrc.InputSourceContext, error
を返すためである。ラップすることで型を合わせている。
altsrc.InitInputSource(flags, func() (altsrc.InputSourceContext, error) {
return altsrc.NewYamlSourceFromFile("config.yaml")
})
後は、これをapp.Before
などに渡すことで自動で実行してくれる。
これによってようやく、決まった設定ファイルを読み込むことができるようになる。
型エラーを回避する
もう一つの問題点として、サンプルコードを実行してみても型エラーが発生してうまく動かない。
この問題は、「urfave/cli/altsrc」のコードのimport
部分を見てみると原因がすぐわかる。
サンプルコードのimport
部分は以下のようになっている。
import (
"github.com/urfave/cli"
"github.com/urfave/cli/altsrc"
)
これに対して、github.com/urfave/cli/altsrc
のimport
部分は、
import (
"gopkg.in/urfave/cli.v1"
)
となっていて、altsrc
の関数が返すcli.XXX
の型はgopkg.in/urfave/cli.v1
を参照して定義されているのに対して、サンプルコード内のcli.XXX
型はgithub.com/urfave/cli
を参照して定義されている。
なので、実行時に違う型として判定されてしまい、型エラーが発生している。
これを回避するには、サンプルコードのimport
部分を以下のようにすれば良い。
import (
"github.com/urfave/cli/altsrc"
"gopkg.in/urfave/cli.v1"
)
おわりに
せっかく調べたけども、このパッケージではやりたいことがうまくできなかったので、結局欲しい機能だけ自分で実装する羽目になってしまった...