Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 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言語の理解を深めるという目的があります。これにより、ドキュメントの利便性と教育的価値が大幅に向上します。

前提知識の解説

このコミットの変更内容を理解するためには、以下の前提知識が必要です。

  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に反映され、共有しやすくなります。
      • preCompilepostCompileといったコールバックオプションが削除され、playground.jsのロジックが簡素化されています。
    • style.cssの追加:
      • div.playという新しいCSSクラスが追加され、Go Playgroundのコードエディタ、出力エリア、ボタンなどのUI要素のスタイルが定義されています。これにより、インタラクティブなコード例が視覚的に統合された形で表示されます。
    • example.htmlの変更:
      • Goのコード例を表示するためのHTMLテンプレートが大幅に修正されました。
      • {{with .Play}}という条件分岐が追加され、Playフィールドが存在する場合(つまり、そのコード例がGo Playgroundで実行可能である場合)に、新しいdiv.play構造がレンダリングされるようになりました。
      • このdiv.play内には、コードを入力するためのtextareaclass="code")、実行結果を表示するためのpreclass="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関数は、受け取ったリクエストを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)

コアとなるコードの解説

  1. 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を提供できるようになりました。
  2. 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をレンダリングするかどうかを判断できます。
  3. 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の仕組みに関する記事やドキュメント(一般的な知識として)
  • 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)

コアとなるコードの解説

  1. 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を提供できるようになりました。
  2. 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をレンダリングするかどうかを判断できます。
  3. 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の仕組みに関する記事やドキュメント(一般的な知識として)
  • 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)