Giant blob of minor changes
[dotfiles/.git] / .config / coc / extensions / coc-go-data / tools / pkg / mod / golang.org / x / tools@v0.0.0-20201105173854-bc9fc8d8c4bc / godoc / template.go
1 // Copyright 2011 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 // Template support for writing HTML documents.
6 // Documents that include Template: true in their
7 // metadata are executed as input to text/template.
8 //
9 // This file defines functions for those templates to invoke.
10
11 // The template uses the function "code" to inject program
12 // source into the output by extracting code from files and
13 // injecting them as HTML-escaped <pre> blocks.
14 //
15 // The syntax is simple: 1, 2, or 3 space-separated arguments:
16 //
17 // Whole file:
18 //      {{code "foo.go"}}
19 // One line (here the signature of main):
20 //      {{code "foo.go" `/^func.main/`}}
21 // Block of text, determined by start and end (here the body of main):
22 //      {{code "foo.go" `/^func.main/` `/^}/`
23 //
24 // Patterns can be `/regular expression/`, a decimal number, or "$"
25 // to signify the end of the file. In multi-line matches,
26 // lines that end with the four characters
27 //      OMIT
28 // are omitted from the output, making it easy to provide marker
29 // lines in the input that will not appear in the output but are easy
30 // to identify by pattern.
31
32 package godoc
33
34 import (
35         "bytes"
36         "fmt"
37         "log"
38         "regexp"
39         "strings"
40
41         "golang.org/x/tools/godoc/vfs"
42 )
43
44 // Functions in this file panic on error, but the panic is recovered
45 // to an error by 'code'.
46
47 // contents reads and returns the content of the named file
48 // (from the virtual file system, so for example /doc refers to $GOROOT/doc).
49 func (c *Corpus) contents(name string) string {
50         file, err := vfs.ReadFile(c.fs, name)
51         if err != nil {
52                 log.Panic(err)
53         }
54         return string(file)
55 }
56
57 // stringFor returns a textual representation of the arg, formatted according to its nature.
58 func stringFor(arg interface{}) string {
59         switch arg := arg.(type) {
60         case int:
61                 return fmt.Sprintf("%d", arg)
62         case string:
63                 if len(arg) > 2 && arg[0] == '/' && arg[len(arg)-1] == '/' {
64                         return fmt.Sprintf("%#q", arg)
65                 }
66                 return fmt.Sprintf("%q", arg)
67         default:
68                 log.Panicf("unrecognized argument: %v type %T", arg, arg)
69         }
70         return ""
71 }
72
73 func (p *Presentation) code(file string, arg ...interface{}) (s string, err error) {
74         defer func() {
75                 if r := recover(); r != nil {
76                         err = fmt.Errorf("%v", r)
77                 }
78         }()
79
80         text := p.Corpus.contents(file)
81         var command string
82         switch len(arg) {
83         case 0:
84                 // text is already whole file.
85                 command = fmt.Sprintf("code %q", file)
86         case 1:
87                 command = fmt.Sprintf("code %q %s", file, stringFor(arg[0]))
88                 text = p.Corpus.oneLine(file, text, arg[0])
89         case 2:
90                 command = fmt.Sprintf("code %q %s %s", file, stringFor(arg[0]), stringFor(arg[1]))
91                 text = p.Corpus.multipleLines(file, text, arg[0], arg[1])
92         default:
93                 return "", fmt.Errorf("incorrect code invocation: code %q [%v, ...] (%d arguments)", file, arg[0], len(arg))
94         }
95         // Trim spaces from output.
96         text = strings.Trim(text, "\n")
97         // Replace tabs by spaces, which work better in HTML.
98         text = strings.Replace(text, "\t", "    ", -1)
99         var buf bytes.Buffer
100         // HTML-escape text and syntax-color comments like elsewhere.
101         FormatText(&buf, []byte(text), -1, true, "", nil)
102         // Include the command as a comment.
103         text = fmt.Sprintf("<pre><!--{{%s}}\n-->%s</pre>", command, buf.Bytes())
104         return text, nil
105 }
106
107 // parseArg returns the integer or string value of the argument and tells which it is.
108 func parseArg(arg interface{}, file string, max int) (ival int, sval string, isInt bool) {
109         switch n := arg.(type) {
110         case int:
111                 if n <= 0 || n > max {
112                         log.Panicf("%q:%d is out of range", file, n)
113                 }
114                 return n, "", true
115         case string:
116                 return 0, n, false
117         }
118         log.Panicf("unrecognized argument %v type %T", arg, arg)
119         return
120 }
121
122 // oneLine returns the single line generated by a two-argument code invocation.
123 func (c *Corpus) oneLine(file, text string, arg interface{}) string {
124         lines := strings.SplitAfter(c.contents(file), "\n")
125         line, pattern, isInt := parseArg(arg, file, len(lines))
126         if isInt {
127                 return lines[line-1]
128         }
129         return lines[match(file, 0, lines, pattern)-1]
130 }
131
132 // multipleLines returns the text generated by a three-argument code invocation.
133 func (c *Corpus) multipleLines(file, text string, arg1, arg2 interface{}) string {
134         lines := strings.SplitAfter(c.contents(file), "\n")
135         line1, pattern1, isInt1 := parseArg(arg1, file, len(lines))
136         line2, pattern2, isInt2 := parseArg(arg2, file, len(lines))
137         if !isInt1 {
138                 line1 = match(file, 0, lines, pattern1)
139         }
140         if !isInt2 {
141                 line2 = match(file, line1, lines, pattern2)
142         } else if line2 < line1 {
143                 log.Panicf("lines out of order for %q: %d %d", text, line1, line2)
144         }
145         for k := line1 - 1; k < line2; k++ {
146                 if strings.HasSuffix(lines[k], "OMIT\n") {
147                         lines[k] = ""
148                 }
149         }
150         return strings.Join(lines[line1-1:line2], "")
151 }
152
153 // match identifies the input line that matches the pattern in a code invocation.
154 // If start>0, match lines starting there rather than at the beginning.
155 // The return value is 1-indexed.
156 func match(file string, start int, lines []string, pattern string) int {
157         // $ matches the end of the file.
158         if pattern == "$" {
159                 if len(lines) == 0 {
160                         log.Panicf("%q: empty file", file)
161                 }
162                 return len(lines)
163         }
164         // /regexp/ matches the line that matches the regexp.
165         if len(pattern) > 2 && pattern[0] == '/' && pattern[len(pattern)-1] == '/' {
166                 re, err := regexp.Compile(pattern[1 : len(pattern)-1])
167                 if err != nil {
168                         log.Panic(err)
169                 }
170                 for i := start; i < len(lines); i++ {
171                         if re.MatchString(lines[i]) {
172                                 return i + 1
173                         }
174                 }
175                 log.Panicf("%s: no match for %#q", file, pattern)
176         }
177         log.Panicf("unrecognized pattern: %q", pattern)
178         return 0
179 }