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