[インデックス 10745] ファイルの概要
コミット
- コミットハッシュ: 6bf84214c117bd1ea081b93437dbf8463e0dabe8
- 作者: Russ Cox rsc@golang.org
- 日付: 2011年12月13日 火曜日 13:33:40 -0500
- コミットメッセージ: godoc: text wrapping
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/6bf84214c117bd1ea081b93437dbf8463e0dabe8
元コミット内容
godoc: text wrapping
Example:
PACKAGE
package utf8
import "unicode/utf8"
Package utf8 implements functions and constants to support text
encoded in UTF-8. This package calls a Unicode character a rune for
brevity.
CONSTANTS
const (
RuneError = unicode.ReplacementChar // the "error" Rune or "replacement character".
RuneSelf = 0x80 // characters below Runeself are represented as themselves in a single byte.
UTFMax = 4 // maximum number of bytes of a UTF-8 encoded Unicode character.
)
Numbers fundamental to the encoding.
FUNCTIONS
func DecodeLastRune(p []byte) (r rune, size int)
DecodeLastRune unpacks the last UTF-8 encoding in p and returns the
rune and its width in bytes.
func DecodeLastRuneInString(s string) (r rune, size int)
DecodeLastRuneInString is like DecodeLastRune but its input is a
string.
func DecodeRune(p []byte) (r rune, size int)
DecodeRune unpacks the first UTF-8 encoding in p and returns the rune
and its width in bytes.
func DecodeRuneInString(s string) (r rune, size int)
DecodeRuneInString is like DecodeRune but its input is a string.
func EncodeRune(p []byte, r rune) int
EncodeRune writes into p (which must be large enough) the UTF-8
encoding of the rune. It returns the number of bytes written.
func FullRune(p []byte) bool
FullRune reports whether the bytes in p begin with a full UTF-8
encoding of a rune. An invalid encoding is considered a full Rune
since it will convert as a width-1 error rune.
func FullRuneInString(s string) bool
FullRuneInString is like FullRune but its input is a string.
func RuneCount(p []byte) int
RuneCount returns the number of runes in p. Erroneous and short
encodings are treated as single runes of width 1 byte.
func RuneCountInString(s string) (n int)
RuneCountInString is like RuneCount but its input is a string.
func RuneLen(r rune) int
RuneLen returns the number of bytes required to encode the rune.
func RuneStart(b byte) bool
RuneStart reports whether the byte could be the first byte of an
encoded rune. Second and subsequent bytes always have the top two
bits set to 10.
func Valid(p []byte) bool
Valid reports whether p consists entirely of valid UTF-8-encoded
runes.
func ValidString(s string) bool
ValidString reports whether s consists entirely of valid UTF-8-encoded
runes.
TYPES
type String struct {
// contains filtered or unexported fields
}
String wraps a regular string with a small structure that provides
more efficient indexing by code point index, as opposed to byte index.
Scanning incrementally forwards or backwards is O(1) per index
operation (although not as fast a range clause going forwards).
Random access is O(N) in the length of the string, but the overhead is
less than always scanning from the beginning. If the string is ASCII,
random access is O(1). Unlike the built-in string type, String has
internal mutable state and is not thread-safe.
func NewString(contents string) *String
NewString returns a new UTF-8 string with the provided contents.
func (s *String) At(i int) rune
At returns the rune with index i in the String. The sequence of runes
is the same as iterating over the contents with a "for range" clause.
func (s *String) Init(contents string) *String
Init initializes an existing String to hold the provided contents.
It returns a pointer to the initialized String.
func (s *String) IsASCII() bool
IsASCII returns a boolean indicating whether the String contains only
ASCII bytes.
func (s *String) RuneCount() int
RuneCount returns the number of runes (Unicode code points) in the
String.
func (s *String) Slice(i, j int) string
Slice returns the string sliced at rune positions [i:j].
func (s *String) String() string
String returns the contents of the String. This method also means the
String is directly printable by fmt.Print.
Fixes #2479.
R=golang-dev, dsymonds, mattn.jp, r, gri, r
CC=golang-dev
https://golang.org/cl/5472051
変更の背景
このコミットは、Go言語のドキュメンテーションツールであるgodoc
のテキスト出力におけるテキストラッピング(自動改行)機能を追加するものです。以前のgodoc
のテキスト出力は、長い行がそのまま表示され、特にターミナルや固定幅の表示環境でドキュメントを読む際に、行が画面外にはみ出してしまい、可読性が低いという問題がありました。この変更により、ドキュメントのテキストが指定された幅(デフォルト80カラム)で適切に折り返されるようになり、可読性が大幅に向上しました。
コミットメッセージに記載されているFixes #2479
については、現在のGo言語のIssueトラッカーでは直接関連するIssueが見つかりませんでした。これは、コミットが2011年のものであり、当時のIssue管理システムやIssue番号の割り当てが現在とは異なる可能性、あるいはGo言語のメインリポジトリ以外のプロジェクトのIssueを参照している可能性が考えられます。
前提知識の解説
godoc
godoc
は、Go言語のソースコードからドキュメンテーションを生成し、表示するためのツールです。Go言語では、コード内のコメントがそのままドキュメンテーションとして機能する「Go Doc」という文化があり、godoc
はそのコメントを解析して整形されたドキュメントとして提供します。ローカルでHTTPサーバーを起動してブラウザで閲覧したり、コマンドラインでテキスト形式で表示したりできます。このコミットは、特にコマンドラインでのテキスト表示の改善を目的としています。
テキストラッピング(自動改行)
テキストラッピングとは、テキストが特定の幅を超えた場合に、自動的に次の行に折り返す処理のことです。これにより、長い文章でも画面の幅に合わせて表示され、ユーザーは水平スクロールなしで内容を読み進めることができます。プログラミングにおいては、コードコメントやドキュメンテーションの可読性を高める上で非常に重要な機能です。
UTF-8とRune
Go言語では、文字列はUTF-8でエンコードされたバイト列として扱われます。Unicodeのコードポイントはrune
型で表現され、これはGo言語における文字の概念に近いです。UTF-8は可変長エンコーディングであり、1つのrune
が1バイトから4バイトの範囲で表現されます。テキストラッピングを正確に行うためには、バイト数ではなくrune
(文字)の数を基準に幅を計算する必要があります。
Goのtext/template
パッケージ
Go言語のtext/template
パッケージは、テキストベースの出力を生成するためのテンプレートエンジンを提供します。godoc
は、このテンプレートエンジンを使用して、Goのソースコードから抽出したドキュメンテーション情報を整形して表示します。テンプレート内で関数を呼び出すことで、動的なコンテンツ生成やフォーマットを行うことができます。
template.FuncMap
template.FuncMap
は、text/template
パッケージで使用されるマップで、テンプレート内で呼び出すことができるカスタム関数を登録するために使用されます。このマップに登録された関数は、テンプレート内で{{funcName .Data}}
のような形式で呼び出すことができます。
技術的詳細
このコミットの主要な変更点は、godoc
のテキスト出力におけるコメントの整形ロジックにテキストラッピング機能を追加したことです。具体的には、以下のファイルが変更されています。
-
lib/godoc/package.txt
:godoc
がパッケージドキュメントを生成する際に使用するテンプレートファイルです。このファイルでは、ドキュメントコメントを表示する箇所で、新しく追加されたcomment_text
テンプレート関数が使用されるように変更されています。これにより、コメントがテキストラッピングされて出力されるようになります。変更前:
{{.Doc}}
変更後:{{comment_text .Doc " " "\t"}}
comment_text
関数は、コメントテキスト、インデント文字列、プリインデント文字列を引数として受け取ります。 -
src/cmd/godoc/godoc.go
:godoc
コマンドのメインロジックが含まれるファイルです。punchCardWidth
という定数が導入されました。これは、テキストラッピングの基準となる固定幅(80カラム)を定義しています。この幅は、環境やTTYの幅に依存せず、常に一定の出力を保証するために固定されています。これは、ls
コマンドのようにTTYへの出力とファイルへの出力で挙動が変わることで混乱を招くことを避けるための設計判断です。comment_textFunc
という新しい関数が追加され、template.FuncMap
にcomment_text
という名前で登録されました。この関数は、コメントテキスト、インデント、プリインデント、そしてpunchCardWidth
から計算された幅を引数としてdoc.ToText
関数を呼び出します。
-
src/pkg/go/doc/comment.go
: Goのドキュメンテーションコメントを処理するためのコアロジックが含まれるファイルです。このファイルが最も大きく変更されています。- 既存の
ToHTML
関数がリファクタリングされ、コメントテキストをHTMLに変換するロジックが改善されました。特に、コメントを段落(opPara
)、見出し(opHead
)、整形済みテキスト(opPre
)のブロックに分割する新しいblocks
関数が導入されました。 block
構造体とop
列挙型が定義され、コメントの構造をより明確に表現できるようになりました。ToText
関数が新しく追加されました。 この関数は、コメントテキストをプレーンテキスト形式で整形し、指定された幅でテキストラッピングを行う主要なロジックを実装しています。lineWrapper
という新しい構造体が導入されました。これは、テキストラッピング処理を効率的に行うためのヘルパー構造体で、出力バッファ、行の幅、インデント、現在の行の長さなどを管理します。write
メソッドは単語をバッファに書き込み、必要に応じて改行を挿入します。flush
メソッドは、残りのバッファ内容をフラッシュします。
- 既存の
-
src/pkg/go/doc/comment_test.go
:src/pkg/go/doc/comment.go
のテストファイルです。- 新しく追加された
blocks
関数の動作を検証するためのテストケースblocksTests
が追加されました。これにより、コメントが正しく構造化されたブロックに分割されることが保証されます。
- 新しく追加された
コアとなるコードの変更箇所
このコミットのコアとなる変更は、src/pkg/go/doc/comment.go
ファイルに新しく追加されたToText
関数と、それに付随するlineWrapper
構造体です。
// src/pkg/go/doc/comment.go (抜粋)
// ToText prepares comment text for presentation in textual output.
// It wraps paragraphs of text to width or fewer Unicode code points
// and then prefixes each line with the indent. In preformatted sections
// (such as program text), it prefixes each non-blank line with preIndent.
func ToText(w io.Writer, text string, indent, preIndent string, width int) {
l := lineWrapper{
out: w,
width: width,
indent: indent,
}
for i, b := range blocks(text) {
switch b.op {
case opPara:
if i > 0 {
w.Write(nl) // blank line before new paragraph
}
for _, line := range b.lines {
l.write(line)
}
l.flush()
case opHead:
w.Write(nl)
for _, line := range b.lines {
l.write(line + "\n") // Headings are always followed by a newline
}
l.flush()
case opPre:
w.Write(nl)
for _, line := range b.lines {
if !isBlank(line) {
w.Write([]byte(preIndent))
w.Write([]byte(line))
}
w.Write(nl) // Each line in preformatted block gets its own newline
}
}
}
}
type lineWrapper struct {
out io.Writer
printed bool // true if anything has been printed to out
width int
indent string
n int // current length of line (in runes)
pendSpace int // number of pending spaces to write
}
var nl = []byte("\n")
var space = []byte(" ")
func (l *lineWrapper) write(text string) {
if l.n == 0 && l.printed {
l.out.Write(nl) // blank line before new paragraph
}
l.printed = true
for _, f := range strings.Fields(text) { // Iterate over words
w := utf8.RuneCountInString(f) // Get width in runes
// wrap if line is too long
if l.n > 0 && l.n+l.pendSpace+w > l.width {
l.out.Write(nl)
l.n = 0
l.pendSpace = 0
}
if l.n == 0 {
l.out.Write([]byte(l.indent)) // Apply indent at start of new line
}
l.out.Write(space[:l.pendSpace]) // Write pending spaces
l.out.Write([]byte(f)) // Write the word
l.n += l.pendSpace + w // Update current line length
l.pendSpace = 1 // Next word needs a space
}
}
func (l *lineWrapper) flush() {
if l.n == 0 {
return
}
l.out.Write(nl) // End the current line
l.pendSpace = 0
l.n = 0
}
コアとなるコードの解説
ToText
関数は、Goのドキュメンテーションコメントをプレーンテキスト形式で整形し、指定された幅でテキストラッピングを行うための中心的な役割を担います。
-
blocks
関数によるコメントの構造化:ToText
関数はまず、入力されたコメントテキストをblocks
関数によって、段落(opPara
)、見出し(opHead
)、整形済みテキスト(opPre
)の論理的なブロックに分割します。これにより、各ブロックの特性に応じた異なるラッピング処理が可能になります。例えば、整形済みテキスト(コードブロックなど)はラッピングされず、そのままの形式で出力されます。 -
lineWrapper
構造体:lineWrapper
は、実際のテキストラッピング処理を行うためのヘルパーです。out io.Writer
: 整形されたテキストの出力先。printed bool
: これまでに出力が行われたかどうかを示すフラグ。新しい段落の前に空行を挿入するかどうかを判断するために使用されます。width int
: テキストを折り返す最大幅(Unicodeルーン数)。indent string
: 各行の先頭に付加するインデント文字列。n int
: 現在の行に書き込まれたルーンの数。pendSpace int
: 次の単語の前に挿入すべきスペースの数。
-
lineWrapper.write
メソッド: このメソッドは、与えられたテキスト(通常は単語)をlineWrapper
の内部バッファに書き込み、必要に応じて改行を挿入します。strings.Fields(text)
: 入力テキストをスペースで区切って単語のリストに分割します。utf8.RuneCountInString(f)
: 各単語の長さをバイト数ではなくUnicodeルーン数で正確に計算します。これにより、マルチバイト文字を含むテキストでも正しくラッピングが行われます。- ラッピングロジック:
if l.n > 0 && l.n+l.pendSpace+w > l.width
の条件で、現在の行の長さ(l.n
)、保留中のスペース(l.pendSpace
)、次の単語の長さ(w
)の合計が指定されたwidth
を超える場合に、改行(l.out.Write(nl)
)を挿入します。 - インデント: 新しい行の開始時には、
l.indent
で指定されたインデントが適用されます。 - スペースの管理:
l.pendSpace
を使用して、単語間のスペースを適切に挿入します。
-
lineWrapper.flush
メソッド: このメソッドは、lineWrapper
にまだ書き込まれていない内容があれば、強制的に改行を挿入して出力バッファをフラッシュします。これにより、段落の最後に残ったテキストが確実に新しい行で閉じられます。
この一連の処理により、godoc
のテキスト出力は、固定幅のターミナル環境でも読みやすいように、適切に整形され、自動改行されるようになりました。特に、Unicodeルーンを考慮した正確な幅計算は、多言語対応のドキュメントにおいても重要な改善点です。
関連リンク
- Go言語の公式ドキュメンテーション: https://go.dev/doc/
godoc
コマンドの現在の推奨代替ツール:go doc
またはpkgsite
(pkg.go.dev)
参考にした情報源リンク
- GitHubコミットページ: https://github.com/golang/go/commit/6bf84214c117bd1ea081b93437dbf8463e0dabe8
- Go言語の
text/template
パッケージ: https://pkg.go.dev/text/template - Go言語の
unicode/utf8
パッケージ: https://pkg.go.dev/unicode/utf8 Fixes #2479
に関するWeb検索結果(Issueの直接的な関連性が見つからなかったことの言及)- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEDP4g9ghvZIykUflC7jwuoesY4sC3_RbXpr7AQMjGzbFK6XbHVgLfgVVNyBYg4Vn5hC944SGPzDGiVj3UpUoLSVC0tn-uD7NkRuUwaYcDIDd48vmGciJrczpImfN6jXpTdnks68h5pOqqb3u9wwP5-QWxY3EnL_BeFyrG532bwCgyBhSzgXwOfdn0Hb-LHbg_hSXFutSPr9LZX6W5NGLwL8w==
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQFulvohn1adtMAmPsIPlVPDkCZKOT84wPBzUI4RGDDXqv9h9AOJ4T0ptswr17KYJPlC-6eIrxXIAEquzSCBOS3ZSwLeWjc6Ml9OowUNKDTWY6PALd7cV96W2JfA04WDJv1kkFcRfcoh3nuK6OK13yZDV5sa8ykrCt8YeCXcVgE4SMj4Si8jTftp-wRvCXMxNbak4C1yU7qqq8LlzGmKNqWT3WDEG62rVQ==
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQGNS9CNX-mv1aE959vKs-d8kJMTmKn9chtMTgc0wNcAULmI7b4VkXoEkhsVatALAHpE_PIV9S3gRqywvFxRumEDZ0BnT5JNxKocW8CCPbEcRCqzhF1XgzA9dTRYi9dRCz_V0oaLlVY1JiouC1xNuW6MzSjOJMK9YU3l0FuCsQbD6s-EEtzl0N_KFZYwDrbdKsd-tE0IP3J0
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQG5m958ADT1_O0Nq-KwwP6Ipu8R5IjmBKmQhtVYZ693BHVC8iuFlGSH_yrY5PurEniY5PJwXgwdNugYEB3k8ZTUJyEC5ymwF4DvvLQRzY4-meDfg3h3aLWRpRe0S2VHFQ0EJ7wfrZ-eKbUp
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQFVFynq2JS1j_UwaOixwQufd7tNbeD52BUx2_tFFWmUUClXiUOX6_OWtQi9QzTFkotP5pZaDzhesuu1lzsJZMMNh6jK5-Z-auFexGwSVpuN7oUHTWeaoiE9i4j2DYwg0MKqkYA-XgTyvBdyP71AeQex543Tr_yr3_hthfbHAa_y9CB1utcQZkNwq5OULGgrQ=