[インデックス 1214] ファイルの概要
このコミットは、Go言語の標準ライブラリの一部である tabwriter
パッケージの機能拡張と改善に関するものです。tabwriter
は、テキストデータを整形し、タブ区切りのカラムを自動的に整列させるためのユーティリティを提供します。これにより、出力が読みやすくなり、表形式のデータをきれいに表示できます。
このコミットで変更された主なファイルは以下の通りです。
src/lib/tabwriter/tabwriter.go
:tabwriter
パッケージのコアロジックが実装されているファイルです。パディング文字の任意指定、右寄せアライメントの導入、エラーハンドリングの改善など、主要な機能変更が行われました。src/lib/tabwriter/tabwriter_test.go
:tabwriter
パッケージのテストファイルです。新しい機能の動作を検証するための多数のテストケースが追加されました。usr/gri/pretty/printer.go
:tabwriter
を利用している内部ツールpretty/printer
のファイルです。tabwriter
のAPI変更に合わせて更新されました。usr/gri/pretty/untab.go
:tabwriter
を利用している内部ツールpretty/untab
のファイルです。tabwriter
のAPI変更に合わせて更新されました。
コミット
このコミットは、tabwriter
パッケージに以下の重要な機能を追加し、既存の機能を改善しました。
- 任意のパディング文字の実装: これまで空白またはタブに限定されていたパディング文字を、任意のASCII文字に指定できるようになりました。
- 右寄せアライメントの実装: 特に数値結果の表示において有用な、右寄せ(right-to-left)アライメントの機能が追加されました。
- コメントとエラーハンドリングの改善: コードの可読性を高め、堅牢性を向上させるために、コメントが追加・修正され、エラーハンドリングが強化されました。
- テストの追加: 新しい機能と既存の機能の正確性を保証するために、より多くのテストケースが追加されました。
- 依存ファイルの更新:
tabwriter
のAPI変更に伴い、それを使用している内部ツールが更新されました。
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/130e6f42f1b993c1764dc1c346c9af222e59e1d2
元コミット内容
- implemented arbitrary padding char for tabwriter
- implemented right-to-left alignment (numerical results)
- better comments and error handling
- added more tests
- updated dependent files
R=r
DELTA=232 (175 added, 11 deleted, 46 changed)
OCL=19761
CL=19780
変更の背景
このコミットが行われた背景には、既存の tabwriter
の機能的な制約がありました。以前の tabwriter
は、カラムの整列において空白またはタブのみをパディング文字として使用でき、アライメントも基本的に左寄せに限定されていました。
しかし、表形式のデータを整形する際には、以下のようなより柔軟な機能が求められます。
- パディング文字の多様性: 特定の用途(例: ログ出力、特殊なレポート)では、空白やタブ以外の文字(例:
.
、-
、_
)でパディングしたい場合があります。これにより、視覚的な区切りを明確にしたり、特定のフォーマット要件を満たしたりできます。 - 右寄せアライメント: 数値データ(例: 金額、統計値)を表示する場合、桁を揃えるために右寄せアライメントが不可欠です。左寄せでは数値の比較が困難になることがあります。
- 堅牢性と使いやすさ: より良いエラーハンドリングと明確なコメントは、ライブラリの堅牢性を高め、開発者がより安全かつ効率的に利用できるようにするために重要です。
これらの要求に応えるため、tabwriter
の設計が見直され、任意のパディング文字と右寄せアライメントの機能が導入されました。また、http://nickgravgaard.com/elastictabstops/index.html
で言及されている「Elastic Tabstops」の概念も、この tabwriter
の設計思想に影響を与えています。Elastic Tabstopsは、タブ区切りのテキストにおいて、各カラムの幅が自動的に調整され、常に最適なアライメントが保たれるようにするアイデアです。
前提知識の解説
tabwriter
の役割
tabwriter
は、Go言語でテキストを整形するためのパッケージです。特に、タブ (\t
) で区切られたテキストの各カラムを自動的に整列させる機能を提供します。これにより、以下のような整形された出力を簡単に生成できます。
Name Age City
Alice 30 New York
Bob 25 London
Charlie 35 Paris
tabwriter
は io.Writer
インターフェースを実装しており、任意の io.Writer
にデータを書き込むことで整形された出力を得られます。
io.Writer
インターフェース
Go言語の io
パッケージで定義されている Writer
インターフェースは、データを書き込むための基本的な抽象化を提供します。
type Writer interface {
Write(p []byte) (n int, err error)
}
tabwriter.Writer
はこのインターフェースを満たしているため、os.Stdout
や bytes.Buffer
など、様々な出力先に整形されたテキストを書き出すことができます。
タブストップとアライメントの概念
- タブストップ: テキストエディタやプリンタにおいて、タブ文字 (
\t
) が到達したときにカーソルが移動する固定位置のことです。伝統的なタブストップは一定間隔(例: 8文字ごと)で設定されます。 - アライメント: テキストを特定の基準に沿って整列させることです。
- 左寄せ (Left Alignment): テキストが左端に揃えられ、右側に空白が追加されます。
- 右寄せ (Right Alignment): テキストが右端に揃えられ、左側に空白が追加されます。数値の桁揃えによく使われます。
- 中央寄せ (Center Alignment): テキストが中央に揃えられ、左右に空白が追加されます。
tabwriter
は、これらの概念を動的に適用し、入力されたテキストの各カラムの幅を計算して、指定されたアライメントとパディングで出力します。
Go言語の基本的な型
byte
: 8ビットの符号なし整数型で、ASCII文字を表すのに使われます。bool
: 真偽値 (true
またはfalse
) を表す型です。int
: 整数型です。システムに依存しますが、通常は32ビットまたは64ビットです。
技術的詳細
このコミットにおける tabwriter
の主要な技術的変更点は以下の通りです。
-
Writer
構造体の変更:tabwidth int
フィールドがcellwidth int
に名称変更されました。これは、タブの幅だけでなく、セルの最小幅をより一般的に表現するためです。usetabs bool
フィールドが削除され、代わりにpadbytes [8]byte
とalign_left bool
が追加されました。padbytes
: パディングに使用する文字を格納するバイト配列です。これにより、任意の文字をパディングに使えるようになりました。配列サイズが8なのは、効率的な書き込みのためと考えられます。align_left
: 左寄せアライメントを行うかどうかを示すブール値です。true
の場合は左寄せ、false
の場合は右寄せを行います。
-
Init
関数の引数と初期化ロジックの変更:- 旧:
func (b *Writer) Init(writer io.Write, tabwidth, padding int, usetabs bool) *Writer
- 新:
func (b *Writer) Init(writer io.Write, cellwidth, padding int, padchar byte, align_left bool) *Writer
tabwidth
がcellwidth
に、usetabs
がpadchar
とalign_left
に置き換わりました。cellwidth
とpadding
が負の値でないかどうかのチェックが追加され、負の値の場合はpanic
を発生させるようになりました。これにより、不正な引数に対する堅牢性が向上しました。padbytes
フィールドは、padchar
で指定された文字で初期化されます。align_left
は、引数で渡された値、またはpadchar
がタブ (\t
) の場合は強制的にtrue
に設定されます。これは、タブ文字によるパディングは常に左寄せとして扱われるべきであるという設計上の考慮に基づいています。
- 旧:
-
パディング文字の任意指定 (
padchar
) とその実装 (padbytes
):Writer
構造体内のpadbytes [8]byte
フィールドが、パディング文字を保持するために導入されました。Init
関数内で、指定されたpadchar
でpadbytes
配列全体が埋められます。WritePadding
関数では、以前は固定のTabs
またはBlanks
スライスを使用していた箇所が、b.padbytes
を使用するように変更されました。これにより、任意の文字でパディングが可能になりました。
-
右寄せアライメント (
align_left
) の導入とWriteLines
での処理:Writer
構造体にalign_left bool
フィールドが追加されました。WriteLines
関数内で、align_left
の値に基づいてテキストの書き込み順序が変更されました。align_left
がtrue
(左寄せ) の場合: まずテキストを書き込み、次にパディングを書き込みます。align_left
がfalse
(右寄せ) の場合: まずパディングを書き込み、次にテキストを書き込みます。
- これにより、同じカラム幅内でテキストが左寄せまたは右寄せに適切に配置されるようになりました。
-
WritePadding
の変更点:- パディング文字がタブ (
\t
) の場合、cellw
をcellwidth
の倍数に調整するロジックが追加されました。これは、タブが固定幅で表示されることを前提とした動作です。 - パディングの書き込みループが、
len(b.padbytes)
を基準に行われるようになりました。
- パディング文字がタブ (
-
New
関数の変更:tabwriter.New
関数のシグネチャもInit
関数に合わせて変更され、padchar
とalign_left
を引数として受け取るようになりました。
これらの変更により、tabwriter
はより柔軟なテキスト整形機能を提供し、特に数値データの表示においてその有用性を高めました。
コアとなるコードの変更箇所
src/lib/tabwriter/tabwriter.go
Writer
構造体の定義変更
--- a/src/lib/tabwriter/tabwriter.go
+++ b/src/lib/tabwriter/tabwriter.go
@@ -63,34 +63,38 @@ func (b *ByteArray) Append(s *[]byte) {
// ----------------------------------------------------------------------------
// 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
+// lines of tab-terminated "cells". Cells in adjacent lines constitute
// 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:
+// Note that any text at the end of a line that is not tab-terminated
+// is not a cell and does not enforce alignment of cells in adjacent
+// rows. To make it a cell it needs to be tab-terminated. (For more
+// information see http://nickgravgaard.com/elastictabstops/index.html)
//
-// tabwidth the minimal with of a cell
-// padding additional padding
-// usetabs use tabs instead of blanks for padding
-// (for correct-looking results, tabwidth must correspond
-// to the tabwidth in the editor used to look at the result)
+// Formatting can be controlled via parameters:
//
-// (See alse http://nickgravgaard.com/elastictabstops/index.html)
+// cellwidth minimal cell width
+// padding additional cell padding
+// padchar ASCII char used for padding
+// if padchar == '\t', the Writer will assume that the
+// width of a '\t' in the formatted output is tabwith,
+// and cells are left-aligned independent of align_left
+// (for correct-looking results, cellwidth must correspond
+// to the tabwidth in the editor used to look at the result)
// TODO Should support UTF-8
// TODO Should probably implement a couple of trivial customization options
// such as arbitrary padding character, left/right alignment, and inde-
// pendant cell and tab width.
+// TODO Should support UTF-8 (requires more complicated width bookkeeping)
export type Writer struct {
// TODO should not export any of the fields
// configuration
writer io.Write;
- tabwidth int;
+ cellwidth int;
padding int;
- usetabs bool;
+ padbytes [8]byte;
+ align_left bool;
// current state
buf ByteArray; // the collected text w/o tabs and newlines
Init
関数のシグネチャと初期化ロジックの変更
--- a/src/lib/tabwriter/tabwriter.go
+++ b/src/lib/tabwriter/tabwriter.go
@@ -105,11 +109,20 @@ func (b *Writer) AddLine() {
}
-func (b *Writer) Init(writer io.Write, tabwidth, padding int, usetabs bool) *Writer {
+func (b *Writer) Init(writer io.Write, cellwidth, padding int, padchar byte, align_left bool) *Writer {
+ if cellwidth < 0 {
+ panic("negative cellwidth");
+ }
+ if padding < 0 {
+ panic("negative padding");
+ }
b.writer = writer;
- b.tabwidth = tabwidth;
+ b.cellwidth = cellwidth;
b.padding = padding;
- b.usetabs = usetabs;
+ for i := len(b.padbytes) - 1; i >= 0; i-- {
+ b.padbytes[i] = padchar;
+ }
+ b.align_left = align_left || padchar == '\t'; // tab enforces left-alignment
b.buf.Init(1024);
b.lines.Init(0);
WritePadding
関数の変更
--- a/src/lib/tabwriter/tabwriter.go
+++ b/src/lib/tabwriter/tabwriter.go
@@ -156,15 +169,12 @@ func (b *Writer) Write0(buf *[]byte) *os.Error {
}
-var Tabs = &[]byte{'\t', '\t', '\t', '\t', '\t', '\t', '\t', '\t'}
-var Blanks = &[]byte{' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '}
var Newline = &[]byte{'\n'}
-
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;
+ if b.padbytes[0] == '\t' {
+ // make cell width a multiple of cellwidth
+ cellw = ((cellw + b.cellwidth - 1) / b.cellwidth) * b.cellwidth;
}
n := cellw - textw;
@@ -172,20 +182,18 @@ func (b *Writer) WritePadding(textw, cellw int) (err *os.Error) {
panic("internal error");
}
- padding := Blanks;
- if b.usetabs {
- n = (n + b.tabwidth - 1) / b.tabwidth;
- padding = Tabs;
+ if b.padbytes[0] == '\t' {
+ n = (n + b.cellwidth - 1) / b.cellwidth;
}
- for n > len(padding) {
- err = b.Write0(padding);
+ for n > len(b.padbytes) {
+ err = b.Write0(&b.padbytes);
if err != nil {
goto exit;
}
- n -= len(padding);
+ n -= len(b.padbytes);
}
- err = b.Write0(padding[0 : n]);
+ err = b.Write0((&b.padbytes)[0 : n]); // BUG 6g should not require ()'s
exit:
return err;
WriteLines
関数のアライメントロジック変更
--- a/src/lib/tabwriter/tabwriter.go
+++ b/src/lib/tabwriter/tabwriter.go
@@ -198,16 +206,33 @@ func (b *Writer) WriteLines(pos0 int, line0, line1 int) (pos int, err *os.Error) {
line := b.Line(i);
for j := 0; j < line.Len(); j++ {
w := line.At(j);
- err = b.Write0(b.buf.a[pos : pos + w]);
- if err != nil {
- goto exit;
- }
- pos += w;
- if j < b.widths.Len() {
- err = b.WritePadding(w, b.widths.At(j));
+
+ if b.align_left {
+ err = b.Write0(b.buf.a[pos : pos + w]);
+ if err != nil {
+ goto exit;
+ }
+ pos += w;
+ if j < b.widths.Len() {
+ err = b.WritePadding(w, b.widths.At(j));
+ if err != nil {
+ goto exit;
+ }
+ }
+
+ } else { // align right
+
+ if j < b.widths.Len() {
+ err = b.WritePadding(w, b.widths.At(j));
+ if err != nil {
+ goto exit;
+ }
+ }
+ err = b.Write0(b.buf.a[pos : pos + w]);
if err != nil {
goto exit;
}
+ pos += w;
}
}
err = b.Write0(Newline);
New
関数のシグネチャ変更
--- a/src/lib/tabwriter/tabwriter.go
+++ b/src/lib/tabwriter/tabwriter.go
@@ -338,6 +363,6 @@ func (b *Writer) Append(buf *[]byte) {
}
-export func New(writer io.Write, tabwidth, padding int, usetabs bool) *Writer {
- return new(Writer).Init(writer, tabwidth, padding, usetabs)
+export func New(writer io.Write, cellwidth, padding int, padchar byte, align_left bool) *Writer {
+ return new(Writer).Init(writer, cellwidth, padding, padchar, align_left)
}
src/lib/tabwriter/tabwriter_test.go
Check
関数のシグネチャ変更とテストケースの追加
--- a/src/lib/tabwriter/tabwriter_test.go
+++ b/src/lib/tabwriter/tabwriter_test.go
@@ -42,40 +42,171 @@ func (b *Buffer) String() string {
}
-func Check(t *testing.T, tabwidth, padding int, usetabs bool, src, expected string) {
+func Check(t *testing.T, tabwidth, padding int, padchar byte, align_left bool, src, expected string) {
var b Buffer;
b.Init(1000);
var w tabwriter.Writer;
- w.Init(&b, tabwidth, padding, usetabs);
+ w.Init(&b, tabwidth, padding, padchar, align_left);
io.WriteString(&w, src);
res := b.String();
if res != expected {
- t.Errorf("src:\n%s\nfound:\n%s\nexpected:\n%s\n", src, res, expected)
+ t.Errorf("--- src:\n%s\n--- found:\n%s\n--- expected:\n%s\n", src, res, expected)
}
}
export func Test1(t *testing.T) {
Check(\n-\t\tt, 8, 1, false,\n+\t\tt, 8, 1, ' ', true,\n \t\t"\\n",\n \t\t"\\n"\n \t);\n
Check(\n-\t\tt, 8, 1, false,\n+\t\tt, 8, 1, '*', true,\n \t\t"Hello, world!\\n",\n \t\t"Hello, world!\\n"\n \t);\n
Check(\n-\t\tt, 8, 1, false,\n-\t\t"a\\tb\\tc\\naa\\tbbb\\tcccc\\naaa\\tbbbb\\n\\n",\n+\t\tt, 0, 0, '.', true,\n+\t\t"1\\t2\\t3\\t4\\n"\n+\t\t"11\\t222\\t3333\\t44444\\n\\n",\n+\n+\t\t"1.2..3...4\\n"\n+\t\t"11222333344444\\n\\n"\n+\t);\n+\n+\tCheck(\n+\t\tt, 5, 0, '.', true,\n+\t\t"1\\t2\\t3\\t4\\n\\n",\n+\t\t"1....2....3....4\\n\\n"\n+\t);\n+\n+\tCheck(\n+\t\tt, 5, 0, '.', true,\n+\t\t"1\\t2\\t3\\t4\\t\\n\\n",\n+\t\t"1....2....3....4....\\n\\n"\n+\t);\n+\n+\tCheck(\n+\t\tt, 8, 1, ' ', true,\n+\t\t"a\\tb\\tc\\n"\n+\t\t"aa\\tbbb\\tcccc\\tddddd\\n"\n+\t\t"aaa\\tbbbb\\n\\n",\n+\n \t\t"a b c\\n"\n-\t\t"aa bbb cccc\\n"\n+\t\t"aa bbb cccc ddddd\\n"\n \t\t"aaa bbbb\\n\\n"\n \t);\n+\n+\tCheck(\n+\t\tt, 8, 1, ' ', false,\n+\t\t"a\\tb\\tc\\t\\n"\n+\t\t"aa\\tbbb\\tcccc\\tddddd\\t\\n"\n+\t\t"aaa\\tbbbb\\t\\n\\n",\n+\n+\t\t" a b c\\n"\n+\t\t" aa bbb cccc ddddd\\n"\n+\t\t" aaa bbbb\\n\\n"\n+\t);\n+\n+\tCheck(\n+\t\tt, 2, 0, ' ', true,\n+\t\t"a\\tb\\tc\\n"\n+\t\t"aa\\tbbb\\tcccc\\n"\n+\t\t"aaa\\tbbbb\\n\\n",\n+\n+\t\t"a b c\\n"\n+\t\t"aa bbbcccc\\n"\n+\t\t"aaabbbb\\n\\n"\n+\t);\n+\n+\tCheck(\n+\t\tt, 8, 1, '_', true,\n+\t\t"a\\tb\\tc\\n"\n+\t\t"aa\\tbbb\\tcccc\\n"\n+\t\t"aaa\\tbbbb\\n\\n",\n+\n+\t\t"a_______b_______c\\n"\n+\t\t"aa______bbb_____cccc\\n"\n+\t\t"aaa_____bbbb\\n\\n"\n+\t);\n+\n+\tCheck(\n+\t\tt, 4, 1, '-', true,\n+\t\t"4444\\t333\\t22\\t1\\t333\\n"\n+\t\t"999999999\\t22\\n"\n+\t\t"7\\t22\\n"\n+\t\t"\\t\\t\\t88888888\\n"\n+\t\t"\\n"\n+\t\t"666666\\t666666\\t666666\\t4444\\n"\n+\t\t"1\\t1\\t999999999\\t0000000000\\n\\n",\n+\n+\t\t"4444------333-22--1---333\\n"\n+\t\t"999999999-22\\n"\n+\t\t"7---------22\\n"\n+\t\t"------------------88888888\\n"\n+\t\t"\\n"\n+\t\t"666666-666666-666666----4444\\n"\n+\t\t"1------1------999999999-0000000000\\n\\n"\n+\t);\n+\n+\tCheck(\n+\t\tt, 4, 3, '.', true,\n+\t\t"4444\\t333\\t22\\t1\\t333\\n"\n+\t\t"999999999\\t22\\n"\n+\t\t"7\\t22\\n"\n+\t\t"\\t\\t\\t88888888\\n"\n+\t\t"\\n"\n+\t\t"666666\\t666666\\t666666\\t4444\\n"\n+\t\t"1\\t1\\t999999999\\t0000000000\\n\\n",\n+\n+\t\t"4444........333...22...1...333\\n"\n+\t\t"999999999...22\\n"\n+\t\t"7...........22\\n"\n+\t\t"....................88888888\\n"\n+\t\t"\\n"\n+\t\t"666666...666666...666666......4444\\n"\n+\t\t"1........1........999999999...0000000000\\n\\n"\n+\t);\n+\n+\tCheck(\n+\t\tt, 8, 1, '\\t', true,\n+\t\t"4444\\t333\\t22\\t1\\t333\\n"\n+\t\t"999999999\\t22\\n"\n+\t\t"7\\t22\\n"\n+\t\t"\\t\\t\\t88888888\\n"\n+\t\t"\\n"\n+\t\t"666666\\t666666\\t666666\\t4444\\n"\n+\t\t"1\\t1\\t999999999\\t0000000000\\n\\n",\n+\n+\t\t"4444\\t\\t333\\t22\\t1\\t333\\n"\n+\t\t"999999999\\t22\\n"\n+\t\t"7\\t\\t22\\n"\n+\t\t"\\t\\t\\t\\t88888888\\n"\n+\t\t"\\n"\n+\t\t"666666\\t666666\\t666666\\t\\t4444\\n"\n+\t\t"1\\t1\\t999999999\\t0000000000\\n\\n"\n+\t);\n+\n+\tCheck(\n+\t\tt, 4, 2, ' ', false,\n+\t\t".0\\t.3\\t2.4\\t-5.1\\t\\n"\n+\t\t"23.0\\t12345678.9\\t2.4\\t-989.4\\t\\n"\n+\t\t"5.1\\t12.0\\t2.4\\t-7.0\\t\\n"\n+\t\t".0\\t0.0\\t332.0\\t8908.0\\t\\n"\n+\t\t".0\\t-.3\\t456.4\\t22.1\\t\\n"\n+\t\t".0\\t1.2\\t44.4\\t-13.3\\t\\n\\n",\n+\n+\t\t" .0 .3 2.4 -5.1\\n"\n+\t\t" 23.0 12345678.9 2.4 -989.4\\n"\n+\t\t" 5.1 12.0 2.4 -7.0\\n"\n+\t\t" .0 0.0 332.0 8908.0\\n"\n+\t\t" .0 -.3 456.4 22.1\\n"\n+\t\t" .0 1.2 44.4 -13.3\\n\\n"\n+\t);\n }\n```
## コアとなるコードの解説
### `Writer` 構造体の定義変更
* `tabwidth` から `cellwidth` への変更は、このフィールドが単にタブの幅を示すだけでなく、各セルの最小幅を制御する汎用的なパラメータであることを明確にしています。
* `usetabs bool` が削除され、`padbytes [8]byte` と `align_left bool` が追加されたのは、パディングの柔軟性とアライメント制御を向上させるための根本的な変更です。
* `padbytes`: 以前は空白かタブのどちらかしか選べませんでしたが、この配列に任意のASCII文字を格納することで、その文字をパディングとして使用できるようになりました。配列サイズが8なのは、`WritePadding` 関数内で効率的にバイト列を書き込むためです。
* `align_left`: これまで暗黙的に左寄せだったアライメントを、明示的に制御できるようにしました。`true` なら左寄せ、`false` なら右寄せとなります。
### `Init` 関数のシグネチャと初期化ロジックの変更
* 新しい引数 `padchar byte` と `align_left bool` は、`Writer` の初期化時にパディング文字とアライメント方向を直接指定できるようにします。
* `cellwidth < 0` または `padding < 0` のチェックが追加され、負の値が渡された場合に `panic` を発生させるようになりました。これは、不正な入力に対する早期のエラー検出と、ライブラリの堅牢性向上に貢献します。
* `for i := len(b.padbytes) - 1; i >= 0; i-- { b.padbytes[i] = padchar; }` のループは、`padbytes` 配列全体を `padchar` で埋めることで、任意のパディング文字を使用するための準備をします。
* `b.align_left = align_left || padchar == '\t';` の行は重要です。これは、ユーザーが `align_left` を `false` (右寄せ) に指定したとしても、もしパディング文字がタブ (`\t`) であれば、強制的に左寄せ (`true`) に設定することを意味します。これは、タブ文字が通常、固定幅のタブストップで表示され、その性質上、左寄せとして機能するためです。
### `WritePadding` 関数の変更
* `if b.usetabs { ... }` のブロックが `if b.padbytes[0] == '\t' { ... }` に変更されました。これは、パディング文字がタブである場合にのみ、`cellw` を `cellwidth` の倍数に調整するロジックを適用するためです。これにより、タブによるパディングが正しく機能します。
* `padding := Blanks;` や `padding = Tabs;` といった固定のバイトスライスを使用していた箇所が削除され、代わりに `b.padbytes` を使用するようになりました。これにより、`Init` 関数で設定された任意の `padchar` がパディングに利用されます。
* `for n > len(b.padbytes) { err = b.Write0(&b.padbytes); ... }` のループは、必要なパディングの長さ `n` が `padbytes` の長さ(8バイト)よりも大きい場合に、`padbytes` を繰り返し書き込むことで効率的にパディングを生成します。
* `err = b.Write0((&b.padbytes)[0 : n]);` は、残りのパディングを書き込む部分です。コメントにある `BUG 6g should not require ()'s` は、当時のGoコンパイラ (6g) のバグを示唆しており、スライス操作のために括弧が必要だったことを示しています。
### `WriteLines` 関数のアライメントロジック変更
* この関数は、バッファに蓄積された行とセルのデータを実際の出力ライターに書き出す役割を担っています。
* `if b.align_left { ... } else { ... }` の条件分岐が追加されたことで、アライメントの制御が実現されました。
* **左寄せ (`b.align_left` が `true`)**: 従来の動作と同様に、まずセルのテキストを書き込み (`b.Write0(b.buf.a[pos : pos + w])`)、その後に必要なパディングを書き込みます (`b.WritePadding(w, b.widths.At(j))`)。
* **右寄せ (`b.align_left` が `false`)**: まず必要なパディングを書き込み (`b.WritePadding(w, b.widths.At(j))`)、その後にセルのテキストを書き込みます (`b.Write0(b.buf.a[pos : pos + w])`)。これにより、テキストがセルの右端に揃えられます。
### `New` 関数のシグネチャ変更
* `tabwriter.New` は `tabwriter.Writer` のコンストラクタ関数であり、そのシグネチャが `Init` 関数に合わせて変更されました。これにより、外部から `tabwriter` を利用する際に、新しいパディングとアライメントのオプションを指定できるようになりました。
これらの変更は、`tabwriter` の柔軟性と表現力を大幅に向上させ、より多様なテキスト整形ニーズに対応できるようになりました。特に、数値データの右寄せアライメントは、表形式のレポートやCLIツールでの出力において非常に重要な機能です。
## 関連リンク
* Elastic Tabstops: [http://nickgravgaard.com/elastictabstops/index.html](http://nickgravgaard.com/elastictabstops/index.html)
## 参考にした情報源リンク
* Go言語の公式ドキュメント (tabwriterパッケージ): [https://pkg.go.dev/text/tabwriter](https://pkg.go.dev/text/tabwriter) (コミット当時のバージョンとは異なる可能性がありますが、概念理解に役立ちます)
* Go言語の `io` パッケージ: [https://pkg.go.dev/io](https://pkg.go.dev/io)
* Gitのコミット履歴と差分表示 (GitHub): [https://github.com/golang/go/commit/130e6f42f1b993c1764dc1c346c9af222e59e1d2](https://github.com/golang/go/commit/130e6f42f1b993c1764dc1c346c9af222e59e1d2)
* Go言語の `panic` について: [https://go.dev/blog/defer-panic-and-recover](https://go.dev/blog/defer-panic-and-recover) (概念理解に役立ちます)