diff options
| author | Russ Cox <rsc@golang.org> | 2021-08-02 11:23:53 -0400 |
|---|---|---|
| committer | Russ Cox <rsc@golang.org> | 2021-08-17 13:59:37 +0000 |
| commit | ef7fed48ece033b41e88ed9a4cf40cdf9bd9de05 (patch) | |
| tree | 5de02efe25f644ee3eb124d5b3f9091d40aab750 /internal/web/code.go | |
| parent | f8f1822414a54ce50388ab00e3a4c20f0ae15046 (diff) | |
| download | go-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.go | 147 |
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 +} |
