_content: serve wiki pages on go.dev

The wiki pages have been copied to go.googlesource.com/wiki
and will be code reviewed using Gerrit, like other Go repos.
Unlike other Go repos, self-review will be permitted, and
there is no requirement for Googlers to be involved to make a change.
These relaxations are possible because the wiki has no production code.
The set of wiki +2'ers is a superset of the usual code +2'ers.

Fixes golang/go#61940.

Change-Id: I01823720091fbaa24e95e9c82abeaadba867a17c
Reviewed-on: https://go-review.googlesource.com/c/website/+/518297
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
Reviewed-by: Dmitri Shuralyov <dmitshur@golang.org>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
This commit is contained in:
Russ Cox 2023-08-10 11:32:33 -04:00
Родитель 76936b10b9
Коммит e2dd66fc34
13 изменённых файлов: 169 добавлений и 27 удалений

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

@ -5370,3 +5370,9 @@ a.downloadBox:hover .filename span {
display: none;
}
}
hr {
border: none;
border-top: 1px solid var(--color-border);
height: 1px;
}

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

@ -71,7 +71,7 @@
<a href="{{if .children}}#{{else}}{{.url}}{{end}}" {{if .children}} class="js-desktop-menu-hover"{{end}} aria-label={{.name}} aria-describedby="dropdown-description">
{{.name}} {{if .children}}<i class="material-icons" aria-hidden="true">arrow_drop_down</i>{{end}}
</a>
<div class="screen-reader-only" id="dropdown-description" hidden>
<div class="screen-reader-only" id="dropdown-description" hidden>
Press Enter to activate/deactivate dropdown
</div>
{{- if .children}}
@ -270,7 +270,7 @@
<script async src="/js/copypaste.js"></script>
</footer>
<section class="Cookie-notice js-cookieNotice">
<div>go.dev uses cookies from Google to deliver and enhance the quality of its services and to
<div>go.dev uses cookies from Google to deliver and enhance the quality of its services and to
analyze traffic. <a target=_blank href="https://policies.google.com/technologies/cookies">Learn more.</a></div>
<div><button class="go-Button">Okay</button></div>
</section>
@ -438,7 +438,7 @@
{{end}}
{{if .title}}
<h1>{{.title}}</h1>
<h1>{{if strings.HasPrefix .URL "/wiki/"}}Go Wiki: {{end}}{{.title}}</h1>
{{else if eq .layout "error"}}
<h1>Error</h1>
{{else if eq .layout "dir"}}
@ -471,6 +471,13 @@
{{end}}
</div>
{{end}}
{{if strings.HasPrefix .URL "/wiki/"}}
<hr>
<p>
<i>This content is part of the <a href="/wiki/">Go Wiki</a>.</i>
</p>
{{end}}
</article>
{{end}}

50
_content/wiki/Comments.md Normal file
Просмотреть файл

@ -0,0 +1,50 @@
---
title: Comments
---
<!--
This is just a placeholder page for enabling a test.
In the deployed site it is overwritten with the content of go.googlesource.com/wiki.
-->
Every package should have a package comment. It should immediately precede the ` package ` statement in one of the files in the package. (It only needs to appear in one file.) It should begin with a single sentence that begins "Package _packagename_" and give a concise summary of the package functionality. This introductory sentence will be used in godoc's list of all packages.
Subsequent sentences and/or paragraphs can give more details. Sentences should be properly punctuated.
```go
// Package superman implements methods for saving the world.
//
// Experience has shown that a small number of procedures can prove
// helpful when attempting to save the world.
package superman
```
Nearly every top-level type, const, var and func should have a comment. A comment for bar should be in the form "_bar_ floats on high o'er vales and hills.". The first letter of _bar_ should not be capitalized unless it's capitalized in the code.
```go
// enterOrbit causes Superman to fly into low Earth orbit, a position
// that presents several possibilities for planet salvation.
func enterOrbit() os.Error {
...
}
```
All text that you indent inside a comment, godoc will render as a pre-formatted block. This facilitates code samples.
```go
// fight can be used on any enemy and returns whether Superman won.
//
// Examples:
//
// fight("a random potato")
// fight(LexLuthor{})
//
func fight(enemy interface{}) bool {
// This is testing proper escaping in the wiki.
for i := 0; i < 10; i++ {
println("fight!")
}
}
```

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

@ -0,0 +1,3 @@
{{define "layout"}}
{{doclayout .}}
{{end}}

5
_content/wiki/index.md Normal file
Просмотреть файл

@ -0,0 +1,5 @@
---
title: Index
---
The Wiki is still loading...

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

@ -3,6 +3,7 @@
# Do not run directly.
steps:
# Clone go repo to _goroot.zip for use by uploaded app.
- name: gcr.io/cloud-builders/git
args: ["clone", "--branch=release-branch.go1.21", "--depth=1", "https://go.googlesource.com/go", "_gotmp"]
- name: gcr.io/cloud-builders/git
@ -10,14 +11,28 @@ steps:
dir: _gotmp
- name: golang
args: ["rm", "-rf", "_gotmp"]
# Clone wiki repo into _content/wiki as initial wiki content.
# The server will replace this with the wiki repo when it can,
# but this provides a good fallback.
- name: gcr.io/cloud-builders/git
args: ["clone", "--depth=1", "https://go.googlesource.com/wiki", "_wikitmp"]
- name: golang
args: ["rm", "-rf", "_wikitmp/.git"]
- name: golang
args: ["cp", "-a", "_wikitmp/*", "_content/wiki"]
# Run tests.
- name: golang
args: ["go", "test", "./..."]
# Coordinate with other Cloud Build jobs to deploy only newest commit.
# May abort job here.
- name: golang
args: ["go", "run", "./cmd/locktrigger", "--project=$PROJECT_ID",
"--build=$BUILD_ID", "--repo=https://go.googlesource.com/website"]
# Deploy site and redirect traffic (maybe; tests again in prod first).
- name: gcr.io/cloud-builders/gcloud
entrypoint: bash
args: ["./go-app-deploy.sh", "cmd/golangorg/app.yaml"]
# Clean up stale versions.
- name: golang
args: ["go", "run", "./cmd/versionprune", "--dry_run=false", "--project=$PROJECT_ID", "--service=default"]

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

@ -59,7 +59,8 @@ var (
runningOnAppEngine = os.Getenv("PORT") != ""
tipFlag = flag.Bool("tip", runningOnAppEngine, "load git content for tip.golang.org")
tipFlag = flag.Bool("tip", runningOnAppEngine, "load git content for tip.golang.org")
wikiFlag = flag.Bool("wiki", runningOnAppEngine, "load git content for go.dev/wiki")
googleAnalytics string
)
@ -157,6 +158,19 @@ func NewHandler(contentDir, goroot string) http.Handler {
gorootFS = os.DirFS(goroot)
}
// go.dev/wiki serves content from the very latest Git commit of the wiki repo.
// Start with the _content/wiki directory as placeholder until Git loads.
var wikiFS atomicFS
wikiDefault, err := fs.Sub(contentFS, "wiki")
if err != nil {
log.Fatalf("loading default wiki content: %v", err)
}
wikiFS.Set(wikiDefault)
if *wikiFlag {
go watchGit(&wikiFS, "https://go.googlesource.com/wiki")
}
contentFS = &mountFS{contentFS, "wiki", &wikiFS}
// tip.golang.org serves content from the very latest Git commit
// of the main Go repo, instead of the one the app is bundled with.
var tipGoroot atomicFS
@ -164,7 +178,7 @@ func NewHandler(contentDir, goroot string) http.Handler {
log.Fatalf("loading tip site: %v", err)
}
if *tipFlag {
go watchTip(&tipGoroot)
go watchGit(&tipGoroot, "https://go.googlesource.com/go")
}
// beta.golang.org is an old name for tip.
@ -280,32 +294,32 @@ func parseRFC3339(s string) (time.Time, error) {
return time.Parse(time.RFC3339, s)
}
// watchTip is a background goroutine that watches the main Go repo for updates.
// When a new commit is available, watchTip downloads the new tree and calls
// tipGoroot.Set to install the new file system.
func watchTip(tipGoroot *atomicFS) {
// watchGit is a background goroutine that watches a Git repo for updates.
// When a new commit is available, watchGit downloads the new tree and calls
// fsys.Set to install the new file system.
func watchGit(fsys *atomicFS, repo string) {
for {
// watchTip1 runs until it panics (hopefully never).
// watchGit1 runs until it panics (hopefully never).
// If that happens, sleep 5 minutes and try again.
watchTip1(tipGoroot)
watchGit1(fsys, repo)
time.Sleep(5 * time.Minute)
}
}
// watchTip1 does the actual work of watchTip and recovers from panics.
func watchTip1(tipGoroot *atomicFS) {
// watchGit1 does the actual work of watchGit and recovers from panics.
func watchGit1(afs *atomicFS, repo string) {
defer func() {
if e := recover(); e != nil {
log.Printf("watchTip panic: %v\n%s", e, debug.Stack())
log.Printf("watchGit %s panic: %v\n%s", repo, e, debug.Stack())
}
}()
var r *gitfs.Repo
for {
var err error
r, err = gitfs.NewRepo("https://go.googlesource.com/go")
r, err = gitfs.NewRepo(repo)
if err != nil {
log.Printf("tip: %v", err)
log.Printf("watchGit %s: %v", repo, err)
time.Sleep(1 * time.Minute)
continue
}
@ -318,11 +332,11 @@ func watchTip1(tipGoroot *atomicFS) {
var err error
h, fsys, err = r.Clone("HEAD")
if err != nil {
log.Printf("tip: %v", err)
log.Printf("watchGit %s: %v", repo, err)
time.Sleep(1 * time.Minute)
continue
}
tipGoroot.Set(fsys)
afs.Set(fsys)
break
}
@ -330,17 +344,17 @@ func watchTip1(tipGoroot *atomicFS) {
time.Sleep(5 * time.Minute)
h2, err := r.Resolve("HEAD")
if err != nil {
log.Printf("tip: %v", err)
log.Printf("watchGit %s: %v", repo, err)
continue
}
if h2 != h {
fsys, err := r.CloneHash(h2)
if err != nil {
log.Printf("tip: %v", err)
log.Printf("watchGit %s: %v", repo, err)
time.Sleep(1 * time.Minute)
continue
}
tipGoroot.Set(fsys)
afs.Set(fsys)
h = h2
}
}
@ -715,6 +729,7 @@ func (fsys fixSpecsFS) Open(name string) (fs.File, error) {
// README.md, and SECURITY.md. The last is particularly problematic
// when running locally on a Mac, because it can be opened as
// security.md, which takes priority over _content/security.html.
// Same for wiki.
type hideRootMDFS struct {
fs fs.FS
}
@ -723,6 +738,10 @@ func (fsys hideRootMDFS) Open(name string) (fs.File, error) {
if !strings.Contains(name, "/") && strings.HasSuffix(name, ".md") {
return nil, errors.New(".md file not available")
}
switch name {
case "wiki/README.md", "wiki/CONTRIBUTING.md", "wiki/LICENSE", "wiki/PATENTS", "wiki/codereview.cfg":
return nil, errors.New("wiki meta file not available")
}
return fsys.fs.Open(name)
}
@ -795,6 +814,23 @@ func (a *atomicFS) Set(fsys fs.FS) {
a.v.Store(&fsys)
}
// A mountFS is a root FS with a second FS mounted at a specific location.
type mountFS struct {
old fs.FS // root file system
dir string // mount point
new fs.FS // fs mounted on dir
}
func (m *mountFS) Open(name string) (fs.File, error) {
if name == m.dir {
return m.new.Open(".")
}
if strings.HasPrefix(name, m.dir) && len(name) > len(m.dir) && name[len(m.dir)] == '/' {
return m.new.Open(name[len(m.dir)+1:])
}
return m.old.Open(name)
}
// Open returns fsys.Open(name) where fsys is the file system passed to the most recent call to Set.
// If there has been no call to Set, Open returns an error with text “no file system”.
func (a *atomicFS) Open(name string) (fs.File, error) {

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

@ -114,6 +114,9 @@ func TestAll(t *testing.T) {
// Do not process these paths or path prefixes.
ignores := []string{
// Wiki is in a different repo; errors there should not block production push.
"/wiki/",
// Support files not meant to be served directly.
"/doc/articles/wiki/",
"/talks/2013/highperf/",

8
cmd/golangorg/testdata/godev.txt поставляемый
Просмотреть файл

@ -109,3 +109,11 @@ redirect == /doc/security/
GET https://go.dev/doc/security/
body contains Security
GET https://go.dev/wiki/
body contains Go Wiki: Index
body contains <i>This content is part of the <a href="/wiki/">Go Wiki</a>.</i>
GET https://go.dev/wiki/Comments
body contains Go Wiki: Comments
body contains <i>This content is part of the <a href="/wiki/">Go Wiki</a>.</i>

8
cmd/golangorg/testdata/live.txt поставляемый
Просмотреть файл

@ -62,3 +62,11 @@ header Content-Type == text/plain; charset=utf-8
body !contains The Go Playground
body !contains About the Playground
body contains Hello, 世界
GET https://go.dev/wiki/Comments
body contains Go Wiki: Comments
body contains <i>This content is part of the <a href="/wiki/">Go Wiki</a>.</i>
GET https://go.dev/wiki/CommonMistakes
body contains Go Wiki: Common Mistakes
body contains <i>This content is part of the <a href="/wiki/">Go Wiki</a>.</i>

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

@ -111,8 +111,6 @@ var redirects = map[string]string{
"/doc/mem": "/ref/mem",
"/doc/spec": "/ref/spec",
"/wiki": "https://github.com/golang/go/wiki",
"/doc/articles/c_go_cgo.html": "/blog/c-go-cgo",
"/doc/articles/concurrency_patterns.html": "/blog/go-concurrency-patterns-timing-out-and",
"/doc/articles/defer_panic_recover.html": "/blog/defer-panic-and-recover",
@ -163,7 +161,6 @@ var newIssueRedirects = [...]string{
var prefixHelpers = map[string]string{
"issue": "https://github.com/golang/go/issues/",
"issues": "https://github.com/golang/go/issues/",
"wiki": "https://github.com/golang/go/wiki/",
}
func Handler(target string) http.Handler {

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

@ -85,9 +85,6 @@ func TestRedirects(t *testing.T) {
"/issues/new/choose": errorResult(404),
"/issues/1/2/3": errorResult(404),
"/wiki/foo": {302, "https://github.com/golang/go/wiki/foo"},
"/wiki/foo/": {302, "https://github.com/golang/go/wiki/foo/"},
"/design": {301, "https://go.googlesource.com/proposal/+/master/design"},
"/design/": {302, "/design"},
"/design/123-foo": {302, "https://go.googlesource.com/proposal/+/master/design/123-foo.md"},

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

@ -127,6 +127,12 @@ func (site *Site) renderHTML(p Page, tmpl string, r *http.Request) ([]byte, erro
// Either the page explicitly requested templating, or it is markdown,
// which is treated as a template by default.
isTemplate, explicit := p["template"].(bool)
// The wiki is not templated by default.
if !explicit && strings.HasPrefix(file, "wiki/") {
isTemplate, explicit = false, true
}
tdata := data
if !explicit || isTemplate {
// Load content as a template.
@ -193,6 +199,7 @@ func markdownToHTML(markdown string) (template.HTML, error) {
extension.WithLinkifyEmailRegexp(regexp.MustCompile(`[^\x00-\x{10FFFF}]`)), // impossible
),
extension.DefinitionList,
extension.NewTable(),
),
)
var buf bytes.Buffer