diff options
| author | Russ Cox <rsc@golang.org> | 2021-02-25 20:49:34 -0500 |
|---|---|---|
| committer | Russ Cox <rsc@golang.org> | 2021-06-14 17:41:27 +0000 |
| commit | 4c9e549253faa360e399b19be93adb3ad820ca4e (patch) | |
| tree | 7956805350ec818eb35d4930e5eb12d9032fdfb3 | |
| parent | 220bf30a7a2fa02ddc669ded0e07b87c8a97e0f3 (diff) | |
| download | go-x-website-4c9e549253faa360e399b19be93adb3ad820ca4e.tar.xz | |
internal/godoc: clean up use of templates
- convert from text/template to html/template
- use proper template set
- always pass *godoc.Page to templates, with custom value in .Data
- move stateful site template functions to methods on *godoc.Page
- unexport Presentation.ServeFile: ServeHTTP is good enough
- reorder api.DB.Func args to match source order (pkg first)
- rename lib/godoc/godoc.html to lib/godoc/site.html
(lib/godoc itself must stay lib/godoc because of links to other content it holds).
Change-Id: I873f17db20107fdab11d276932e6d847a6081015
Reviewed-on: https://go-review.googlesource.com/c/website/+/317655
Trust: Russ Cox <rsc@golang.org>
Run-TryBot: Russ Cox <rsc@golang.org>
Reviewed-by: Dmitri Shuralyov <dmitshur@golang.org>
45 files changed, 389 insertions, 499 deletions
diff --git a/_content/doc/articles/race_detector.html b/_content/doc/articles/race_detector.html index 09188c15..01278a26 100644 --- a/_content/doc/articles/race_detector.html +++ b/_content/doc/articles/race_detector.html @@ -1,6 +1,5 @@ <!--{ - "Title": "Data Race Detector", - "Template": true + "Title": "Data Race Detector" }--> <h2 id="Introduction">Introduction</h2> diff --git a/_content/doc/code.html b/_content/doc/code.html index 99a1729a..1f020c98 100644 --- a/_content/doc/code.html +++ b/_content/doc/code.html @@ -183,7 +183,7 @@ easy: <pre> # Windows users should consult https://github.com/golang/go/wiki/SettingGOPATH # for setting %PATH%. -$ <b>export PATH=$PATH:$(dirname $(go list -f '{{"{{"}}.Target{{"}}"}}' .))</b> +$ <b>export PATH=$PATH:$(dirname $(go list -f '{{"{{.Target}}"}}' .))</b> $ <b>hello</b> Hello, world. $ diff --git a/_content/doc/diagnostics.html b/_content/doc/diagnostics.html index 438cdce4..c97140ae 100644 --- a/_content/doc/diagnostics.html +++ b/_content/doc/diagnostics.html @@ -1,6 +1,5 @@ <!--{ - "Title": "Diagnostics", - "Template": true + "Title": "Diagnostics" }--> <!-- diff --git a/_content/doc/editors.html b/_content/doc/editors.html index e0d0c530..0ba8836b 100644 --- a/_content/doc/editors.html +++ b/_content/doc/editors.html @@ -1,6 +1,5 @@ <!--{ - "Title": "Editor plugins and IDEs", - "Template": true + "Title": "Editor plugins and IDEs" }--> <h2 id="introduction">Introduction</h2> diff --git a/_content/doc/effective_go.html b/_content/doc/effective_go.html index 3db4d1b3..f2f056d4 100644 --- a/_content/doc/effective_go.html +++ b/_content/doc/effective_go.html @@ -3638,13 +3638,13 @@ from data items passed to <code>templ.Execute</code>, in this case the form value. Within the template text (<code>templateStr</code>), double-brace-delimited pieces denote template actions. -The piece from <code>{{html "{{if .}}"}}</code> -to <code>{{html "{{end}}"}}</code> executes only if the value of the current data item, called <code>.</code> (dot), +The piece from <code>{{"{{if .}}"}}</code> +to <code>{{"{{end}}"}}</code> executes only if the value of the current data item, called <code>.</code> (dot), is non-empty. That is, when the string is empty, this piece of the template is suppressed. </p> <p> -The two snippets <code>{{html "{{.}}"}}</code> say to show the data presented to +The two snippets <code>{{"{{.}}"}}</code> say to show the data presented to the template—the query string—on the web page. The HTML template package automatically provides appropriate escaping so the text is safe to display. diff --git a/_content/doc/go1.1.html b/_content/doc/go1.1.html index a1130c08..edda047b 100644 --- a/_content/doc/go1.1.html +++ b/_content/doc/go1.1.html @@ -1,6 +1,5 @@ <!--{ - "Title": "Go 1.1 Release Notes", - "Template": true + "Title": "Go 1.1 Release Notes" }--> <h2 id="introduction">Introduction to Go 1.1</h2> diff --git a/_content/doc/go1.10.html b/_content/doc/go1.10.html index 0445026e..5201e4ac 100644 --- a/_content/doc/go1.10.html +++ b/_content/doc/go1.10.html @@ -1,6 +1,5 @@ <!--{ - "Title": "Go 1.10 Release Notes", - "Template": true + "Title": "Go 1.10 Release Notes" }--> <!-- diff --git a/_content/doc/go1.11.html b/_content/doc/go1.11.html index fad1e0d6..b513890c 100644 --- a/_content/doc/go1.11.html +++ b/_content/doc/go1.11.html @@ -1,6 +1,5 @@ <!--{ - "Title": "Go 1.11 Release Notes", - "Template": true + "Title": "Go 1.11 Release Notes" }--> <!-- @@ -907,11 +906,11 @@ for k := range m { Modifying template variables via assignments is now permitted via the <code>=</code> token: </p> <pre> - {{"{{"}} $v := "init" {{"}}"}} - {{"{{"}} if true {{"}}"}} - {{"{{"}} $v = "changed" {{"}}"}} - {{"{{"}} end {{"}}"}} - v: {{"{{"}} $v {{"}}"}} {{"{{"}}/* "changed" */{{"}}"}}</pre> + {{ $v := "init" }} + {{ if true }} + {{ $v = "changed" }} + {{ end }} + v: {{ $v }} {{/* "changed" */}}</pre> <p><!-- CL 95215 --> In previous versions untyped <code>nil</code> values passed to diff --git a/_content/doc/go1.12.html b/_content/doc/go1.12.html index c3181e0d..21d77f11 100644 --- a/_content/doc/go1.12.html +++ b/_content/doc/go1.12.html @@ -1,6 +1,5 @@ <!--{ - "Title": "Go 1.12 Release Notes", - "Template": true + "Title": "Go 1.12 Release Notes" }--> <!-- diff --git a/_content/doc/go1.13.html b/_content/doc/go1.13.html index 6bbc84ee..af1d8bb3 100644 --- a/_content/doc/go1.13.html +++ b/_content/doc/go1.13.html @@ -1,6 +1,5 @@ <!--{ - "Title": "Go 1.13 Release Notes", - "Template": true + "Title": "Go 1.13 Release Notes" }--> <!-- diff --git a/_content/doc/go1.2.html b/_content/doc/go1.2.html index acc4436e..aa059345 100644 --- a/_content/doc/go1.2.html +++ b/_content/doc/go1.2.html @@ -1,6 +1,5 @@ <!--{ - "Title": "Go 1.2 Release Notes", - "Template": true + "Title": "Go 1.2 Release Notes" }--> <h2 id="introduction">Introduction to Go 1.2</h2> @@ -554,7 +553,7 @@ argument with one or more following arguments. The template in this example, </p> <pre> -{{"{{"}}if eq .A 1 2 3 {{"}}"}} equal {{"{{"}}else{{"}}"}} not equal {{"{{"}}end{{"}}"}} +{{if eq .A 1 2 3}} equal {{else}} not equal {{end}} </pre> <p> @@ -567,7 +566,7 @@ Instead of writing, </p> <pre> -{{"{{"}}if eq .A 1{{"}}"}} X {{"{{"}}else{{"}}"}} {{"{{"}}if eq .A 2{{"}}"}} Y {{"{{"}}end{{"}}"}} {{"{{"}}end{{"}}"}} +{{if eq .A 1}} X {{else}} {{if eq .A 2}} Y {{end}} {{end}} </pre> <p> @@ -575,7 +574,7 @@ one can fold the second "if" into the "else" and have only one "end", like this: </p> <pre> -{{"{{"}}if eq .A 1{{"}}"}} X {{"{{"}}else if eq .A 2{{"}}"}} Y {{"{{"}}end{{"}}"}} +{{if eq .A 1}} X {{else if eq .A 2}} Y {{end}} </pre> <p> diff --git a/_content/doc/go1.3.html b/_content/doc/go1.3.html index 57d1d7ff..8edb6ee4 100644 --- a/_content/doc/go1.3.html +++ b/_content/doc/go1.3.html @@ -1,6 +1,5 @@ <!--{ - "Title": "Go 1.3 Release Notes", - "Template": true + "Title": "Go 1.3 Release Notes" }--> <h2 id="introduction">Introduction to Go 1.3</h2> diff --git a/_content/doc/go1.4.html b/_content/doc/go1.4.html index 0233ad49..1c306f6c 100644 --- a/_content/doc/go1.4.html +++ b/_content/doc/go1.4.html @@ -1,6 +1,5 @@ <!--{ - "Title": "Go 1.4 Release Notes", - "Template": true + "Title": "Go 1.4 Release Notes" }--> <h2 id="introduction">Introduction to Go 1.4</h2> diff --git a/_content/doc/go1.5.html b/_content/doc/go1.5.html index d9529f26..bd7517c2 100644 --- a/_content/doc/go1.5.html +++ b/_content/doc/go1.5.html @@ -1,6 +1,5 @@ <!--{ - "Title": "Go 1.5 Release Notes", - "Template": true + "Title": "Go 1.5 Release Notes" }--> diff --git a/_content/doc/go1.6.html b/_content/doc/go1.6.html index 2f7093d8..edf89d26 100644 --- a/_content/doc/go1.6.html +++ b/_content/doc/go1.6.html @@ -1,6 +1,5 @@ <!--{ - "Title": "Go 1.6 Release Notes", - "Template": true + "Title": "Go 1.6 Release Notes" }--> <!-- @@ -443,9 +442,9 @@ For example, the template </p> <pre> -{{"{{"}}23 -}} +{{23 -}} < -{{"{{"}}- 45}} +{{- 45}} </pre> <p> @@ -453,7 +452,7 @@ formats as <code>23<45</code>. </p> <p> -Second, the new <a href="/pkg/text/template/#hdr-Actions"><code>{{"{{"}}block}}</code> action</a>, +Second, the new <a href="/pkg/text/template/#hdr-Actions"><code>{{block}}</code> action</a>, combined with allowing redefinition of named templates, provides a simple way to define pieces of a template that can be replaced in different instantiations. diff --git a/_content/doc/go1.7.html b/_content/doc/go1.7.html index 21388718..79236d6c 100644 --- a/_content/doc/go1.7.html +++ b/_content/doc/go1.7.html @@ -1,6 +1,5 @@ <!--{ - "Title": "Go 1.7 Release Notes", - "Template": true + "Title": "Go 1.7 Release Notes" }--> <!-- diff --git a/_content/doc/go1.8.html b/_content/doc/go1.8.html index adb8991f..56290f0d 100644 --- a/_content/doc/go1.8.html +++ b/_content/doc/go1.8.html @@ -1,6 +1,5 @@ <!--{ - "Title": "Go 1.8 Release Notes", - "Template": true + "Title": "Go 1.8 Release Notes" }--> <!-- diff --git a/_content/doc/go1.9.html b/_content/doc/go1.9.html index ddd310f4..374ac357 100644 --- a/_content/doc/go1.9.html +++ b/_content/doc/go1.9.html @@ -1,6 +1,5 @@ <!--{ - "Title": "Go 1.9 Release Notes", - "Template": true + "Title": "Go 1.9 Release Notes" }--> <!-- diff --git a/_content/lib/godoc/codewalk.html b/_content/lib/godoc/codewalk.html index 0f3d22a2..89360bf9 100644 --- a/_content/lib/godoc/codewalk.html +++ b/_content/lib/godoc/codewalk.html @@ -4,6 +4,7 @@ license that can be found in the LICENSE file. --> +{{with .Data}} <style type='text/css'>@import "/doc/codewalk/codewalk.css";</style> <script type="text/javascript" src="/doc/codewalk/codewalk.js"></script> @@ -17,7 +18,7 @@ </a> <select id="code-selector"> {{range .File}} - <option value="/doc/codewalk/?fileprint=/{{urlquery .}}">{{html .}}</option> + <option value="/doc/codewalk/?fileprint=/{{.}}">{{.}}</option> {{end}} </select> </div> @@ -35,15 +36,15 @@ <div id="comment-area"> {{range .Step}} <div class="comment first last"> - <a class="comment-link" href="/doc/codewalk/?fileprint=/{{urlquery .File}}&lo={{urlquery .Lo}}&hi={{urlquery .Hi}}#mark" target="code-display"></a> - <div class="comment-title">{{html .Title}}</div> + <a class="comment-link" href="/doc/codewalk/?fileprint=/{{.File}}&lo={{.Lo}}&hi={{.Hi}}#mark" target="code-display"></a> + <div class="comment-title">{{.Title}}</div> <div class="comment-text"> {{with .Err}} - ERROR LOADING FILE: {{html .}}<br/><br/> + ERROR LOADING FILE: {{.}}<br/><br/> {{end}} - {{.XML}} + {{.HTML}} </div> - <div class="comment-text file-name"><span class="path-file">{{html .}}</span></div> + <div class="comment-text file-name"><span class="path-file">{{.}}</span></div> </div> {{end}} </div> @@ -54,3 +55,4 @@ </div> </div> </div> +{{end}} diff --git a/_content/lib/godoc/codewalkdir.html b/_content/lib/godoc/codewalkdir.html index b7674c6c..5479429a 100644 --- a/_content/lib/godoc/codewalkdir.html +++ b/_content/lib/godoc/codewalkdir.html @@ -5,12 +5,11 @@ --> <table class="layout"> -{{range .}} +{{range .Data}} <tr> - {{$name_html := html .Name}} - <td><a href="{{$name_html}}">{{$name_html}}</a></td> + <td><a href="{{.Name}}">{{.Name}}</a></td> <td width="25"> </td> - <td>{{html .Title}}</td> + <td>{{.Title}}</td> </tr> {{end}} </table> diff --git a/_content/lib/godoc/dirlist.html b/_content/lib/godoc/dirlist.html index 78530e6b..6f324032 100644 --- a/_content/lib/godoc/dirlist.html +++ b/_content/lib/godoc/dirlist.html @@ -4,6 +4,7 @@ license that can be found in the LICENSE file. --> +{{with .Data}} <p> <table class="layout"> <tr> @@ -15,11 +16,12 @@ <td><a href="../">../</a></td> </tr> {{range .}}{{if .IsDir}} - <tr><td align="left"><a href="{{html .Name}}/">{{html .Name}}/</a><td></tr> + <tr><td align="left"><a href="{{.Name}}/">{{.Name}}/</a><td></tr> {{end}}{{end}} {{range .}}{{if not .IsDir}} - <tr><td align="left"><a href="{{html .Name}}">{{html .Name}}</a><td align="right">{{html .Size}}</tr> + <tr><td align="left"><a href="{{.Name}}">{{.Name}}</a><td align="right">{{.Size}}</tr> {{end}}{{end}} </table> </p> +{{end}} diff --git a/_content/lib/godoc/error.html b/_content/lib/godoc/error.html index 7573aa23..cb5325ea 100644 --- a/_content/lib/godoc/error.html +++ b/_content/lib/godoc/error.html @@ -4,6 +4,8 @@ license that can be found in the LICENSE file. --> +{{with .Data}} <p> -<span class="alert" style="font-size:120%">{{html .}}</span> +<span class="alert" style="font-size:120%">{{.}}</span> </p> +{{end}} diff --git a/_content/lib/godoc/example.html b/_content/lib/godoc/example.html index bde02c68..aef6edb4 100644 --- a/_content/lib/godoc/example.html +++ b/_content/lib/godoc/example.html @@ -1,14 +1,15 @@ +{{with .Data}} <div id="example_{{.Name}}" class="toggle"> <div class="collapsed"> <p class="exampleHeading toggleButton">▹ <span class="text">Example{{example_suffix .Name}}</span></p> </div> <div class="expanded"> <p class="exampleHeading toggleButton">▾ <span class="text">Example{{example_suffix .Name}}</span></p> - {{with .Doc}}<p>{{html .}}</p>{{end}} + {{with .Doc}}<p>{{.}}</p>{{end}} {{$output := .Output}} {{with .Play}} <div class="play"> - <div class="input"><textarea class="code" spellcheck="false">{{html .}}</textarea></div> + <div class="input"><textarea class="code" spellcheck="false">{{.}}</textarea></div> <div class="output"><pre>{{html $output}}</pre></div> <div class="buttons"> <button class="Button Button--primary run" title="Run this code [shift-enter]">Run</button> @@ -23,8 +24,9 @@ <pre class="code">{{.Code}}</pre> {{with .Output}} <p>Output:</p> - <pre class="output">{{html .}}</pre> + <pre class="output">{{.}}</pre> {{end}} {{end}} </div> </div> +{{end}} diff --git a/_content/lib/godoc/godocs.js b/_content/lib/godoc/godocs.js index 68e2a892..e581cb38 100644 --- a/_content/lib/godoc/godocs.js +++ b/_content/lib/godoc/godocs.js @@ -372,7 +372,7 @@ personalizeInstallInstructions(); updateVersionTags(); - // godoc.html defines window.initFuncs in the <head> tag, and root.html and + // site.html defines window.initFuncs in the <head> tag, and root.html and // codewalk.js push their on-page-ready functions to the list. // We execute those functions here, to avoid loading jQuery until the page // content is loaded. diff --git a/_content/lib/godoc/package.html b/_content/lib/godoc/package.html index 9f0c8793..3d6e8ba9 100644 --- a/_content/lib/godoc/package.html +++ b/_content/lib/godoc/package.html @@ -9,23 +9,24 @@ them to conflict with generated attributes (some of which correspond to Go identifiers). --> -{{with .PDoc}} - {{if $.IsMain}} +{{$pkg := .Data}} +{{with $pkg.PDoc}} + {{if $pkg.IsMain}} {{/* command documentation */}} - {{comment_html .Doc}} + {{$.Comment .Doc}} {{else}} {{/* package documentation */}} <div id="short-nav"> <dl> - <dd><code>import "{{html .ImportPath}}"</code></dd> + <dd><code>import "{{.ImportPath}}"</code></dd> </dl> <dl> <dd><a href="#pkg-overview" class="overviewLink">Overview</a></dd> <dd><a href="#pkg-index" class="indexLink">Index</a></dd> - {{if $.Examples}} + {{if $pkg.Examples}} <dd><a href="#pkg-examples" class="examplesLink">Examples</a></dd> {{end}} - {{if $.Dirs}} + {{if $pkg.Dirs}} <dd><a href="#pkg-subdirectories">Subdirectories</a></dd> {{end}} </dl> @@ -37,8 +38,8 @@ </div> <div class="expanded"> <h2 class="toggleButton" title="Click to hide Overview section">Overview ▾</h2> - {{comment_html .Doc}} - {{example_html $ ""}} + {{$.Comment .Doc}} + {{$.Example ""}} </div> </div> @@ -59,33 +60,30 @@ <dd><a href="#pkg-variables">Variables</a></dd> {{end}} {{range .Funcs}} - {{$name_html := html .Name}} - <dd><a href="#{{$name_html}}">{{node_html $ .Decl false | sanitize}}</a></dd> + <dd><a href="#{{.Name}}">{{$.NodeTOC .Decl}}</a></dd> {{end}} {{range .Types}} - {{$tname_html := html .Name}} - <dd><a href="#{{$tname_html}}">type {{$tname_html}}</a></dd> + {{$typeName := .Name}} + <dd><a href="#{{.Name}}">type {{.Name}}</a></dd> {{range .Funcs}} - {{$name_html := html .Name}} - <dd> <a href="#{{$name_html}}">{{node_html $ .Decl false | sanitize}}</a></dd> + <dd> <a href="#{{.Name}}">{{$.NodeTOC .Decl}}</a></dd> {{end}} {{range .Methods}} - {{$name_html := html .Name}} - <dd> <a href="#{{$tname_html}}.{{$name_html}}">{{node_html $ .Decl false | sanitize}}</a></dd> + <dd> <a href="#{{$typeName}}.{{.Name}}">{{$.NodeTOC .Decl}}</a></dd> {{end}} {{end}} - {{if $.Bugs}} + {{if $pkg.Bugs}} <dd><a href="#pkg-note-BUG">Bugs</a></dd> {{end}} </dl> </div><!-- #manual-nav --> - {{if $.Examples}} + {{if $pkg.Examples}} <div id="pkg-examples"> <h3>Examples</h3> <div class="js-expandAll expandAll collapsed">(Expand All)</div> <dl> - {{range $.Examples}} + {{range $pkg.Examples}} <dd><a class="exampleLink" href="#example_{{.Name}}">{{example_name .Name}}</a></dd> {{end}} </dl> @@ -97,7 +95,7 @@ <p> <span style="font-size:90%"> {{range .}} - <a href="{{.|srcLink|html}}">{{.|filename|html}}</a> + <a href="/src/{{.}}">{{basename .}}</a> {{end}} </span> </p> @@ -108,93 +106,87 @@ {{with .Consts}} <h2 id="pkg-constants">Constants</h2> {{range .}} - {{comment_html .Doc}} - <pre>{{node_html $ .Decl true}}</pre> + {{$.Comment .Doc}} + <pre>{{$.Node .Decl}}</pre> {{end}} {{end}} {{with .Vars}} <h2 id="pkg-variables">Variables</h2> {{range .}} - {{comment_html .Doc}} - <pre>{{node_html $ .Decl true}}</pre> + {{$.Comment .Doc}} + <pre>{{$.Node .Decl}}</pre> {{end}} {{end}} {{range .Funcs}} {{/* Name is a string - no need for FSet */}} - {{$name_html := html .Name}} - <h2 id="{{$name_html}}">func <a href="{{posLink_url $ .Decl}}">{{$name_html}}</a> - <a class="permalink" href="#{{$name_html}}">¶</a> - {{$since := since "func" "" .Name $.PDoc.ImportPath}} + <h2 id="{{.Name}}">func <a href="{{$.SrcPosLink .Decl}}">{{.Name}}</a> + <a class="permalink" href="#{{.Name}}">¶</a> + {{$since := $.Since "func" "" .Name}} {{if $since}}<span title="Added in Go {{$since}}">{{$since}}</span>{{end}} </h2> - <pre>{{node_html $ .Decl true}}</pre> - {{comment_html .Doc}} - {{example_html $ .Name}} - + <pre>{{$.Node .Decl}}</pre> + {{$.Comment .Doc}} + {{$.Example .Name}} {{end}} {{range .Types}} - {{$tname := .Name}} - {{$tname_html := html .Name}} - <h2 id="{{$tname_html}}">type <a href="{{posLink_url $ .Decl}}">{{$tname_html}}</a> - <a class="permalink" href="#{{$tname_html}}">¶</a> - {{$since := since "type" "" .Name $.PDoc.ImportPath}} + {{$typeName := .Name}} + <h2 id="{{.Name}}">type <a href="{{$.SrcPosLink .Decl}}">{{$typeName}}</a> + <a class="permalink" href="#{{.Name}}">¶</a> + {{$since := $.Since "type" "" .Name}} {{if $since}}<span title="Added in Go {{$since}}">{{$since}}</span>{{end}} </h2> - {{comment_html .Doc}} - <pre>{{node_html $ .Decl true}}</pre> + {{$.Comment .Doc}} + <pre>{{$.Node .Decl}}</pre> {{range .Consts}} - {{comment_html .Doc}} - <pre>{{node_html $ .Decl true}}</pre> + {{$.Comment .Doc}} + <pre>{{$.Node .Decl}}</pre> {{end}} {{range .Vars}} - {{comment_html .Doc}} - <pre>{{node_html $ .Decl true}}</pre> + {{$.Comment .Doc}} + <pre>{{$.Node .Decl}}</pre> {{end}} - {{example_html $ $tname}} + {{$.Example .Name}} {{range .Funcs}} - {{$name_html := html .Name}} - <h3 id="{{$name_html}}">func <a href="{{posLink_url $ .Decl}}">{{$name_html}}</a> - <a class="permalink" href="#{{$name_html}}">¶</a> - {{$since := since "func" "" .Name $.PDoc.ImportPath}} + <h3 id="{{.Name}}">func <a href="{{$.SrcPosLink .Decl}}">{{.Name}}</a> + <a class="permalink" href="#{{.Name}}">¶</a> + {{$since := $.Since "func" "" .Name}} {{if $since}}<span title="Added in Go {{$since}}">{{$since}}</span>{{end}} </h3> - <pre>{{node_html $ .Decl true}}</pre> - {{comment_html .Doc}} - {{example_html $ .Name}} + <pre>{{$.Node .Decl}}</pre> + {{$.Comment .Doc}} + {{$.Example .Name}} {{end}} {{range .Methods}} - {{$name_html := html .Name}} - <h3 id="{{$tname_html}}.{{$name_html}}">func ({{html .Recv}}) <a href="{{posLink_url $ .Decl}}">{{$name_html}}</a> - <a class="permalink" href="#{{$tname_html}}.{{$name_html}}">¶</a> - {{$since := since "method" .Recv .Name $.PDoc.ImportPath}} + <h3 id="{{$typeName}}.{{.Name}}">func ({{html .Recv}}) <a href="{{$.SrcPosLink .Decl}}">{{.Name}}</a> + <a class="permalink" href="#{{$typeName}}.{{.Name}}">¶</a> + {{$since := $.Since "method" .Recv .Name}} {{if $since}}<span title="Added in Go {{$since}}">{{$since}}</span>{{end}} </h3> - <pre>{{node_html $ .Decl true}}</pre> - {{comment_html .Doc}} - {{$name := printf "%s_%s" $tname .Name}} - {{example_html $ $name}} + <pre>{{$.Node .Decl}}</pre> + {{$.Comment .Doc}} + {{$.Example (printf "%s_%s" $typeName .Name)}} {{end}} {{end}} {{end}} - {{with $.Bugs}} + {{with $pkg.Bugs}} <h2 id="pkg-note-BUG">Bugs</h2> <ul style="list-style: none; padding: 0;"> {{range .}} - <li><a href="{{posLink_url $ .}}" style="float: left;">☞</a> {{comment_html .Body}}</li> + <li><a href="{{$.SrcPosLink .}}" style="float: left;">☞</a> {{$.Comment .Body}}</li> {{end}} </ul> {{end}} {{end}} -{{with .Dirs}} +{{with $pkg.Dirs}} {{/* DirList entries are numbers and strings - no need for FSet */}} - {{if $.PDoc}} + {{if $pkg.PDoc}} <h2 id="pkg-subdirectories">Subdirectories</h2> {{end}} <div class="pkg-dir"> @@ -204,7 +196,7 @@ <th class="pkg-synopsis">Synopsis</th> </tr> - {{if not (or (eq $.Dirname "/src/cmd") $.DirFlat)}} + {{if not (or (eq $pkg.Dirname "/src/cmd") $pkg.DirFlat)}} <tr> <td colspan="2"><a href="..">..</a></td> </tr> @@ -212,19 +204,19 @@ {{range .List}} <tr> - {{if $.DirFlat}} + {{if $pkg.DirFlat}} {{if .HasPkg}} <td class="pkg-name"> - <a href="{{html .Path}}/{{modeQueryString $.Mode | html}}">{{html .Path}}</a> + <a href="{{.Path}}/{{$.ModeQuery}}">{{.Path}}</a> </td> {{end}} {{else}} <td class="pkg-name" style="padding-left: {{multiply .Depth 20}}px;"> - <a href="{{html .Path}}/{{modeQueryString $.Mode | html}}">{{html .Name}}</a> + <a href="{{.Path}}/{{$.ModeQuery}}">{{.Name}}</a> </td> {{end}} <td class="pkg-synopsis"> - {{html .Synopsis}} + {{.Synopsis}} </td> </tr> {{end}} diff --git a/_content/lib/godoc/packageroot.html b/_content/lib/godoc/packageroot.html index 5727926c..218c8455 100644 --- a/_content/lib/godoc/packageroot.html +++ b/_content/lib/godoc/packageroot.html @@ -9,10 +9,11 @@ them to conflict with generated attributes (some of which correspond to Go identifiers). --> +{{$pkg := .Data}} -{{with .Dirs}} +{{with $pkg.Dirs}} {{/* DirList entries are numbers and strings - no need for FSet */}} - {{if $.PDoc}} + {{if $pkg.PDoc}} <h2 id="pkg-subdirectories">Subdirectories</h2> {{end}} <div id="manual-nav"> @@ -40,19 +41,19 @@ {{range .List}} <tr> - {{if $.DirFlat}} + {{if $pkg.DirFlat}} {{if .HasPkg}} <td class="pkg-name"> - <a href="{{html .Path}}/{{modeQueryString $.Mode | html}}">{{html .Path}}</a> + <a href="{{.Path}}/{{$.ModeQuery}}">{{.Path}}</a> </td> {{end}} {{else}} <td class="pkg-name" style="padding-left: {{multiply .Depth 20}}px;"> - <a href="{{html .Path}}/{{modeQueryString $.Mode | html}}">{{html .Name}}</a> + <a href="{{.Path}}/{{$.ModeQuery}}">{{.Name}}</a> </td> {{end}} <td class="pkg-synopsis"> - {{html .Synopsis}} + {{.Synopsis}} </td> </tr> {{end}} diff --git a/_content/lib/godoc/godoc.html b/_content/lib/godoc/site.html index f1d14e4d..2f3e4961 100644 --- a/_content/lib/godoc/godoc.html +++ b/_content/lib/godoc/site.html @@ -4,8 +4,8 @@ <meta name="description" content="Go is an open source programming language that makes it easy to build simple, reliable, and efficient software."> <meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="theme-color" content="#00ADD8"> -{{with .Tabtitle}} - <title>{{html .}} - The Go Programming Language</title> +{{with .TabTitle}} + <title>{{.}} - The Go Programming Language</title> {{else}} <title>The Go Programming Language</title> {{end}} @@ -63,18 +63,18 @@ window.trackEvent = function(category, action, opt_label, opt_value, opt_noninte {{if or .Title .SrcPath}} <h1> - {{html .Title}} - {{html .SrcPath | srcBreadcrumb}} + {{.Title}} + {{$.SrcBreadcrumb}} </h1> {{end}} {{with .Subtitle}} - <h2>{{html .}}</h2> + <h2>{{.}}</h2> {{end}} {{with .SrcPath}} <h2> - Documentation: {{html . | srcToPkgLink}} + Documentation: {{$.SrcPkgLink}} </h2> {{end}} @@ -82,8 +82,11 @@ window.trackEvent = function(category, action, opt_label, opt_value, opt_noninte Do not delete this <div>. */}} <div id="nav"></div> -{{/* Body is HTML-escaped elsewhere */}} -{{printf "%s" .Body}} +{{if .Template}} +{{.Invoke .Template .Data}} +{{else}} +{{.Data}} +{{end}} </div><!-- .container --> </main><!-- #page --> @@ -108,4 +111,3 @@ window.trackEvent = function(category, action, opt_label, opt_value, opt_noninte })(); </script> {{end}} - diff --git a/cmd/golangorg/codewalk.go b/cmd/golangorg/codewalk.go index b5713433..d8d22841 100644 --- a/cmd/golangorg/codewalk.go +++ b/cmd/golangorg/codewalk.go @@ -16,10 +16,10 @@ package main import ( - "bytes" "encoding/xml" "errors" "fmt" + "html/template" "io" "io/fs" "log" @@ -30,14 +30,11 @@ import ( "sort" "strconv" "strings" - "text/template" "unicode/utf8" "golang.org/x/website/internal/godoc" ) -var codewalkHTML, codewalkdirHTML *template.Template - // Handler for /doc/codewalk/ and below. func codewalk(w http.ResponseWriter, r *http.Request) { relpath := r.URL.Path[len("/doc/codewalk/"):] @@ -58,7 +55,7 @@ func codewalk(w http.ResponseWriter, r *http.Request) { // If file exists, serve using standard file server. if err == nil { - pres.ServeFile(w, r) + pres.ServeHTTP(w, r) return } @@ -69,7 +66,7 @@ func codewalk(w http.ResponseWriter, r *http.Request) { cw, err := loadCodewalk(abspath + ".xml") if err != nil { log.Print(err) - pres.ServeError(w, r, relpath, err) + pres.ServeError(w, r, err) return } @@ -78,10 +75,11 @@ func codewalk(w http.ResponseWriter, r *http.Request) { return } - pres.ServePage(w, godoc.Page{ + pres.ServePage(w, r, godoc.Page{ Title: "Codewalk: " + cw.Title, - Tabtitle: cw.Title, - Body: applyTemplate(codewalkHTML, "codewalk", cw), + TabTitle: cw.Title, + Template: "codewalk.html", + Data: cw, }) } @@ -99,14 +97,6 @@ func redir(w http.ResponseWriter, r *http.Request) (redirected bool) { return } -func applyTemplate(t *template.Template, name string, data interface{}) []byte { - var buf bytes.Buffer - if err := t.Execute(&buf, data); err != nil { - log.Printf("%s.Execute: %s", name, err) - } - return buf.Bytes() -} - // A Codewalk represents a single codewalk read from an XML file. type Codewalk struct { Title string `xml:"title,attr"` @@ -131,6 +121,10 @@ type Codestep struct { Data []byte } +func (c *Codestep) HTML() template.HTML { + return template.HTML(c.XML) +} + // String method for printing in template. // Formats file address nicely. func (st *Codestep) String() string { @@ -217,7 +211,7 @@ func codewalkDir(w http.ResponseWriter, r *http.Request, relpath, abspath string dir, err := fs.ReadDir(fsys, toFS(abspath)) if err != nil { log.Print(err) - pres.ServeError(w, r, relpath, err) + pres.ServeError(w, r, err) return } var v []interface{} @@ -234,9 +228,10 @@ func codewalkDir(w http.ResponseWriter, r *http.Request, relpath, abspath string } } - pres.ServePage(w, godoc.Page{ - Title: "Codewalks", - Body: applyTemplate(codewalkdirHTML, "codewalkdir", v), + pres.ServePage(w, r, godoc.Page{ + Title: "Codewalks", + Template: "codewalkdir.html", + Data: v, }) } @@ -251,7 +246,7 @@ func codewalkFileprint(w http.ResponseWriter, r *http.Request, f string) { data, err := fs.ReadFile(fsys, toFS(abspath)) if err != nil { log.Print(err) - pres.ServeError(w, r, f, err) + pres.ServeError(w, r, err) return } lo, _ := strconv.Atoi(r.FormValue("lo")) diff --git a/cmd/golangorg/handlers.go b/cmd/golangorg/handlers.go index 28a3b806..1ee843da 100644 --- a/cmd/golangorg/handlers.go +++ b/cmd/golangorg/handlers.go @@ -11,7 +11,6 @@ import ( "encoding/json" "go/format" "io/fs" - "log" "net/http" pathpkg "path" "strings" @@ -93,16 +92,6 @@ func registerHandlers(pres *godoc.Presentation) *http.ServeMux { return mux } -func readTemplates(p *godoc.Presentation) { - var err error - if codewalkHTML, err = p.ReadTemplate("codewalk.html"); err != nil { - log.Fatal(err) - } - if codewalkdirHTML, err = p.ReadTemplate("codewalkdir.html"); err != nil { - log.Fatal(err) - } -} - type fmtResponse struct { Body string Error string diff --git a/cmd/golangorg/local.go b/cmd/golangorg/local.go index 37353b38..ba00aaa3 100644 --- a/cmd/golangorg/local.go +++ b/cmd/golangorg/local.go @@ -27,7 +27,7 @@ func earlySetup() { os.Exit(2) } dir := filepath.Join(file, "../../../_content") - if _, err := os.Stat(filepath.Join(dir, "lib/godoc/godoc.html")); err != nil { + if _, err := os.Stat(filepath.Join(dir, "lib/godoc/site.html")); err != nil { log.Printf("warning: cannot find template dir; using embedded copy") return } diff --git a/cmd/golangorg/main.go b/cmd/golangorg/main.go index 283f4e0a..9c20031a 100644 --- a/cmd/golangorg/main.go +++ b/cmd/golangorg/main.go @@ -85,7 +85,6 @@ func main() { } pres.GoogleCN = googleCN - readTemplates(pres) mux := registerHandlers(pres) lateSetup(mux) diff --git a/cmd/golangorg/release_test.go b/cmd/golangorg/release_test.go index 4b198849..5c6a5746 100644 --- a/cmd/golangorg/release_test.go +++ b/cmd/golangorg/release_test.go @@ -32,7 +32,6 @@ func TestReleaseHistory(t *testing.T) { if err != nil { t.Fatal(err) } - readTemplates(pres) mux := registerHandlers(pres) req := httptest.NewRequest(http.MethodGet, "/doc/devel/release", nil) diff --git a/internal/api/api.go b/internal/api/api.go index a9669f20..7fcb23da 100644 --- a/internal/api/api.go +++ b/internal/api/api.go @@ -51,7 +51,7 @@ type PkgDB struct { // // The name is the symbol name ("Server") and the pkg is the package // ("net/http"). -func (v DB) Func(kind, receiver, name, pkg string) string { +func (v DB) Func(pkg, kind, receiver, name string) string { pv := v[pkg] switch kind { case "func": diff --git a/internal/api/api_test.go b/internal/api/api_test.go index 64c88006..ced9aa46 100644 --- a/internal/api/api_test.go +++ b/internal/api/api_test.go @@ -137,8 +137,8 @@ func TestAPIVersion(t *testing.T) { if tc.want != "" && !hasTag("go"+tc.want) { continue } - if got := av.Func(tc.kind, tc.receiver, tc.name, tc.pkg); got != tc.want { - t.Errorf(`sinceFunc("%s", "%s", "%s", "%s") = "%s"; want "%s"`, tc.kind, tc.receiver, tc.name, tc.pkg, got, tc.want) + if got := av.Func(tc.pkg, tc.kind, tc.receiver, tc.name); got != tc.want { + t.Errorf(`sinceFunc(%q, %q, %q, %q) = %q; want %q`, tc.pkg, tc.kind, tc.receiver, tc.name, got, tc.want) } } } diff --git a/internal/godoc/astfuncs.go b/internal/godoc/astfuncs.go index f6cafd44..e21b52a3 100644 --- a/internal/godoc/astfuncs.go +++ b/internal/godoc/astfuncs.go @@ -14,6 +14,7 @@ import ( "go/doc" "go/printer" "go/token" + "html/template" "io" "log" "unicode" @@ -25,26 +26,36 @@ import ( var slashSlash = []byte("//") -func (p *Presentation) nodeFunc(info *pkgdoc.Page, node interface{}) string { - var buf bytes.Buffer - p.writeNode(&buf, info, info.FSet, node) - return buf.String() +// Node formats the given AST node as HTML. +// Identifiers in the rendered node +// are turned into links to their documentation. +func (p *Page) Node(node interface{}) template.HTML { + info := p.Data.(*pkgdoc.Page) + var buf1 bytes.Buffer + p.pres.writeNode(&buf1, info, info.FSet, node) + + var buf2 bytes.Buffer + n, _ := node.(ast.Node) + buf2.Write(texthtml.Format(buf1.Bytes(), texthtml.Config{ + AST: n, + GoComments: true, + })) + return template.HTML(buf2.String()) } -func (p *Presentation) node_htmlFunc(info *pkgdoc.Page, node interface{}, linkify bool) string { +// NodeTOC formats the given AST node as HTML +// for inclusion in the table of contents. +func (p *Page) NodeTOC(node interface{}) template.HTML { + info := p.Data.(*pkgdoc.Page) var buf1 bytes.Buffer - p.writeNode(&buf1, info, info.FSet, node) + p.pres.writeNode(&buf1, info, info.FSet, node) var buf2 bytes.Buffer - var n ast.Node - if linkify { - n, _ = node.(ast.Node) - } buf2.Write(texthtml.Format(buf1.Bytes(), texthtml.Config{ - AST: n, GoComments: true, })) - return buf2.String() + + return sanitize(template.HTML(buf2.String())) } const TabWidth = 4 @@ -140,18 +151,19 @@ func firstIdent(x []byte) string { return string(x[:i]) } -func comment_htmlFunc(comment string) string { +// Comment formats the given documentation comment as HTML. +func (p *Page) Comment(comment string) template.HTML { var buf bytes.Buffer // TODO(gri) Provide list of words (e.g. function parameters) // to be emphasized by ToHTML. doc.ToHTML(&buf, comment, nil) // does html-escaping - return buf.String() + return template.HTML(buf.String()) } -// sanitizeFunc sanitizes the argument src by replacing newlines with +// sanitize sanitizes the argument src by replacing newlines with // blanks, removing extra blanks, and by removing trailing whitespace // and commas before closing parentheses. -func sanitizeFunc(src string) string { +func sanitize(src template.HTML) template.HTML { buf := make([]byte, len(src)) j := 0 // buf index comma := -1 // comma index if >= 0 @@ -189,5 +201,13 @@ func sanitizeFunc(src string) string { if j > 0 && buf[j-1] == ' ' { j-- } - return string(buf[:j]) + return template.HTML(buf[:j]) +} + +// Since reports the Go version that introduced the API feature +// identified by kind, reeciver, name. +// The current package is deduced from p.Data, which must be a *pkgdoc.Page. +func (p *Page) Since(kind, receiver, name string) string { + pkg := p.Data.(*pkgdoc.Page).PDoc.ImportPath + return p.pres.api.Func(pkg, kind, receiver, name) } diff --git a/internal/godoc/examplefuncs.go b/internal/godoc/examplefuncs.go index 3d10fc1c..67099ec3 100644 --- a/internal/godoc/examplefuncs.go +++ b/internal/godoc/examplefuncs.go @@ -12,6 +12,7 @@ import ( "go/ast" "go/format" "go/printer" + "html/template" "log" "regexp" "strings" @@ -20,7 +21,10 @@ import ( "golang.org/x/website/internal/pkgdoc" ) -func (p *Presentation) example_htmlFunc(info *pkgdoc.Page, funcName string) string { +// Example renders the examples for the given function name as HTML. +// The current package is deduced from p.Data, which must be a *pkgdoc.Page. +func (p *Page) Example(funcName string) template.HTML { + info := p.Data.(*pkgdoc.Page) var buf bytes.Buffer for _, eg := range info.Examples { name := pkgdoc.TrimExampleSuffix(eg.Name) @@ -31,7 +35,7 @@ func (p *Presentation) example_htmlFunc(info *pkgdoc.Page, funcName string) stri // print code cnode := &printer.CommentedNode{Node: eg.Code, Comments: eg.Comments} - code := p.node_htmlFunc(info, cnode, true) + code := string(p.Node(cnode)) out := eg.Output wholeFile := true @@ -66,20 +70,23 @@ func (p *Presentation) example_htmlFunc(info *pkgdoc.Page, funcName string) stri out = "" } - if p.ExampleHTML == nil { - out = "" + t := p.pres.Templates.Lookup("example.html") + if t == nil { return "" } - err := p.ExampleHTML.Execute(&buf, struct { + newPage := *p + newPage.Data = struct { Name, Doc, Code, Play, Output string - GoogleCN bool - }{eg.Name, eg.Doc, code, play, out, info.GoogleCN}) + }{ + eg.Name, eg.Doc, code, play, out, + } + err := t.Execute(&buf, &newPage) if err != nil { log.Print(err) } } - return buf.String() + return template.HTML(buf.String()) } // replaceLeadingIndentation replaces oldIndent at the beginning of each line @@ -190,7 +197,7 @@ func filterOutBuildAnnotations(cg []*ast.CommentGroup) []*ast.CommentGroup { // example_nameFunc takes an example function name and returns its display // name. For example, "Foo_Bar_quux" becomes "Foo.Bar (Quux)". -func (p *Presentation) example_nameFunc(s string) string { +func example_nameFunc(s string) string { name, suffix := pkgdoc.SplitExampleName(s) // replace _ with . for method names name = strings.Replace(name, "_", ".", 1) @@ -203,7 +210,7 @@ func (p *Presentation) example_nameFunc(s string) string { // example_suffixFunc takes an example function name and returns its suffix in // parenthesized form. For example, "Foo_Bar_quux" becomes " (Quux)". -func (p *Presentation) example_suffixFunc(name string) string { +func example_suffixFunc(name string) string { _, suffix := pkgdoc.SplitExampleName(name) return suffix } diff --git a/internal/godoc/godoc.go b/internal/godoc/godoc.go index 33bd6313..7e6f9897 100644 --- a/internal/godoc/godoc.go +++ b/internal/godoc/godoc.go @@ -13,67 +13,35 @@ import ( "go/ast" "go/doc" "go/token" + "html" + "html/template" "path" - "strconv" "strings" - "text/template" "golang.org/x/website/internal/history" "golang.org/x/website/internal/pkgdoc" ) func (p *Presentation) initFuncMap() { - // Template function maps. - // Convention: template function names ending in "_html" or "_url" produce - // HTML- or URL-escaped strings; all other function results may - // require explicit escaping in the template. - - p.DocFuncs = template.FuncMap{ + p.docFuncs = template.FuncMap{ "code": p.code, "releases": func() []*history.Major { return history.Majors }, } - - p.SiteFuncs = template.FuncMap{ - // various helpers - "filename": filenameFunc, - "since": p.api.Func, - - // formatting of AST nodes - "node": p.nodeFunc, - "node_html": p.node_htmlFunc, - "comment_html": comment_htmlFunc, - "sanitize": sanitizeFunc, - - // support for URL attributes - "pkgLink": pkgLinkFunc, - "srcLink": srcLinkFunc, - "posLink_url": posLink_urlFunc, - "docLink": docLinkFunc, - "queryLink": queryLinkFunc, - "srcBreadcrumb": srcBreadcrumbFunc, - "srcToPkgLink": srcToPkgLinkFunc, - - // formatting of Examples - "example_html": p.example_htmlFunc, - "example_name": p.example_nameFunc, - "example_suffix": p.example_suffixFunc, - - // Number operation - "multiply": multiply, - - // formatting of PageInfoMode query string - "modeQueryString": modeQueryString, - } } -func multiply(a, b int) int { return a * b } +var siteFuncs = template.FuncMap{ + // various helpers + "basename": path.Base, + + // formatting of Examples + "example_name": example_nameFunc, + "example_suffix": example_suffixFunc, -func filenameFunc(name string) string { - _, localname := path.Split(name) - return localname + // Number operation + "multiply": func(a, b int) int { return a * b }, } -func pkgLinkFunc(path string) string { +func srcToPkg(path string) string { // because of the irregular mapping under goroot // we need to correct certain relative paths path = strings.TrimPrefix(path, "/") @@ -82,26 +50,26 @@ func pkgLinkFunc(path string) string { return "pkg/" + path } -// srcToPkgLinkFunc builds an <a> tag linking to the package -// documentation of relpath. -func srcToPkgLinkFunc(relpath string) string { - relpath = pkgLinkFunc(relpath) - relpath = path.Dir(relpath) - if relpath == "pkg" { +// SrcPkgLink builds an <a> tag linking to the package documentation +// for p.SrcPath. +func (p *Page) SrcPkgLink() template.HTML { + dir := path.Dir(srcToPkg(p.SrcPath)) + if dir == "pkg" { return `<a href="/pkg">Index</a>` } - return fmt.Sprintf(`<a href="/%s">%s</a>`, relpath, relpath[len("pkg/"):]) + dir = html.EscapeString(dir) + return template.HTML(fmt.Sprintf(`<a href="/%s">%s</a>`, dir, dir[len("pkg/"):])) } -// srcBreadcrumbFun converts each segment of relpath to a HTML <a>. +// SrcBreadcrumb converts each segment of p.SrcPath to a HTML <a>. // Each segment links to its corresponding src directories. -func srcBreadcrumbFunc(relpath string) string { - segments := strings.Split(relpath, "/") +func (p *Page) SrcBreadcrumb() template.HTML { + segments := strings.Split(p.SrcPath, "/") var buf bytes.Buffer var selectedSegment string var selectedIndex int - if strings.HasSuffix(relpath, "/") { + if strings.HasSuffix(p.SrcPath, "/") { // relpath is a directory ending with a "/". // Selected segment is the segment before the last slash. selectedIndex = len(segments) - 2 @@ -113,18 +81,22 @@ func srcBreadcrumbFunc(relpath string) string { for i := range segments[:selectedIndex] { buf.WriteString(fmt.Sprintf(`<a href="/%s">%s</a>/`, - strings.Join(segments[:i+1], "/"), - segments[i], + html.EscapeString(strings.Join(segments[:i+1], "/")), + html.EscapeString(segments[i]), )) } buf.WriteString(`<span class="text-muted">`) - buf.WriteString(selectedSegment) + buf.WriteString(html.EscapeString(selectedSegment)) buf.WriteString(`</span>`) - return buf.String() + return template.HTML(buf.String()) } -func posLink_urlFunc(info *pkgdoc.Page, n interface{}) string { +// SrcPosLink returns a link to the specific source code position containing n, +// which must be either an ast.Node or a *doc.Note. +// The current package is deduced from p.Data, which must be a *pkgdoc.Page. +func (p *Page) SrcPosLink(n interface{}) template.HTML { + info := p.Data.(*pkgdoc.Page) // n must be an ast.Node or a *doc.Note var pos, end token.Pos @@ -136,7 +108,7 @@ func posLink_urlFunc(info *pkgdoc.Page, n interface{}) string { pos = n.Pos end = n.End default: - panic(fmt.Sprintf("wrong type for posLink_url template formatter: %T", n)) + panic(fmt.Sprintf("wrong type for SrcPosLink template formatter: %T", n)) } var relpath string @@ -156,8 +128,11 @@ func posLink_urlFunc(info *pkgdoc.Page, n interface{}) string { return srcPosLinkFunc(relpath, line, low, high) } -func srcPosLinkFunc(s string, line, low, high int) string { - s = srcLinkFunc(s) +func srcPosLinkFunc(s string, line, low, high int) template.HTML { + s = path.Clean("/" + s) + if !strings.HasPrefix(s, "/src/") { + s = "/src" + s + } var buf bytes.Buffer template.HTMLEscape(&buf, []byte(s)) // selection ranges are of form "s=low:high" @@ -175,30 +150,5 @@ func srcPosLinkFunc(s string, line, low, high int) string { if line > 0 { fmt.Fprintf(&buf, "#L%d", line) // no need for URL escaping } - return buf.String() -} - -func srcLinkFunc(s string) string { - s = path.Clean("/" + s) - if !strings.HasPrefix(s, "/src/") { - s = "/src" + s - } - return s -} - -// queryLinkFunc returns a URL for a line in a source file with a highlighted -// query term. -// s is expected to be a path to a source file. -// query is expected to be a string that has already been appropriately escaped -// for use in a URL query. -func queryLinkFunc(s, query string, line int) string { - url := path.Clean("/"+s) + "?h=" + query - if line > 0 { - url += "#L" + strconv.Itoa(line) - } - return url -} - -func docLinkFunc(s string, ident string) string { - return path.Clean("/pkg/"+s) + "/#" + ident + return template.HTML(buf.String()) } diff --git a/internal/godoc/godoc_test.go b/internal/godoc/godoc_test.go index 74e472cd..eba1bd03 100644 --- a/internal/godoc/godoc_test.go +++ b/internal/godoc/godoc_test.go @@ -12,13 +12,14 @@ import ( "fmt" "go/parser" "go/token" + "html/template" "strings" "testing" "golang.org/x/website/internal/pkgdoc" ) -func TestPkgLinkFunc(t *testing.T) { +func TestSrcToPkg(t *testing.T) { for _, tc := range []struct { path string want string @@ -27,9 +28,11 @@ func TestPkgLinkFunc(t *testing.T) { {"src/fmt", "pkg/fmt"}, {"/fmt", "pkg/fmt"}, {"fmt", "pkg/fmt"}, + {"src/pkg/fmt", "pkg/fmt"}, + {"/src/pkg/fmt", "pkg/fmt"}, } { - if got := pkgLinkFunc(tc.path); got != tc.want { - t.Errorf("pkgLinkFunc(%v) = %v; want %v", tc.path, got, tc.want) + if got := srcToPkg(tc.path); got != tc.want { + t.Errorf("srcToPkg(%v) = %v; want %v", tc.path, got, tc.want) } } } @@ -40,7 +43,7 @@ func TestSrcPosLinkFunc(t *testing.T) { line int low int high int - want string + want template.HTML }{ {"/src/fmt/print.go", 42, 30, 50, "/src/fmt/print.go?s=30:50#L32"}, {"/src/fmt/print.go", 2, 1, 5, "/src/fmt/print.go?s=1:5#L1"}, @@ -51,64 +54,15 @@ func TestSrcPosLinkFunc(t *testing.T) { {"fmt/print.go", 0, 1, 5, "/src/fmt/print.go?s=1:5#L1"}, } { if got := srcPosLinkFunc(tc.src, tc.line, tc.low, tc.high); got != tc.want { - t.Errorf("srcLinkFunc(%v, %v, %v, %v) = %v; want %v", tc.src, tc.line, tc.low, tc.high, got, tc.want) + t.Errorf("srcPosLink(%v, %v, %v, %v) = %v; want %v", tc.src, tc.line, tc.low, tc.high, got, tc.want) } } } -func TestSrcLinkFunc(t *testing.T) { +func TestSanitize(t *testing.T) { for _, tc := range []struct { - src string - want string - }{ - {"/src/fmt/print.go", "/src/fmt/print.go"}, - {"src/fmt/print.go", "/src/fmt/print.go"}, - {"/fmt/print.go", "/src/fmt/print.go"}, - {"fmt/print.go", "/src/fmt/print.go"}, - } { - if got := srcLinkFunc(tc.src); got != tc.want { - t.Errorf("srcLinkFunc(%v) = %v; want %v", tc.src, got, tc.want) - } - } -} - -func TestQueryLinkFunc(t *testing.T) { - for _, tc := range []struct { - src string - query string - line int - want string - }{ - {"/src/fmt/print.go", "Sprintf", 33, "/src/fmt/print.go?h=Sprintf#L33"}, - {"/src/fmt/print.go", "Sprintf", 0, "/src/fmt/print.go?h=Sprintf"}, - {"src/fmt/print.go", "EOF", 33, "/src/fmt/print.go?h=EOF#L33"}, - {"src/fmt/print.go", "a%3f+%26b", 1, "/src/fmt/print.go?h=a%3f+%26b#L1"}, - } { - if got := queryLinkFunc(tc.src, tc.query, tc.line); got != tc.want { - t.Errorf("queryLinkFunc(%v, %v, %v) = %v; want %v", tc.src, tc.query, tc.line, got, tc.want) - } - } -} - -func TestDocLinkFunc(t *testing.T) { - for _, tc := range []struct { - src string - ident string - want string - }{ - {"fmt", "Sprintf", "/pkg/fmt/#Sprintf"}, - {"fmt", "EOF", "/pkg/fmt/#EOF"}, - } { - if got := docLinkFunc(tc.src, tc.ident); got != tc.want { - t.Errorf("docLinkFunc(%v, %v) = %v; want %v", tc.src, tc.ident, got, tc.want) - } - } -} - -func TestSanitizeFunc(t *testing.T) { - for _, tc := range []struct { - src string - want string + src template.HTML + want template.HTML }{ {}, {"foo", "foo"}, @@ -121,8 +75,8 @@ func TestSanitizeFunc(t *testing.T) { {"{ a, b}", "{a, b}"}, {"[ a, b]", "[a, b]"}, } { - if got := sanitizeFunc(tc.src); got != tc.want { - t.Errorf("sanitizeFunc(%v) = %v; want %v", tc.src, got, tc.want) + if got := sanitize(tc.src); got != tc.want { + t.Errorf("sanitize(%v) = %v; want %v", tc.src, got, tc.want) } } } @@ -241,11 +195,15 @@ func linkifySource(t *testing.T, src []byte) string { pi := &pkgdoc.Page{ FSet: fset, } + pg := &Page{ + pres: p, + Data: pi, + } sep := "" for _, decl := range af.Decls { buf.WriteString(sep) sep = "\n" - buf.WriteString(p.node_htmlFunc(pi, decl, true)) + buf.WriteString(string(pg.Node(decl))) } return buf.String() } @@ -279,29 +237,29 @@ func TestReplaceLeadingIndentation(t *testing.T) { func TestSrcBreadcrumbFunc(t *testing.T) { for _, tc := range []struct { path string - want string + want template.HTML }{ {"src/", `<span class="text-muted">src/</span>`}, {"src/fmt/", `<a href="/src">src</a>/<span class="text-muted">fmt/</span>`}, {"src/fmt/print.go", `<a href="/src">src</a>/<a href="/src/fmt">fmt</a>/<span class="text-muted">print.go</span>`}, } { - if got := srcBreadcrumbFunc(tc.path); got != tc.want { + if got := (&Page{SrcPath: tc.path}).SrcBreadcrumb(); got != tc.want { t.Errorf("srcBreadcrumbFunc(%v) = %v; want %v", tc.path, got, tc.want) } } } -func TestSrcToPkgLinkFunc(t *testing.T) { +func TestSrcPkgLink(t *testing.T) { for _, tc := range []struct { path string - want string + want template.HTML }{ {"src/", `<a href="/pkg">Index</a>`}, {"src/fmt/", `<a href="/pkg/fmt">fmt</a>`}, {"pkg/", `<a href="/pkg">Index</a>`}, {"pkg/LICENSE", `<a href="/pkg">Index</a>`}, } { - if got := srcToPkgLinkFunc(tc.path); got != tc.want { + if got := (&Page{SrcPath: tc.path}).SrcPkgLink(); got != tc.want { t.Errorf("srcToPkgLinkFunc(%v) = %v; want %v", tc.path, got, tc.want) } } diff --git a/internal/godoc/meta.go b/internal/godoc/meta.go index 0c5809e6..27f4def1 100644 --- a/internal/godoc/meta.go +++ b/internal/godoc/meta.go @@ -17,7 +17,6 @@ import ( ) var ( - doctype = []byte("<!DOCTYPE ") jsonStart = []byte("<!--{") jsonEnd = []byte("}-->") ) diff --git a/internal/godoc/page.go b/internal/godoc/page.go index 6c06ba58..9426dd76 100644 --- a/internal/godoc/page.go +++ b/internal/godoc/page.go @@ -9,50 +9,64 @@ package godoc import ( "net/http" - "os" - "path/filepath" "runtime" ) -// Page describes the contents of the top-level godoc webpage. +// A Page describes the contents of a webpage to be served. +// +// A Page's Methods are for use by the templates rendering the page. type Page struct { - Title string - Tabtitle string - Subtitle string - SrcPath string - Query string - Body []byte - GoogleCN bool // page is being served from golang.google.cn + Title string // <h1> + TabTitle string // prefix in <title>; defaults to Title + Subtitle string // subtitle (date for spec, memory model) + SrcPath string // path to file in /src for text view - // filled in by ServePage - Version string - GoogleAnalytics string + // Template and Data describe the data to be + // rendered into the overall site frame template. + // If Template is empty, then Data should be a template.HTML + // holding raw HTML to render into the site frame. + // Otherwise, Template should be the name of a template file + // in _content/lib/godoc (for example, "package.html"), + // and that template will be executed + // (with the *Page as its data argument) to produce HTML. + // + // The overall site template site.html is also invoked with + // the *Page as its data argument. It is what arranges to call Template. + Template string // template to apply to data (empty string when Data is raw template.HTML) + Data interface{} // data to be rendered into page frame + + // Filled in automatically by ServePage + GoogleCN bool // page is being served from golang.google.cn + GoogleAnalytics string // Google Analytics tag + Version string // current Go version + + pres *Presentation } -func (p *Presentation) ServePage(w http.ResponseWriter, page Page) { - if page.Tabtitle == "" { - page.Tabtitle = page.Title +// fullPage returns a copy of page with the “automatic” fields filled in. +func (p *Presentation) fullPage(r *http.Request, page Page) Page { + if page.TabTitle == "" { + page.TabTitle = page.Title } page.Version = runtime.Version() + page.GoogleCN = p.googleCN(r) page.GoogleAnalytics = p.GoogleAnalytics - applyTemplateToResponseWriter(w, p.GodocHTML, page) + page.pres = p + return page } -func (p *Presentation) ServeError(w http.ResponseWriter, r *http.Request, relpath string, err error) { +// ServePage responds to the request with the content described by page. +func (p *Presentation) ServePage(w http.ResponseWriter, r *http.Request, page Page) { + page = p.fullPage(r, page) + applyTemplateToResponseWriter(w, p.Templates.Lookup("site.html"), &page) +} + +// ServeError responds to the request with the given error. +func (p *Presentation) ServeError(w http.ResponseWriter, r *http.Request, err error) { w.WriteHeader(http.StatusNotFound) - if perr, ok := err.(*os.PathError); ok { - rel, err := filepath.Rel(runtime.GOROOT(), perr.Path) - if err != nil { - perr.Path = "REDACTED" - } else { - perr.Path = filepath.Join("$GOROOT", rel) - } - } - p.ServePage(w, Page{ - Title: "File " + relpath, - Subtitle: relpath, - Body: applyTemplate(p.ErrorHTML, "errorHTML", err), - GoogleCN: p.googleCN(r), - GoogleAnalytics: p.GoogleAnalytics, + p.ServePage(w, r, Page{ + Title: r.URL.Path, + Template: "error.html", + Data: err, }) } diff --git a/internal/godoc/pres.go b/internal/godoc/pres.go index b60e9b6c..51928c4a 100644 --- a/internal/godoc/pres.go +++ b/internal/godoc/pres.go @@ -8,15 +8,15 @@ package godoc import ( + "html/template" "io/fs" "net/http" - "text/template" "golang.org/x/website/internal/api" "golang.org/x/website/internal/pkgdoc" ) -// Presentation generates output from a file system. +// Presentation is a website served from a file system. type Presentation struct { fs fs.FS api api.DB @@ -24,12 +24,7 @@ type Presentation struct { mux *http.ServeMux fileServer http.Handler - DirlistHTML, - ErrorHTML, - ExampleHTML, - GodocHTML, - PackageHTML, - PackageRootHTML *template.Template + Templates *template.Template // GoogleCN reports whether this request should be marked GoogleCN. // If the function is nil, no requests are marked GoogleCN. @@ -39,8 +34,7 @@ type Presentation struct { // tracking ID to each page. GoogleAnalytics string - DocFuncs template.FuncMap - SiteFuncs template.FuncMap + docFuncs template.FuncMap } // NewPresentation returns a new Presentation from a file system. @@ -61,31 +55,19 @@ func NewPresentation(fsys fs.FS) (*Presentation, error) { } p.mux.Handle("/cmd/", docs) p.mux.Handle("/pkg/", docs) - p.mux.HandleFunc("/", p.ServeFile) + p.mux.HandleFunc("/", p.serveFile) p.initFuncMap() - if p.DirlistHTML, err = p.ReadTemplate("dirlist.html"); err != nil { - return nil, err - } - if p.ErrorHTML, err = p.ReadTemplate("error.html"); err != nil { - return nil, err - } - if p.ExampleHTML, err = p.ReadTemplate("example.html"); err != nil { - return nil, err - } - if p.GodocHTML, err = p.ReadTemplate("godoc.html"); err != nil { - return nil, err - } - if p.PackageHTML, err = p.ReadTemplate("package.html"); err != nil { - return nil, err - } - if p.PackageRootHTML, err = p.ReadTemplate("packageroot.html"); err != nil { + t, err := template.New("").Funcs(siteFuncs).ParseFS(fsys, "lib/godoc/*.html") + if err != nil { return nil, err } + p.Templates = t return p, nil } +// ServeHTTP implements http.Handler, dispatching the request appropriately. func (p *Presentation) ServeHTTP(w http.ResponseWriter, r *http.Request) { p.mux.ServeHTTP(w, r) } @@ -93,15 +75,3 @@ func (p *Presentation) ServeHTTP(w http.ResponseWriter, r *http.Request) { func (p *Presentation) googleCN(r *http.Request) bool { return p.GoogleCN != nil && p.GoogleCN(r) } - -func (p *Presentation) ReadTemplate(name string) (*template.Template, error) { - data, err := fs.ReadFile(p.fs, "lib/godoc/"+name) - if err != nil { - return nil, err - } - t, err := template.New(name).Funcs(p.SiteFuncs).Parse(string(data)) - if err != nil { - return nil, err - } - return t, nil -} diff --git a/internal/godoc/server.go b/internal/godoc/server.go index c10b189a..90b119f8 100644 --- a/internal/godoc/server.go +++ b/internal/godoc/server.go @@ -12,6 +12,7 @@ import ( "encoding/json" "fmt" htmlpkg "html" + "html/template" "io" "io/fs" "log" @@ -20,7 +21,6 @@ import ( "regexp" "strconv" "strings" - "text/template" "golang.org/x/website/internal/pkgdoc" "golang.org/x/website/internal/spec" @@ -47,7 +47,8 @@ func (h *docServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { } // TODO(rsc): URL should be clean already. - relpath := path.Clean(strings.TrimPrefix(r.URL.Path, "/pkg/")) + relpath := path.Clean(strings.TrimPrefix(r.URL.Path, "/pkg")) + relpath = strings.TrimPrefix(relpath, "/") abspath := path.Join("/src", relpath) mode := pkgdoc.ParseMode(r.FormValue("m")) @@ -60,7 +61,7 @@ func (h *docServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { info := pkgdoc.Doc(h.d, abspath, relpath, mode, r.FormValue("GOOS"), r.FormValue("GOARCH")) if info.Err != nil { log.Print(info.Err) - h.p.ServeError(w, r, relpath, info.Err) + h.p.ServeError(w, r, info.Err) return } @@ -93,23 +94,23 @@ func (h *docServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { tabtitle = "Commands" } - info.GoogleCN = h.p.googleCN(r) - var body []byte + name := "package.html" if info.Dirname == "/src" { - body = applyTemplate(h.p.PackageRootHTML, "packageRootHTML", info) - } else { - body = applyTemplate(h.p.PackageHTML, "packageHTML", info) + name = "packageroot.html" } - h.p.ServePage(w, Page{ + h.p.ServePage(w, r, Page{ Title: title, - Tabtitle: tabtitle, + TabTitle: tabtitle, Subtitle: subtitle, - Body: body, - GoogleCN: info.GoogleCN, + Template: name, + Data: info, }) } -func modeQueryString(m pkgdoc.Mode) string { +// ModeQuery returns the "?m=..." query for the current page. +// The page's Data must be a *pkgdoc.Page (to find the mode). +func (p *Page) ModeQuery() string { + m := p.Data.(*pkgdoc.Page).Mode s := m.String() if s == "" { return "" @@ -117,12 +118,17 @@ func modeQueryString(m pkgdoc.Mode) string { return "?m=" + s } -func applyTemplate(t *template.Template, name string, data interface{}) []byte { +// Invoke invokes the template with the given name on +// a copy of p with .Data set to data, returning the resulting HTML. +func (p *Page) Invoke(name string, data interface{}) template.HTML { + t := p.pres.Templates.Lookup(name) var buf bytes.Buffer - if err := t.Execute(&buf, data); err != nil { - log.Printf("%s.Execute: %s", name, err) + p1 := *p + p1.Data = data + if err := t.Execute(&buf, &p1); err != nil { + log.Printf("%s.Execute: %s", t.Name(), err) } - return buf.Bytes() + return template.HTML(buf.String()) } type writerCapturesErr struct { @@ -202,12 +208,12 @@ func (p *Presentation) serveTextFile(w http.ResponseWriter, r *http.Request, abs src, err := fs.ReadFile(p.fs, toFS(abspath)) if err != nil { log.Printf("ReadFile: %s", err) - p.ServeError(w, r, relpath, err) + p.ServeError(w, r, err) return } if r.FormValue("m") == "text" { - p.ServeText(w, src) + p.serveText(w, src) return } @@ -229,12 +235,11 @@ func (p *Presentation) serveTextFile(w http.ResponseWriter, r *http.Request, abs if strings.HasSuffix(relpath, ".go") { title = "Source file" } - p.ServePage(w, Page{ + p.ServePage(w, r, Page{ Title: title, SrcPath: relpath, - Tabtitle: relpath, - Body: buf.Bytes(), - GoogleCN: p.googleCN(r), + TabTitle: relpath, + Data: template.HTML(buf.String()), }) } @@ -245,7 +250,7 @@ func (p *Presentation) serveDirectory(w http.ResponseWriter, r *http.Request, ab list, err := fs.ReadDir(p.fs, toFS(abspath)) if err != nil { - p.ServeError(w, r, relpath, err) + p.ServeError(w, r, err) return } @@ -257,15 +262,17 @@ func (p *Presentation) serveDirectory(w http.ResponseWriter, r *http.Request, ab } } - p.ServePage(w, Page{ + p.ServePage(w, r, Page{ Title: "Directory", SrcPath: relpath, - Tabtitle: relpath, - Body: applyTemplate(p.DirlistHTML, "dirlistHTML", info), - GoogleCN: p.googleCN(r), + TabTitle: relpath, + Template: "dirlist.html", + Data: info, }) } +var doctype = []byte("<!DOCTYPE ") + func (p *Presentation) serveHTML(w http.ResponseWriter, r *http.Request, f *file) { src := f.Body isMarkdown := strings.HasSuffix(f.FilePath, ".md") @@ -280,21 +287,21 @@ func (p *Presentation) serveHTML(w http.ResponseWriter, r *http.Request, f *file page := Page{ Title: f.Title, Subtitle: f.Subtitle, - GoogleCN: p.googleCN(r), } // evaluate as template if indicated if f.Template { - tmpl, err := template.New("main").Funcs(p.DocFuncs).Parse(string(src)) + page = p.fullPage(r, page) + tmpl, err := template.New("main").Funcs(p.docFuncs).Parse(string(src)) if err != nil { log.Printf("parsing template %s: %v", f.Path, err) - p.ServeError(w, r, f.Path, err) + p.ServeError(w, r, err) return } var buf bytes.Buffer if err := tmpl.Execute(&buf, page); err != nil { log.Printf("executing template %s: %v", f.Path, err) - p.ServeError(w, r, f.Path, err) + p.ServeError(w, r, err) return } src = buf.Bytes() @@ -306,7 +313,7 @@ func (p *Presentation) serveHTML(w http.ResponseWriter, r *http.Request, f *file html, err := renderMarkdown(src) if err != nil { log.Printf("executing markdown %s: %v", f.Path, err) - p.ServeError(w, r, f.Path, err) + p.ServeError(w, r, err) return } src = html @@ -319,12 +326,8 @@ func (p *Presentation) serveHTML(w http.ResponseWriter, r *http.Request, f *file src = buf.Bytes() } - page.Body = src - p.ServePage(w, page) -} - -func (p *Presentation) ServeFile(w http.ResponseWriter, r *http.Request) { - p.serveFile(w, r) + page.Data = template.HTML(src) + p.ServePage(w, r, page) } func (p *Presentation) serveFile(w http.ResponseWriter, r *http.Request) { @@ -361,7 +364,7 @@ func (p *Presentation) serveFile(w http.ResponseWriter, r *http.Request) { return } } - p.ServeError(w, r, relpath, err) + p.ServeError(w, r, err) return } @@ -385,7 +388,7 @@ func (p *Presentation) serveFile(w http.ResponseWriter, r *http.Request) { p.fileServer.ServeHTTP(w, r) } -func (p *Presentation) ServeText(w http.ResponseWriter, text []byte) { +func (p *Presentation) serveText(w http.ResponseWriter, text []byte) { w.Header().Set("Content-Type", "text/plain; charset=utf-8") w.Write(text) } diff --git a/internal/godoc/server_test.go b/internal/godoc/server_test.go index 152e9344..18290360 100644 --- a/internal/godoc/server_test.go +++ b/internal/godoc/server_test.go @@ -14,14 +14,13 @@ import ( "strings" "testing" "testing/fstest" - "text/template" ) func testServeBody(t *testing.T, p *Presentation, path, body string) { t.Helper() r := &http.Request{URL: &url.URL{Path: path}} rw := httptest.NewRecorder() - p.ServeFile(rw, r) + p.serveFile(rw, r) if rw.Code != 200 || !strings.Contains(rw.Body.String(), body) { t.Fatalf("GET %s: expected 200 w/ %q: got %d w/ body:\n%s", path, body, rw.Code, rw.Body) @@ -30,11 +29,12 @@ func testServeBody(t *testing.T, p *Presentation, path, body string) { func TestRedirectAndMetadata(t *testing.T) { fsys := fstest.MapFS{ - "doc/x/index.html": {Data: []byte("Hello, x.")}, + "doc/x/index.html": {Data: []byte("Hello, x.")}, + "lib/godoc/site.html": {Data: []byte(`{{.Data}}`)}, } - p := &Presentation{ - fs: fsys, - GodocHTML: template.Must(template.New("").Parse(`{{printf "%s" .Body}}`)), + p, err := NewPresentation(fsys) + if err != nil { + t.Fatal(err) } // Test that redirect is sent back correctly. @@ -43,7 +43,7 @@ func TestRedirectAndMetadata(t *testing.T) { r := &http.Request{URL: &url.URL{Path: dir + "index.html"}} rw := httptest.NewRecorder() - p.ServeFile(rw, r) + p.serveFile(rw, r) loc := rw.Result().Header.Get("Location") if rw.Code != 301 || loc != dir { t.Errorf("GET %s: expected 301 -> %q, got %d -> %q", r.URL.Path, dir, rw.Code, loc) @@ -53,12 +53,13 @@ func TestRedirectAndMetadata(t *testing.T) { } func TestMarkdown(t *testing.T) { - p := &Presentation{ - fs: fstest.MapFS{ - "doc/test.md": {Data: []byte("**bold**")}, - "doc/test2.md": {Data: []byte(`{{"*template*"}}`)}, - }, - GodocHTML: template.Must(template.New("").Parse(`{{printf "%s" .Body}}`)), + p, err := NewPresentation(fstest.MapFS{ + "doc/test.md": {Data: []byte("**bold**")}, + "doc/test2.md": {Data: []byte(`{{"*template*"}}`)}, + "lib/godoc/site.html": {Data: []byte(`{{.Data}}`)}, + }) + if err != nil { + t.Fatal(err) } testServeBody(t, p, "/doc/test", "<strong>bold</strong>") diff --git a/internal/godoc/template.go b/internal/godoc/template.go index 138b71ff..214a1591 100644 --- a/internal/godoc/template.go +++ b/internal/godoc/template.go @@ -37,6 +37,7 @@ package godoc import ( "bytes" "fmt" + "html/template" "io/fs" "log" "regexp" @@ -74,7 +75,7 @@ func stringFor(arg interface{}) string { return "" } -func (p *Presentation) code(file string, arg ...interface{}) (s string, err error) { +func (p *Presentation) code(file string, arg ...interface{}) (_ template.HTML, err error) { defer func() { if r := recover(); r != nil { err = fmt.Errorf("%v", r) @@ -105,7 +106,7 @@ func (p *Presentation) code(file string, arg ...interface{}) (s string, err erro buf.Write(texthtml.Format([]byte(text), texthtml.Config{GoComments: true})) // Include the command as a comment. text = fmt.Sprintf("<pre><!--{{%s}}\n-->%s</pre>", command, buf.Bytes()) - return text, nil + return template.HTML(text), nil } // parseArg returns the integer or string value of the argument and tells which it is. diff --git a/internal/pkgdoc/doc.go b/internal/pkgdoc/doc.go index a5b98aca..02e7b5b3 100644 --- a/internal/pkgdoc/doc.go +++ b/internal/pkgdoc/doc.go @@ -44,9 +44,8 @@ func NewDocs(fsys fs.FS) *Docs { } type Page struct { - Dirname string // directory containing the package - Err error // error or nil - GoogleCN bool // page is being served from golang.google.cn + Dirname string // directory containing the package + Err error // error or nil Mode Mode // display metadata from query string |
