[インデックス 11001] ファイルの概要
このコミットは、Go言語の標準ライブラリであるflag
パッケージに、time.Duration
型のコマンドラインフラグを追加するものです。これにより、ユーザーはコマンドライン引数として時間間隔(例: 10s
, 5m
, 2h30m
)を直接指定できるようになり、プログラム内でtime.Duration
型として簡単に扱えるようになります。
コミット
- Author: David Symonds dsymonds@golang.org
- Date: Fri Dec 23 16:29:38 2011 +1100
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/cf506f6eacbe50661380f8d1e483b6375e423f06
元コミット内容
flag: add Duration flag type.
This works in the expected way: flag.Duration returns a *time.Duration,
and uses time.ParseDuration for parsing the input.
R=golang-dev, r
CC=golang-dev
https://golang.org/cl/5489113
変更の背景
Go言語のflag
パッケージは、コマンドライン引数をパースするための標準的な方法を提供します。しかし、このコミット以前は、time.Duration
型を直接サポートするフラグタイプが存在しませんでした。プログラムで時間間隔を扱う場合、通常はtime.Duration
型を使用しますが、コマンドラインから文字列として受け取った時間値を手動でtime.ParseDuration
などを使って変換する必要がありました。
このコミットは、このような一般的なユースケースに対応し、開発者がより簡潔かつ安全に時間間隔をコマンドライン引数として扱えるようにするために、Duration
フラグタイプを追加しました。これにより、他のプリミティブ型(int
, string
, bool
など)と同様に、time.Duration
もflag
パッケージの機能としてシームレスに利用できるようになります。
前提知識の解説
Go言語のflag
パッケージ
flag
パッケージは、Goプログラムがコマンドライン引数をパースするための機能を提供します。主な機能は以下の通りです。
- フラグの定義:
flag.Int()
,flag.String()
,flag.Bool()
などの関数を使って、フラグの名前、デフォルト値、説明(usage string)を定義します。これらの関数は、フラグの値が格納される変数のポインタを返します。 - フラグのパース:
flag.Parse()
を呼び出すことで、コマンドライン引数がパースされ、定義されたフラグ変数に値が設定されます。 flag.Value
インターフェース:flag
パッケージは、カスタムのフラグタイプを定義するためのValue
インターフェースを提供します。このインターフェースは以下の2つのメソッドを持ちます。Set(string) bool
: コマンドラインから読み取った文字列値を、フラグの型に変換して設定します。成功した場合はtrue
を返します。String() string
: フラグの現在の値を文字列として返します。これは、フラグのデフォルト値や現在の値を表示する際に使用されます。
time.Duration
型
time.Duration
は、Go言語のtime
パッケージで定義されている型で、ナノ秒単位の時間間隔を表します。これはint64
のエイリアスであり、時間計算やタイマー、タイムアウトなどの処理で広く使用されます。
time.ParseDuration
関数
time.ParseDuration
は、time
パッケージの関数で、"1h30m", "10s", "500ms"のような文字列をtime.Duration
型にパースします。この関数は、時間単位("ns", "us" (or "µs"), "ms", "s", "m", "h")を認識し、それに基づいてtime.Duration
値を返します。
fmt.Stringer
インターフェース
Go言語のfmt
パッケージで定義されているStringer
インターフェースは、String() string
メソッドを持つ型が満たすべきインターフェースです。このインターフェースを実装することで、fmt.Print
などの関数でその型の値を人間が読める形式の文字列として出力できるようになります。flag.Value
インターフェースもString()
メソッドを含むため、flag.Value
を実装する型は必然的にfmt.Stringer
も満たします。
技術的詳細
このコミットでは、flag
パッケージにtime.Duration
型のフラグを追加するために、以下の主要な変更が行われました。
time
パッケージのインポート:src/pkg/flag/flag.go
に"time"
パッケージがインポートされました。durationValue
型の定義:type durationValue time.Duration
この型は、time.Duration
を基底とする新しい型として定義され、flag.Value
インターフェースを実装します。newDurationValue
関数の追加:func newDurationValue(val time.Duration, p *time.Duration) *durationValue
このヘルパー関数は、durationValue
の新しいインスタンスを作成し、指定されたtime.Duration
値を初期値として設定します。durationValue
のSet
メソッドの実装:func (d *durationValue) Set(s string) bool
このメソッドは、コマンドラインから渡された文字列s
をtime.Duration
型に変換するためにtime.ParseDuration(s)
を使用します。パースが成功した場合、その値をdurationValue
の基底となるtime.Duration
に設定し、true
を返します。エラーが発生した場合はfalse
を返します。durationValue
のString
メソッドの実装:func (d *durationValue) String() string
このメソッドは、durationValue
の現在の値を文字列として返します。これは、time.Duration
型のString()
メソッド(例:1h30m0s
)を呼び出すことで実現されます。DurationVar
関数の追加:func (f *FlagSet) DurationVar(p *time.Duration, name string, value time.Duration, usage string)
func DurationVar(p *time.Duration, name string, value time.Duration, usage string)
これらの関数は、既存のIntVar
,StringVar
などと同様に、time.Duration
型のフラグを定義します。p
はフラグの値が格納されるtime.Duration
変数のポインタ、name
はフラグ名、value
はデフォルト値、usage
は説明文字列です。内部的にはnewDurationValue
を使ってdurationValue
を作成し、FlagSet.Var
またはcommandLine.Var
に渡します。Duration
関数の追加:func (f *FlagSet) Duration(name string, value time.Duration, usage string) *time.Duration
func Duration(name string, value time.Duration, usage string) *time.Duration
これらの関数は、DurationVar
と同様にtime.Duration
型のフラグを定義しますが、フラグの値が格納されるtime.Duration
変数のポインタを返します。これは、flag.Int()
やflag.String()
などの既存の関数と同様のパターンです。- テストの追加:
src/pkg/flag/flag_test.go
に、新しく追加されたDuration
フラグの動作を検証するためのテストケースが追加されました。これには、フラグの定義、値の設定、パース後の値の検証などが含まれます。
これらの変更により、flag
パッケージはtime.Duration
型をネイティブにサポートし、開発者はコマンドライン引数として時間間隔をより直感的に扱えるようになりました。
コアとなるコードの変更箇所
src/pkg/flag/flag.go
import (
"os"
"sort"
"strconv"
"time" // + timeパッケージのインポート
)
// ... 既存のコード ...
// -- time.Duration Value // + 新しいDurationフラグの型定義
type durationValue time.Duration
func newDurationValue(val time.Duration, p *time.Duration) *durationValue {
*p = val
return (*durationValue)(p)
}
func (d *durationValue) Set(s string) bool {
v, err := time.ParseDuration(s) // + time.ParseDurationを使用して文字列をパース
*d = durationValue(v)
return err == nil
}
func (d *durationValue) String() string { return (*time.Duration)(d).String() } // + time.DurationのString()メソッドを使用
// ... 既存のコード ...
// DurationVar defines a time.Duration flag with specified name, default value, and usage string.
// The argument p points to a time.Duration variable in which to store the value of the flag.
func (f *FlagSet) DurationVar(p *time.Duration, name string, value time.Duration, usage string) {
f.Var(newDurationValue(value, p), name, usage)
}
// DurationVar defines a time.Duration flag with specified name, default value, and usage string.
// The argument p points to a time.Duration variable in which to store the value of the flag.
func DurationVar(p *time.Duration, name string, value time.Duration, usage string) {
commandLine.Var(newDurationValue(value, p), name, usage)
}
// Duration defines a time.Duration flag with specified name, default value, and usage string.
// The return value is the address of a time.Duration variable that stores the value of the flag.
func (f *FlagSet) Duration(name string, value time.Duration, usage string) *time.Duration {
p := new(time.Duration)
f.DurationVar(p, name, value, usage)
return p
}
// Duration defines a time.Duration flag with specified name, default value, and usage string.
// The return value is the address of a time.Duration variable that stores the value of the flag.
func Duration(name string, value time.Duration, usage string) *time.Duration {
return commandLine.Duration(name, value, usage)
}
src/pkg/flag/flag_test.go
import (
"os"
"sort"
"testing"
"time" // + timeパッケージのインポート
)
var (
// ... 既存のテストフラグ定義 ...
test_duration = Duration("test_duration", 0, "time.Duration value") // + Durationフラグの追加
)
// ... 既存のテストコード ...
func TestEverything(t *testing.T) {
// ... 既存のテストケース ...
case f.Name == "test_duration" && f.Value.String() == desired+"s": // + Durationフラグのテストケース追加
ok = true
// ...
if len(m) != 8 { // + フラグの総数を7から8に変更
t.Error("VisitAll misses some flags")
for k, v := range m {
t.Log(k, *v)
}
}
// ...
Set("test_duration", "1s") // + Durationフラグに値を設定
desired = "1"
Visit(visitor)
if len(m) != 8 { // + フラグの総数を7から8に変更
t.Error("Visit fails after set")
for k, v := range m {
t.Log(k, *v)
}
}
// ...
}
func testParse(f *FlagSet, t *testing.T) {
// ... 既存のテストフラグ定義 ...
durationFlag := f.Duration("duration", 5*time.Second, "time.Duration value") // + Durationフラグの定義
extra := "one-extra-argument"
args := []string{
// ... 既存の引数 ...
"-duration", "2m", // + Durationフラグの引数追加
extra,
}
// ...
if *durationFlag != 2*time.Minute { // + Durationフラグの値の検証
t.Error("duration flag should be 2m, is ", *durationFlag)
}
// ...
}
コアとなるコードの解説
durationValue
型とflag.Value
インターフェースの実装
type durationValue time.Duration
:time.Duration
を基底とする新しい型を定義しています。これにより、time.Duration
の持つメソッド(例:String()
)を継承しつつ、flag.Value
インターフェースのメソッドを独自に実装できます。func (d *durationValue) Set(s string) bool
: このメソッドがflag
パッケージの核心部分です。コマンドラインから渡された文字列s
をtime.ParseDuration
関数を使ってtime.Duration
型に変換します。例えば、-duration=1h30m
という引数が与えられた場合、s
は"1h30m"
となり、これがtime.ParseDuration
によって適切なtime.Duration
値に変換され、d
(*time.Duration
へのポインタ)が指す変数に設定されます。パースに失敗した場合はfalse
を返し、flag
パッケージがエラーを処理できるようにします。func (d *durationValue) String() string
: このメソッドは、フラグの現在の値を文字列として表現するために使用されます。time.Duration
型自体がString()
メソッドを実装しているため、そのメソッドを呼び出すことで、例えば1h30m0s
のような標準的な時間間隔の文字列表現が得られます。これは、--help
オプションなどでフラグのデフォルト値や現在の値を表示する際に利用されます。
DurationVar
とDuration
ヘルパー関数
これらの関数は、他のプリミティブ型(Int
, String
など)と同様に、time.Duration
型のフラグを簡単に定義するためのものです。
DurationVar(p *time.Duration, name string, value time.Duration, usage string)
: この関数は、ユーザーが提供するtime.Duration
型の変数p
に、パースされたフラグの値を格納します。これは、プログラム内で特定の変数にフラグの値を直接バインドしたい場合に便利です。内部的には、newDurationValue
を使ってdurationValue
のインスタンスを作成し、それをFlagSet.Var
(またはグローバルなcommandLine.Var
)に渡して、カスタムのflag.Value
実装として登録します。Duration(name string, value time.Duration, usage string) *time.Duration
: この関数は、新しいtime.Duration
変数を内部的に作成し、そのポインタを返します。ユーザーは返されたポインタを介してフラグの値にアクセスします。これは、フラグの値を格納するための新しい変数を宣言する必要がある場合に、より簡潔な構文を提供します。
これらの変更により、flag
パッケージはtime.Duration
をファーストクラスの型として扱い、Go開発者はコマンドライン引数として時間間隔を扱う際のコードの記述量を減らし、エラーの可能性を低減できるようになりました。
関連リンク
- Go
flag
パッケージのドキュメント: https://pkg.go.dev/flag - Go
time
パッケージのドキュメント: https://pkg.go.dev/time time.ParseDuration
関数のドキュメント: https://pkg.go.dev/time#ParseDuration
参考にした情報源リンク
- Go言語の公式ドキュメント
- コミットメッセージと差分情報
- Go言語の
flag
パッケージのソースコード - Go言語の
time
パッケージのソースコード