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