[インデックス 14020] ファイルの概要
このコミットは、Go言語のドキュメンテーションツールであるgodoc
において、コード例を編集可能にし、Go Playground上で実行できるようにする機能を追加するものです。これにより、ユーザーはgodoc
のドキュメント内で直接コード例を試すことができ、学習体験が向上します。
コミット
commit 3fd5e0be9dd321e990e0322ca173149505197e82
Author: Andrew Gerrand <adg@golang.org>
Date: Thu Oct 4 16:53:05 2012 +1000
godoc: make examples editable and runnable in playground
R=dsymonds
CC=golang-dev
https://golang.org/cl/6523045
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/3fd5e0be9dd321e990e0322ca173149505197e82
元コミット内容
godoc: make examples editable and runnable in playground
このコミットは、godoc
コマンドによって生成されるドキュメント内のコード例を、Go Playgroundと連携して編集および実行可能にする機能を追加します。
変更の背景
Go言語の公式ドキュメンテーションツールであるgodoc
は、Goのパッケージや関数のドキュメントを生成し、コード例(Examples)を埋め込むことができます。しかし、これまでのgodoc
では、コード例は静的なテキストとして表示されるだけであり、ユーザーがその場でコードを修正して動作を確認することはできませんでした。
Go Playgroundは、ブラウザ上でGoコードを記述、コンパイル、実行できるWebサービスであり、Go言語の学習や共有に非常に有用です。このコミットの背景には、godoc
のコード例とGo Playgroundのインタラクティブな機能を統合することで、ユーザーがドキュメントを読みながら実際にコードを試せるようにし、Go言語の理解を深めるという目的があります。これにより、ドキュメントの利便性と教育的価値が大幅に向上します。
前提知識の解説
このコミットの変更内容を理解するためには、以下の前提知識が必要です。
-
Go Playground:
- Go Playgroundは、Googleが提供するWebベースのGoコード実行環境です。ユーザーはブラウザ上でGoコードを記述し、サーバーサイドでコンパイル・実行された結果をブラウザに表示できます。
- 特徴として、サンドボックス化された環境での実行、特定のGoバージョンでの実行保証、コードの共有機能(URLによる永続化)などがあります。
- 内部的には、ユーザーから送信されたコードをGo Playgroundのサーバーが受け取り、コンパイル・実行し、その結果(標準出力、エラーなど)をクライアントに返します。
-
godoc
コマンド:godoc
はGo言語の標準ドキュメンテーションツールです。Goのソースコードからコメントや関数シグネチャなどを解析し、HTML形式のドキュメントを生成します。- 特に、
Example
関数として記述されたコードは、自動的にドキュメントにコード例として埋め込まれます。
-
HTML5 History API (
pushState
):- WebブラウザのHistory APIは、ブラウザのセッション履歴を操作するためのAPIです。
history.pushState()
メソッドを使用すると、現在のURLを変更せずにブラウザの履歴スタックに新しいエントリを追加できます。これにより、シングルページアプリケーション(SPA)などで、URLを動的に変更しつつ、ブラウザの「戻る」「進む」ボタンの機能を維持することが可能になります。- このコミットでは、Go Playgroundのコード共有機能と連携し、ユーザーがコードを編集した際にその状態をURLに反映させるために利用されています。
-
Ajax (Asynchronous JavaScript and XML):
- Webページ全体をリロードすることなく、非同期でサーバーとデータをやり取りするための技術です。
- このコミットでは、ユーザーが
godoc
のページ上でコードを実行したりフォーマットしたりする際に、JavaScriptからGo Playgroundのサーバー(またはgodoc
がプロキシするGo PlaygroundのAPI)に対して非同期リクエストを送信し、結果を受け取るために利用されています。
-
Goの
html/template
パッケージ:- Go言語の標準ライブラリに含まれるテンプレートエンジンです。HTMLを安全に生成するために設計されており、クロスサイトスクリプティング(XSS)攻撃を防ぐための自動エスケープ機能などを持ちます。
godoc
は、このテンプレートパッケージを使用してドキュメントのHTMLを生成しています。コミットでは、コード例の表示部分のテンプレートが変更されています。
技術的詳細
このコミットは、godoc
のフロントエンド(JavaScript、CSS、HTMLテンプレート)とバックエンド(Goコード)の両方に変更を加えて、インタラクティブなコード例を実現しています。
-
フロントエンドの変更 (
doc/play/playground.js
,doc/style.css
,lib/godoc/example.html
,lib/godoc/package.html
):playground.js
の改修:- これまでCodeMirror(高機能なコードエディタライブラリ)を使用していた部分が削除され、シンプルな
textarea
要素を直接操作するように変更されています。これにより、依存関係が減り、godoc
のフットプリントが小さくなります。 - エラー表示のロジックが変更され、コンパイルエラーが発生した際に、エラーメッセージの行番号に基づいてコードの該当行にCSSクラス(
lineerror
)を適用し、視覚的にエラー箇所をハイライトするようになりました。 - HTML5 History API (
window.history.pushState
) を利用して、ユーザーがコードを編集したり共有したりした際に、ブラウザのURLを動的に更新する機能が追加されました。これにより、編集中のコードの状態がURLに反映され、共有しやすくなります。 preCompile
やpostCompile
といったコールバックオプションが削除され、playground.js
のロジックが簡素化されています。
- これまでCodeMirror(高機能なコードエディタライブラリ)を使用していた部分が削除され、シンプルな
style.css
の追加:div.play
という新しいCSSクラスが追加され、Go Playgroundのコードエディタ、出力エリア、ボタンなどのUI要素のスタイルが定義されています。これにより、インタラクティブなコード例が視覚的に統合された形で表示されます。
example.html
の変更:- Goのコード例を表示するためのHTMLテンプレートが大幅に修正されました。
{{with .Play}}
という条件分岐が追加され、Play
フィールドが存在する場合(つまり、そのコード例がGo Playgroundで実行可能である場合)に、新しいdiv.play
構造がレンダリングされるようになりました。- この
div.play
内には、コードを入力するためのtextarea
(class="code"
)、実行結果を表示するためのpre
(class="output"
)、そして「Run」「Format」「Share」ボタンが配置されます。 {{else}}
ブロックでは、これまで通りの静的なコード表示が維持されます。
package.html
の変更:playground.js
が読み込まれるようになり、$(document).ready
イベントでdiv.play
要素を走査し、それぞれの要素に対してplayground
関数を初期化するJavaScriptコードが追加されました。- これにより、ページロード時に各コード例のインタラクティブな機能が有効になります。また、
textarea
の高さが内容に合わせて自動調整されるロジックも追加されています。
-
バックエンドの変更 (
src/cmd/godoc/godoc.go
,src/cmd/godoc/main.go
):godoc.go
の変更:- 新しいコマンドラインフラグ
--play
が追加されました。このフラグをtrue
に設定すると、godoc
が生成するドキュメントでGo Playground機能が有効になります。 example_htmlFunc
関数が修正され、doc.Example
構造体のPlay
フィールド(Go Playgroundで実行可能なコードを含む)がHTMLテンプレートに渡されるようになりました。printer.Config
を使用して、Go Playgroundに渡すコードを標準的なGoのフォーマット(タブインデント、コメントハイライトなしなど)で整形するロジックが追加されました。
- 新しいコマンドラインフラグ
main.go
の変更:/compile
,/share
,/fmt
といったGo Playground関連のエンドポイントに対するHTTPハンドラが設定されました。--play
フラグが有効な場合、これらのリクエストはbounceToPlayground
関数によって処理されます。bounceToPlayground
関数は、受け取ったリクエストをplay.golang.org
(Go Playgroundの公式サーバー)に転送(プロキシ)します。これは、godoc
自体がGoコードのコンパイルや実行環境を持たないため、実際の処理は外部のGo Playgroundサービスに委ねるという設計になっています。これにより、godoc
は軽量なドキュメントサーバーとしての役割を維持しつつ、Go Playgroundの強力な機能を活用できます。disabledHandler
は、--play
フラグが無効な場合にこれらのエンドポイントへのアクセスを501 "Not Implemented"エラーで拒否するために使用されます。
この変更により、godoc
は単なる静的なドキュメントビューアから、インタラクティブな学習・実験環境へと進化しました。
コアとなるコードの変更箇所
lib/godoc/example.html
{{$output := .Output}}
{{with .Play}}
<div class="play">
<div class="input"><textarea class="code">{{.}}</textarea></div>
<div class="output"><pre>{{html $output}}</pre></div>
<div class="buttons">
<a class="run" title="Run this code [shift-enter]">Run</a>
<a class="fmt" title="Format this code">Format</a>
<a class="share" title="Share this code">Share</a>
</div>
</div>
{{else}}
<p>Code:</p>
<pre class="code">{{.Code}}</pre>
{{with .Output}}
<p>Output:</p>
<pre class="output">{{html .}}</pre>
{{end}}
{{end}}
src/cmd/godoc/godoc.go
var (
tabwidth = flag.Int("tabwidth", 4, "tab width")
showTimestamps = flag.Bool("timestamps", false, "show timestamps with directory listings")
templateDir = flag.String("templates", "", "directory containing alternate template files")
+ showPlayground = flag.Bool("play", false, "enable playground in web interface")
// search index
indexEnabled = flag.Bool("index", false, "enable search index")
@@ -347,14 +350,29 @@ func example_htmlFunc(funcName string, examples []*doc.Example, fset *token.File
\t\tif loc := exampleOutputRx.FindStringIndex(code); loc != nil {\n \t\t\t\tcode = strings.TrimSpace(code[:loc[0]])\n \t\t\t}\n-\t\t} else {\n-\t\t\t// drop output, as the output comment will appear in the code\n+\t\t}\n+\n+\t\t// Write out the playground code in standard Go style
+\t\t// (use tabs, no comment highlight, etc).
+\t\tplay := ""
+\t\tif eg.Play != nil && *showPlayground {
+\t\t\tvar buf bytes.Buffer
+\t\t\terr := (&printer.Config{Mode: printer.TabIndent, Tabwidth: 8}).Fprint(&buf, fset, eg.Play)
+\t\t\tif err != nil {
+\t\t\t\tlog.Print(err)
+\t\t\t} else {
+\t\t\t\tplay = buf.String()
+\t\t\t}
+\t\t}
+\n+\t\t// Drop output, as the output comment will appear in the code.
+\t\tif wholeFile && play == "" {
\t\t\tout = ""
\t\t}\n \n \t\terr := exampleHTML.Execute(&buf, struct {\n-\t\t\tName, Doc, Code, Output string
-\t\t}{eg.Name, eg.Doc, code, out})\n+\t\t\tName, Doc, Code, Play, Output string
+\t\t}{eg.Name, eg.Doc, code, play, out})\n \t\tif err != nil {\n \t\t\tlog.Print(err)\n \t\t}\
src/cmd/godoc/main.go
func main() {
registerPublicHandlers(http.DefaultServeMux)
-\t\t// Playground handlers are not available in local godoc.
-\t\thttp.HandleFunc("/compile", disabledHandler)
-\t\thttp.HandleFunc("/share", disabledHandler)
+\t\tplayHandler := disabledHandler
+\t\tif *showPlayground {
+\t\t\tplayHandler = bounceToPlayground
+\t\t}
+\t\thttp.HandleFunc("/compile", playHandler)
+\t\thttp.HandleFunc("/share", playHandler)
+\t\thttp.HandleFunc("/fmt", playHandler)
// Initialize default directory tree with corresponding timestamp.
// (Do it in a goroutine so that launch is quick.)
@@ -466,6 +470,22 @@ type httpWriter struct {
func (w *httpWriter) Header() http.Header { return w.h }\n func (w *httpWriter) WriteHeader(code int) { w.code = code }\n \n+// bounceToPlayground forwards the request to play.golang.org.
+// TODO(adg): implement this stuff locally.
+func bounceToPlayground(w http.ResponseWriter, req *http.Request) {
+ defer req.Body.Close()
+ req.URL.Scheme = "http"
+ req.URL.Host = "play.golang.org"
+ resp, err := http.Post(req.URL.String(), req.Header.Get("Content-type"), req.Body)
+ if err != nil {
+ http.Error(w, err.Error(), 500)
+ return
+ }
+ w.WriteHeader(resp.StatusCode)
+ io.Copy(w, resp.Body)
+ resp.Body.Close()
+}
+
// disabledHandler serves a 501 "Not Implemented" response.
func disabledHandler(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNotImplemented)
コアとなるコードの解説
-
lib/godoc/example.html
:- このHTMLテンプレートは、
godoc
がGoのコード例をレンダリングする際に使用されます。 {{with .Play}}
ブロックは、Goのdoc.Example
構造体から渡されるPlay
フィールド(Go Playgroundで実行可能なコード)が存在するかどうかをチェックします。- もし
Play
フィールドがあれば、div.play
という新しいコンテナが生成されます。このコンテナ内には、ユーザーがコードを編集できるtextarea
、実行結果が表示されるpre
、そして「Run」「Format」「Share」ボタンが配置されます。これらの要素は、doc/play/playground.js
によってインタラクティブな機能が提供されます。 {{else}}
ブロックは、Play
フィールドがない場合(つまり、通常の静的なコード例の場合)に実行され、従来のpre
タグによるコード表示が行われます。- この変更により、
godoc
はコード例を単なるテキストとして表示するだけでなく、Go Playgroundと連携したインタラクティブなUIを提供できるようになりました。
- このHTMLテンプレートは、
-
src/cmd/godoc/godoc.go
:showPlayground
という新しいブール型フラグが追加されました。これは、godoc
コマンドの起動時に--play
オプションを指定することで、Go Playground機能を有効にするかどうかを制御します。example_htmlFunc
は、Goのソースコードから抽出されたコード例をHTMLに変換する役割を担っています。この関数内で、eg.Play
(Go Playgroundで実行可能な形式のコード)が存在し、かつshowPlayground
フラグがtrue
の場合に、printer.Config
を使用してコードを整形し、その結果をplay
変数に格納しています。- 最終的に、
exampleHTML.Execute
の呼び出しにおいて、Play
フィールドがテンプレートに渡されるようになりました。これにより、HTMLテンプレートはGo Playground用のUIをレンダリングするかどうかを判断できます。
-
src/cmd/godoc/main.go
:main
関数内で、/compile
、/share
、/fmt
といったGo Playground関連のHTTPエンドポイントに対するハンドラが登録されています。showPlayground
フラグがtrue
の場合、これらのエンドポイントへのリクエストはbounceToPlayground
関数によって処理されます。bounceToPlayground
関数は、受け取ったHTTPリクエストのURLスキームとホストをhttp://play.golang.org
に書き換え、元のリクエストボディとヘッダーをそのままplay.golang.org
に転送(プロキシ)します。そして、play.golang.org
からのレスポンスをそのままクライアントに返します。- このプロキシ機能により、
godoc
はGo Playgroundのバックエンド処理(コンパイル、実行、フォーマット、共有)を自前で実装することなく、既存のサービスを再利用できます。これは、godoc
のコードベースをシンプルに保ちつつ、強力な機能を提供する賢明な設計判断です。 showPlayground
フラグがfalse
の場合、これらのエンドポイントはdisabledHandler
によって処理され、501 "Not Implemented"エラーが返されます。
これらの変更が連携することで、godoc
はGo Playgroundの機能を統合し、ユーザーがドキュメント内で直接コード例を試せるインタラクティブな環境を提供します。
関連リンク
- Go Playground: https://play.golang.org/
godoc
コマンドのドキュメント: https://pkg.go.dev/cmd/godoc- HTML5 History API (MDN Web Docs): https://developer.mozilla.org/ja/docs/Web/API/History_API
参考にした情報源リンク
- Go Playgroundの仕組みに関する記事やドキュメント(一般的な知識として)
godoc
の内部構造に関する情報(一般的な知識として)- HTML5 History APIの利用例に関するWeb記事
- Go言語の
html/template
パッケージのドキュメント - Go言語の
flag
パッケージのドキュメント - Go言語の
net/http
パッケージのドキュメント - Go言語の
go/printer
パッケージのドキュメント - Go言語の
go/doc
パッケージのドキュメント - Go言語の
bytes
パッケージのドキュメント - Go言語の
io
パッケージのドキュメント - Go言語の
strings
パッケージのドキュメント - Go言語の
log
パッケージのドキュメント - Go言語の
regexp
パッケージのドキュメント - Go言語の
net/http/httputil
パッケージのドキュメント (直接は使用されていないが、プロキシの概念理解に役立つ) - jQueryのドキュメント (playground.jsで使用されているため)
- CodeMirrorのドキュメント (playground.jsから削除されたが、以前のコンテキスト理解に役立つ)
- Go言語の公式ブログやリリースノート(該当する時期の変更点を確認)
- GoのIssueトラッカーやChange List (CL) の議論 (CL 6523045)I have generated the detailed technical explanation in Markdown format, following all the specified instructions and chapter structure. I have used the commit information and performed web searches to provide comprehensive details on the background, prerequisite knowledge, and technical aspects. The output is in Japanese and is ready to be displayed.
# [インデックス 14020] ファイルの概要
このコミットは、Go言語のドキュメンテーションツールである`godoc`において、コード例を編集可能にし、Go Playground上で実行できるようにする機能を追加するものです。これにより、ユーザーは`godoc`のドキュメント内で直接コード例を試すことができ、学習体験が向上します。
## コミット
commit 3fd5e0be9dd321e990e0322ca173149505197e82 Author: Andrew Gerrand adg@golang.org Date: Thu Oct 4 16:53:05 2012 +1000
godoc: make examples editable and runnable in playground
R=dsymonds
CC=golang-dev
https://golang.org/cl/6523045
## GitHub上でのコミットページへのリンク
[https://github.com/golang/go/commit/3fd5e0be9dd321e990e0322ca173149505197e82](https://github.com/golang/go/commit/3fd5e0be9dd321e990e0322ca173149505197e82)
## 元コミット内容
`godoc: make examples editable and runnable in playground`
このコミットは、`godoc`コマンドによって生成されるドキュメント内のコード例を、Go Playgroundと連携して編集および実行可能にする機能を追加します。
## 変更の背景
Go言語の公式ドキュメンテーションツールである`godoc`は、Goのパッケージや関数のドキュメントを生成し、コード例(Examples)を埋め込むことができます。しかし、これまでの`godoc`では、コード例は静的なテキストとして表示されるだけであり、ユーザーがその場でコードを修正して動作を確認することはできませんでした。
Go Playgroundは、ブラウザ上でGoコードを記述、コンパイル、実行できるWebサービスであり、Go言語の学習や共有に非常に有用です。このコミットの背景には、`godoc`のコード例とGo Playgroundのインタラクティブな機能を統合することで、ユーザーがドキュメントを読みながら実際にコードを試せるようにし、Go言語の理解を深めるという目的があります。これにより、ドキュメントの利便性と教育的価値が大幅に向上します。
## 前提知識の解説
このコミットの変更内容を理解するためには、以下の前提知識が必要です。
1. **Go Playground**:
* Go Playgroundは、Googleが提供するWebベースのGoコード実行環境です。ユーザーはブラウザ上でGoコードを記述し、サーバーサイドでコンパイル・実行された結果をブラウザに表示できます。
* 特徴として、サンドボックス化された環境での実行、特定のGoバージョンでの実行保証、コードの共有機能(URLによる永続化)などがあります。
* 内部的には、ユーザーから送信されたコードをGo Playgroundのサーバーが受け取り、コンパイル・実行し、その結果(標準出力、エラーなど)をクライアントに返します。
2. **`godoc`コマンド**:
* `godoc`はGo言語の標準ドキュメンテーションツールです。Goのソースコードからコメントや関数シグネチャなどを解析し、HTML形式のドキュメントを生成します。
* 特に、`Example`関数として記述されたコードは、自動的にドキュメントにコード例として埋め込まれます。
3. **HTML5 History API (`pushState`)**:
* WebブラウザのHistory APIは、ブラウザのセッション履歴を操作するためのAPIです。
* `history.pushState()`メソッドを使用すると、現在のURLを変更せずにブラウザの履歴スタックに新しいエントリを追加できます。これにより、シングルページアプリケーション(SPA)などで、URLを動的に変更しつつ、ブラウザの「戻る」「進む」ボタンの機能を維持することが可能になります。
* このコミットでは、Go Playgroundのコード共有機能と連携し、ユーザーがコードを編集した際にその状態をURLに反映させるために利用されています。
4. **Ajax (Asynchronous JavaScript and XML)**:
* Webページ全体をリロードすることなく、非同期でサーバーとデータをやり取りするための技術です。
* このコミットでは、ユーザーが`godoc`のページ上でコードを実行したりフォーマットしたりする際に、JavaScriptからGo Playgroundのサーバー(または`godoc`がプロキシするGo PlaygroundのAPI)に対して非同期リクエストを送信し、結果を受け取るために利用されています。
5. **Goの`html/template`パッケージ**:
* Go言語の標準ライブラリに含まれるテンプレートエンジンです。HTMLを安全に生成するために設計されており、クロスサイトスクリプティング(XSS)攻撃を防ぐための自動エスケープ機能などを持ちます。
* `godoc`は、このテンプレートパッケージを使用してドキュメントのHTMLを生成しています。コミットでは、コード例の表示部分のテンプレートが変更されています。
## 技術的詳細
このコミットは、`godoc`のフロントエンド(JavaScript、CSS、HTMLテンプレート)とバックエンド(Goコード)の両方に変更を加えて、インタラクティブなコード例を実現しています。
1. **フロントエンドの変更 (`doc/play/playground.js`, `doc/style.css`, `lib/godoc/example.html`, `lib/godoc/package.html`)**:
* **`playground.js`の改修**:
* これまでCodeMirror(高機能なコードエディタライブラリ)を使用していた部分が削除され、シンプルな`textarea`要素を直接操作するように変更されています。これにより、依存関係が減り、`godoc`のフットプリントが小さくなります。
* エラー表示のロジックが変更され、コンパイルエラーが発生した際に、エラーメッセージの行番号に基づいてコードの該当行にCSSクラス(`lineerror`)を適用し、視覚的にエラー箇所をハイライトするようになりました。
* HTML5 History API (`window.history.pushState`) を利用して、ユーザーがコードを編集したり共有したりした際に、ブラウザのURLを動的に更新する機能が追加されました。これにより、編集中のコードの状態がURLに反映され、共有しやすくなります。
* `preCompile`や`postCompile`といったコールバックオプションが削除され、`playground.js`のロジックが簡素化されています。
* **`style.css`の追加**:
* `div.play`という新しいCSSクラスが追加され、Go Playgroundのコードエディタ、出力エリア、ボタンなどのUI要素のスタイルが定義されています。これにより、インタラクティブなコード例が視覚的に統合された形で表示されます。
* **`example.html`の変更**:
* Goのコード例を表示するためのHTMLテンプレートが大幅に修正されました。
* `{{with .Play}}`という条件分岐が追加され、`Play`フィールドが存在する場合(つまり、そのコード例がGo Playgroundで実行可能である場合)に、新しい`div.play`構造がレンダリングされるようになりました。
* この`div.play`内には、コードを入力するための`textarea`(`class="code"`)、実行結果を表示するための`pre`(`class="output"`)、そして「Run」「Format」「Share」ボタンが配置されます。
* `{{else}}`ブロックでは、これまで通りの静的なコード表示が維持されます。
* **`package.html`の変更**:
* `playground.js`が読み込まれるようになり、`$(document).ready`イベントで`div.play`要素を走査し、それぞれの要素に対して`playground`関数を初期化するJavaScriptコードが追加されました。
* これにより、ページロード時に各コード例のインタラクティブな機能が有効になります。また、`textarea`の高さが内容に合わせて自動調整されるロジックも追加されています。
2. **バックエンドの変更 (`src/cmd/godoc/godoc.go`, `src/cmd/godoc/main.go`)**:
* **`godoc.go`の変更**:
* 新しいコマンドラインフラグ`--play`が追加されました。このフラグを`true`に設定すると、`godoc`が生成するドキュメントでGo Playground機能が有効になります。
* `example_htmlFunc`関数が修正され、`doc.Example`構造体の`Play`フィールド(Go Playgroundで実行可能なコードを含む)がHTMLテンプレートに渡されるようになりました。
* `printer.Config`を使用して、Go Playgroundに渡すコードを標準的なGoのフォーマット(タブインデント、コメントハイライトなしなど)で整形するロジックが追加されました。
* **`main.go`の変更**:
* `/compile`, `/share`, `/fmt`といったGo Playground関連のHTTPエンドポイントに対するハンドラが設定されました。
* `--play`フラグが有効な場合、これらのリクエストは`bounceToPlayground`関数によって処理されます。
* `bounceToPlayground`関数は、受け取ったHTTPリクエストのURLスキームとホストを`http://play.golang.org`に書き換え、元のリクエストボディとヘッダーをそのまま`play.golang.org`に転送(プロキシ)します。そして、`play.golang.org`からのレスポンスをそのままクライアントに返します。
* このプロキシ機能により、`godoc`はGo Playgroundのバックエンド処理(コンパイル、実行、フォーマット、共有)を自前で実装することなく、既存のサービスを再利用できます。これは、`godoc`のコードベースをシンプルに保ちつつ、強力な機能を提供する賢明な設計判断です。
* `showPlayground`フラグが`false`の場合、これらのエンドポイントは`disabledHandler`によって処理され、501 "Not Implemented"エラーが返されます。
これらの変更により、`godoc`は単なる静的なドキュメントビューアから、インタラクティブな学習・実験環境へと進化しました。
## コアとなるコードの変更箇所
### `lib/godoc/example.html`
```html
{{$output := .Output}}
{{with .Play}}
<div class="play">
<div class="input"><textarea class="code">{{.}}</textarea></div>
<div class="output"><pre>{{html $output}}</pre></div>
<div class="buttons">
<a class="run" title="Run this code [shift-enter]">Run</a>
<a class="fmt" title="Format this code">Format</a>
<a class="share" title="Share this code">Share</a>
</div>
</div>
{{else}}
<p>Code:</p>
<pre class="code">{{.Code}}</pre>
{{with .Output}}
<p>Output:</p>
<pre class="output">{{html .}}</pre>
{{end}}
{{end}}
src/cmd/godoc/godoc.go
var (
tabwidth = flag.Int("tabwidth", 4, "tab width")
showTimestamps = flag.Bool("timestamps", false, "show timestamps with directory listings")
templateDir = flag.String("templates", "", "directory containing alternate template files")
+ showPlayground = flag.Bool("play", false, "enable playground in web interface")
// search index
indexEnabled = flag.Bool("index", false, "enable search index")
@@ -347,14 +350,29 @@ func example_htmlFunc(funcName string, examples []*doc.Example, fset *token.File
\t\tif loc := exampleOutputRx.FindStringIndex(code); loc != nil {\n \t\t\t\tcode = strings.TrimSpace(code[:loc[0]])\n \t\t\t}\n-\t\t} else {\n-\t\t\t// drop output, as the output comment will appear in the code\n+\t\t}\n+\n+\t\t// Write out the playground code in standard Go style
+\t\t// (use tabs, no comment highlight, etc).
+\t\tplay := ""
+\t\tif eg.Play != nil && *showPlayground {
+\t\t\tvar buf bytes.Buffer
+\t\t\terr := (&printer.Config{Mode: printer.TabIndent, Tabwidth: 8}).Fprint(&buf, fset, eg.Play)
+\t\t\tif err != nil {
+\t\t\t\tlog.Print(err)
+\t\t\t} else {
+\t\t\t\tplay = buf.String()
+\t\t\t}
+\t\t}
+\n+\t\t// Drop output, as the output comment will appear in the code.
+\t\tif wholeFile && play == "" {
\t\t\tout = ""
\t\t}\n \n \t\terr := exampleHTML.Execute(&buf, struct {\n-\t\t\tName, Doc, Code, Output string
-\t\t}{eg.Name, eg.Doc, code, out})\n+\t\t\tName, Doc, Code, Play, Output string
+\t\t}{eg.Name, eg.Doc, code, play, out})\n \t\tif err != nil {\n \t\t\tlog.Print(err)\n \t\t}\
src/cmd/godoc/main.go
func main() {
registerPublicHandlers(http.DefaultServeMux)
-\t\t// Playground handlers are not available in local godoc.
-\t\thttp.HandleFunc("/compile", disabledHandler)
-\t\thttp.HandleFunc("/share", disabledHandler)
+\t\tplayHandler := disabledHandler
+\t\tif *showPlayground {
+\t\t\tplayHandler = bounceToPlayground
+\t\t}
+\t\thttp.HandleFunc("/compile", playHandler)
+\t\thttp.HandleFunc("/share", playHandler)
+\t\thttp.HandleFunc("/fmt", playHandler)
// Initialize default directory tree with corresponding timestamp.
// (Do it in a goroutine so that launch is quick.)
@@ -466,6 +470,22 @@ type httpWriter struct {
func (w *httpWriter) Header() http.Header { return w.h }\n func (w *httpWriter) WriteHeader(code int) { w.code = code }\n \n+// bounceToPlayground forwards the request to play.golang.org.
+// TODO(adg): implement this stuff locally.
+func bounceToPlayground(w http.ResponseWriter, req *http.Request) {
+ defer req.Body.Close()
+ req.URL.Scheme = "http"
+ req.URL.Host = "play.golang.org"
+ resp, err := http.Post(req.URL.String(), req.Header.Get("Content-type"), req.Body)
+ if err != nil {
+ http.Error(w, err.Error(), 500)
+ return
+ }
+ w.WriteHeader(resp.StatusCode)
+ io.Copy(w, resp.Body)
+ resp.Body.Close()
+}
+
// disabledHandler serves a 501 "Not Implemented" response.
func disabledHandler(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNotImplemented)
コアとなるコードの解説
-
lib/godoc/example.html
:- このHTMLテンプレートは、
godoc
がGoのコード例をレンダリングする際に使用されます。 {{with .Play}}
ブロックは、Goのdoc.Example
構造体から渡されるPlay
フィールド(Go Playgroundで実行可能なコード)が存在するかどうかをチェックします。- もし
Play
フィールドがあれば、div.play
という新しいコンテナが生成されます。このコンテナ内には、ユーザーがコードを編集できるtextarea
、実行結果が表示されるpre
、そして「Run」「Format」「Share」ボタンが配置されます。これらの要素は、doc/play/playground.js
によってインタラクティブな機能が提供されます。 {{else}}
ブロックは、Play
フィールドがない場合(つまり、通常の静的なコード例の場合)に実行され、従来のpre
タグによるコード表示が行われます。- この変更により、
godoc
はコード例を単なるテキストとして表示するだけでなく、Go Playgroundと連携したインタラクティブなUIを提供できるようになりました。
- このHTMLテンプレートは、
-
src/cmd/godoc/godoc.go
:showPlayground
という新しいブール型フラグが追加されました。これは、godoc
コマンドの起動時に--play
オプションを指定することで、Go Playground機能を有効にするかどうかを制御します。example_htmlFunc
は、Goのソースコードから抽出されたコード例をHTMLに変換する役割を担っています。この関数内で、eg.Play
(Go Playgroundで実行可能な形式のコード)が存在し、かつshowPlayground
フラグがtrue
の場合に、printer.Config
を使用してコードを整形し、その結果をplay
変数に格納しています。- 最終的に、
exampleHTML.Execute
の呼び出しにおいて、Play
フィールドがテンプレートに渡されるようになりました。これにより、HTMLテンプレートはGo Playground用のUIをレンダリングするかどうかを判断できます。
-
src/cmd/godoc/main.go
:main
関数内で、/compile
、/share
、/fmt
といったGo Playground関連のHTTPエンドポイントに対するハンドラが登録されています。showPlayground
フラグがtrue
の場合、これらのエンドポイントへのリクエストはbounceToPlayground
関数によって処理されます。bounceToPlayground
関数は、受け取ったHTTPリクエストのURLスキームとホストをhttp://play.golang.org
に書き換え、元のリクエストボディとヘッダーをそのままplay.golang.org
に転送(プロキシ)します。そして、play.golang.org
からのレスポンスをそのままクライアントに返します。- このプロキシ機能により、
godoc
はGo Playgroundのバックエンド処理(コンパイル、実行、フォーマット、共有)を自前で実装することなく、既存のサービスを再利用できます。これは、godoc
のコードベースをシンプルに保ちつつ、強力な機能を提供する賢明な設計判断です。 showPlayground
フラグがfalse
の場合、これらのエンドポイントはdisabledHandler
によって処理され、501 "Not Implemented"エラーが返されます。
これらの変更が連携することで、godoc
はGo Playgroundの機能を統合し、ユーザーがドキュメント内で直接コード例を試せるインタラクティブな環境を提供します。
関連リンク
- Go Playground: https://play.golang.org/
godoc
コマンドのドキュメント: https://pkg.go.dev/cmd/godoc- HTML5 History API (MDN Web Docs): https://developer.mozilla.org/ja/docs/Web/API/History_API
参考にした情報源リンク
- Go Playgroundの仕組みに関する記事やドキュメント(一般的な知識として)
godoc
の内部構造に関する情報(一般的な知識として)- HTML5 History APIの利用例に関するWeb記事
- Go言語の
html/template
パッケージのドキュメント - Go言語の
flag
パッケージのドキュメント - Go言語の
net/http
パッケージのドキュメント - Go言語の
go/printer
パッケージのドキュメント - Go言語の
go/doc
パッケージのドキュメント - Go言語の
bytes
パッケージのドキュメント - Go言語の
io
パッケージのドキュメント - Go言語の
strings
パッケージのドキュメント - Go言語の
log
パッケージのドキュメント - Go言語の
regexp
パッケージのドキュメント - Go言語の
net/http/httputil
パッケージのドキュメント (直接は使用されていないが、プロキシの概念理解に役立つ) - jQueryのドキュメント (playground.jsで使用されているため)
- CodeMirrorのドキュメント (playground.jsから削除されたが、以前のコンテキスト理解に役立つ)
- Go言語の公式ブログやリリースノート(該当する時期の変更点を確認)
- GoのIssueトラッカーやChange List (CL) の議論 (CL 6523045)