flagパッケージを使うと、string型やint型、bool型のオプションを設定することができますが、オプションをsliceとして受け取るする場合などは独自の型を定義する必要があります。
flagパッケージの中身を読みながら独自の型を定義する方法をまとめていきます。
flagパッケージのドキュメント、中身をみる
flag.Var
flagパッケージのドキュメントをみると次の関数があります。
// Var defines a flag with the specified name and usage string. The type and // value of the flag are represented by the first argument, of type Value, which // typically holds a user-defined implementation of Value. For instance, the // caller could create a flag that turns a comma-separated string into a slice // of strings by giving the slice the methods of Value; in particular, Set would // decompose the comma-separated string into the slice. func Var(value Value, name string, usage string) { CommandLine.Var(value, name, usage) }
https://golang.org/pkg/flag/#Var
コメントを読むと
Var
は、指定された名前と使い方のフラグを定義します。フラグの型と値は第一引数のValue
で表現されます(Value
はインターフェース)。通常、このValue
は、ユーザーが定義するValue
インターフェースの実装です。 例えば、スライスにValue
インターフェースを実装することにより、カンマで区切られた文字列を文字列のスライスに変換するフラグを作ることができます。このとき、Set
はカンマ区切りの文字列をスライスに分割します。
ふーん?。といった感じですが、
Value型を定義して、Var関数を呼び出せば独自の型のオプションを定義できそうです。
Value インターフェース
Value
が何なのか見てみると、インターフェースになっていて、String() string
とSet(string) error
を定義していれば良いっぽいです。
// Value is the interface to the dynamic value stored in a flag. // (The default value is represented as a string.) // // If a Value has an IsBoolFlag() bool method returning true, // the command-line parser makes -name equivalent to -name=true // rather than using the next command-line argument. // // Set is called once, in command line order, for each flag present. // The flag package may call the String method with a zero-valued receiver, // such as a nil pointer. type Value interface { String() string Set(string) error }
https://golang.org/pkg/flag/#Value
Setとは?
まず、Set
が何をするものなのか調べます。
// bool の例 type boolValue bool func (b *boolValue) Set(s string) error { v, err := strconv.ParseBool(s) if err != nil { err = errParse } *b = boolValue(v) return err } // intの例 type intValue int func (i *intValue) Set(s string) error { v, err := strconv.ParseInt(s, 0, strconv.IntSize) if err != nil { err = numError(err) } *i = intValue(v) return err }
https://golang.org/src/flag/flag.go#L116
文字列型の値をフラグの型に変換しています。先程のVar
関数のコメントにあった
Set would decompose the comma-separated string into the slice.
(Setはカンマ区切りの文字列をスライスに分割します。)
の意味がわかりましたね。
Setメソッドは型で渡されるコマンドライン引数の値を、各フラグの型に変換する処理のようです。
String()とは?
次にString()
が何なのかを調べます。 そのために、もう一度Var
func Var(value Value, name string, usage string) { CommandLine.Var(value, name, usage) }
CommandLine
はパッケージ変数で*flag.FlagSet
var CommandLine = NewFlagSet(os.Args[0], ExitOnError)
と定義されています。
のVar
// Var defines a flag with the specified name and usage string. The type and // value of the flag are represented by the first argument, of type Value, which // typically holds a user-defined implementation of Value. For instance, the // caller could create a flag that turns a comma-separated string into a slice // of strings by giving the slice the methods of Value; in particular, Set would // decompose the comma-separated string into the slice. func (f *FlagSet) Var(value Value, name string, usage string) { // Remember the default value as a string; it won't change. flag := &Flag{name, usage, value, value.String()} _, alreadythere := f.formal[name] if alreadythere { var msg string if f.name == "" { msg = fmt.Sprintf("flag redefined: %s", name) } else { msg = fmt.Sprintf("%s flag redefined: %s", f.name, name) } fmt.Fprintln(f.Output(), msg) panic(msg) // Happens only if flags are declared with identical names } if f.formal == nil { f.formal = make(map[string]*Flag) } // flagが未定義なら登録 f.formal[name] = flag }
https://golang.org/pkg/flag/#FlagSet.Var
Value
型の値とフラグの名前、使い方から*flag.Flag
*flag.Flag
型の値を生成するときの4つ目のフィールドの値にString()
type Flag struct { Name string // name as it appears on command line Usage string // help message Value Value // value as set DefValue string // default value (as text); for usage message }
https://golang.org/pkg/flag/#Flag
どうやらString() は、使い方メッセージ内のデフォルト値の表示に使われるようです。
- 関数を使えば独自の型のオプションを定義できそう
Var
関数にはValue
インターフェースを満たした値を渡す必要があるValue
インターフェースを満たすには、String()
とSet(string) error
を実装する必要があるString()
はヘルプメッセージ内のデフォルト値(文字列)を返す処理-
Set(string) error
カンマ区切りの数値をint
のスライスとして受け取れるオプションを作ってみます。
やることは、
- 独自の型
intSliceValue
-
flag.Var()
を使ってオプションを設定
package main import ( "errors" "flag" "fmt" "strconv" "strings" ) func main() { var numbers []int flag.Var((*intSliceValue)(&numbers), "numbers", "usage") // numbersを独自に定義したフラグの型に変換ƒ flag.Parse() fmt.Println(numbers) } type intSliceValue []int func (v *intSliceValue) Set(s string) error { strs := strings.Split(s, ",") ints := make([]int, len(strs)) var err error for i, v := range strs { ints[i], err = strconv.Atoi(v) if err != nil { return errors.New("parse error") } } *v = append(*v, ints...) return nil } func (v *intSliceValue) String() string { return "" }
実行結果