[インデックス 1314] ファイルの概要
このコミットは、Go言語の初期開発段階における go/pretty
ツール(コード整形ツール)のHTML出力機能に関する重要な変更を記録しています。具体的には、HTMLタグの生成とエスケープ処理を簡素化し、より正確なHTMLページをソースコードから生成できるようにするためのリファクタリングが行われました。これにより、独立した htmlwriter
パッケージが廃止され、その機能が printer
パッケージに直接統合されました。
コミット
commit 4873bb217c18b838feb12c58200b6a02e49c906c
Author: Robert Griesemer <gri@golang.org>
Date: Wed Dec 10 13:51:19 2008 -0800
Snapshot:
- fixed html tag generation
- simplified html escaping machinery
(not 100% correct for strings yet)
- can now produce the first mostly correct formatted html pages from source
with (fake) links: e.g. pretty -html srcfile.go > srcfile.html
R=r
OCL=20915
CL=20915
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/4873bb217c18b838feb12c58200b6a02e49c906c
元コミット内容
このコミットは、go/pretty
ツールにおけるHTML出力機能のスナップショット更新です。主な変更点は以下の通りです。
- HTMLタグの生成が修正されました。
- HTMLエスケープの仕組みが簡素化されました(ただし、文字列に対するエスケープはまだ100%正確ではないとされています)。
- ソースコードから、ほぼ正確に整形されたHTMLページ(擬似的なリンクを含む)を生成できるようになりました。例えば、
pretty -html srcfile.go > srcfile.html
のように実行できます。
変更の背景
このコミットが行われた2008年12月は、Go言語がまだ一般に公開される前の非常に初期の開発段階でした。go/pretty
ツールは、Goのソースコードを整形し、場合によってはHTML形式で出力する機能を持っていました。
初期の実装では、HTMLの生成とエスケープ処理が htmlwriter
という独立したパッケージで行われていました。しかし、このような分離は、HTML生成ロジックとコード整形ロジックの間の密接な連携を妨げ、複雑さを増していた可能性があります。特に、HTMLエスケープはセキュリティ(クロスサイトスクリプティング (XSS) 攻撃の防止)と表示の正確性の両方において非常に重要であり、その処理が不完全であったり、扱いにくかったりすると問題が生じます。
このコミットの背景には、以下の目的があったと考えられます。
- コードの簡素化と統合:
htmlwriter
パッケージの機能をprinter
パッケージに直接取り込むことで、HTML生成とエスケープのロジックを一元化し、コードベース全体の理解と保守を容易にする。 - HTML出力の品質向上: HTMLタグの生成におけるバグを修正し、エスケープ処理を改善することで、より正確でブラウザで正しく表示されるHTML出力を実現する。
- 開発効率の向上:
pretty
ツールがより信頼性の高いHTML出力を提供できるようになることで、Go言語のソースコードをWeb上で閲覧する際の利便性を高め、開発者がコードを共有・レビューしやすくする。
コミットメッセージにある「(not 100% correct for strings yet)」という記述は、この時点ではまだHTMLエスケープ処理が完璧ではないことを示しており、継続的な改善の途上であったことを示唆しています。
前提知識の解説
このコミットを理解するためには、以下の概念やGo言語の初期の文脈に関する知識が役立ちます。
- Go言語の初期開発: Go言語は2009年11月に一般公開されましたが、このコミットはそれ以前の2008年に行われています。当時のGo言語はまだ実験的な段階であり、APIやパッケージ構造は流動的でした。
usr/gri/pretty
のようなパスは、開発者の個人作業ディレクトリを示している可能性があり、現在のGoの標準ライブラリの構造とは異なります。 go/pretty
ツール: これはGo言語のソースコードを整形(pretty-print)するためのツールです。コードの可読性を高めるために、インデント、改行、スペースなどを統一する役割を担います。また、このコミットが示すように、HTML形式での出力機能も持っていました。- HTMLエスケープ: HTML文書内で特殊な意味を持つ文字(例:
<
、>
、&
、"
、'
)を、それらの文字自体として表示するために、対応するHTMLエンティティ(例:<
、>
、&
、"
、'
)に変換する処理です。これは、ブラウザがHTMLタグとして解釈するのを防ぎ、XSS(クロスサイトスクリプティング)などのセキュリティ脆弱性を防ぐ上で不可欠です。 Makefile
:make
ユーティリティが使用する設定ファイルで、ソフトウェアのビルドプロセスを自動化するために使われます。依存関係を定義し、それらを解決するためのコマンドを記述します。このコミットでは、printer.6
のビルド依存関係からhtmlwriter.6
が削除されています。.6
拡張子は、当時のGoコンパイラが生成するオブジェクトファイルやパッケージファイルを示している可能性があります。io.Writer
インターフェース: Go言語の標準ライブラリio
パッケージで定義されているインターフェースで、データを書き込むための抽象化を提供します。Write([]byte) (n int, err error)
メソッドを持ち、ファイル、ネットワーク接続、メモリバッファなど、様々な出力先に統一的にデータを書き込むことができます。tabwriter
パッケージ: Go言語の標準ライブラリtext/tabwriter
パッケージ(当時はtabwriter
として直接インポートされていた可能性)は、テキストをタブ区切りで整形し、列を揃えるための機能を提供します。このコミットでは、pretty
ツールが整形されたテキストをHTML出力に利用していることが示唆されます。
技術的詳細
このコミットの技術的な核心は、HTMLエスケープとHTMLタグ生成のロジックを、独立した htmlwriter
パッケージから printer
パッケージに直接移行し、より統合されたアプローチを採用した点にあります。
-
htmlwriter.go
の削除:usr/gri/pretty/htmlwriter.go
ファイルが完全に削除されました。これは、HTMLエスケープとタグ生成の責任がこのファイルから他の場所へ移されたことを意味します。Makefile
からもprinter.6
の依存関係としてhtmlwriter.6
が削除され、ビルドシステムレベルでの分離が完了しました。
-
printer.go
への機能統合:printer.go
からhtmlwriter
パッケージのインポートが削除されました。Printer
構造体からtags *htmlwriter.Writer
フィールドが削除され、Printer
がhtmlwriter
に直接依存しなくなりました。Printer.Init
メソッドのシグネチャが変更され、tags *htmlwriter.Writer
引数が削除されました。これにより、Printer
の初期化時にhtmlwriter
のインスタンスを渡す必要がなくなりました。
-
HtmlEscape
関数の導入:printer.go
内にHtmlEscape(s string) string
という新しい関数が追加されました。この関数は、入力文字列s
内のHTML特殊文字(<
と&
)を対応するHTMLエンティティ(<
と&
)に再帰的に変換します。- 注目すべきは、
html.BVal()
というフラグがチェックされている点です。これは、HTML出力モードが有効な場合にのみエスケープ処理が行われることを示唆しています。 - 実装は再帰的であり、文字列を走査し、特殊文字が見つかった場合にその文字をエスケープし、残りの文字列に対して再帰呼び出しを行うというものです。これは、当時のGo言語の文字列操作や再帰の典型的なパターンを示しています。
-
HTMLタグ生成ロジックの変更:
- 以前は
P.tags.Tag(...)
を使用してHTMLタグを生成していましたが、これがP.TaggedString(pos, tag, s, endtag string)
メソッドの呼び出しに置き換えられました。 TaggedString
メソッドは、開始タグ、エスケープされた文字列、終了タグをまとめて出力する汎用的な機能を提供します。これにより、HTMLタグとコンテンツの出力がより密接に連携するようになりました。HtmlPrologue
(HTMLヘッダとボディの開始) およびHtmlEpilogue
(HTMLボディとドキュメントの終了) メソッドもP.tags.Tag
からP.TaggedString
を使用するように変更されました。特に、HtmlPrologue
ではtitle
もHtmlEscape
されるようになりました。HtmlIdentifier
メソッドも同様にP.TaggedString
を使用するように変更され、識別子自体はエスケープの必要がないことがコメントで明記されています。
- 以前は
-
文字列出力の変更:
P.String(pos int, s string)
メソッドが、内部的にP.TaggedString(pos, "", s, "")
を呼び出すように変更されました。これは、通常の文字列出力もTaggedString
の汎用的なフレームワークに乗せることで、コードの一貫性を高める意図があったと考えられます。- コメントの出力部分 (
ctext
) もHtmlEscape(ctext)
を通すように変更され、コメント内の特殊文字がHTMLで正しく表示されるようになりました。 Printf
の代わりにString(0, ";")
を使用する箇所が追加され、HTML出力モードでのセミコロンの扱いが統一されました。
-
Print
関数の変更:Print
関数内でhtmlwriter.New(text)
を呼び出してtags
インスタンスを生成していた部分が削除されました。これは、htmlwriter
パッケージが不要になったことを直接的に示しています。P.Init
の呼び出しも、tags
引数が削除された新しいシグネチャに合わせて変更されました。
-
test.sh
の変更:usr/gri/pretty/test.sh
にtypes_amd64_darwin.go
がスキップ対象ファイルとして追加されました。これは、この特定のファイルが当時のpretty
ツールの整形またはHTML出力テストで問題を引き起こしていたことを示唆しています。
これらの変更は、Go言語のコード整形ツールが、より堅牢で統合されたHTML出力機能を持つように進化する過程を示しています。特に、HTMLエスケープのロジックをコアの printer
パッケージに直接組み込むことで、将来的な改善や機能拡張が容易になる基盤を築いたと言えます。
コアとなるコードの変更箇所
usr/gri/pretty/Makefile
:printer.6
の依存関係からhtmlwriter.6
を削除。
usr/gri/pretty/htmlwriter.go
:- ファイル全体が削除。
usr/gri/pretty/printer.go
:import "htmlwriter";
の削除。type Printer struct
からtags *htmlwriter.Writer;
フィールドの削除。func (P *Printer) Init(...)
のシグネチャ変更(tags *htmlwriter.Writer
引数の削除)。- 新関数
func HtmlEscape(s string) string
の追加。 func (P *Printer) String(pos int, s string)
がfunc (P *Printer) TaggedString(pos int, tag, s, endtag string)
に変更され、旧String
はTaggedString
を呼び出すラッパーに。Printf
やコメント出力部分でHtmlEscape
の使用を開始。HtmlPrologue
,HtmlEpilogue
,HtmlIdentifier
メソッドの内部実装がP.tags.Tag
からP.TaggedString
を使用するように変更。Print
関数内でhtmlwriter.New
の呼び出しを削除し、P.Init
の引数を変更。
usr/gri/pretty/test.sh
:- スキップ対象ファイルリストに
types_amd64_darwin.go
を追加。
- スキップ対象ファイルリストに
コアとなるコードの解説
usr/gri/pretty/Makefile
の変更
--- a/usr/gri/pretty/Makefile
+++ b/usr/gri/pretty/Makefile
@@ -37,7 +37,7 @@ parser.6: scanner.6 ast.6
platform.6: utils.6
-printer.6: scanner.6 ast.6 htmlwriter.6
+printer.6: scanner.6 ast.6
%.6: %.go
$(G) $(F) $<
printer.6
のビルドに必要な依存関係から htmlwriter.6
が削除されました。これは、printer
パッケージが htmlwriter
パッケージに依存しなくなったことをビルドシステムに伝えています。これにより、htmlwriter.go
ファイルの削除と整合性が取れます。
usr/gri/pretty/htmlwriter.go
の削除
--- a/usr/gri/pretty/htmlwriter.go
+++ /dev/null
@@ -1,69 +0,0 @@
-// Copyright 2009 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package htmlwriter
-
-import (
- "os";
- "io";
- "fmt";
-)
-
-// Writer is a filter implementing the io.Write interface.
-// It provides facilities to generate HTML tags and does
-// HTML-escaping for text written through Write. Incoming
-// text is assumed to be UTF-8 encoded.
-
-export type Writer struct {
- // TODO should not export any of the fields
- writer io.Write;
-}
-
-
-func (b *Writer) Init(writer io.Write) *Writer {
- b.writer = writer;
- return b;
-}
-
-
-/* export */ func (p *Writer) Write(buf *[]byte) (written int, err *os.Error) {
- i0 := 0;
- for i := i0; i < len(buf); i++ {
- var s string;
- switch buf[i] {
- case '<': s = "<";
- case '&': s = "&";
- default: continue;
- }
- // write HTML escape instead of buf[i]
- w1, e1 := p.writer.Write(buf[i0 : i]);
- if e1 != nil {
- return i0 + w1, e1;
- }
- w2, e2 := io.WriteString(p.writer, s);
- if e2 != nil {
- return i0 + w1 /* not w2! */, e2;
- }
- i0 = i + 1;
- }
- written, err = p.writer.Write(buf[i0 : len(buf)]);
- return len(buf), err;
-}
-
-
-// ----------------------------------------------------------------------------
-// HTML-specific interface
-
-/* export */ func (p *Writer) Tag(s string) {
- // TODO proper error handling
- io.WriteString(p.writer, s);
-}
-
-
-// ----------------------------------------------------------------------------
-//
-
-export func New(writer io.Write) *Writer {
- return new(Writer).Init(writer);
-}
htmlwriter.go
は、HTMLエスケープとタグ生成の機能を提供していた独立したパッケージでした。このファイルの削除は、これらの機能が printer
パッケージに直接統合されたことを明確に示しています。これにより、コードベースのモジュール構成が変更され、HTML出力に関するロジックがより一元化されました。
usr/gri/pretty/printer.go
の変更
インポートと構造体の変更
--- a/usr/gri/pretty/printer.go
+++ b/usr/gri/pretty/printer.go
@@ -11,7 +11,6 @@ import (
"tabwriter";
"flag";
"fmt";
- "htmlwriter";
Scanner "scanner";
AST "ast";
)
@@ -57,7 +56,6 @@ const (
type Printer struct {
// output
text io.Write;
- tags *htmlwriter.Writer;
// comments
comments *array.Array; // the list of all comments
@@ -94,10 +92,9 @@ func (P *Printer) NextComment() {
}
-func (P *Printer) Init(text io.Write, tags *htmlwriter.Writer, comments *array.Array) {
+func (P *Printer) Init(text io.Write, comments *array.Array) {
// writers
P.text = text;
- P.tags = tags;
// comments
P.comments = comments;
htmlwriter
パッケージのインポートが削除され、Printer
構造体から tags *htmlwriter.Writer
フィールドが削除されました。これに伴い、Printer.Init
メソッドのシグネチャも変更され、htmlwriter.Writer
のインスタンスを受け取らなくなりました。これは、Printer
がHTMLエスケープとタグ生成の責任を直接負うようになったことを示しています。
HtmlEscape
関数の追加
--- a/usr/gri/pretty/printer.go
+++ b/usr/gri/pretty/printer.go
@@ -111,6 +108,22 @@ func (P *Printer) Init(text io.Write, tags *htmlwriter.Writer, comments *array.A
// ----------------------------------------------------------------------------
// Printing support
+func HtmlEscape(s string) string {
+ if html.BVal() {
+ var esc string;
+ for i := 0; i < len(s); i++ {
+ switch s[i] {
+ case '<': esc = "<";
+ case '&': esc = "&";
+ default: continue;
+ }
+ return s[0 : i] + esc + HtmlEscape(s[i+1 : len(s)]);
+ }
+ }
+ return s;
+}
+
+
func (P *Printer) Printf(format string, s ...) {
n, err := fmt.fprintf(P.text, format, s);
if err != nil {
HtmlEscape
関数が追加されました。この関数は、HTML出力モード (html.BVal()
) が有効な場合に、入力文字列 s
内の <
と &
をそれぞれ <
と &
に変換します。再帰的な実装により、文字列全体を走査し、特殊文字をエスケープします。この関数が printer
パッケージ内に直接定義されたことで、HTMLエスケープ処理がより密接にコード整形ロジックと連携するようになりました。
String
および TaggedString
メソッドの変更
--- a/usr/gri/pretty/printer.go
+++ b/usr/gri/pretty/printer.go
@@ -135,7 +148,7 @@ func (P *Printer) Newline(n int) {
}\n \n \n-func (P *Printer) String(pos int, s string) {\n+func (P *Printer) TaggedString(pos int, tag, s, endtag string) {\n \t// use estimate for pos if we don\'t have one\n \tif pos == 0 {\n \t\tpos = P.lastpos;\n@@ -230,7 +243,7 @@ func (P *Printer) String(pos int, s string) {\n \t\t\tif debug.BVal() {\n \t\t\t\tP.Printf(\"[%d]\", P.cpos);\n \t\t\t}\n-\t\t\tP.Printf(\"%s\", ctext);\n+\t\t\tP.Printf(\"%s\", HtmlEscape(ctext));\n \n \t\t\tif ctext[1] == \'/\' {\n \t\t\t\t//-style comments must end in newline\n@@ -276,7 +289,7 @@ func (P *Printer) String(pos int, s string) {\n \tif debug.BVal() {\n \t\tP.Printf(\"[%d]\", pos);\n \t}\n-\tP.Printf(\"%s\", s);\n+\tP.Printf(\"%s%s%s\", tag, HtmlEscape(s), endtag);\n \n \t// --------------------------------\n \t// interpret state\n@@ -300,6 +313,11 @@ func (P *Printer) String(pos int, s string) {\n }\n \n \n+func (P *Printer) String(pos int, s string) {\n+\tP.TaggedString(pos, \"\", s, \"\");\n+}\n+\n+\n func (P *Printer) Token(pos int, tok int) {\n \tP.String(pos, Scanner.TokenString(tok));\n }\n```
`String` メソッドが `TaggedString` にリネームされ、開始タグ、文字列、終了タグを受け取るようになりました。これにより、HTMLタグで囲まれたコンテンツをより柔軟に出力できるようになります。元の `String` メソッドは、新しい `TaggedString` を呼び出すラッパーとして再定義され、既存のコードとの互換性を保っています。コメントのテキスト (`ctext`) や通常の文字列出力も `HtmlEscape` を通すようになり、HTML出力時の安全性が向上しました。
#### HTML関連メソッドの変更
```diff
--- a/usr/gri/pretty/printer.go
+++ b/usr/gri/pretty/printer.go
@@ -316,36 +334,39 @@ func (P *Printer) Error(pos int, tok int, msg string) {\n // HTML support\n \n func (P *Printer) HtmlPrologue(title string) {\n-\tif P.tags != nil {\n-\t\tP.tags.Tag(\n+\tif html.BVal() {\n+\t\tP.TaggedString(0,\n \t\t\t"<html>\\n"\n \t\t\t"<head>\\n"\n \t\t\t"\t<META HTTP-EQUIV=\"Content-Type\" CONTENT=\"text/html; charset=UTF-8\">\\n"\n-\t\t\t"\t<title>" + title + "</title>\\n"\n+\t\t\t"\t<title>" + HtmlEscape(title) + "</title>\\n"\n \t\t\t"\t<style type=\"text/css\">\\n"\n \t\t\t"\t</style>\\n"\n \t\t\t"</head>\\n"\n \t\t\t"<body>\\n"\n-\t\t\t"<pre>\\n"\n+\t\t\t"<pre>\\n",\n+\t\t\t"", ""\n \t\t)\n \t}\n }\n \n \n func (P *Printer) HtmlEpilogue() {\n-\tif P.tags != nil {\n-\t\tP.tags.Tag(\n+\tif html.BVal() {\n+\t\tP.TaggedString(0, \n \t\t\t"</pre>\\n"\n \t\t\t"</body>\\n"\n-\t\t\t"<html>\\n"\n+\t\t\t"<html>\\n",\n+\t\t\t"", ""\n \t\t)\n \t}\n }\n \n \n func (P *Printer) HtmlIdentifier(pos int, ident string) {\n-\tif false && P.tags != nil {\n-\t\tP.tags.Tag(`<a href="#` + ident + `">` + ident + `</a>`);\n+\tif html.BVal() {\n+\t\t// no need to HtmlEscape ident\n+\t\tP.TaggedString(pos, `<a href="#` + ident + `">`, ident, `</a>`);\n \t} else {\n \t\tP.String(pos, ident);\n \t}\n```
`HtmlPrologue`, `HtmlEpilogue`, `HtmlIdentifier` の各メソッドも、`P.tags.Tag` の代わりに新しく導入された `P.TaggedString` を使用するように変更されました。これにより、HTMLタグの生成が `printer` パッケージ内で一貫して処理されるようになりました。特に、`HtmlPrologue` ではタイトルも `HtmlEscape` されるようになり、セキュリティが向上しています。
#### `Print` 関数の変更
```diff
--- a/usr/gri/pretty/printer.go
+++ b/usr/gri/pretty/printer.go
@@ -838,14 +859,8 @@ export func Print(prog *AST.Program) {\n \tif usetabs.BVal() {\n \t\tpadchar = '\t';\n \t}\n-\tvar (\n-\t\ttext = tabwriter.New(os.Stdout, int(tabwidth.IVal()), 1, padchar, true, html.BVal());\n-\t\ttags *htmlwriter.Writer;\n-\t)\n-\tif html.BVal() {\n-\t\ttags = htmlwriter.New(text);\n-\t}\n-\tP.Init(text, tags, prog.comments);\n+\ttext := tabwriter.New(os.Stdout, int(tabwidth.IVal()), 1, padchar, true, html.BVal());\n+\tP.Init(text, prog.comments);\n \n \tP.HtmlPrologue("<the source>");\n \tP.Program(prog);\
Print
関数は、pretty
ツールのエントリポイントの一つです。この変更では、htmlwriter.New
を呼び出して tags
インスタンスを生成するロジックが完全に削除されました。これは、htmlwriter
パッケージが不要になったことを最終的に示しています。P.Init
の呼び出しも、tags
引数が削除された新しいシグネチャに合わせて更新されています。
usr/gri/pretty/test.sh
の変更
--- a/usr/gri/pretty/test.sh
+++ b/usr/gri/pretty/test.sh
@@ -23,7 +23,7 @@ apply1() {\n #echo $1 $2\n case `basename $F` in\n # these files don\'t pass the idempotency test yet\n-\tlog.go | type.go | \\\n+\tlog.go | type.go | types_amd64_darwin.go | \\\
\t\\\
\tselftest1.go | func3.go | bug014.go | bug029.go | bug032.go | bug050.go | \\\
\tbug068.go | bug088.go | bug083.go | bug106.go | bug125.go ) ;; # skip - files contain syntax errors
test.sh
スクリプトは、pretty
ツールのテストを実行するためのものです。この変更では、types_amd64_darwin.go
が、まだ冪等性テスト(整形しても結果が変わらないこと)をパスしないファイルとしてスキップリストに追加されました。これは、この特定のファイルが、新しいHTML出力ロジックや整形ロジックの変更によって、期待通りの結果を生成していなかった可能性を示唆しています。
関連リンク
- Go言語公式サイト: https://go.dev/
- Go言語の初期の歴史に関する情報(非公式リソースを含む)
参考にした情報源リンク
- Go言語の公式ドキュメント(現在のバージョン)
- HTMLエスケープに関する一般的な情報(OWASPなど)
- Gitのコミットログと差分表示の解釈に関する知識
Makefile
の基本的な構文と使用法- Go言語の
io
パッケージとtext/tabwriter
パッケージのドキュメント(当時のバージョンに相当する概念) - Go言語の初期開発に関するコミュニティの議論やブログ記事(もしあれば)I have provided the detailed explanation as requested, following all the specified instructions and chapter structure. I have used the commit data and the provided metadata, and incorporated technical details and background information. The output is in Markdown format and printed to standard output only.```markdown
[インデックス 1314] ファイルの概要
このコミットは、Go言語の初期開発段階における go/pretty
ツール(コード整形ツール)のHTML出力機能に関する重要な変更を記録しています。具体的には、HTMLタグの生成とエスケープ処理を簡素化し、より正確なHTMLページをソースコードから生成できるようにするためのリファクタリングが行われました。これにより、独立した htmlwriter
パッケージが廃止され、その機能が printer
パッケージに直接統合されました。
コミット
commit 4873bb217c18b838feb12c58200b6a02e49c906c
Author: Robert Griesemer <gri@golang.org>
Date: Wed Dec 10 13:51:19 2008 -0800
Snapshot:
- fixed html tag generation
- simplified html escaping machinery
(not 100% correct for strings yet)
- can now produce the first mostly correct formatted html pages from source
with (fake) links: e.g. pretty -html srcfile.go > srcfile.html
R=r
OCL=20915
CL=20915
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/4873bb217c18b838feb12c58200b6a02e49c906c
元コミット内容
このコミットは、go/pretty
ツールにおけるHTML出力機能のスナップショット更新です。主な変更点は以下の通りです。
- HTMLタグの生成が修正されました。
- HTMLエスケープの仕組みが簡素化されました(ただし、文字列に対するエスケープはまだ100%正確ではないとされています)。
- ソースコードから、ほぼ正確に整形されたHTMLページ(擬似的なリンクを含む)を生成できるようになりました。例えば、
pretty -html srcfile.go > srcfile.html
のように実行できます。
変更の背景
このコミットが行われた2008年12月は、Go言語がまだ一般に公開される前の非常に初期の開発段階でした。go/pretty
ツールは、Goのソースコードを整形し、場合によってはHTML形式で出力する機能を持っていました。
初期の実装では、HTMLの生成とエスケープ処理が htmlwriter
という独立したパッケージで行われていました。しかし、このような分離は、HTML生成ロジックとコード整形ロジックの間の密接な連携を妨げ、複雑さを増していた可能性があります。特に、HTMLエスケープはセキュリティ(クロスサイトスクリプティング (XSS) 攻撃の防止)と表示の正確性の両方において非常に重要であり、その処理が不完全であったり、扱いにくかったりすると問題が生じます。
このコミットの背景には、以下の目的があったと考えられます。
- コードの簡素化と統合:
htmlwriter
パッケージの機能をprinter
パッケージに直接取り込むことで、HTML生成とエスケープのロジックを一元化し、コードベース全体の理解と保守を容易にする。 - HTML出力の品質向上: HTMLタグの生成におけるバグを修正し、エスケープ処理を改善することで、より正確でブラウザで正しく表示されるHTML出力を実現する。
- 開発効率の向上:
pretty
ツールがより信頼性の高いHTML出力を提供できるようになることで、Go言語のソースコードをWeb上で閲覧する際の利便性を高め、開発者がコードを共有・レビューしやすくする。
コミットメッセージにある「(not 100% correct for strings yet)」という記述は、この時点ではまだHTMLエスケープ処理が完璧ではないことを示しており、継続的な改善の途上であったことを示唆しています。
前提知識の解説
このコミットを理解するためには、以下の概念やGo言語の初期の文脈に関する知識が役立ちます。
- Go言語の初期開発: Go言語は2009年11月に一般公開されましたが、このコミットはそれ以前の2008年に行われています。当時のGo言語はまだ実験的な段階であり、APIやパッケージ構造は流動的でした。
usr/gri/pretty
のようなパスは、開発者の個人作業ディレクトリを示している可能性があり、現在のGoの標準ライブラリの構造とは異なります。 go/pretty
ツール: これはGo言語のソースコードを整形(pretty-print)するためのツールです。コードの可読性を高めるために、インデント、改行、スペースなどを統一する役割を担います。また、このコミットが示すように、HTML形式での出力機能も持っていました。- HTMLエスケープ: HTML文書内で特殊な意味を持つ文字(例:
<
、>
、&
、"
、'
)を、それらの文字自体として表示するために、対応するHTMLエンティティ(例:<
、>
、&
、"
、'
)に変換する処理です。これは、ブラウザがHTMLタグとして解釈するのを防ぎ、XSS(クロスサイトスクリプティング)などのセキュリティ脆弱性を防ぐ上で不可欠です。 Makefile
:make
ユーティリティが使用する設定ファイルで、ソフトウェアのビルドプロセスを自動化するために使われます。依存関係を定義し、それらを解決するためのコマンドを記述します。このコミットでは、printer.6
のビルド依存関係からhtmlwriter.6
が削除されています。.6
拡張子は、当時のGoコンパイラが生成するオブジェクトファイルやパッケージファイルを示している可能性があります。io.Writer
インターフェース: Go言語の標準ライブラリio
パッケージで定義されているインターフェースで、データを書き込むための抽象化を提供します。Write([]byte) (n int, err error)
メソッドを持ち、ファイル、ネットワーク接続、メモリバッファなど、様々な出力先に統一的にデータを書き込むことができます。tabwriter
パッケージ: Go言語の標準ライブラリtext/tabwriter
パッケージ(当時はtabwriter
として直接インポートされていた可能性)は、テキストをタブ区切りで整形し、列を揃えるための機能を提供します。このコミットでは、pretty
ツールが整形されたテキストをHTML出力に利用していることが示唆されます。
技術的詳細
このコミットの技術的な核心は、HTMLエスケープとHTMLタグ生成のロジックを、独立した htmlwriter
パッケージから printer
パッケージに直接移行し、より統合されたアプローチを採用した点にあります。
-
htmlwriter.go
の削除:usr/gri/pretty/htmlwriter.go
ファイルが完全に削除されました。これは、HTMLエスケープとタグ生成の責任がこのファイルから他の場所へ移されたことを意味します。Makefile
からもprinter.6
の依存関係としてhtmlwriter.6
が削除され、ビルドシステムレベルでの分離が完了しました。
-
printer.go
への機能統合:printer.go
からhtmlwriter
パッケージのインポートが削除されました。Printer
構造体からtags *htmlwriter.Writer
フィールドが削除され、Printer
がhtmlwriter
に直接依存しなくなりました。Printer.Init
メソッドのシグネチャが変更され、tags *htmlwriter.Writer
引数が削除されました。これにより、Printer
の初期化時にhtmlwriter
のインスタンスを渡す必要がなくなりました。
-
HtmlEscape
関数の導入:printer.go
内にHtmlEscape(s string) string
という新しい関数が追加されました。この関数は、入力文字列s
内のHTML特殊文字(<
と&
)を対応するHTMLエンティティ(<
と&
)に再帰的に変換します。- 注目すべきは、
html.BVal()
というフラグがチェックされている点です。これは、HTML出力モードが有効な場合にのみエスケープ処理が行われることを示唆しています。 - 実装は再帰的であり、文字列を走査し、特殊文字が見つかった場合にその文字をエスケープし、残りの文字列に対して再帰呼び出しを行うというものです。これは、当時のGo言語の文字列操作や再帰の典型的なパターンを示しています。
-
HTMLタグ生成ロジックの変更:
- 以前は
P.tags.Tag(...)
を使用してHTMLタグを生成していましたが、これがP.TaggedString(pos, tag, s, endtag string)
メソッドの呼び出しに置き換えられました。 TaggedString
メソッドは、開始タグ、エスケープされた文字列、終了タグをまとめて出力する汎用的な機能を提供します。これにより、HTMLタグとコンテンツの出力がより密接に連携するようになりました。HtmlPrologue
(HTMLヘッダとボディの開始) およびHtmlEpilogue
(HTMLボディとドキュメントの終了) メソッドもP.tags.Tag
からP.TaggedString
を使用するように変更されました。特に、HtmlPrologue
ではtitle
もHtmlEscape
されるようになりました。HtmlIdentifier
メソッドも同様にP.TaggedString
を使用するように変更され、識別子自体はエスケープの必要がないことがコメントで明記されています。
- 以前は
-
文字列出力の変更:
P.String(pos int, s string)
メソッドが、内部的にP.TaggedString(pos, "", s, "")
を呼び出すように変更されました。これは、通常の文字列出力もTaggedString
の汎用的なフレームワークに乗せることで、コードの一貫性を高める意図があったと考えられます。- コメントの出力部分 (
ctext
) もHtmlEscape(ctext)
を通すように変更され、コメント内の特殊文字がHTMLで正しく表示されるようになりました。 Printf
の代わりにString(0, ";")
を使用する箇所が追加され、HTML出力モードでのセミコロンの扱いが統一されました。
-
Print
関数の変更:Print
関数内でhtmlwriter.New(text)
を呼び出してtags
インスタンスを生成していた部分が削除されました。これは、htmlwriter
パッケージが不要になったことを最終的に示しています。P.Init
の呼び出しも、tags
引数が削除された新しいシグネチャに合わせて変更されました。
-
test.sh
の変更:usr/gri/pretty/test.sh
にtypes_amd64_darwin.go
がスキップ対象ファイルとして追加されました。これは、この特定のファイルが当時のpretty
ツールの整形またはHTML出力テストで問題を引き起こしていたことを示唆しています。
これらの変更は、Go言語のコード整形ツールが、より堅牢で統合されたHTML出力機能を持つように進化する過程を示しています。特に、HTMLエスケープのロジックをコアの printer
パッケージに直接組み込むことで、将来的な改善や機能拡張が容易になる基盤を築いたと言えます。
コアとなるコードの変更箇所
usr/gri/pretty/Makefile
:printer.6
の依存関係からhtmlwriter.6
を削除。
usr/gri/pretty/htmlwriter.go
:- ファイル全体が削除。
usr/gri/pretty/printer.go
:import "htmlwriter";
の削除。type Printer struct
からtags *htmlwriter.Writer;
フィールドの削除。func (P *Printer) Init(...)
のシグネチャ変更(tags *htmlwriter.Writer
引数の削除)。- 新関数
func HtmlEscape(s string) string
の追加。 func (P *Printer) String(pos int, s string)
がfunc (P *Printer) TaggedString(pos int, tag, s, endtag string)
に変更され、旧String
はTaggedString
を呼び出すラッパーに。Printf
やコメント出力部分でHtmlEscape
の使用を開始。HtmlPrologue
,HtmlEpilogue
,HtmlIdentifier
メソッドの内部実装がP.tags.Tag
からP.TaggedString
を使用するように変更。Print
関数内でhtmlwriter.New
の呼び出しを削除し、P.Init
の引数を変更。
usr/gri/pretty/test.sh
:- スキップ対象ファイルリストに
types_amd64_darwin.go
を追加。
- スキップ対象ファイルリストに
コアとなるコードの解説
usr/gri/pretty/Makefile
の変更
--- a/usr/gri/pretty/Makefile
+++ b/usr/gri/pretty/Makefile
@@ -37,7 +37,7 @@ parser.6: scanner.6 ast.6
platform.6: utils.6
-printer.6: scanner.6 ast.6 htmlwriter.6
+printer.6: scanner.6 ast.6
%.6: %.go
$(G) $(F) $<
printer.6
のビルドに必要な依存関係から htmlwriter.6
が削除されました。これは、printer
パッケージが htmlwriter
パッケージに依存しなくなったことをビルドシステムに伝えています。これにより、htmlwriter.go
ファイルの削除と整合性が取れます。
usr/gri/pretty/htmlwriter.go
の削除
--- a/usr/gri/pretty/htmlwriter.go
+++ /dev/null
@@ -1,69 +0,0 @@
-// Copyright 2009 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package htmlwriter
-
-import (
- "os";
- "io";
- "fmt";
-)
-
-// Writer is a filter implementing the io.Write interface.
-// It provides facilities to generate HTML tags and does
-// HTML-escaping for text written through Write. Incoming
-// text is assumed to be UTF-8 encoded.
-
-export type Writer struct {
- // TODO should not export any of the fields
- writer io.Write;
-}
-
-
-func (b *Writer) Init(writer io.Write) *Writer {
- b.writer = writer;
- return b;
-}
-
-
-/* export */ func (p *Writer) Write(buf *[]byte) (written int, err *os.Error) {
- i0 := 0;
- for i := i0; i < len(buf); i++ {
- var s string;
- switch buf[i] {
- case '<': s = "<";
- case '&': s = "&";
- default: continue;
- }
- // write HTML escape instead of buf[i]
- w1, e1 := p.writer.Write(buf[i0 : i]);
- if e1 != nil {
- return i0 + w1, e1;
- }
- w2, e2 := io.WriteString(p.writer, s);
- if e2 != nil {
- return i0 + w1 /* not w2! */, e2;
- }
- i0 = i + 1;
- }
- written, err = p.writer.Write(buf[i0 : len(buf)]);
- return len(buf), err;
-}
-
-
-// ----------------------------------------------------------------------------
-// HTML-specific interface
-
-/* export */ func (p *Writer) Tag(s string) {
- // TODO proper error handling
- io.WriteString(p.writer, s);
-}
-
-
-// ----------------------------------------------------------------------------
-//
-
-export func New(writer io.Write) *Writer {
- return new(Writer).Init(writer);
-}
htmlwriter.go
は、HTMLエスケープとタグ生成の機能を提供していた独立したパッケージでした。このファイルの削除は、これらの機能が printer
パッケージに直接統合されたことを明確に示しています。これにより、コードベースのモジュール構成が変更され、HTML出力に関するロジックがより一元化されました。
usr/gri/pretty/printer.go
の変更
インポートと構造体の変更
--- a/usr/gri/pretty/printer.go
+++ b/usr/gri/pretty/printer.go
@@ -11,7 +11,6 @@ import (
"tabwriter";
"flag";
"fmt";
- "htmlwriter";
Scanner "scanner";
AST "ast";
)
@@ -57,7 +56,6 @@ const (
type Printer struct {
// output
text io.Write;
- tags *htmlwriter.Writer;
// comments
comments *array.Array; // the list of all comments
@@ -94,10 +92,9 @@ func (P *Printer) NextComment() {
}
-func (P *Printer) Init(text io.Write, tags *htmlwriter.Writer, comments *array.Array) {
+func (P *Printer) Init(text io.Write, comments *array.Array) {
// writers
P.text = text;
- P.tags = tags;
// comments
P.comments = comments;
htmlwriter
パッケージのインポートが削除され、Printer
構造体から tags *htmlwriter.Writer
フィールドが削除されました。これに伴い、Printer.Init
メソッドのシグネチャも変更され、htmlwriter.Writer
のインスタンスを受け取らなくなりました。これは、Printer
がHTMLエスケープとタグ生成の責任を直接負うようになったことを示しています。
HtmlEscape
関数の追加
--- a/usr/gri/pretty/printer.go
+++ b/usr/gri/pretty/printer.go
@@ -111,6 +108,22 @@ func (P *Printer) Init(text io.Write, tags *htmlwriter.Writer, comments *array.A
// ----------------------------------------------------------------------------
// Printing support
+func HtmlEscape(s string) string {
+ if html.BVal() {
+ var esc string;
+ for i := 0; i < len(s); i++ {
+ switch s[i] {
+ case '<': esc = "<";
+ case '&': esc = "&";
+ default: continue;
+ }
+ return s[0 : i] + esc + HtmlEscape(s[i+1 : len(s)]);
+ }
+ }
+ return s;
+}
+
+
func (P *Printer) Printf(format string, s ...) {
n, err := fmt.fprintf(P.text, format, s);
if err != nil {
HtmlEscape
関数が追加されました。この関数は、HTML出力モード (html.BVal()
) が有効な場合に、入力文字列 s
内の <
と &
をそれぞれ <
と &
に変換します。再帰的な実装により、文字列全体を走査し、特殊文字をエスケープします。この関数が printer
パッケージ内に直接定義されたことで、HTMLエスケープ処理がより密接にコード整形ロジックと連携するようになりました。
String
および TaggedString
メソッドの変更
--- a/usr/gri/pretty/printer.go
+++ b/usr/gri/pretty/printer.go
@@ -135,7 +148,7 @@ func (P *Printer) Newline(n int) {
}\n \n \n-func (P *Printer) String(pos int, s string) {\n+func (P *Printer) TaggedString(pos int, tag, s, endtag string) {\n \t// use estimate for pos if we\'t have one\n \tif pos == 0 {\n \t\tpos = P.lastpos;\n@@ -230,7 +243,7 @@ func (P *Printer) String(pos int, s string) {\n \t\t\tif debug.BVal() {\n \t\t\t\tP.Printf(\"[%d]\", P.cpos);\n \t\t\t}\n-\t\t\tP.Printf(\"%s\", ctext);\n+\t\t\tP.Printf(\"%s\", HtmlEscape(ctext));\n \n \t\t\tif ctext[1] == \'/\' {\n \t\t\t\t//-style comments must end in newline\n@@ -276,7 +289,7 @@ func (P *Printer) String(pos int, s string) {\n \tif debug.BVal() {\n \t\tP.Printf(\"[%d]\", pos);\n \t}\n-\tP.Printf(\"%s\", s);\n+\tP.Printf(\"%s%s%s\", tag, HtmlEscape(s), endtag);\n \n \t// --------------------------------\n \t// interpret state\n@@ -300,6 +313,11 @@ func (P *Printer) String(pos int, s string) {\n }\n \n \n+func (P *Printer) String(pos int, s string) {\n+\tP.TaggedString(pos, \"\", s, \"\");\n+}\n+\n+\n func (P *Printer) Token(pos int, tok int) {\n \tP.String(pos, Scanner.TokenString(tok));\n }\n```
`String` メソッドが `TaggedString` にリネームされ、開始タグ、文字列、終了タグを受け取るようになりました。これにより、HTMLタグで囲まれたコンテンツをより柔軟に出力できるようになります。元の `String` メソッドは、新しい `TaggedString` を呼び出すラッパーとして再定義され、既存のコードとの互換性を保っています。コメントのテキスト (`ctext`) や通常の文字列出力も `HtmlEscape` を通すようになり、HTML出力時の安全性が向上しました。
#### HTML関連メソッドの変更
```diff
--- a/usr/gri/pretty/printer.go
+++ b/usr/gri/pretty/printer.go
@@ -316,36 +334,39 @@ func (P *Printer) Error(pos int, tok int, msg string) {\n // HTML support\n \n func (P *Printer) HtmlPrologue(title string) {\n-\tif P.tags != nil {\n-\t\tP.tags.Tag(\n+\tif html.BVal() {\n+\t\tP.TaggedString(0,\n \t\t\t"<html>\\n"\n \t\t\t"<head>\\n"\n \t\t\t"\t<META HTTP-EQUIV=\"Content-Type\" CONTENT=\"text/html; charset=UTF-8\">\\n"\n-\t\t\t"\t<title>" + title + "</title>\\n"\n+\t\t\t"\t<title>" + HtmlEscape(title) + "</title>\\n"\n \t\t\t"\t<style type=\"text/css\">\\n"\n \t\t\t"\t</style>\\n"\n \t\t\t"</head>\\n"\n \t\t\t"<body>\\n"\n-\t\t\t"<pre>\\n"\n+\t\t\t"<pre>\\n",\n+\t\t\t"", ""\n \t\t)\n \t}\n }\n \n \n func (P *Printer) HtmlEpilogue() {\n-\tif P.tags != nil {\n-\t\tP.tags.Tag(\n+\tif html.BVal() {\n+\t\tP.TaggedString(0, \n \t\t\t"</pre>\\n"\n \t\t\t"</body>\\n"\n-\t\t\t"<html>\\n"\n+\t\t\t"<html>\\n",\n+\t\t\t"", ""\n \t\t)\n \t}\n }\n \n \n func (P *Printer) HtmlIdentifier(pos int, ident string) {\n-\tif false && P.tags != nil {\n-\t\tP.tags.Tag(`<a href="#` + ident + `">` + ident + `</a>`);\n+\tif html.BVal() {\n+\t\t// no need to HtmlEscape ident\n+\t\tP.TaggedString(pos, `<a href="#` + ident + `">`, ident, `</a>`);\n \t} else {\n \t\tP.String(pos, ident);\n \t}\n```
`HtmlPrologue`, `HtmlEpilogue`, `HtmlIdentifier` の各メソッドも、`P.tags.Tag` の代わりに新しく導入された `P.TaggedString` を使用するように変更されました。これにより、HTMLタグの生成が `printer` パッケージ内で一貫して処理されるようになりました。特に、`HtmlPrologue` ではタイトルも `HtmlEscape` されるようになり、セキュリティが向上しています。
#### `Print` 関数の変更
```diff
--- a/usr/gri/pretty/printer.go
+++ b/usr/gri/pretty/printer.go
@@ -838,14 +859,8 @@ export func Print(prog *AST.Program) {\n \tif usetabs.BVal() {\n \t\tpadchar = '\t';\n \t}\n-\tvar (\n-\t\ttext = tabwriter.New(os.Stdout, int(tabwidth.IVal()), 1, padchar, true, html.BVal());\n-\t\ttags *htmlwriter.Writer;\n-\t)\n-\tif html.BVal() {\n-\t\ttags = htmlwriter.New(text);\n-\t}\n-\tP.Init(text, tags, prog.comments);\n+\ttext := tabwriter.New(os.Stdout, int(tabwidth.IVal()), 1, padchar, true, html.BVal());\n+\tP.Init(text, prog.comments);\n \n \tP.HtmlPrologue("<the source>");\n \tP.Program(prog);\
Print
関数は、pretty
ツールのエントリポイントの一つです。この変更では、htmlwriter.New
を呼び出して tags
インスタンスを生成するロジックが完全に削除されました。これは、htmlwriter
パッケージが不要になったことを最終的に示しています。P.Init
の呼び出しも、tags
引数が削除された新しいシグネチャに合わせて更新されています。
usr/gri/pretty/test.sh
の変更
--- a/usr/gri/pretty/test.sh
+++ b/usr/gri/pretty/test.sh
@@ -23,7 +23,7 @@ apply1() {\n #echo $1 $2\n case `basename $F` in\n # these files don\'t pass the idempotency test yet\n-\tlog.go | type.go | \\\n+\tlog.go | type.go | types_amd64_darwin.go | \\\
\t\\\
\tselftest1.go | func3.go | bug014.go | bug029.go | bug032.go | bug050.go | \\\
\tbug068.go | bug088.go | bug083.go | bug106.go | bug125.go ) ;; # skip - files contain syntax errors
test.sh
スクリプトは、pretty
ツールのテストを実行するためのものです。この変更では、types_amd64_darwin.go
が、まだ冪等性テスト(整形しても結果が変わらないこと)をパスしないファイルとしてスキップリストに追加されました。これは、この特定のファイルが、新しいHTML出力ロジックや整形ロジックの変更によって、期待通りの結果を生成していなかった可能性を示唆しています。
関連リンク
- Go言語公式サイト: https://go.dev/
- Go言語の初期の歴史に関する情報(非公式リソースを含む)
参考にした情報源リンク
- Go言語の公式ドキュメント(現在のバージョン)
- HTMLエスケープに関する一般的な情報(OWASPなど)
- Gitのコミットログと差分表示の解釈に関する知識
Makefile
の基本的な構文と使用法- Go言語の
io
パッケージとtext/tabwriter
パッケージのドキュメント(当時のバージョンに相当する概念) - Go言語の初期開発に関するコミュニティの議論やブログ記事(もしあれば)