aboutsummaryrefslogtreecommitdiff
path: root/internal/web/code.go
diff options
context:
space:
mode:
authorRuss Cox <rsc@golang.org>2021-08-02 11:23:53 -0400
committerRuss Cox <rsc@golang.org>2021-08-17 13:59:37 +0000
commitef7fed48ece033b41e88ed9a4cf40cdf9bd9de05 (patch)
tree5de02efe25f644ee3eb124d5b3f9091d40aab750 /internal/web/code.go
parentf8f1822414a54ce50388ab00e3a4c20f0ae15046 (diff)
downloadgo-x-website-ef7fed48ece033b41e88ed9a4cf40cdf9bd9de05.tar.xz
internal/web: merge with go.dev/cmd/internal/site
internal/web was the framework left serving golang.org. go.dev/cmd/internal/site was the framework serving go.dev. This CL merges the two into a coherent, simple site serving framework that works for both sites, a step toward merging the sites themselves. The CL is difficult to break up, so it's a bit larger than would be ideal. The best place to start is the doc comment in internal/web/site.go and then the other changes in that directory. The rest of the CL is just minor adjustments to the repo to match. Change-Id: I927dea29396104a817bd81b6bf25fa43f996968f Reviewed-on: https://go-review.googlesource.com/c/website/+/339403 Trust: Russ Cox <rsc@golang.org> Website-Publish: Russ Cox <rsc@golang.org> Reviewed-by: Jamal Carvalho <jamal@golang.org>
Diffstat (limited to 'internal/web/code.go')
-rw-r--r--internal/web/code.go147
1 files changed, 147 insertions, 0 deletions
diff --git a/internal/web/code.go b/internal/web/code.go
new file mode 100644
index 00000000..3da494e2
--- /dev/null
+++ b/internal/web/code.go
@@ -0,0 +1,147 @@
+// Copyright 2013 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 web
+
+import (
+ "bytes"
+ "fmt"
+ "log"
+ "regexp"
+ "strings"
+
+ "golang.org/x/website/internal/backport/html/template"
+ "golang.org/x/website/internal/texthtml"
+)
+
+func (s *siteDir) code(file string, arg ...interface{}) (_ template.HTML, err error) {
+ defer func() {
+ if r := recover(); r != nil {
+ err = fmt.Errorf("%v", r)
+ }
+ }()
+
+ btext, err := s.readFile(s.dir, file)
+ if err != nil {
+ return "", err
+ }
+ text := string(btext)
+ var command string
+ switch len(arg) {
+ case 0:
+ // text is already whole file.
+ command = fmt.Sprintf("code %q", file)
+ case 1:
+ command = fmt.Sprintf("code %q %s", file, stringFor(arg[0]))
+ text = s.oneLine(file, text, arg[0])
+ case 2:
+ command = fmt.Sprintf("code %q %s %s", file, stringFor(arg[0]), stringFor(arg[1]))
+ text = s.multipleLines(file, text, arg[0], arg[1])
+ default:
+ return "", fmt.Errorf("incorrect code invocation: code %q [%v, ...] (%d arguments)", file, arg[0], len(arg))
+ }
+ // Trim spaces from output.
+ text = strings.Trim(text, "\n")
+ // Replace tabs by spaces, which work better in HTML.
+ text = strings.Replace(text, "\t", " ", -1)
+ var buf bytes.Buffer
+ // HTML-escape text and syntax-color comments like elsewhere.
+ buf.Write(texthtml.Format([]byte(text), texthtml.Config{GoComments: true}))
+ // Include the command as a comment.
+ text = fmt.Sprintf("<pre><!--{{%s}}\n-->%s</pre>", command, buf.Bytes())
+ return template.HTML(text), nil
+}
+
+// Functions in this file panic on error, but the panic is recovered
+// to an error by 'code'.
+
+// stringFor returns a textual representation of the arg, formatted according to its nature.
+func stringFor(arg interface{}) string {
+ switch arg := arg.(type) {
+ case int:
+ return fmt.Sprintf("%d", arg)
+ case string:
+ if len(arg) > 2 && arg[0] == '/' && arg[len(arg)-1] == '/' {
+ return fmt.Sprintf("%#q", arg)
+ }
+ return fmt.Sprintf("%q", arg)
+ default:
+ log.Panicf("unrecognized argument: %v type %T", arg, arg)
+ }
+ return ""
+}
+
+// oneLine returns the single line generated by a two-argument code invocation.
+func (s *Site) oneLine(file, text string, arg interface{}) string {
+ lines := strings.SplitAfter(text, "\n")
+ line, pattern, isInt := parseArg(arg, file, len(lines))
+ if isInt {
+ return lines[line-1]
+ }
+ return lines[match(file, 0, lines, pattern)-1]
+}
+
+// multipleLines returns the text generated by a three-argument code invocation.
+func (s *Site) multipleLines(file, text string, arg1, arg2 interface{}) string {
+ lines := strings.SplitAfter(text, "\n")
+ line1, pattern1, isInt1 := parseArg(arg1, file, len(lines))
+ line2, pattern2, isInt2 := parseArg(arg2, file, len(lines))
+ if !isInt1 {
+ line1 = match(file, 0, lines, pattern1)
+ }
+ if !isInt2 {
+ line2 = match(file, line1, lines, pattern2)
+ } else if line2 < line1 {
+ log.Panicf("lines out of order for %q: %d %d", file, line1, line2)
+ }
+ for k := line1 - 1; k < line2; k++ {
+ if strings.HasSuffix(lines[k], "OMIT\n") {
+ lines[k] = ""
+ }
+ }
+ return strings.Join(lines[line1-1:line2], "")
+}
+
+// parseArg returns the integer or string value of the argument and tells which it is.
+func parseArg(arg interface{}, file string, max int) (ival int, sval string, isInt bool) {
+ switch n := arg.(type) {
+ case int:
+ if n <= 0 || n > max {
+ log.Panicf("%q:%d is out of range", file, n)
+ }
+ return n, "", true
+ case string:
+ return 0, n, false
+ }
+ log.Panicf("unrecognized argument %v type %T", arg, arg)
+ return
+}
+
+// match identifies the input line that matches the pattern in a code invocation.
+// If start>0, match lines starting there rather than at the beginning.
+// The return value is 1-indexed.
+func match(file string, start int, lines []string, pattern string) int {
+ // $ matches the end of the file.
+ if pattern == "$" {
+ if len(lines) == 0 {
+ log.Panicf("%q: empty file", file)
+ }
+ return len(lines)
+ }
+ // /regexp/ matches the line that matches the regexp.
+ if len(pattern) > 2 && pattern[0] == '/' && pattern[len(pattern)-1] == '/' {
+ re, err := regexp.Compile(pattern[1 : len(pattern)-1])
+ if err != nil {
+ log.Panic(err)
+ }
+ for i := start; i < len(lines); i++ {
+ if re.MatchString(lines[i]) {
+ return i + 1
+ }
+ }
+ log.Panicf("%s: no match for %#q", file, pattern)
+ }
+ log.Panicf("unrecognized pattern: %q", pattern)
+ return 0
+}