4
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

codegangsta/cli の YAML ファイルの読み込み機能を試してみた

Last updated at Posted at 2016-03-24

https://github.com/codegangsta/cli のオプションを YAML ファイルからロードする機能がついたようなので試してみたのでメモします。

パッケージを準備する

いきなりですが、YAMLファイルの読み込みが実装されたコミットそのもののリビジョンを利用しても動きません。それに対して自分が行ったコントリビューションがあるのですが、これより後のリビジョンが利用されるようにしてください(最新バージョンを利用すれば良いと思います)。

codegangsta/cli/altsrc パッケージ

まず、完全に動作するサンプルを https://github.com/ykanda/cli-example に用意しましたので、省略されていないコードを見たいかた、実際に動作させたい方は、こちらを見てください。

さて、YAML ファイルのロード機能は、codegangsta/cli のサブパッケージ codegangsta/cli/altsrc として実装されています。これを元々の codegangsta/cli と組み合わせて使うことで、オプションで読み込みファイルを指定したり、ファイルから入力できるオプションを作ることができます。
cli.App のオプションフラグについて、YAML ファイルのロードをサポートするための簡単なサンプルコードは次のようになります。

package main

import "fmt"
import "os"

import "github.com/codegangsta/cli"
import "github.com/codegangsta/cli/altsrc"

func main() {
  app := cli.NewApp()
  app.Flags = []cli.Flag{
    altsrc.NewStringFlag(
      cli.StringFlag{
        Name:  "test",
        Value: "test default value",
      },
    ),
    cli.StringFlag{
      Name:  "load",
      Value: "./.rc1",
    },
  }
  app.Action = func(ctx *cli.Context) {
    opt := ctx.String("test")
    fmt.Println("cli-example --test =", opt)
  }
  app.Before = altsrc.InitInputSourceWithContext(
    app.Flags,
    altsrc.NewYamlSourceFromFlagFunc("load"),
  )
  app.Run(os.Args)
}

このコードは、カレントディレクトリにある .rc1 というファイルをロードするようになっています。このファイルの中身は、次のようなものとします。

test: "test by .rc1"

このコードをビルドして実行します。
オプションを指定しない場合、ファイルから読み込んだ値が利用されます。
読み込む元なるファイルは、--load オプションによって指定される値が利用されていて、
メインコマンドの --load オプションは、デフォルト値として "./.rc1 " を持っていて、
特別に指定しなくとも .rc1 ファイルが利用されるというわけです。

$ ./cli-example 
cli-example --test = test by .rc1

--test オプションを指定することで、オプションをコマンドラインから指定することもできます。こうして指定されたオプションは、ファイルからの入力より優先されます。

$ ./cli-example --test "foo"
cli-example --test = foo

オプションを指定するキーを持たないファイルを読むと、デフォルト値が使われます。
ためしに、/dev/null あたりを読んでみると次のようになります。

$ ./cli-example --load /dev/null     
cli-example --test = test default value

少し詳しい解説

altsrc を利用するときのポイントは次のとおりです。

  • App.Flags に指定する []cli.Flag の要素を alstc.New*Flag で生成する
  • Before フックを altsrc.InitInputSourceWithContext で生成する

altsrc.New*Flag() によるオプション生成

ファイル入力によって指定したいオプションは、altsrc.New*Flag() 系の関数を使います。
これには NewIntFlag() であるとか NewStringFlag() といった種類があり、目的とする値の型によって使い分けます。

app := cli.NewApp()
app.Flags = []cli.Flag{
  altsrc.NewStringFlag( // ファイル入力するオプションを生成
    cli.StringFlag{
      Name:  "test",
      Value: "test default value",
    },
  ),
}

Before フックを altsrc.InitInputSourceWithContext で生成する

cli.App には Before というフィールドがあり、これにはオプションが解析される前段階における処理を行うフック関数を指定することができます。cli/altsrc によるファイルからのオプション読み込みはこれを利用して行います。自前でやろうとしたら、ファイルの入力、YAMLのパース、一致するオプションを探してその値を設定する、といったことを行わなければならないことは想像できると思います。

これらの処理を行う関数を生成することができるのが、altsrc.InitInputSourceWithContext() というユーティリティ関数です。次のような使い方をします。

app := cli.NewApp()
app.Flags = []cli.Flag{
  cli.StringFlag{
    Name:  "load",
    Value: "./.rc1",
  },
}
app.Before = altsrc.InitInputSourceWithContext(
  app.Flags,
  altsrc.NewYamlSourceFromFlagFunc("load"),
)

上記の例では、第一引数に []cli.Flag、第二引数に cli.NewYamlSourceFromFlagFunc("load") という関数の戻り値を取っています。この辺がすこしややこしいですが、--load というフラグの値を使ってファイルを読むようにする、という意味があるという形で理解しておけばよいでしょう。

サブコマンドのオプションをファイルから読み込む

codegangsta/cli にはサブコマンドを作る機能があり、これに対してもファイルからオプションを読み込む機能を提供することができます。基本的には cli.App に対する場合と代わりませんので、いきなりコードからみてみましょう。

subCommand := cli.Command{}
subCommand.Name = "sub"
subCommand.Action = func(ctx *cli.Context) {
  opt := ctx.String("test-sub")
  fmt.Println("cli-example sub --test-sub =", opt)
}
subCommand.Flags = []cli.Flag{
  altsrc.NewStringFlag(
    cli.StringFlag{
      Name:  "test-sub",
      Value: "test-sub default value",
    },
  ),
  cli.StringFlag{
    Name:  "load",
    Value: "./.rc2",
  },
}
subCommand.Before = altsrc.InitInputSourceWithContext(
  subCommand.Flags,
  altsrc.NewYamlSourceFromFlagFunc("load"),
)

app := cli.NewApp()
app.Commands = []cli.Command{
  subCommand,
}
app.Run(os.Args)

単に、cli.App.Command の要素となる cli.Command に対して cli.App と同じように設定していると考えれば、さほどむずかしいことはないでしょう。設定ファイルは次のようなものとしてみましょう。

test-sub: "test-sub by .rc2"

実行結果は次のようになります。サブコマンドを与えると、サブコマンドのアクションが実行されることになりますが、そのとき .rc2 というファイルから入力された値が使われます。オプションを指定するキーを持たないファイルを渡したときや、オプションを明示的に渡したときの動作も全く同様となります。

$ ./cli-example sub             
cli-example sub --test-sub = test-sub by .rc2

サブサブコマンドのオプションをファイルから読み込む

cli.Command はさらに Subcommand というフィールドを持ち、サブコマンドのサブコマンド、たとえば git remote add などのような形のコマンドを作ることができます。Subcommands の型は実に []cli.Command であるため、まったく同じような形とすることで、同じようにファイルからオプションをロードすることができます。ここでは詳細な説明は省きますが、冒頭で述べたとおり、完全に動作するサンプルを https://github.com/ykanda/cli-example に用意しましたので、そちらをごらんください。

暗黙的にホームディレクトリ以下にあるファイルを読むようにする

--load オプションに設定するデフォルト値によって、暗黙的にユーザのホームディレクトリ直下にあるファイルを読むようにしたい、という場合は https://github.com/codegangsta/cli/pull/348#issuecomment-208034424 に示されているように os/user パッケージを用いると良いようです。

u, err := user.Current()
// do something if err != nil
subCommand := new(cli.Command)
subCommand.Flags = []cli.Flag{
    cli.StringFlag{
        Name:   "load",
        Value:  filepath.Join(user.HomeDir, ".mysrc"),  // default value
    },
}
subCommand.Before = altsrc.InitInputSourceWithContext(
    subCommandCommand.Flags,
    altsrc.NewYamlSourceFromFlagFunc("load"),
)
4
3
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
4
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?