[インデックス 1211] ファイルの概要
このコミットは、Go言語の初期開発段階において、テキスト整形のためのtabwriter機能を個人の実験的なディレクトリ(usr/gri/pretty)から標準ライブラリ(src/lib/tabwriter)へ移動させ、より汎用的なio.Writerインターフェースに準拠するように型名を変更し、初期のテストを追加したものです。これにより、tabwriterはGo言語の標準的なパッケージとして利用可能になり、コードの再利用性と保守性が向上しました。
コミット
commit 01b44bbfc8ca90d9eb3418ad47d9d7a472bb4cde
Author: Robert Griesemer <gri@golang.org>
Date: Thu Nov 20 17:39:41 2008 -0800
- move tabwriter into library
- added preliminary tests (more to do)
- renamed type from TabWriter -> Writer
- adjusted my code where necessary
R=r
DELTA=825 (474 added, 346 deleted, 5 changed)
OCL=19744
CL=19753
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/01b44bbfc8ca90d9eb3418ad47d9d7a472bb4cde
元コミット内容
このコミットの目的は以下の通りです。
tabwriter機能をライブラリとして移動する。- 予備的なテストを追加する(さらなる追加が必要)。
- 型名を
TabWriterからWriterへ変更する。 - 必要に応じて自身のコードを調整する。
変更の背景
このコミットは、Go言語の初期段階における標準ライブラリの整備の一環として行われました。tabwriterは、テキストを整形し、タブ区切りのデータを列ごとに揃えるためのユーティリティです。元々は開発者個人の作業ディレクトリ(usr/gri/pretty)に存在していましたが、その汎用性と有用性から、Go言語の標準ライブラリの一部として提供されるべきであると判断されました。
変更の主な背景は以下の点にあります。
- モジュール化と再利用性:
tabwriter機能を独立したライブラリとして切り出すことで、他のGoプログラムから容易に利用できるようになります。これにより、コードの再利用性が高まり、Goエコシステム全体の発展に寄与します。 - 標準化と命名規則の遵守: Go言語では、
io.Writerインターフェースを実装する型は、慣習的にWriterという名前を持つことが推奨されます。既存のTabWriter型をWriterにリネームすることで、この命名規則に準拠し、Goの標準ライブラリとしての整合性を保ちます。 - 品質保証: ライブラリとして公開するにあたり、その機能が正しく動作することを保証するためのテストが不可欠です。このコミットでは、そのための予備的なテストが追加されました。
- 依存関係の整理:
Makefileの変更は、新しいライブラリのビルドとクリーンアップのプロセスをGoのビルドシステムに統合し、依存関係を適切に管理するためのものです。
前提知識の解説
このコミットを理解するためには、以下のGo言語に関する基本的な知識が必要です。
-
Go言語のパッケージシステム: Go言語はパッケージ(
package)によってコードを整理します。各パッケージは独立した名前空間を持ち、他のパッケージからインポートして利用できます。このコミットでは、tabwriterが独立したパッケージとしてsrc/lib/tabwriterに移動されました。 -
io.Writerインターフェース: Go言語の標準ライブラリioパッケージには、データを書き込むための汎用的なインターフェースWriterが定義されています。これは、Write([]byte) (n int, err error)メソッドを持つ型であれば、どのような型でもio.Writerとして扱うことができるというものです。tabwriterがio.Writerインターフェースを実装することで、ファイル、ネットワーク接続、バッファなど、様々な出力先に透過的にデータを書き込めるようになります。 -
Makefile:Makefileは、ソフトウェアのビルドプロセスを自動化するためのファイルです。Go言語の初期のプロジェクトでは、Makefileがコンパイル、テスト、クリーンアップなどのタスクを管理するために広く使われていました。このコミットでは、tabwriterライブラリのビルドとインストールに関するルールがMakefileに追加されています。 -
タブライター(TabWriter)の概念:
tabwriterは、テキスト内のタブ区切りのデータを整形し、列を揃えるためのツールです。例えば、以下のようなタブ区切りのテキストがあったとします。Name Age City Alice 30 New York Bob 25 London Charlie 35 Paristabwriterを使用すると、各列の幅を自動的に調整し、以下のようにきれいに揃えることができます。Name Age City Alice 30 New York Bob 25 London Charlie 35 Parisこれは、ドキュメントの整形やCLIツールの出力などで非常に有用です。
技術的詳細
このコミットにおける技術的な変更点は多岐にわたります。
-
ファイル移動とリネーム:
usr/gri/pretty/tabwriter.goがsrc/lib/tabwriter/tabwriter.goに移動されました。これは、tabwriterが個人の実験的なコードからGoの標準ライブラリの一部へと昇格したことを意味します。usr/gri/pretty/Makefileとusr/gri/pretty/untab.goも変更され、tabwriterへの参照が新しいパスに更新されています。
-
型名のリネーム:
tabwriter.go内で定義されていたTabWriterという構造体名がWriterに変更されました。- これに伴い、
TabWriterを参照していたすべてのメソッド(例:(*TabWriter) AddLine()->(*Writer) AddLine())や、コンストラクタ関数(例:New(writer io.Write, ...) *TabWriter->New(writer io.Write, ...) *Writer)のシグネチャも変更されています。 - この変更は、Goの慣習である
io.Writerインターフェースを実装する型にはWriterという名前を付けるという原則に則ったものです。これにより、tabwriterパッケージのWriter型がio.Writerインターフェースと互換性があることが、名前から直感的に理解できるようになります。
-
Makefileの更新:src/lib/tabwriter/Makefileという新しいMakefileが追加されました。このMakefileは、tabwriterパッケージのビルド、テスト、クリーンアップ、インストールに関するルールを定義しています。src/lib/clean.bashとsrc/lib/make.bashも更新され、Goのビルドシステムがtabwriterパッケージを認識し、適切に処理するように変更されています。特にclean.bashでは、クリーンアップ対象のパッケージリストにtabwriterが追加され、make.bashではビルド対象のディレクトリにtabwriterが追加されています。
-
テストの追加:
src/lib/tabwriter/tabwriter_test.goという新しいテストファイルが追加されました。これは、tabwriterパッケージの基本的な機能が正しく動作することを検証するためのものです。Check関数は、与えられた入力文字列がtabwriterによって期待される出力文字列に変換されるかを検証します。Test1関数は、いくつかの基本的なテストケース(空行、単純な文字列、タブ区切りの文字列)を実行し、tabwriterの動作を確認します。- テストには、
Bufferというカスタムのio.Writer実装が使用されており、tabwriter.Writerの出力をキャプチャして検証できるようにしています。
これらの変更により、tabwriterはGo言語の標準ライブラリとして、より堅牢で、Goの設計思想に沿った形で提供されることになりました。
コアとなるコードの変更箇所
このコミットのコアとなるコードの変更は、主に以下のファイルに集中しています。
-
usr/gri/pretty/tabwriter.goからsrc/lib/tabwriter/tabwriter.goへの移動と内容変更:- ファイル自体が移動し、パッケージ名が
mainからtabwriterに変更されました。 - 最も重要な変更は、構造体
TabWriterの名前がWriterに変更された点です。--- a/usr/gri/pretty/tabwriter.go +++ b/src/lib/tabwriter/tabwriter.go @@ -61,12 +61,12 @@ func (b *ByteArray) Append(s *[]byte) { // ---------------------------------------------------------------------------- -// Tabwriter is a filter implementing the IO.Write interface. It assumes +// Writer is a filter implementing the io.Write interface. It assumes // that the incoming bytes represent ASCII encoded text consisting of // lines of tab-separated "cells". Cells in adjacent lines constitute -// a column. Tabwriter rewrites the incoming text such that all cells -// in a column have the same width; thus it effectively aligns cells. -// It does this by adding padding where necessary. +// a column. Writer rewrites the incoming text such that all cells in +// a column have the same width; thus it effectively aligns cells. It +// does this by adding padding where necessary. // // Formatting can be controlled via parameters: // @@ -84,7 +84,7 @@ func (b *ByteArray) Append(s *[]byte) { // pendant cell and tab width. -export type TabWriter struct { +export type Writer struct { // TODO should not export any of the fields // configuration writer io.Write; @@ -100,12 +100,12 @@ export type TabWriter struct { } -func (b *TabWriter) AddLine() { +func (b *Writer) AddLine() { b.lines.Push(array.NewIntArray(0)); } -func (b *TabWriter) Init(writer io.Write, tabwidth, padding int, usetabs bool) *TabWriter { +func (b *Writer) Init(writer io.Write, tabwidth, padding int, usetabs bool) *Writer { b.writer = writer; b.tabwidth = tabwidth; b.padding = padding; @@ -120,18 +120,18 @@ func (b *TabWriter) Init(writer io.Write, tabwidth, padding int, usetabs bool) * } -func (b *TabWriter) Line(i int) *array.IntArray { +func (b *Writer) Line(i int) *array.IntArray { return b.lines.At(i).(*array.IntArray); } -func (b *TabWriter) LastLine() *array.IntArray { +func (b *Writer) LastLine() *array.IntArray { return b.lines.At(b.lines.Len() - 1).(*array.IntArray); } // debugging support -func (b *TabWriter) Dump() { +func (b *Writer) Dump() { pos := 0; for i := 0; i < b.lines.Len(); i++ { line := b.Line(i); @@ -147,7 +147,7 @@ func (b *TabWriter) Dump() { } -func (b *TabWriter) Write0(buf *[]byte) *os.Error { +func (b *Writer) Write0(buf *[]byte) *os.Error { n, err := b.writer.Write(buf); if n != len(buf) && err == nil { err = os.EIO; @@ -161,7 +161,7 @@ var Blanks = &[]byte{' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '} var Newline = &[]byte{'\n'} -func (b *TabWriter) WritePadding(textw, cellw int) (err *os.Error) { +func (b *Writer) WritePadding(textw, cellw int) (err *os.Error) { if b.usetabs { // make cell width a multiple of tabwidth cellw = ((cellw + b.tabwidth - 1) / b.tabwidth) * b.tabwidth; @@ -192,7 +192,7 @@ exit: } -func (b *TabWriter) WriteLines(pos0 int, line0, line1 int) (pos int, err *os.Error) { +func (b *Writer) WriteLines(pos0 int, line0, line1 int) (pos int, err *os.Error) { pos = pos0; for i := line0; i < line1; i++ { line := b.Line(i); @@ -233,7 +233,7 @@ func utflen(buf *[]byte) int { } -func (b *TabWriter) Format(pos0 int, line0, line1 int) (pos int, err *os.Error) { +func (b *Writer) Format(pos0 int, line0, line1 int) (pos int, err *os.Error) { pos = pos0; column := b.widths.Len(); last := line0; @@ -284,13 +284,13 @@ exit: } -func (b *TabWriter) Append(buf *[]byte) { +func (b *Writer) Append(buf *[]byte) { b.buf.Append(buf); b.width += len(buf); } -/* export */ func (b *TabWriter) Flush() *os.Error { +/* export */ func (b *Writer) Flush() *os.Error { dummy, err := b.Format(0, 0, b.lines.Len()); // reset (even in the presence of errors) b.buf.Clear(); @@ -301,7 +301,7 @@ func (b *TabWriter) Append(buf *[]byte) { } -/* export */ func (b *TabWriter) Write(buf *[]byte) (written int, err *os.Error) { +/* export */ func (b *Writer) Write(buf *[]byte) (written int, err *os.Error) { i0, n := 0, len(buf); // split text into cells @@ -319,7 +319,7 @@ func (b *TabWriter) Append(buf *[]byte) { // The last line has only one cell which does not have an // impact on the formatting of the following lines (the // last cell per line is ignored by Format), thus we can - // flush the TabWriter contents. + // flush the Writer contents. err = b.Flush(); if err != nil { return i0, err; @@ -338,6 +338,6 @@ func (b *TabWriter) Append(buf *[]byte) { } -export func New(writer io.Write, tabwidth, padding int, usetabs bool) *TabWriter { - return new(TabWriter).Init(writer, tabwidth, padding, usetabs) +export func New(writer io.Write, tabwidth, padding int, usetabs bool) *Writer { + return new(Writer).Init(writer, tabwidth, padding, usetabs) }
- ファイル自体が移動し、パッケージ名が
-
src/lib/tabwriter/Makefileの新規追加:tabwriterパッケージをビルドするためのMakefileが新しく作成されました。これには、コンパイル、テスト、クリーンアップ、インストールなどのターゲットが含まれています。
-
src/lib/tabwriter/tabwriter_test.goの新規追加:tabwriterパッケージの機能テストを行うためのGoテストファイルが追加されました。
コアとなるコードの解説
tabwriter.go の変更
- 型名のリネーム:
TabWriterからWriterへの変更は、Go言語のインターフェース設計における重要な慣習を反映しています。io.Writerインターフェースは、Write([]byte) (n int, err error)メソッドを持つ任意の型が実装できます。tabwriterパッケージのWriter型がこのインターフェースを実装することで、tabwriter.Writerのインスタンスをio.Writerを期待するあらゆる関数に渡すことができるようになります。これにより、tabwriterはGoのI/Oエコシステムにシームレスに統合され、柔軟な利用が可能になります。例えば、fmt.Fprintfのような関数にtabwriter.Writerを渡して、整形された出力を直接生成することができます。 - コメントの更新: 型名のリネームに伴い、関連するコメントも
TabwriterからWriterに更新され、コードの整合性が保たれています。
tabwriter_test.go の追加
- テストフレームワークの利用: Go言語の標準テストパッケージ
testingが使用されています。*testing.Tはテスト関数に渡され、テストの失敗を報告したり、ログを出力したりするために使用されます。 - カスタム
Bufferの実装:tabwriter.Writerはio.Writerインターフェースを実装する任意の型に出力できます。テストでは、Bufferというカスタム型がio.Writerインターフェースを実装し、tabwriter.Writerからの出力をメモリ上にキャプチャします。これにより、生成された出力を期待される文字列と比較して、tabwriterの動作を検証することができます。 Check関数の導入:Check関数は、テストケースの共通ロジックをカプセル化しています。これにより、各テストケースは簡潔に記述でき、可読性が向上します。- 基本的なテストケース:
Test1関数では、空行、単純な文字列、タブ区切りの文字列など、tabwriterの基本的な動作を検証するテストケースが用意されています。これにより、tabwriterが期待通りにテキストを整形し、列を揃えることができるかを確認します。
これらの変更は、tabwriterがGo言語の標準ライブラリとして成熟し、より使いやすく、信頼性の高いものになるための重要なステップでした。
関連リンク
- Go言語の
ioパッケージ: https://pkg.go.dev/io - Go言語の
text/tabwriterパッケージ: https://pkg.go.dev/text/tabwriter (現在のGoの標準ライブラリにおけるtabwriterのドキュメント)
参考にした情報源リンク
- Go言語の公式ドキュメント
- Go言語のソースコードリポジトリ
- Go言語の初期開発に関する情報(必要に応じてWeb検索)