[インデックス 1208] ファイルの概要
このコミットは、usr/gri/pretty
ディレクトリ内の3つのGo言語ファイル、printer.go
、tabwriter.go
、untab.go
に変更を加えています。主な変更は、tabwriter.go
における大幅な修正と機能強化であり、エラーハンドリングの改善、ドキュメンテーションの追加、および新しいオプションの導入が含まれています。
コミット
commit 0998eaf4a197cbdba2171fb6ccddf2830a31b110
Author: Robert Griesemer <gri@golang.org>
Date: Thu Nov 20 16:26:43 2008 -0800
- correct error handling throughout
- documentation, cleanups
- more options
R=r
OCL=19736
CL=19736
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/0998eaf4a197cbdba2171fb6ccddf2830a31b110
元コミット内容
- correct error handling throughout
- documentation, cleanups
- more options
変更の背景
このコミットは、Go言語の初期段階におけるpretty
パッケージ(現在のtext/tabwriter
パッケージの前身)の改善を目的としています。主な背景としては、以下の点が挙げられます。
- エラーハンドリングの強化: 以前の実装ではエラー処理が不十分であったため、堅牢性を高めるために全体的なエラーハンドリングの見直しと修正が行われました。特に、
io.Writer
インターフェースを介した書き込み操作において、エラーが発生した場合に適切に伝播させる必要がありました。 - ドキュメンテーションの充実: コードの可読性と保守性を向上させるため、
TabWriter
の機能、動作原理、および設定オプションに関する詳細なコメントが追加されました。これにより、他の開発者がこのパッケージを理解し、利用しやすくなります。 - 機能オプションの追加:
TabWriter
の柔軟性を高めるために、新しい設定オプションが導入されました。具体的には、パディングの量を制御するpadding
オプションが追加され、より細かなフォーマット調整が可能になりました。 - コードのクリーンアップとリファクタリング:
ByteArray
構造体から不要なメソッドが削除され、TabWriter
の初期化関数がMakeTabWriter
からNew
に変更されるなど、全体的なコードの整理とリファクタリングが行われました。これにより、コードベースがより簡潔で効率的になりました。 - Go言語の標準ライブラリへの統合を見据えて: この
pretty
パッケージは、後にGoの標準ライブラリであるtext/tabwriter
として統合されることになります。このコミットは、その標準ライブラリとしての品質と機能性を高めるための重要なステップであったと考えられます。特に、弾性タブストップの概念をGoのツールチェーンに組み込む上で、その基盤となるtabwriter
の実装を洗練させる必要がありました。
前提知識の解説
1. text/tabwriter
パッケージと「弾性タブストップ (Elastic Tabstops)」
Go言語の標準ライブラリであるtext/tabwriter
パッケージは、テキストを整形し、列を揃えるための強力なツールです。このパッケージは、Nick Gravgaardによって提唱された「弾性タブストップ (Elastic Tabstops)」というアルゴリズムを実装しています。
-
弾性タブストップとは: 従来の固定幅のタブストップとは異なり、弾性タブストップは、各列の幅をその列内の最長の要素に合わせて動的に調整します。これにより、異なる長さのテキストが混在していても、常にきれいに列が揃った出力が得られます。例えば、以下のようなテキストがあった場合:
Name Value Description Short 1 A short description VeryLongName 10000 A very long description that spans multiple words
弾性タブストップを適用すると、各列の幅が自動的に調整され、以下のように整形されます。
Name Value Description Short 1 A short description VeryLongName 10000 A very long description that spans multiple words
これは、コードの整形(特に変数宣言や構造体のフィールドなど)や、CLIツールの出力整形において非常に有用です。
-
tabwriter
の動作原理:tabwriter
はio.Writer
インターフェースを実装しており、入力されたバイトストリームを処理し、タブ (\t
) や改行 (\n
) を区切り文字として認識します。そして、内部的に各セルの幅を計算し、必要に応じて空白やタブを挿入して列を揃えた後、最終的な整形済みテキストを出力ライターに書き込みます。
2. io.Writer
インターフェース
Go言語におけるio.Writer
インターフェースは、バイトのスライスを書き込むための基本的なインターフェースです。
type Writer interface {
Write(p []byte) (n int, err error)
}
Write
メソッドは、p
バイトをデータストリームに書き込み、書き込まれたバイト数n
と、書き込み中に発生したエラーerr
を返します。このインターフェースは、ファイル、ネットワーク接続、標準出力など、様々な出力先に対して統一された書き込み操作を提供します。tabwriter
がio.Writer
を実装しているということは、tabwriter
のインスタンスを、io.Writer
を引数にとるあらゆる関数に渡すことができることを意味します。
3. gofmt
との関連
gofmt
は、Go言語の公式なコード整形ツールです。gofmt
は、Goのコードを標準的なスタイルに自動的に整形するためにtext/tabwriter
パッケージを内部的に利用しています。これにより、Goのコードベース全体で一貫したフォーマットが保証され、可読性が向上します。このコミットで行われたtabwriter
の改善は、gofmt
の整形品質にも直接的に影響を与えるものでした。
技術的詳細
このコミットは、usr/gri/pretty
パッケージのprinter.go
、tabwriter.go
、untab.go
の3つのファイルにわたる変更を含んでいます。
usr/gri/pretty/printer.go
TabWriter
の初期化変更:- 変更前:
P.writer = TabWriter.MakeTabWriter(OS.Stdout, usetabs.BVal(), int(tabwidth.IVal()));
- 変更後:
P.writer = TabWriter.New(OS.Stdout, int(tabwidth.IVal()), 1, usetabs.BVal());
TabWriter
のコンストラクタがMakeTabWriter
からNew
に変更され、引数の順序と内容が更新されました。特に、新しいpadding
引数(ここでは1
が渡されている)が追加されています。
- 変更前:
usr/gri/pretty/tabwriter.go
このファイルが最も広範な変更を受けています。
ByteArray
の変更:ByteArray
構造体からLen()
,At()
,Set()
メソッドが削除されました。これは、ByteArray
がより汎用的なライブラリに移行されることを示唆しており、tabwriter
内部でのByteArray
の利用方法が変更されたことを意味します。Slice
メソッドのコメントが// BUG should really be &b.a[i : j]
から// BUG should really be &b.a[i : j]
に変更されていますが、これはコメントの修正であり、機能的な変更ではありません。
TabWriter
構造体の変更:TabWriter
構造体にpadding int
フィールドが追加されました。これにより、セル間の追加パディング量を設定できるようになります。// TODO should not export any of the fields
というコメントが追加され、内部フィールドのエクスポートに関する将来的な変更の意図が示されています。
Init
メソッドのシグネチャ変更:- 変更前:
func (b *TabWriter) Init(writer io.Write, usetabs bool, tabwidth int)
- 変更後:
func (b *TabWriter) Init(writer io.Write, tabwidth, padding int, usetabs bool) *TabWriter
padding
引数が追加され、引数の順序が変更されました。また、メソッドが*TabWriter
を返すようになり、メソッドチェーンを可能にしています。
- 変更前:
- エラーハンドリングの導入と改善:
Write0
という新しいヘルパー関数が導入され、io.Writer
への書き込みエラーをより適切に処理するようになりました。Padding
メソッドがWritePadding
にリネームされ、err *os.Error
を返すようになりました。これにより、パディング書き込み時のエラーが呼び出し元に伝播されるようになります。PrintLines
メソッドがWriteLines
にリネームされ、pos int, err *os.Error
を返すようになりました。これにより、行の書き込みエラーが処理されるようになります。Format
メソッドもpos int, err *os.Error
を返すように変更され、再帰的なフォーマット処理中のエラーが適切に伝播されるようになりました。goto exit;
というラベル付きgoto
文が導入され、エラー発生時にクリーンアップ処理をスキップして関数を終了するパターンが採用されています。これはGoの初期の慣習であり、現代のGoではあまり推奨されません。
Write
メソッドのロジック変更:Write
メソッドの内部ロジックが大幅に変更されました。以前はタブと改行を個別に処理していましたが、新しい実装では、入力バッファをタブまたは改行で区切られた「セル」に分割し、各セルをb.Append
で内部バッファに追加し、b.LastLine().Push(b.width)
でセルの幅を記録するようになりました。- 改行 (
\n
) が検出された場合、現在の行が1つのセルしか持たない(つまり、その行がフォーマットに影響を与えない)場合にのみFlush()
を呼び出すようになりました。これにより、部分的なフラッシュが可能になり、パフォーマンスが向上する可能性があります。
Tab()
とNewline()
メソッドの削除:- 以前は
Tab()
とNewline()
がWrite
メソッドから直接呼び出されていましたが、新しいWrite
メソッドのロジックではこれらのメソッドは不要となり、削除されました。
- 以前は
Flush()
メソッドの導入:Flush()
メソッドが導入され、内部バッファの内容を強制的に出力ライターに書き出し、TabWriter
の状態をリセットする機能が提供されました。
New
コンストラクタ関数の導入:MakeTabWriter
の代わりに、New
という新しいコンストラクタ関数が導入されました。これは、new(TabWriter).Init(...)
という形式でTabWriter
のインスタンスを初期化し、返すための慣用的な方法です。
utflen
関数の追加:// TODO use utflen for correct formatting
というコメントとともにutflen
関数が追加されました。これは、UTF-8文字列の表示幅を正しく計算するためのプレースホルダー関数であり、将来的なUTF-8サポートの意図を示しています。
usr/gri/pretty/untab.go
TabWriter
の初期化変更:printer.go
と同様に、tabwriter.MakeTabWriter
の呼び出しがtabwriter.New
に変更され、新しいpadding
引数(ここでも1
)が追加されています。
コアとなるコードの変更箇所
このコミットのコアとなる変更は、usr/gri/pretty/tabwriter.go
ファイルに集中しています。特に以下の部分が重要です。
TabWriter
構造体へのpadding
フィールドの追加:--- a/usr/gri/pretty/tabwriter.go +++ b/usr/gri/pretty/tabwriter.go @@ -76,27 +61,42 @@ func (b *ByteArray) Append(s *[]byte) { // ---------------------------------------------------------------------------- -// Implementation of flexible tab stops. - -// TabWriter is a representation for a list of lines consisting of -// cells. A new cell is added for each Tab() call, and a new line -// is added for each Newline() call.\n//\n// The lines are formatted and printed such that all cells in a column\n-// of adjacent cells have the same width (by adding padding). For more\n-// details see: http://nickgravgaard.com/elastictabstops/index.html .\n+// Tabwriter is a filter implementing the IO.Write interface. It assumes\n+// that the incoming bytes represent ASCII encoded text consisting of\n+// lines of tab-separated "cells". Cells in adjacent lines constitute\n+// a column. Tabwriter rewrites the incoming text such that all cells\n+// in a column have the same width; thus it effectively aligns cells.\n+// It does this by adding padding where necessary.\n+//\n+// Formatting can be controlled via parameters:\n+//\n+// tabwidth the minimal with of a cell\n+// padding additional padding\n+// usetabs use tabs instead of blanks for padding\n+// (for correct-looking results, tabwidth must correspond\n+// to the tabwidth in the editor used to look at the result)\n //\n -// The lines are formatted and printed such that all cells in a column\n -// of adjacent cells have the same width (by adding padding). For more\n -// details see: http://nickgravgaard.com/elastictabstops/index.html .\n+// (See alse http://nickgravgaard.com/elastictabstops/index.html)\n+\n+// TODO Should support UTF-8\n+// TODO Should probably implement a couple of trivial customization options\n+// such as arbitrary padding character, left/right alignment, and inde-\n+// pendant cell and tab width.\n+\n export type TabWriter struct {\n +\t// TODO should not export any of the fields\n \t// configuration\n \twriter io.Write;\n -\tusetabs bool;\n \ttabwidth int;\n +\tpadding int;\n +\tusetabs bool;\n \t// current state \tbuf ByteArray; // the collected text w/o tabs and newlines \twidth int; // width of last incomplete cell \tlines array.Array; // list of lines; each line is a list of cell widths -\twidths array.IntArray; // list of column widths - (re-)used during formatting +\twidths array.IntArray; // list of column widths - re-used during formatting }
Init
メソッドのシグネチャ変更とNew
コンストラクタの導入:--- a/usr/gri/pretty/tabwriter.go +++ b/usr/gri/pretty/tabwriter.go @@ -105,15 +105,18 @@ func (b *TabWriter) AddLine() { } -func (b *TabWriter) Init(writer io.Write, usetabs bool, tabwidth int) { +func (b *TabWriter) Init(writer io.Write, tabwidth, padding int, usetabs bool) *TabWriter { \tb.writer = writer; -\tb.usetabs = usetabs;\n \tb.tabwidth = tabwidth;\n +\tb.padding = padding;\n +\tb.usetabs = usetabs;\n \t \tb.buf.Init(1024);\n \tb.lines.Init(0);\n \tb.widths.Init(0);\n \tb.AddLine(); // the very first line +\t\n+\treturn b;\n } -export func MakeTabWriter(writer io.Write, usetabs bool, tabwidth int) *TabWriter { -\tb := new(TabWriter);\n-\tb.Init(writer, usetabs, tabwidth);\n-\treturn b;\n +export func New(writer io.Write, tabwidth, padding int, usetabs bool) *TabWriter { +\treturn new(TabWriter).Init(writer, tabwidth, padding, usetabs) }
Write
メソッドのロジック変更とエラーハンドリングの導入:--- a/usr/gri/pretty/tabwriter.go +++ b/usr/gri/pretty/tabwriter.go @@ -226,85 +267,77 @@ func (b *TabWriter) Format(pos int, line0, line1 int) int { } -func (b *TabWriter) EmptyLine() bool { -\treturn b.LastLine().Len() == 0 && b.width == 0;\n } -func (b *TabWriter) Tab() { -\tb.LastLine().Push(b.width);\n \tb.width = 0;\n -} - - -func (b *TabWriter) Newline() { -\tb.Tab(); // add last cell to current line -\n-\tif b.LastLine().Len() == 1 { -\t\t// The current line has only one cell which does not have an impact -\t\t// on the formatting of the following lines (the last cell per line -\t\t// is ignored by Format), thus we can print the TabWriter contents.\n-\t\tif b.widths.Len() != 0 { -\t\t\tpanic("internal error");\n-\t\t}\n-\t\tb.Format(0, 0, b.lines.Len());\n-\t\tif b.widths.Len() != 0 { -\t\t\tpanic("internal error");\n-\t\t}\n-\n-\t\t// reset TabWriter -\t\tb.width = 0;\n-\t\tb.buf.Clear();\n-\t\tb.lines.Init(0);\n-\t}\n-\n \tb.AddLine(); +\t/* export */ func (b *TabWriter) Flush() *os.Error { +\t\tdummy, err := b.Format(0, 0, b.lines.Len()); +\t\t// reset (even in the presence of errors) +\t\tb.buf.Clear(); +\t\tb.width = 0; +\t\tb.lines.Init(0); +\t\tb.AddLine(); +\t\treturn err; +\t} } -func (b *TabWriter) Write(buf *[]byte) (i int, err *os.Error) { +\t/* export */ func (b *TabWriter) Write(buf *[]byte) (written int, err *os.Error) { \ti0, n := 0, len(buf); -\tfor i = 0; i < n; i++ { -\t\tswitch buf[i] { -\t\tcase '\t': -\t\t\tb.width += i - i0; -\t\t\tb.buf.Append(buf[i0 : i]); -\t\t\ti0 = i + 1; // don't append '\t' -\t\t\tb.Tab(); -\t\tcase '\n': -\t\t\tb.width += i - i0; -\t\t\tb.buf.Append(buf[i0 : i]); -\t\t\ti0 = i + 1; // don't append '\n' -\t\t\tb.Newline(); +\t\n+\t// split text into cells +\tfor i := 0; i < n; i++ { +\t\tif ch := buf[i]; ch == '\t' || ch == '\n' { +\t\t\tb.Append(buf[i0 : i]); +\t\t\ti0 = i + 1; // exclude ch from (next) cell +\n+\t\t\t// terminate cell +\t\t\tb.LastLine().Push(b.width); +\t\t\tb.width = 0; +\n+\t\t\tif ch == '\n' { +\t\t\t\tif b.LastLine().Len() == 1 { +\t\t\t\t\t// The last line has only one cell which does not have an +\t\t\t\t\t// impact on the formatting of the following lines (the +\t\t\t\t\t// last cell per line is ignored by Format), thus we can +\t\t\t\t\t// flush the TabWriter contents. +\t\t\t\t\terr = b.Flush(); +\t\t\t\t\tif err != nil { +\t\t\t\t\t\treturn i0, err; +\t\t\t\t\t} +\t\t\t\t} else { +\t\t\t\t\t// We can't flush yet - just add a new line. +\t\t\t\t\tb.AddLine(); +\t\t\t\t} +\t\t\t} \t\t} \t} -\tb.width += n - i0;\n-\tb.buf.Append(buf[i0 : n]); -\treturn i, nil;\n+\t\n+\t// append leftover text +\tb.Append(buf[i0 : n]); +\treturn n, nil; }
コアとなるコードの解説
1. TabWriter
構造体へのpadding
フィールドの追加
padding
フィールドの追加は、TabWriter
の整形能力を向上させる重要な変更です。これにより、各セルの内容と次のセルの開始位置との間に、ユーザーが指定した追加の空白(またはタブ)を挿入できるようになります。これは、出力の視覚的な間隔を調整し、より読みやすいレイアウトを作成するために使用されます。例えば、コードの整形において、変数名と型の間に追加のスペースを入れることで、視覚的な区切りを明確にすることができます。
2. Init
メソッドのシグネチャ変更とNew
コンストラクタの導入
Init
メソッドの変更:Init
メソッドのシグネチャが変更され、padding
引数が追加されたことで、TabWriter
の初期化時にパディングの値を設定できるようになりました。また、*TabWriter
を返すようになったことで、new(TabWriter).Init(...)
のようなチェーン呼び出しが可能になり、より簡潔な初期化コードが書けるようになりました。New
コンストラクタの導入:MakeTabWriter
からNew
への変更は、Go言語におけるコンストラクタ関数の命名規則に合わせたものです。New
という名前は、新しいインスタンスを作成して返すことを明確に示します。これは、Goの標準ライブラリで広く採用されているパターンであり、コードの一貫性と可読性を高めます。
3. Write
メソッドのロジック変更とエラーハンドリングの導入
この変更は、TabWriter
の内部動作と堅牢性に大きな影響を与えます。
- ロジックの変更: 以前の
Write
メソッドは、タブと改行を個別のイベントとして処理し、Tab()
やNewline()
といった内部メソッドを呼び出していました。新しいロジックでは、入力バイトストリームを直接解析し、タブや改行をセルの区切りとして扱います。これにより、より効率的で直接的なテキスト処理が可能になります。特に、b.Append(buf[i0 : i])
でセルの内容を内部バッファに追加し、b.LastLine().Push(b.width)
でそのセルの幅を記録するアプローチは、弾性タブストップの計算に必要な情報を効率的に収集します。 - エラーハンドリングの強化: 以前のバージョンでは、
io.Writer
への書き込みエラーが適切に処理されていませんでした。新しい実装では、Write0
、WritePadding
、WriteLines
といったヘルパー関数が導入され、これらの関数が*os.Error
を返すようになりました。これにより、下位レベルの書き込み操作で発生したエラーが、Write
メソッド、さらにはFlush
メソッドを通じて呼び出し元に正確に伝播されるようになります。goto exit;
の使用は、エラー発生時に複数のクリーンアップステップをスキップして関数を終了させるためのGo初期のイディオムです。 - 部分的なフラッシュ:
Write
メソッド内で改行が検出された際に、b.LastLine().Len() == 1
(つまり、現在の行が1つのセルしか持たない)という条件でb.Flush()
を呼び出すロジックは、パフォーマンス最適化の一環です。これは、その行が後続のフォーマットに影響を与えないため、早期にバッファをフラッシュしてリソースを解放できることを意味します。これにより、特に大きな入力ストリームを処理する際に、メモリ使用量を抑え、処理の応答性を向上させることができます。
これらの変更は、TabWriter
がより堅牢で、柔軟性があり、効率的なテキスト整形ツールとなるための基盤を築きました。特に、エラーハンドリングの改善は、このパッケージがGoの標準ライブラリとして採用される上で不可欠な品質向上でした。
関連リンク
- GitHubコミットページ: https://github.com/golang/go/commit/0998eaf4a197cbdba2171fb6ccddf2830a31b110
- Go
text/tabwriter
パッケージ (現在のドキュメント): https://pkg.go.dev/text/tabwriter - Elastic Tabstops by Nick Gravgaard: http://nickgravgaard.com/elastictabstops/index.html
参考にした情報源リンク
- https://go.dev/pkg/text/tabwriter/
- https://pkg.go.dev/text/tabwriter
- https://github.com/golang/go/blob/master/src/text/tabwriter/tabwriter.go
- https://go-language.org/docs/pkg/text/tabwriter/
- https://reintech.io/blog/go-text-tabwriter-package-tutorial
- https://nick-gravgaard.com/elastictabstops/
- https://go.dev/blog/gofmt