-
Notifications
You must be signed in to change notification settings - Fork 207
/
examples.go
153 lines (129 loc) · 3.48 KB
/
examples.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
// Copyright 2021 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 main
import (
"fmt"
"net/http"
"os"
"path/filepath"
"runtime"
"sort"
"strings"
"time"
)
// examplesHandler serves example content out of the examples directory.
type examplesHandler struct {
modtime time.Time
examples []example
}
type example struct {
Title string
Path string
Content string
}
func (h *examplesHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
for _, e := range h.examples {
if e.Path == req.URL.Path {
http.ServeContent(w, req, e.Path, h.modtime, strings.NewReader(e.Content))
return
}
}
http.NotFound(w, req)
}
// hello returns the hello text for this instance, which depends on the Go
// version and whether or not we are serving Gotip examples.
func (h *examplesHandler) hello() string {
return h.examples[0].Content
}
// newExamplesHandler reads from the examples directory, returning a handler to
// serve their content.
//
// If gotip is set, all files ending in .txt will be included in the set of
// examples. If gotip is not set, files ending in .gotip.txt are excluded.
// Examples must start with a line beginning "// Title:" that sets their title.
//
// modtime is used for content caching headers.
func newExamplesHandler(gotip bool, modtime time.Time) (*examplesHandler, error) {
const dir = "examples"
entries, err := os.ReadDir(dir)
if err != nil {
return nil, err
}
var examples []example
for _, entry := range entries {
name := entry.Name()
// Read examples ending in .txt, skipping those ending in .gotip.txt if
// gotip is not set.
prefix := "" // if non-empty, this is a relevant example file
if strings.HasSuffix(name, ".gotip.txt") {
if gotip {
prefix = strings.TrimSuffix(name, ".gotip.txt")
}
} else if strings.HasSuffix(name, ".txt") {
prefix = strings.TrimSuffix(name, ".txt")
}
if prefix == "" {
continue
}
data, err := os.ReadFile(filepath.Join(dir, name))
if err != nil {
return nil, err
}
content := string(data)
// Extract the magic "// Title:" comment specifying the example's title.
nl := strings.IndexByte(content, '\n')
const titlePrefix = "// Title:"
if nl == -1 || !strings.HasPrefix(content, titlePrefix) {
return nil, fmt.Errorf("malformed example for %q: must start with a title line beginning %q", name, titlePrefix)
}
title := strings.TrimPrefix(content[:nl], titlePrefix)
title = strings.TrimSpace(title)
examples = append(examples, example{
Title: title,
Path: name,
Content: content[nl+1:],
})
}
// Sort by title, before prepending the hello example (we always want Hello
// to be first).
sort.Slice(examples, func(i, j int) bool {
return examples[i].Title < examples[j].Title
})
// For Gotip, serve hello content that includes the Go version.
hi := hello
if gotip {
hi = helloGotip
}
examples = append([]example{
{"Hello, playground", "hello.txt", hi},
}, examples...)
return &examplesHandler{
modtime: modtime,
examples: examples,
}, nil
}
const hello = `package main
import (
"fmt"
)
func main() {
fmt.Println("Hello, playground")
}
`
var helloGotip = fmt.Sprintf(`package main
import (
"fmt"
)
// This playground uses a development build of Go:
// %s
func Print[T any](s ...T) {
for _, v := range s {
fmt.Print(v)
}
}
func main() {
Print("Hello, ", "playground\n")
}
`, runtime.Version())