diff --git a/blog/blog.go b/blog/blog.go index 4055b1b7c6..633014d31f 100644 --- a/blog/blog.go +++ b/blog/blog.go @@ -6,7 +6,6 @@ package blog // import "golang.org/x/tools/blog" import ( - "bytes" "encoding/json" "encoding/xml" "fmt" @@ -24,7 +23,17 @@ import ( "golang.org/x/tools/present" ) -var validJSONPFunc = regexp.MustCompile(`(?i)^[a-z_][a-z0-9_.]*$`) +var ( + validJSONPFunc = regexp.MustCompile(`(?i)^[a-z_][a-z0-9_.]*$`) + // used to serve relative paths when ServeLocalLinks is enabled. + // TODO(agnivade): change blog article links to all have https. + golangOrgAbsLinkReplacer = strings.NewReplacer( + `href="http://golang.org/pkg`, `href="/pkg`, + `href="https://golang.org/pkg`, `href="/pkg`, + `href="http://golang.org/cmd`, `href="/cmd`, + `href="https://golang.org/cmd`, `href="/cmd`, + ) +) // Config specifies Server configuration values. type Config struct { @@ -40,7 +49,8 @@ type Config struct { FeedArticles int // Articles to include in Atom and JSON feeds. FeedTitle string // The title of the Atom XML feed - PlayEnabled bool + PlayEnabled bool + ServeLocalLinks bool // rewrite golang.org/{pkg,cmd} links to host-less, relative paths. } // Doc represents an article adorned with presentation data. @@ -143,7 +153,7 @@ func sectioned(d *present.Doc) bool { // authors returns a comma-separated list of author names. func authors(authors []present.Author) string { - var b bytes.Buffer + var b strings.Builder last := len(authors) - 1 for i, a := range authors { if i > 0 { @@ -191,8 +201,8 @@ func (s *Server) loadDocs(root string) error { if err != nil { return err } - html := new(bytes.Buffer) - err = d.Render(html, s.template.doc) + var html strings.Builder + err = d.Render(&html, s.template.doc) if err != nil { return err } @@ -359,7 +369,7 @@ func summary(d *Doc) string { // skip everything but non-text elements continue } - var buf bytes.Buffer + var buf strings.Builder for _, s := range text.Lines { buf.WriteString(string(present.Style(s))) buf.WriteByte('\n') @@ -417,7 +427,18 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { d.Doc = doc t = s.template.article } - err := t.ExecuteTemplate(w, "root", d) + var err error + if s.cfg.ServeLocalLinks { + var buf strings.Builder + err = t.ExecuteTemplate(&buf, "root", d) + if err != nil { + log.Println(err) + return + } + _, err = golangOrgAbsLinkReplacer.WriteString(w, buf.String()) + } else { + err = t.ExecuteTemplate(w, "root", d) + } if err != nil { log.Println(err) } diff --git a/blog/blog_test.go b/blog/blog_test.go new file mode 100644 index 0000000000..6e9f6411ce --- /dev/null +++ b/blog/blog_test.go @@ -0,0 +1,44 @@ +// Copyright 2018 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. + +package blog + +import ( + "strings" + "testing" +) + +func TestLinkRewrite(t *testing.T) { + tests := []struct { + input string + output string + }{ + { + `For instance, the bytes package from the standard library exports the Buffer type.`, + `For instance, the bytes package from the standard library exports the Buffer type.`}, + { + `(The gofmt command has a -r flag that provides a syntax-aware search and replace, making large-scale refactoring easier.)`, + `(The gofmt command has a -r flag that provides a syntax-aware search and replace, making large-scale refactoring easier.)`, + }, + { + `BSD license.
Terms of Service `, + `BSD license.
Terms of Service `, + }, + { + `For instance, the websocket package from the go.net sub-repository has an import path of "golang.org/x/net/websocket".`, + `For instance, the websocket package from the go.net sub-repository has an import path of "golang.org/x/net/websocket".`, + }, + } + for _, test := range tests { + var buf strings.Builder + _, err := golangOrgAbsLinkReplacer.WriteString(&buf, test.input) + if err != nil { + t.Errorf("unexpected error during replacing links. Got: %#v, Want: nil.\n", err) + continue + } + if got, want := buf.String(), test.output; got != want { + t.Errorf("WriteString(%q) = %q. Expected: %q", test.input, got, want) + } + } +} diff --git a/cmd/godoc/blog.go b/cmd/godoc/blog.go index dec4732fc7..e47b73eb09 100644 --- a/cmd/godoc/blog.go +++ b/cmd/godoc/blog.go @@ -34,12 +34,14 @@ var ( func init() { // Initialize blog only when first accessed. http.HandleFunc(blogPath, func(w http.ResponseWriter, r *http.Request) { - blogInitOnce.Do(blogInit) + blogInitOnce.Do(func() { + blogInit(r.Host) + }) blogServer.ServeHTTP(w, r) }) } -func blogInit() { +func blogInit(host string) { // Binary distributions will include the blog content in "/blog". root := filepath.Join(runtime.GOROOT(), "blog") @@ -57,12 +59,13 @@ func blogInit() { } s, err := blog.NewServer(blog.Config{ - BaseURL: blogPath, - BasePath: strings.TrimSuffix(blogPath, "/"), - ContentPath: filepath.Join(root, "content"), - TemplatePath: filepath.Join(root, "template"), - HomeArticles: 5, - PlayEnabled: playEnabled, + BaseURL: blogPath, + BasePath: strings.TrimSuffix(blogPath, "/"), + ContentPath: filepath.Join(root, "content"), + TemplatePath: filepath.Join(root, "template"), + HomeArticles: 5, + PlayEnabled: playEnabled, + ServeLocalLinks: strings.HasPrefix(host, "localhost"), }) if err != nil { log.Fatal(err)