[インデックス 12206] ファイルの概要
このコミットは、Go言語のコマンドラインツール go list
において、出力がない場合に余分な空行が印字される問題を修正するものです。特に、go list -f "..."
のようにカスタムフォーマットを指定し、そのフォーマットが特定の条件で何も出力しない場合に、大量の空行が生成されるのを防ぐための変更が加えられました。
コミット
commit 1086dd7cfb70e382d6bb3242d26e7f673fffb808
Author: Rob Pike <r@golang.org>
Date: Sat Feb 25 08:00:55 2012 +1100
cmd/go: in list, don't print blank lines for no output
Otherwise
go list -f "{{if .Stale}}{{.ImportPath}}{{end}}" all
and similar commands can print pages of empty lines.
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/5696058
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/1086dd7cfb70e382d6bb3242d26e7f673fffb808
元コミット内容
go list
コマンドは、Goパッケージの情報を表示するためのツールです。-f
フラグを使用すると、text/template
パッケージのテンプレート構文を用いて出力フォーマットをカスタマイズできます。
このコミット以前は、go list -f "..."
のようにカスタムフォーマットを指定した場合、テンプレートの評価結果が空文字列であっても、go list
は常に改行を出力していました。これにより、例えば go list -f "{{if .Stale}}{{.ImportPath}}{{end}}" all
のように、特定の条件(この例ではパッケージが古い場合)でのみ出力を行うテンプレートを使用すると、条件に合致しないパッケージに対しては空行が大量に生成され、出力が非常に読みにくくなるという問題がありました。
変更の背景
go list
コマンドは、スクリプトや他のツールからGoパッケージの情報をプログラム的に取得する際に非常に強力な機能を提供します。しかし、前述の空行の問題は、特に大量のパッケージを処理する場合や、特定の条件に合致するパッケージのみを抽出したい場合に、出力のパースを困難にし、無駄な出力を生み出していました。
この変更は、go list
の出力をよりクリーンにし、プログラムによる処理を容易にすることを目的としています。出力が実際にあった場合にのみ改行を付加することで、ユーザー体験とスクリプトの堅牢性を向上させます。
前提知識の解説
go list
コマンド: Go言語のビルドシステムの一部であり、Goパッケージに関する情報を表示するために使用されます。パッケージのパス、依存関係、ビルド情報など、様々なメタデータにアクセスできます。text/template
パッケージ: Go言語の標準ライブラリの一部で、テキストベースのテンプレートを生成するための機能を提供します。HTML、XML、プレーンテキストなど、様々な形式の出力を動的に生成するのに使われます。go list -f
で使用されるテンプレートエンジンもこれに基づいています。bufio.Writer
:io.Writer
インターフェースを実装するバッファリングされたライターです。書き込み操作をバッファリングすることで、I/Oの効率を向上させます。通常、os.Stdout
のような低レベルのライターをラップして使用されます。io.Writer
インターフェース: Go言語の標準ライブラリio
パッケージで定義されているインターフェースで、データを書き込むための抽象化を提供します。Write([]byte) (n int, err error)
メソッドを持ちます。
技術的詳細
このコミットの核心は、go list
がテンプレートの評価結果を標準出力に書き込む際に、実際にデータが書き込まれたかどうかを追跡する新しい CountingWriter
型を導入した点にあります。
従来の go list
の実装では、text/template
の Execute
メソッドが bufio.Writer
に直接書き込みを行い、その後、無条件に改行文字 (\n
) を出力していました。このため、テンプレートが何も出力しなくても改行だけが印字されていました。
新しいアプローチでは、以下の変更が行われました。
-
CountingWriter
の導入:CountingWriter
はbufio.Writer
を内部に持ち、io.Writer
インターフェースを実装します。Write
メソッドが呼び出されるたびに、書き込まれたバイト数をcount
フィールドに加算します。Reset
メソッドを提供し、count
を0にリセットできるようにします。これは、各パッケージの処理を開始する前に、そのパッケージの出力カウントをリセットするために使用されます。Count
メソッドを提供し、現在の書き込みバイト数を取得できるようにします。
-
go list
の出力ロジックの変更:runList
関数内で、os.Stdout
を直接bufio.NewWriter
でラップする代わりに、newCountingWriter(os.Stdout)
を使用してCountingWriter
のインスタンスを作成します。- テンプレートのパース時に、
*listFmt + "\n"
ではなく、*listFmt
のみをパースするように変更されました。これにより、テンプレート自体が改行を出力しないようになります。 - 各パッケージの処理ループ内で、テンプレートを実行する前に
out.Reset()
を呼び出し、そのパッケージの出力カウントをリセットします。 - テンプレートの実行後、
out.Count() > 0
をチェックし、実際に何らかのデータが書き込まれた場合にのみout.w.WriteRune('\n')
を呼び出して改行を出力します。
この変更により、テンプレートが空文字列を生成した場合、CountingWriter
の count
は0のままであり、結果として余分な改行は出力されなくなります。
コアとなるコードの変更箇所
src/cmd/go/list.go
ファイルに以下の変更が加えられました。
--- a/src/cmd/go/list.go
+++ b/src/cmd/go/list.go
@@ -7,6 +7,7 @@ package main
import (
"bufio"
"encoding/json"
+ "io" // 追加
"os"
"text/template"
)
@@ -82,8 +83,8 @@ var listJson = cmdList.Flag.Bool("json", false, "")
var nl = []byte{'\n'}
func runList(cmd *Command, args []string) {
- out := bufio.NewWriter(os.Stdout)
- defer out.Flush()
+ out := newCountingWriter(os.Stdout) // CountingWriterを使用
+ defer out.w.Flush() // 内部のbufio.WriterのFlushを呼び出す
var do func(*Package)
if *listJson {
@@ -97,15 +98,19 @@ func runList(cmd *Command, args []string) {
out.Write(nl)
}
} else {
- tmpl, err := template.New("main").Parse(*listFmt + "\n") // 改行をテンプレートから削除
+ tmpl, err := template.New("main").Parse(*listFmt)
if err != nil {
fatalf("%s", err)
}
do = func(p *Package) {
- if err := tmpl.Execute(out, p); err != nil {
+ out.Reset() // 各パッケージ処理前にカウントをリセット
+ if err := tmpl.Execute(out, p); err != nil { // CountingWriterに書き込む
out.Flush()
fatalf("%s", err)
}
+ if out.Count() > 0 { // 出力があった場合のみ改行
+ out.w.WriteRune('\n')
+ }
}
}
@@ -118,3 +123,33 @@ func runList(cmd *Command, args []string) {
do(pkg)
}
}
+
+// CountingWriter counts its data, so we can avoid appending a newline
+// if there was no actual output.
+type CountingWriter struct {
+ w *bufio.Writer
+ count int64
+}
+
+func newCountingWriter(w io.Writer) *CountingWriter {
+ return &CountingWriter{
+ w: bufio.NewWriter(w),
+ }
+}
+
+func (cw *CountingWriter) Write(p []byte) (n int, err error) {
+ cw.count += int64(len(p))
+ return cw.w.Write(p)
+}
+
+func (cw *CountingWriter) Flush() {
+ cw.w.Flush()
+}
+
+func (cw *CountingWriter) Reset() {
+ cw.count = 0
+}
+
+func (cw *CountingWriter) Count() int64 {
+ return cw.count
+}
コアとなるコードの解説
CountingWriter
構造体
type CountingWriter struct {
w *bufio.Writer
count int64
}
w *bufio.Writer
: 実際の書き込みを行うbufio.Writer
のインスタンスを保持します。これは、標準出力へのバッファリングされた書き込みを処理します。count int64
:Write
メソッドが呼び出されるたびに、書き込まれたバイト数を累積するカウンターです。
newCountingWriter
関数
func newCountingWriter(w io.Writer) *CountingWriter {
return &CountingWriter{
w: bufio.NewWriter(w),
}
}
io.Writer
を受け取り、それをbufio.NewWriter
でラップしてCountingWriter
の新しいインスタンスを返します。これにより、CountingWriter
は任意のio.Writer
に対応できるようになります。
Write
メソッド
func (cw *CountingWriter) Write(p []byte) (n int, err error) {
cw.count += int64(len(p))
return cw.w.Write(p)
}
io.Writer
インターフェースのWrite
メソッドを実装します。- 引数
p
の長さ (len(p)
) をcw.count
に加算し、書き込まれたバイト数を追跡します。 - 実際の書き込みは内部の
cw.w.Write(p)
に委譲します。
Flush
メソッド
func (cw *CountingWriter) Flush() {
cw.w.Flush()
}
- 内部の
bufio.Writer
のFlush
メソッドを呼び出し、バッファリングされたデータを強制的に基になるio.Writer
(この場合はos.Stdout
) に書き出します。
Reset
メソッド
func (cw *CountingWriter) Reset() {
cw.count = 0
}
count
フィールドを0にリセットします。これは、各パッケージの出力処理を開始する前に呼び出され、そのパッケージの出力が実際にあったかどうかを正確に判断するために使用されます。
Count
メソッド
func (cw *CountingWriter) Count() int64 {
return cw.count
}
- 現在の
count
の値(書き込まれたバイト数)を返します。この値が0より大きい場合にのみ改行を出力するというロジックで使用されます。
runList
関数内の変更
out := newCountingWriter(os.Stdout)
:os.Stdout
をラップするCountingWriter
が作成され、out
変数に割り当てられます。defer out.w.Flush()
:runList
関数が終了する際に、内部のbufio.Writer
のバッファをフラッシュするように変更されました。tmpl, err := template.New("main").Parse(*listFmt)
: テンプレート文字列から無条件の改行が削除されました。これにより、テンプレート自体は改行を出力しなくなります。out.Reset()
: 各パッケージのテンプレートを実行する直前に呼び出され、そのパッケージの出力バイト数をリセットします。if out.Count() > 0 { out.w.WriteRune('\n') }
: テンプレートの実行後、CountingWriter
のCount()
メソッドをチェックし、もし出力が1バイトでもあった場合(count > 0
)、内部のbufio.Writer
を通じて明示的に改行文字 (\n
) を出力します。
これらの変更により、go list
は、テンプレートが実際に何かを出力した場合にのみ改行を付加するようになり、出力のクリーンさが大幅に向上しました。
関連リンク
- Go CL 5696058: https://golang.org/cl/5696058
参考にした情報源リンク
- Go言語の公式ドキュメント (
go list
,text/template
,bufio
,io
パッケージ) - Go言語のソースコード (
src/cmd/go/list.go
) - Go言語のコミット履歴
go list
の使用例に関する一般的な情報源