internal/godoc: refactor into internal/web [generated]

What's left of internal/godoc is mainly a simple web server
for static content (plus the package docs provided by internal/pkgdoc)
and bears little resemblance to the original godoc.

Revamp the API and retire the name, moving to internal/web.

CL generated by the script below.

[git-generate]
cd internal/godoc
mv util.go istext.go
mv godoc_test.go template_test.go
mv server_test.go site_test.go

rf '
	mv TabWidth tabWidth
	mv IsText isText
	mv TabSpacer tabSpacer

	mv Presentation Site
	mv NewPresentation NewSite
	mv writerCapturesErr writeErrorSaver
	rm marshalJSON
	mv redirect maybeRedirect
	mv redirectFile maybeRedirectFile
	mv Site.serveText Site.serveRawText
	mv Site.serveTextFile Site.serveText
	mv Site.serveDirectory Site.serveDir
	mv Site.initFuncMap Site.initDocFuncs

	mv \
		toFS \
		Site \
		NewSite \
		Site.ServeError \
		Site.ServeHTTP \
		Site.ServePage \
		Page \
		Site.fullPage \
		Page.Invoke \
		writeErrorSaver \
		writeErrorSaver.Write \
		applyTemplateToResponseWriter \
		Site.serveFile \
		maybeRedirect \
		maybeRedirectFile \
		doctype \
		Site.serveHTML \
		Site.serveDir \
		Site.serveText \
		selRx \
		rangeSelection \
		Site.serveRawText \
		Site.googleCN \
		site.go

	mv example_nameFunc example_name
	mv example_suffixFunc example_suffix
	mv srcPosLinkFunc srcPosLink

	mv \
		siteFuncs \
		example_name \
		example_suffix \
		srcToPkg \
		Page.SrcPkgLink \
		Page.SrcBreadcrumb \
		Page.SrcPosLink \
		srcPosLink \
		sitefuncs.go

	mv \
		docServer \
		docServer.ServeHTTP \
		Page.ModeQuery \
		pkgdoc.go

	mv metaJSON fileJSON
	mv extractMetadata parseFile
	mv \
		file \
		fileJSON \
		join \
		open \
		jsonStart \
		parseFile \
		file.go

	mv \
		Site.initDocFuncs \
		Site.code \
		Site.contents \
		stringFor \
		Site.oneLine \
		Site.multipleLines \
		parseArg \
		match \
		docfuncs.go

	mv Site.ServeError.p Site.ServeError.s
	mv Site.ServeHTTP.p Site.ServeHTTP.s
	mv Site.ServePage.p Site.ServePage.s
	mv Site.code.p Site.code.s
	mv Site.contents.p Site.contents.s
	mv Site.fullPage.p Site.fullPage.s
	mv Site.googleCN.p Site.googleCN.s
	mv Site.initDocFuncs.p Site.initDocFuncs.s
	mv Site.multipleLines.p Site.multipleLines.s
	mv Site.oneLine.p Site.oneLine.s
	mv Site.serveDir.p Site.serveDir.s
	mv Site.serveFile.p Site.serveFile.s
	mv Site.serveHTML.p Site.serveHTML.s
	mv Site.serveRawText.p Site.serveRawText.s
	mv Site.serveText.p Site.serveText.s
	mv Site.writeNode.p Site.writeNode.s

	mv Page.pres Page.site

	mv astfuncs.go docfuncs.go examplefuncs.go \
		file.go istext.go markdown.go pkgdoc.go \
		site.go site_test.go sitefuncs.go \
		tab.go template_test.go \
		golang.org/x/website/internal/web
'
rm godoc.go meta.go page.go pres.go server.go template.go

cd ../../cmd/golangorg
rf '
	mv pres site
'

Change-Id: Ic03a2dbe14f74c60bd6a5a86ba4d3f36d8c5bea8
Reviewed-on: https://go-review.googlesource.com/c/website/+/317656
Trust: Russ Cox <rsc@golang.org>
Run-TryBot: Russ Cox <rsc@golang.org>
Reviewed-by: Dmitri Shuralyov <dmitshur@golang.org>
This commit is contained in:
Russ Cox 2021-06-01 10:33:20 -04:00
Родитель 4c9e549253
Коммит 117627b3c2
18 изменённых файлов: 564 добавлений и 618 удалений

Просмотреть файл

@ -32,7 +32,7 @@ import (
"strings"
"unicode/utf8"
"golang.org/x/website/internal/godoc"
"golang.org/x/website/internal/web"
)
// Handler for /doc/codewalk/ and below.
@ -55,7 +55,7 @@ func codewalk(w http.ResponseWriter, r *http.Request) {
// If file exists, serve using standard file server.
if err == nil {
pres.ServeHTTP(w, r)
site.ServeHTTP(w, r)
return
}
@ -66,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, err)
site.ServeError(w, r, err)
return
}
@ -75,7 +75,7 @@ func codewalk(w http.ResponseWriter, r *http.Request) {
return
}
pres.ServePage(w, r, godoc.Page{
site.ServePage(w, r, web.Page{
Title: "Codewalk: " + cw.Title,
TabTitle: cw.Title,
Template: "codewalk.html",
@ -211,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, err)
site.ServeError(w, r, err)
return
}
var v []interface{}
@ -228,7 +228,7 @@ func codewalkDir(w http.ResponseWriter, r *http.Request, relpath, abspath string
}
}
pres.ServePage(w, r, godoc.Page{
site.ServePage(w, r, web.Page{
Title: "Codewalks",
Template: "codewalkdir.html",
Data: v,
@ -246,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, err)
site.ServeError(w, r, err)
return
}
lo, _ := strconv.Atoi(r.FormValue("lo"))

Просмотреть файл

@ -16,12 +16,12 @@ import (
"strings"
"golang.org/x/website/internal/env"
"golang.org/x/website/internal/godoc"
"golang.org/x/website/internal/redirect"
"golang.org/x/website/internal/web"
)
var (
pres *godoc.Presentation
site *web.Site
fsys fs.FS
)
@ -76,7 +76,7 @@ func (h hostEnforcerHandler) validHost(host string) bool {
return false
}
func registerHandlers(pres *godoc.Presentation) *http.ServeMux {
func registerHandlers(pres *web.Site) *http.ServeMux {
if pres == nil {
panic("nil Presentation")
}

Просмотреть файл

@ -30,7 +30,7 @@ import (
"runtime"
"golang.org/x/website"
"golang.org/x/website/internal/godoc"
"golang.org/x/website/internal/web"
)
var (
@ -79,13 +79,13 @@ func main() {
fsys = unionFS{content, os.DirFS(*goroot)}
var err error
pres, err = godoc.NewPresentation(fsys)
site, err = web.NewSite(fsys)
if err != nil {
log.Fatal(err)
}
pres.GoogleCN = googleCN
site.GoogleCN = googleCN
mux := registerHandlers(pres)
mux := registerHandlers(site)
lateSetup(mux)
var handler http.Handler = http.DefaultServeMux

Просмотреть файл

@ -14,7 +14,7 @@ import (
"testing"
"golang.org/x/website"
"golang.org/x/website/internal/godoc"
"golang.org/x/website/internal/web"
)
// Test that the release history page includes expected entries.
@ -24,15 +24,15 @@ import (
// It can be relaxed whenever the presentation of the release history
// page needs to be changed.
func TestReleaseHistory(t *testing.T) {
origFS, origPres := fsys, pres
defer func() { fsys, pres = origFS, origPres }()
origFS, origPres := fsys, site
defer func() { fsys, site = origFS, origPres }()
fsys = website.Content
var err error
pres, err = godoc.NewPresentation(fsys)
site, err = web.NewSite(fsys)
if err != nil {
t.Fatal(err)
}
mux := registerHandlers(pres)
mux := registerHandlers(site)
req := httptest.NewRequest(http.MethodGet, "/doc/devel/release", nil)
rr := httptest.NewRecorder()

Просмотреть файл

@ -1,72 +0,0 @@
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build go1.16
// +build go1.16
package godoc
import (
"net/http"
"runtime"
)
// 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 // <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
// 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
}
// 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
page.pres = p
return page
}
// 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)
p.ServePage(w, r, Page{
Title: r.URL.Path,
Template: "error.html",
Data: err,
})
}

Просмотреть файл

@ -1,77 +0,0 @@
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build go1.16
// +build go1.16
package godoc
import (
"html/template"
"io/fs"
"net/http"
"golang.org/x/website/internal/api"
"golang.org/x/website/internal/pkgdoc"
)
// Presentation is a website served from a file system.
type Presentation struct {
fs fs.FS
api api.DB
mux *http.ServeMux
fileServer http.Handler
Templates *template.Template
// GoogleCN reports whether this request should be marked GoogleCN.
// If the function is nil, no requests are marked GoogleCN.
GoogleCN func(*http.Request) bool
// GoogleAnalytics optionally adds Google Analytics via the provided
// tracking ID to each page.
GoogleAnalytics string
docFuncs template.FuncMap
}
// NewPresentation returns a new Presentation from a file system.
func NewPresentation(fsys fs.FS) (*Presentation, error) {
apiDB, err := api.Load(fsys)
if err != nil {
return nil, err
}
p := &Presentation{
fs: fsys,
api: apiDB,
mux: http.NewServeMux(),
fileServer: http.FileServer(http.FS(fsys)),
}
docs := &docServer{
p: p,
d: pkgdoc.NewDocs(fsys),
}
p.mux.Handle("/cmd/", docs)
p.mux.Handle("/pkg/", docs)
p.mux.HandleFunc("/", p.serveFile)
p.initFuncMap()
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)
}
func (p *Presentation) googleCN(r *http.Request) bool {
return p.GoogleCN != nil && p.GoogleCN(r)
}

Просмотреть файл

@ -5,7 +5,7 @@
//go:build go1.16
// +build go1.16
package godoc
package web
import (
"bufio"
@ -32,7 +32,7 @@ var slashSlash = []byte("//")
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)
p.site.writeNode(&buf1, info, info.FSet, node)
var buf2 bytes.Buffer
n, _ := node.(ast.Node)
@ -48,7 +48,7 @@ func (p *Page) Node(node interface{}) template.HTML {
func (p *Page) NodeTOC(node interface{}) template.HTML {
info := p.Data.(*pkgdoc.Page)
var buf1 bytes.Buffer
p.pres.writeNode(&buf1, info, info.FSet, node)
p.site.writeNode(&buf1, info, info.FSet, node)
var buf2 bytes.Buffer
buf2.Write(texthtml.Format(buf1.Bytes(), texthtml.Config{
@ -58,14 +58,14 @@ func (p *Page) NodeTOC(node interface{}) template.HTML {
return sanitize(template.HTML(buf2.String()))
}
const TabWidth = 4
const tabWidth = 4
// writeNode writes the AST node x to w.
//
// The provided fset must be non-nil. The pageInfo is optional. If
// present, the pageInfo is used to add comments to struct fields to
// say which version of Go introduced them.
func (p *Presentation) writeNode(w io.Writer, pageInfo *pkgdoc.Page, fset *token.FileSet, x interface{}) {
func (s *Site) writeNode(w io.Writer, pageInfo *pkgdoc.Page, fset *token.FileSet, x interface{}) {
// convert trailing tabs into spaces using a tconv filter
// to ensure a good outcome in most browsers (there may still
// be tabs in comments and strings, but converting those into
@ -85,7 +85,7 @@ func (p *Presentation) writeNode(w io.Writer, pageInfo *pkgdoc.Page, fset *token
structName = ts.Name.Name
}
}
apiInfo = p.api[pkgName]
apiInfo = s.api[pkgName]
}
var out = w
@ -95,7 +95,7 @@ func (p *Presentation) writeNode(w io.Writer, pageInfo *pkgdoc.Page, fset *token
}
mode := printer.TabIndent | printer.UseSpaces
err := (&printer.Config{Mode: mode, Tabwidth: TabWidth}).Fprint(TabSpacer(out, TabWidth), fset, x)
err := (&printer.Config{Mode: mode, Tabwidth: tabWidth}).Fprint(tabSpacer(out, tabWidth), fset, x)
if err != nil {
log.Print(err)
}
@ -209,5 +209,5 @@ func sanitize(src template.HTML) template.HTML {
// 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)
return p.site.api.Func(pkg, kind, receiver, name)
}

Просмотреть файл

@ -1,38 +1,11 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build go1.16
// +build go1.16
// Template support for writing HTML documents.
// Documents that include Template: true in their
// metadata are executed as input to text/template.
//
// This file defines functions for those templates to invoke.
// The template uses the function "code" to inject program
// source into the output by extracting code from files and
// injecting them as HTML-escaped <pre> blocks.
//
// The syntax is simple: 1, 2, or 3 space-separated arguments:
//
// Whole file:
// {{code "foo.go"}}
// One line (here the signature of main):
// {{code "foo.go" `/^func.main/`}}
// Block of text, determined by start and end (here the body of main):
// {{code "foo.go" `/^func.main/` `/^}/`
//
// Patterns can be `/regular expression/`, a decimal number, or "$"
// to signify the end of the file. In multi-line matches,
// lines that end with the four characters
// OMIT
// are omitted from the output, making it easy to provide marker
// lines in the input that will not appear in the output but are easy
// to identify by pattern.
package godoc
package web
import (
"bytes"
@ -43,16 +16,58 @@ import (
"regexp"
"strings"
"golang.org/x/website/internal/history"
"golang.org/x/website/internal/texthtml"
)
func (s *Site) initDocFuncs() {
s.docFuncs = template.FuncMap{
"code": s.code,
"releases": func() []*history.Major { return history.Majors },
}
}
func (s *Site) code(file string, arg ...interface{}) (_ template.HTML, err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("%v", r)
}
}()
text := s.contents(file)
var command string
switch len(arg) {
case 0:
// text is already whole file.
command = fmt.Sprintf("code %q", file)
case 1:
command = fmt.Sprintf("code %q %s", file, stringFor(arg[0]))
text = s.oneLine(file, text, arg[0])
case 2:
command = fmt.Sprintf("code %q %s %s", file, stringFor(arg[0]), stringFor(arg[1]))
text = s.multipleLines(file, text, arg[0], arg[1])
default:
return "", fmt.Errorf("incorrect code invocation: code %q [%v, ...] (%d arguments)", file, arg[0], len(arg))
}
// Trim spaces from output.
text = strings.Trim(text, "\n")
// Replace tabs by spaces, which work better in HTML.
text = strings.Replace(text, "\t", " ", -1)
var buf bytes.Buffer
// HTML-escape text and syntax-color comments like elsewhere.
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 template.HTML(text), nil
}
// Functions in this file panic on error, but the panic is recovered
// to an error by 'code'.
// contents reads and returns the content of the named file
// (from the virtual file system, so for example /doc refers to $GOROOT/doc).
func (p *Presentation) contents(name string) string {
file, err := fs.ReadFile(p.fs, toFS(name))
func (s *Site) contents(name string) string {
file, err := fs.ReadFile(s.fs, toFS(name))
if err != nil {
log.Panic(err)
}
@ -75,58 +90,9 @@ func stringFor(arg interface{}) string {
return ""
}
func (p *Presentation) code(file string, arg ...interface{}) (_ template.HTML, err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("%v", r)
}
}()
text := p.contents(file)
var command string
switch len(arg) {
case 0:
// text is already whole file.
command = fmt.Sprintf("code %q", file)
case 1:
command = fmt.Sprintf("code %q %s", file, stringFor(arg[0]))
text = p.oneLine(file, text, arg[0])
case 2:
command = fmt.Sprintf("code %q %s %s", file, stringFor(arg[0]), stringFor(arg[1]))
text = p.multipleLines(file, text, arg[0], arg[1])
default:
return "", fmt.Errorf("incorrect code invocation: code %q [%v, ...] (%d arguments)", file, arg[0], len(arg))
}
// Trim spaces from output.
text = strings.Trim(text, "\n")
// Replace tabs by spaces, which work better in HTML.
text = strings.Replace(text, "\t", " ", -1)
var buf bytes.Buffer
// HTML-escape text and syntax-color comments like elsewhere.
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 template.HTML(text), nil
}
// parseArg returns the integer or string value of the argument and tells which it is.
func parseArg(arg interface{}, file string, max int) (ival int, sval string, isInt bool) {
switch n := arg.(type) {
case int:
if n <= 0 || n > max {
log.Panicf("%q:%d is out of range", file, n)
}
return n, "", true
case string:
return 0, n, false
}
log.Panicf("unrecognized argument %v type %T", arg, arg)
return
}
// oneLine returns the single line generated by a two-argument code invocation.
func (p *Presentation) oneLine(file, text string, arg interface{}) string {
lines := strings.SplitAfter(p.contents(file), "\n")
func (s *Site) oneLine(file, text string, arg interface{}) string {
lines := strings.SplitAfter(s.contents(file), "\n")
line, pattern, isInt := parseArg(arg, file, len(lines))
if isInt {
return lines[line-1]
@ -135,8 +101,8 @@ func (p *Presentation) oneLine(file, text string, arg interface{}) string {
}
// multipleLines returns the text generated by a three-argument code invocation.
func (p *Presentation) multipleLines(file, text string, arg1, arg2 interface{}) string {
lines := strings.SplitAfter(p.contents(file), "\n")
func (s *Site) multipleLines(file, text string, arg1, arg2 interface{}) string {
lines := strings.SplitAfter(s.contents(file), "\n")
line1, pattern1, isInt1 := parseArg(arg1, file, len(lines))
line2, pattern2, isInt2 := parseArg(arg2, file, len(lines))
if !isInt1 {
@ -155,6 +121,21 @@ func (p *Presentation) multipleLines(file, text string, arg1, arg2 interface{})
return strings.Join(lines[line1-1:line2], "")
}
// parseArg returns the integer or string value of the argument and tells which it is.
func parseArg(arg interface{}, file string, max int) (ival int, sval string, isInt bool) {
switch n := arg.(type) {
case int:
if n <= 0 || n > max {
log.Panicf("%q:%d is out of range", file, n)
}
return n, "", true
case string:
return 0, n, false
}
log.Panicf("unrecognized argument %v type %T", arg, arg)
return
}
// match identifies the input line that matches the pattern in a code invocation.
// If start>0, match lines starting there rather than at the beginning.
// The return value is 1-indexed.

Просмотреть файл

@ -5,7 +5,7 @@
//go:build go1.16
// +build go1.16
package godoc
package web
import (
"bytes"
@ -45,7 +45,7 @@ func (p *Page) Example(funcName string) template.HTML {
// remove surrounding braces
code = code[1 : n-1]
// unindent
code = replaceLeadingIndentation(code, strings.Repeat(" ", TabWidth), "")
code = replaceLeadingIndentation(code, strings.Repeat(" ", tabWidth), "")
// remove output comment
if loc := exampleOutputRx.FindStringIndex(code); loc != nil {
code = strings.TrimSpace(code[:loc[0]])
@ -70,7 +70,7 @@ func (p *Page) Example(funcName string) template.HTML {
out = ""
}
t := p.pres.Templates.Lookup("example.html")
t := p.site.Templates.Lookup("example.html")
if t == nil {
return ""
}
@ -194,23 +194,3 @@ func filterOutBuildAnnotations(cg []*ast.CommentGroup) []*ast.CommentGroup {
// There weren't any non-build tags, return an empty slice.
return []*ast.CommentGroup{}
}
// example_nameFunc takes an example function name and returns its display
// name. For example, "Foo_Bar_quux" becomes "Foo.Bar (Quux)".
func example_nameFunc(s string) string {
name, suffix := pkgdoc.SplitExampleName(s)
// replace _ with . for method names
name = strings.Replace(name, "_", ".", 1)
// use "Package" if no name provided
if name == "" {
name = "Package"
}
return name + suffix
}
// example_suffixFunc takes an example function name and returns its suffix in
// parenthesized form. For example, "Foo_Bar_quux" becomes " (Quux)".
func example_suffixFunc(name string) string {
_, suffix := pkgdoc.SplitExampleName(name)
return suffix
}

Просмотреть файл

@ -5,7 +5,7 @@
//go:build go1.16
// +build go1.16
package godoc
package web
import (
"bytes"
@ -16,11 +16,6 @@ import (
"strings"
)
var (
jsonStart = []byte("<!--{")
jsonEnd = []byte("}-->")
)
type file struct {
// Copied from document metadata directives
Title string
@ -32,33 +27,13 @@ type file struct {
Body []byte // content after metadata
}
type metaJSON struct {
type fileJSON struct {
Title string
Subtitle string
Template bool
Redirect string // if set, redirect to other URL
}
// extractMetadata extracts the metaJSON from a byte slice.
// It returns the metadata and the remaining text.
// If no metadata is present, it returns an empty metaJSON and the original text.
func extractMetadata(b []byte) (meta metaJSON, tail []byte, err error) {
tail = b
if !bytes.HasPrefix(b, jsonStart) {
return
}
end := bytes.Index(b, jsonEnd)
if end < 0 {
return
}
b = b[len(jsonStart)-1 : end+1] // drop leading <!-- and include trailing }
if err = json.Unmarshal(b, &meta); err != nil {
return
}
tail = tail[end+len(jsonEnd):]
return
}
var join = path.Join
// open returns the file for a given absolute path or nil if none exists.
@ -112,7 +87,7 @@ func open(fsys fs.FS, path string) *file {
path = filePath[:strings.LastIndex(filePath, "/")+1]
}
js, body, err := extractMetadata(b)
js, body, err := parseFile(b)
if err != nil {
log.Printf("extractMetadata %s: %v", path, err)
return nil
@ -133,3 +108,28 @@ func open(fsys fs.FS, path string) *file {
return f
}
var (
jsonStart = []byte("<!--{")
jsonEnd = []byte("}-->")
)
// parseFile extracts the metaJSON from a byte slice.
// It returns the metadata and the remaining text.
// If no metadata is present, it returns an empty metaJSON and the original text.
func parseFile(b []byte) (meta fileJSON, tail []byte, err error) {
tail = b
if !bytes.HasPrefix(b, jsonStart) {
return
}
end := bytes.Index(b, jsonEnd)
if end < 0 {
return
}
b = b[len(jsonStart)-1 : end+1] // drop leading <!-- and include trailing }
if err = json.Unmarshal(b, &meta); err != nil {
return
}
tail = tail[end+len(jsonEnd):]
return
}

Просмотреть файл

@ -5,7 +5,7 @@
//go:build go1.16
// +build go1.16
package godoc
package web
import (
"io/fs"
@ -14,9 +14,9 @@ import (
"unicode/utf8"
)
// IsText reports whether a significant prefix of s looks like correct UTF-8;
// isText reports whether a significant prefix of s looks like correct UTF-8;
// that is, if it is likely that s is human-readable text.
func IsText(s []byte) bool {
func isText(s []byte) bool {
const max = 1024 // at least utf8.UTFMax
if len(s) > max {
s = s[0:max]
@ -62,5 +62,5 @@ func isTextFile(fsys fs.FS, filename string) bool {
return false
}
return IsText(buf[0:n])
return isText(buf[0:n])
}

Просмотреть файл

@ -5,7 +5,7 @@
//go:build go1.16
// +build go1.16
package godoc
package web
import (
"bytes"

100
internal/web/pkgdoc.go Normal file
Просмотреть файл

@ -0,0 +1,100 @@
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build go1.16
// +build go1.16
package web
import (
"log"
"net/http"
"path"
"strings"
"golang.org/x/website/internal/pkgdoc"
)
// docServer serves a package doc tree (/cmd or /pkg).
type docServer struct {
p *Site
d *pkgdoc.Docs
}
func (h *docServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if maybeRedirect(w, r) {
return
}
// TODO(rsc): URL should be clean already.
relpath := path.Clean(strings.TrimPrefix(r.URL.Path, "/pkg"))
relpath = strings.TrimPrefix(relpath, "/")
abspath := path.Join("/src", relpath)
mode := pkgdoc.ParseMode(r.FormValue("m"))
if relpath == "builtin" {
// The fake built-in package contains unexported identifiers,
// but we want to show them. Also, disable type association,
// since it's not helpful for this fake package (see issue 6645).
mode |= pkgdoc.ModeAll | pkgdoc.ModeBuiltin
}
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, info.Err)
return
}
var tabtitle, title, subtitle string
switch {
case info.PDoc != nil:
tabtitle = info.PDoc.Name
default:
tabtitle = info.Dirname
title = "Directory "
}
if title == "" {
if info.IsMain {
// assume that the directory name is the command name
_, tabtitle = path.Split(relpath)
title = "Command "
} else {
title = "Package "
}
}
title += tabtitle
// special cases for top-level package/command directories
switch tabtitle {
case "/src":
title = "Packages"
tabtitle = "Packages"
case "/src/cmd":
title = "Commands"
tabtitle = "Commands"
}
name := "package.html"
if info.Dirname == "/src" {
name = "packageroot.html"
}
h.p.ServePage(w, r, Page{
Title: title,
TabTitle: tabtitle,
Subtitle: subtitle,
Template: name,
Data: info,
})
}
// 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 ""
}
return "?m=" + s
}

Просмотреть файл

@ -5,13 +5,12 @@
//go:build go1.16
// +build go1.16
package godoc
package web
import (
"bytes"
"encoding/json"
"fmt"
htmlpkg "html"
"html"
"html/template"
"io"
"io/fs"
@ -19,9 +18,11 @@ import (
"net/http"
"path"
"regexp"
"runtime"
"strconv"
"strings"
"golang.org/x/website/internal/api"
"golang.org/x/website/internal/pkgdoc"
"golang.org/x/website/internal/spec"
"golang.org/x/website/internal/texthtml"
@ -35,93 +36,125 @@ func toFS(name string) string {
return path.Clean(strings.TrimPrefix(name, "/"))
}
// docServer serves a package doc tree (/cmd or /pkg).
type docServer struct {
p *Presentation
d *pkgdoc.Docs
// Site is a website served from a file system.
type Site struct {
fs fs.FS
api api.DB
mux *http.ServeMux
fileServer http.Handler
Templates *template.Template
// GoogleCN reports whether this request should be marked GoogleCN.
// If the function is nil, no requests are marked GoogleCN.
GoogleCN func(*http.Request) bool
// GoogleAnalytics optionally adds Google Analytics via the provided
// tracking ID to each page.
GoogleAnalytics string
docFuncs template.FuncMap
}
func (h *docServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if redirect(w, r) {
return
// NewSite returns a new Presentation from a file system.
func NewSite(fsys fs.FS) (*Site, error) {
apiDB, err := api.Load(fsys)
if err != nil {
return nil, err
}
p := &Site{
fs: fsys,
api: apiDB,
mux: http.NewServeMux(),
fileServer: http.FileServer(http.FS(fsys)),
}
docs := &docServer{
p: p,
d: pkgdoc.NewDocs(fsys),
}
p.mux.Handle("/cmd/", docs)
p.mux.Handle("/pkg/", docs)
p.mux.HandleFunc("/", p.serveFile)
p.initDocFuncs()
// TODO(rsc): URL should be clean already.
relpath := path.Clean(strings.TrimPrefix(r.URL.Path, "/pkg"))
relpath = strings.TrimPrefix(relpath, "/")
t, err := template.New("").Funcs(siteFuncs).ParseFS(fsys, "lib/godoc/*.html")
if err != nil {
return nil, err
}
p.Templates = t
abspath := path.Join("/src", relpath)
mode := pkgdoc.ParseMode(r.FormValue("m"))
if relpath == "builtin" {
// The fake built-in package contains unexported identifiers,
// but we want to show them. Also, disable type association,
// since it's not helpful for this fake package (see issue 6645).
mode |= pkgdoc.ModeAll | pkgdoc.ModeBuiltin
}
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, info.Err)
return
}
return p, nil
}
var tabtitle, title, subtitle string
switch {
case info.PDoc != nil:
tabtitle = info.PDoc.Name
default:
tabtitle = info.Dirname
title = "Directory "
}
if title == "" {
if info.IsMain {
// assume that the directory name is the command name
_, tabtitle = path.Split(relpath)
title = "Command "
} else {
title = "Package "
}
}
title += tabtitle
// special cases for top-level package/command directories
switch tabtitle {
case "/src":
title = "Packages"
tabtitle = "Packages"
case "/src/cmd":
title = "Commands"
tabtitle = "Commands"
}
name := "package.html"
if info.Dirname == "/src" {
name = "packageroot.html"
}
h.p.ServePage(w, r, Page{
Title: title,
TabTitle: tabtitle,
Subtitle: subtitle,
Template: name,
Data: info,
// ServeError responds to the request with the given error.
func (s *Site) ServeError(w http.ResponseWriter, r *http.Request, err error) {
w.WriteHeader(http.StatusNotFound)
s.ServePage(w, r, Page{
Title: r.URL.Path,
Template: "error.html",
Data: err,
})
}
// 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 ""
// ServeHTTP implements http.Handler, dispatching the request appropriately.
func (s *Site) ServeHTTP(w http.ResponseWriter, r *http.Request) {
s.mux.ServeHTTP(w, r)
}
// ServePage responds to the request with the content described by page.
func (s *Site) ServePage(w http.ResponseWriter, r *http.Request, page Page) {
page = s.fullPage(r, page)
applyTemplateToResponseWriter(w, s.Templates.Lookup("site.html"), &page)
}
// 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 // <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
// 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
site *Site
}
// fullPage returns a copy of page with the “automatic” fields filled in.
func (s *Site) fullPage(r *http.Request, page Page) Page {
if page.TabTitle == "" {
page.TabTitle = page.Title
}
return "?m=" + s
page.Version = runtime.Version()
page.GoogleCN = s.googleCN(r)
page.GoogleAnalytics = s.GoogleAnalytics
page.site = s
return page
}
// 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)
t := p.site.Templates.Lookup(name)
var buf bytes.Buffer
p1 := *p
p1.Data = data
@ -131,12 +164,12 @@ func (p *Page) Invoke(name string, data interface{}) template.HTML {
return template.HTML(buf.String())
}
type writerCapturesErr struct {
type writeErrorSaver struct {
w io.Writer
err error
}
func (w *writerCapturesErr) Write(p []byte) (int, error) {
func (w *writeErrorSaver) Write(p []byte) (int, error) {
n, err := w.w.Write(p)
if err != nil {
w.err = err
@ -151,7 +184,7 @@ func (w *writerCapturesErr) Write(p []byte) (int, error) {
// polluting log files with error messages due to networking issues, such as
// client disconnects and http HEAD protocol violations.
func applyTemplateToResponseWriter(rw http.ResponseWriter, t *template.Template, data interface{}) {
w := &writerCapturesErr{w: rw}
w := &writeErrorSaver{w: rw}
err := t.Execute(w, data)
// There are some cases where template.Execute does not return an error when
// rw returns an error, and some where it does. So check w.err first.
@ -161,7 +194,65 @@ func applyTemplateToResponseWriter(rw http.ResponseWriter, t *template.Template,
}
}
func redirect(w http.ResponseWriter, r *http.Request) (redirected bool) {
func (s *Site) serveFile(w http.ResponseWriter, r *http.Request) {
if strings.HasSuffix(r.URL.Path, "/index.html") {
// We'll show index.html for the directory.
// Use the dir/ version as canonical instead of dir/index.html.
http.Redirect(w, r, r.URL.Path[0:len(r.URL.Path)-len("index.html")], http.StatusMovedPermanently)
return
}
// Check to see if we need to redirect or serve another file.
abspath := r.URL.Path
if f := open(s.fs, abspath); f != nil {
if f.Path != abspath {
// Redirect to canonical path.
http.Redirect(w, r, f.Path, http.StatusMovedPermanently)
return
}
// Serve from the actual filesystem path.
s.serveHTML(w, r, f)
return
}
relpath := abspath[1:] // strip leading slash
dir, err := fs.Stat(s.fs, toFS(abspath))
if err != nil {
// Check for spurious trailing slash.
if strings.HasSuffix(abspath, "/") {
trimmed := abspath[:len(abspath)-1]
if _, err := fs.Stat(s.fs, toFS(trimmed)); err == nil ||
open(s.fs, trimmed) != nil {
http.Redirect(w, r, trimmed, http.StatusMovedPermanently)
return
}
}
s.ServeError(w, r, err)
return
}
fsPath := toFS(abspath)
if dir != nil && dir.IsDir() {
if maybeRedirect(w, r) {
return
}
s.serveDir(w, r, abspath, relpath)
return
}
if isTextFile(s.fs, fsPath) {
if maybeRedirectFile(w, r) {
return
}
s.serveText(w, r, abspath, relpath)
return
}
s.fileServer.ServeHTTP(w, r)
}
func maybeRedirect(w http.ResponseWriter, r *http.Request) (redirected bool) {
canonical := path.Clean(r.URL.Path)
if !strings.HasSuffix(canonical, "/") {
canonical += "/"
@ -175,7 +266,7 @@ func redirect(w http.ResponseWriter, r *http.Request) (redirected bool) {
return
}
func redirectFile(w http.ResponseWriter, r *http.Request) (redirected bool) {
func maybeRedirectFile(w http.ResponseWriter, r *http.Request) (redirected bool) {
c := path.Clean(r.URL.Path)
c = strings.TrimRight(c, "/")
if r.URL.Path != c {
@ -187,6 +278,132 @@ func redirectFile(w http.ResponseWriter, r *http.Request) (redirected bool) {
return
}
var doctype = []byte("<!DOCTYPE ")
func (s *Site) serveHTML(w http.ResponseWriter, r *http.Request, f *file) {
src := f.Body
isMarkdown := strings.HasSuffix(f.FilePath, ".md")
// if it begins with "<!DOCTYPE " assume it is standalone
// html that doesn't need the template wrapping.
if bytes.HasPrefix(src, doctype) {
w.Write(src)
return
}
page := Page{
Title: f.Title,
Subtitle: f.Subtitle,
}
// evaluate as template if indicated
if f.Template {
page = s.fullPage(r, page)
tmpl, err := template.New("main").Funcs(s.docFuncs).Parse(string(src))
if err != nil {
log.Printf("parsing template %s: %v", f.Path, err)
s.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)
s.ServeError(w, r, err)
return
}
src = buf.Bytes()
}
// Apply markdown as indicated.
// (Note template applies before Markdown.)
if isMarkdown {
html, err := renderMarkdown(src)
if err != nil {
log.Printf("executing markdown %s: %v", f.Path, err)
s.ServeError(w, r, err)
return
}
src = html
}
// if it's the language spec, add tags to EBNF productions
if strings.HasSuffix(f.FilePath, "go_spec.html") {
var buf bytes.Buffer
spec.Linkify(&buf, src)
src = buf.Bytes()
}
page.Data = template.HTML(src)
s.ServePage(w, r, page)
}
func (s *Site) serveDir(w http.ResponseWriter, r *http.Request, abspath, relpath string) {
if maybeRedirect(w, r) {
return
}
list, err := fs.ReadDir(s.fs, toFS(abspath))
if err != nil {
s.ServeError(w, r, err)
return
}
var info []fs.FileInfo
for _, d := range list {
i, err := d.Info()
if err == nil {
info = append(info, i)
}
}
s.ServePage(w, r, Page{
Title: "Directory",
SrcPath: relpath,
TabTitle: relpath,
Template: "dirlist.html",
Data: info,
})
}
func (s *Site) serveText(w http.ResponseWriter, r *http.Request, abspath, relpath string) {
src, err := fs.ReadFile(s.fs, toFS(abspath))
if err != nil {
log.Printf("ReadFile: %s", err)
s.ServeError(w, r, err)
return
}
if r.FormValue("m") == "text" {
s.serveRawText(w, src)
return
}
cfg := texthtml.Config{
GoComments: path.Ext(abspath) == ".go",
Highlight: r.FormValue("h"),
Selection: rangeSelection(r.FormValue("s")),
Line: 1,
}
var buf bytes.Buffer
buf.WriteString("<pre>")
buf.Write(texthtml.Format(src, cfg))
buf.WriteString("</pre>")
fmt.Fprintf(&buf, `<p><a href="/%s?m=text">View as plain text</a></p>`, html.EscapeString(relpath))
title := "Text file"
if strings.HasSuffix(relpath, ".go") {
title = "Source file"
}
s.ServePage(w, r, Page{
Title: title,
SrcPath: relpath,
TabTitle: relpath,
Data: template.HTML(buf.String()),
})
}
var selRx = regexp.MustCompile(`^([0-9]+):([0-9]+)`)
// rangeSelection computes the Selection for a text range described
@ -204,206 +421,11 @@ func rangeSelection(str string) texthtml.Selection {
return nil
}
func (p *Presentation) serveTextFile(w http.ResponseWriter, r *http.Request, abspath, relpath string) {
src, err := fs.ReadFile(p.fs, toFS(abspath))
if err != nil {
log.Printf("ReadFile: %s", err)
p.ServeError(w, r, err)
return
}
if r.FormValue("m") == "text" {
p.serveText(w, src)
return
}
cfg := texthtml.Config{
GoComments: path.Ext(abspath) == ".go",
Highlight: r.FormValue("h"),
Selection: rangeSelection(r.FormValue("s")),
Line: 1,
}
var buf bytes.Buffer
buf.WriteString("<pre>")
buf.Write(texthtml.Format(src, cfg))
buf.WriteString("</pre>")
fmt.Fprintf(&buf, `<p><a href="/%s?m=text">View as plain text</a></p>`, htmlpkg.EscapeString(relpath))
title := "Text file"
if strings.HasSuffix(relpath, ".go") {
title = "Source file"
}
p.ServePage(w, r, Page{
Title: title,
SrcPath: relpath,
TabTitle: relpath,
Data: template.HTML(buf.String()),
})
}
func (p *Presentation) serveDirectory(w http.ResponseWriter, r *http.Request, abspath, relpath string) {
if redirect(w, r) {
return
}
list, err := fs.ReadDir(p.fs, toFS(abspath))
if err != nil {
p.ServeError(w, r, err)
return
}
var info []fs.FileInfo
for _, d := range list {
i, err := d.Info()
if err == nil {
info = append(info, i)
}
}
p.ServePage(w, r, Page{
Title: "Directory",
SrcPath: relpath,
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")
// if it begins with "<!DOCTYPE " assume it is standalone
// html that doesn't need the template wrapping.
if bytes.HasPrefix(src, doctype) {
w.Write(src)
return
}
page := Page{
Title: f.Title,
Subtitle: f.Subtitle,
}
// evaluate as template if indicated
if f.Template {
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, 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, err)
return
}
src = buf.Bytes()
}
// Apply markdown as indicated.
// (Note template applies before Markdown.)
if isMarkdown {
html, err := renderMarkdown(src)
if err != nil {
log.Printf("executing markdown %s: %v", f.Path, err)
p.ServeError(w, r, err)
return
}
src = html
}
// if it's the language spec, add tags to EBNF productions
if strings.HasSuffix(f.FilePath, "go_spec.html") {
var buf bytes.Buffer
spec.Linkify(&buf, src)
src = buf.Bytes()
}
page.Data = template.HTML(src)
p.ServePage(w, r, page)
}
func (p *Presentation) serveFile(w http.ResponseWriter, r *http.Request) {
if strings.HasSuffix(r.URL.Path, "/index.html") {
// We'll show index.html for the directory.
// Use the dir/ version as canonical instead of dir/index.html.
http.Redirect(w, r, r.URL.Path[0:len(r.URL.Path)-len("index.html")], http.StatusMovedPermanently)
return
}
// Check to see if we need to redirect or serve another file.
abspath := r.URL.Path
if f := open(p.fs, abspath); f != nil {
if f.Path != abspath {
// Redirect to canonical path.
http.Redirect(w, r, f.Path, http.StatusMovedPermanently)
return
}
// Serve from the actual filesystem path.
p.serveHTML(w, r, f)
return
}
relpath := abspath[1:] // strip leading slash
dir, err := fs.Stat(p.fs, toFS(abspath))
if err != nil {
// Check for spurious trailing slash.
if strings.HasSuffix(abspath, "/") {
trimmed := abspath[:len(abspath)-1]
if _, err := fs.Stat(p.fs, toFS(trimmed)); err == nil ||
open(p.fs, trimmed) != nil {
http.Redirect(w, r, trimmed, http.StatusMovedPermanently)
return
}
}
p.ServeError(w, r, err)
return
}
fsPath := toFS(abspath)
if dir != nil && dir.IsDir() {
if redirect(w, r) {
return
}
p.serveDirectory(w, r, abspath, relpath)
return
}
if isTextFile(p.fs, fsPath) {
if redirectFile(w, r) {
return
}
p.serveTextFile(w, r, abspath, relpath)
return
}
p.fileServer.ServeHTTP(w, r)
}
func (p *Presentation) serveText(w http.ResponseWriter, text []byte) {
func (s *Site) serveRawText(w http.ResponseWriter, text []byte) {
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
w.Write(text)
}
func marshalJSON(x interface{}) []byte {
var data []byte
var err error
const indentJSON = false // for easier debugging
if indentJSON {
data, err = json.MarshalIndent(x, "", " ")
} else {
data, err = json.Marshal(x)
}
if err != nil {
panic(fmt.Sprintf("json.Marshal failed: %s", err))
}
return data
func (s *Site) googleCN(r *http.Request) bool {
return s.GoogleCN != nil && s.GoogleCN(r)
}

Просмотреть файл

@ -5,7 +5,7 @@
//go:build go1.16
// +build go1.16
package godoc
package web
import (
"net/http"
@ -16,7 +16,7 @@ import (
"testing/fstest"
)
func testServeBody(t *testing.T, p *Presentation, path, body string) {
func testServeBody(t *testing.T, p *Site, path, body string) {
t.Helper()
r := &http.Request{URL: &url.URL{Path: path}}
rw := httptest.NewRecorder()
@ -32,7 +32,7 @@ func TestRedirectAndMetadata(t *testing.T) {
"doc/x/index.html": {Data: []byte("Hello, x.")},
"lib/godoc/site.html": {Data: []byte(`{{.Data}}`)},
}
p, err := NewPresentation(fsys)
p, err := NewSite(fsys)
if err != nil {
t.Fatal(err)
}
@ -53,7 +53,7 @@ func TestRedirectAndMetadata(t *testing.T) {
}
func TestMarkdown(t *testing.T) {
p, err := NewPresentation(fstest.MapFS{
p, err := NewSite(fstest.MapFS{
"doc/test.md": {Data: []byte("**bold**")},
"doc/test2.md": {Data: []byte(`{{"*template*"}}`)},
"lib/godoc/site.html": {Data: []byte(`{{.Data}}`)},

Просмотреть файл

@ -5,7 +5,7 @@
//go:build go1.16
// +build go1.16
package godoc
package web
import (
"bytes"
@ -18,29 +18,41 @@ import (
"path"
"strings"
"golang.org/x/website/internal/history"
"golang.org/x/website/internal/pkgdoc"
)
func (p *Presentation) initFuncMap() {
p.docFuncs = template.FuncMap{
"code": p.code,
"releases": func() []*history.Major { return history.Majors },
}
}
var siteFuncs = template.FuncMap{
// various helpers
"basename": path.Base,
// formatting of Examples
"example_name": example_nameFunc,
"example_suffix": example_suffixFunc,
"example_name": example_name,
"example_suffix": example_suffix,
// Number operation
"multiply": func(a, b int) int { return a * b },
}
// example_name takes an example function name and returns its display
// name. For example, "Foo_Bar_quux" becomes "Foo.Bar (Quux)".
func example_name(s string) string {
name, suffix := pkgdoc.SplitExampleName(s)
// replace _ with . for method names
name = strings.Replace(name, "_", ".", 1)
// use "Package" if no name provided
if name == "" {
name = "Package"
}
return name + suffix
}
// example_suffix takes an example function name and returns its suffix in
// parenthesized form. For example, "Foo_Bar_quux" becomes " (Quux)".
func example_suffix(name string) string {
_, suffix := pkgdoc.SplitExampleName(name)
return suffix
}
func srcToPkg(path string) string {
// because of the irregular mapping under goroot
// we need to correct certain relative paths
@ -125,10 +137,10 @@ func (p *Page) SrcPosLink(n interface{}) template.HTML {
high = info.FSet.Position(end).Offset
}
return srcPosLinkFunc(relpath, line, low, high)
return srcPosLink(relpath, line, low, high)
}
func srcPosLinkFunc(s string, line, low, high int) template.HTML {
func srcPosLink(s string, line, low, high int) template.HTML {
s = path.Clean("/" + s)
if !strings.HasPrefix(s, "/src/") {
s = "/src" + s

Просмотреть файл

@ -5,7 +5,7 @@
//go:build go1.16
// +build go1.16
package godoc
package web
import "io"
@ -16,9 +16,9 @@ const (
collecting
)
// TabSpacer returns a writer that passes writes through to w,
// tabSpacer returns a writer that passes writes through to w,
// expanding tabs to one or more spaces ending at a width-spaces-aligned boundary.
func TabSpacer(w io.Writer, width int) io.Writer {
func tabSpacer(w io.Writer, width int) io.Writer {
return &tconv{output: w, tabWidth: width}
}

Просмотреть файл

@ -5,7 +5,7 @@
//go:build go1.16
// +build go1.16
package godoc
package web
import (
"bytes"
@ -53,7 +53,7 @@ func TestSrcPosLinkFunc(t *testing.T) {
{"fmt/print.go", 0, 0, 0, "/src/fmt/print.go"},
{"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 {
if got := srcPosLink(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)
}
}
@ -185,7 +185,7 @@ func (h Header) Get(key string) string`))
}
func linkifySource(t *testing.T, src []byte) string {
p := &Presentation{}
p := &Site{}
fset := token.NewFileSet()
af, err := parser.ParseFile(fset, "foo.go", src, parser.ParseComments)
if err != nil {
@ -196,7 +196,7 @@ func linkifySource(t *testing.T, src []byte) string {
FSet: fset,
}
pg := &Page{
pres: p,
site: p,
Data: pi,
}
sep := ""