blog: serve relative links when run locally

Currently, links inside blog articles are absolute links to golang.org.
But when a godoc server is run locally, the blog package should
serve local links pointing to the local godoc server. It is not possible
to simply change the links inside the blog templates to relative urls
because the blog articles are independant pages on their own.
And moreover, they are served from blog.golang.org.

Rather, the blog package consumes and serves blog articles.
So, a flag was added in the Config struct to denote whether
to convert the links or not. This flag is then set from the
call site in godoc package where the blog server is initialized from.

This was required because "golang.org/x/tools/blog" is a package
which can be used by other code to serve blog pages and not just godoc.
This preserves existing functionality for all working code which
imports "golang.org/x/tools/blog" and changes the functionality only
when a godoc server is run locally.

And while here, replace relevant bytes.Buffer occurences
with strings.Builder.

Fixes golang/go#22681

Change-Id: I7dbf9c5f2f93fd0b7e17915238de1c084fcd1431
Reviewed-on: https://go-review.googlesource.com/105835
Reviewed-by: Andrew Bonventre <andybons@golang.org>
Run-TryBot: Andrew Bonventre <andybons@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
This commit is contained in:
Agniva De Sarker 2018-04-08 12:25:29 +05:30 коммит произвёл Andrew Bonventre
Родитель 5e86cd2985
Коммит d11f6ec946
3 изменённых файлов: 84 добавлений и 16 удалений

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

@ -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)
}

44
blog/blog_test.go Normal file
Просмотреть файл

@ -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 <a href="http://golang.org/pkg/bytes/" target="_blank">bytes package</a> from the standard library exports the <code>Buffer</code> type.`,
`For instance, the <a href="/pkg/bytes/" target="_blank">bytes package</a> from the standard library exports the <code>Buffer</code> type.`},
{
`(The <a href="http://golang.org/cmd/gofmt/" target="_blank">gofmt command</a> has a <code>-r</code> flag that provides a syntax-aware search and replace, making large-scale refactoring easier.)`,
`(The <a href="/cmd/gofmt/" target="_blank">gofmt command</a> has a <code>-r</code> flag that provides a syntax-aware search and replace, making large-scale refactoring easier.)`,
},
{
`<a href="//golang.org/LICENSE">BSD license</a>.<br> <a href="//golang.org/doc/tos.html">Terms of Service</a> `,
`<a href="//golang.org/LICENSE">BSD license</a>.<br> <a href="//golang.org/doc/tos.html">Terms of Service</a> `,
},
{
`For instance, the <code>websocket</code> package from the <code>go.net</code> sub-repository has an import path of <code>&#34;golang.org/x/net/websocket&#34;</code>.`,
`For instance, the <code>websocket</code> package from the <code>go.net</code> sub-repository has an import path of <code>&#34;golang.org/x/net/websocket&#34;</code>.`,
},
}
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)
}
}
}

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

@ -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)