github.com/xushiwei/go@v0.0.0-20130601165731-2b9d83f45bc9/src/cmd/godoc/template.go (about) 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 main 33 34 import ( 35 "bytes" 36 "fmt" 37 "log" 38 "regexp" 39 "strings" 40 "text/template" 41 ) 42 43 // Functions in this file panic on error, but the panic is recovered 44 // to an error by 'code'. 45 46 var templateFuncs = template.FuncMap{ 47 "code": code, 48 } 49 50 // contents reads and returns the content of the named file 51 // (from the virtual file system, so for example /doc refers to $GOROOT/doc). 52 func contents(name string) string { 53 file, err := ReadFile(fs, name) 54 if err != nil { 55 log.Panic(err) 56 } 57 return string(file) 58 } 59 60 // stringFor returns a textual representation of the arg, formatted according to its nature. 61 func stringFor(arg interface{}) string { 62 switch arg := arg.(type) { 63 case int: 64 return fmt.Sprintf("%d", arg) 65 case string: 66 if len(arg) > 2 && arg[0] == '/' && arg[len(arg)-1] == '/' { 67 return fmt.Sprintf("%#q", arg) 68 } 69 return fmt.Sprintf("%q", arg) 70 default: 71 log.Panicf("unrecognized argument: %v type %T", arg, arg) 72 } 73 return "" 74 } 75 76 func code(file string, arg ...interface{}) (s string, err error) { 77 defer func() { 78 if r := recover(); r != nil { 79 err = fmt.Errorf("%v", r) 80 } 81 }() 82 83 text := contents(file) 84 var command string 85 switch len(arg) { 86 case 0: 87 // text is already whole file. 88 command = fmt.Sprintf("code %q", file) 89 case 1: 90 command = fmt.Sprintf("code %q %s", file, stringFor(arg[0])) 91 text = oneLine(file, text, arg[0]) 92 case 2: 93 command = fmt.Sprintf("code %q %s %s", file, stringFor(arg[0]), stringFor(arg[1])) 94 text = multipleLines(file, text, arg[0], arg[1]) 95 default: 96 return "", fmt.Errorf("incorrect code invocation: code %q %q", file, arg) 97 } 98 // Trim spaces from output. 99 text = strings.Trim(text, "\n") 100 // Replace tabs by spaces, which work better in HTML. 101 text = strings.Replace(text, "\t", " ", -1) 102 var buf bytes.Buffer 103 // HTML-escape text and syntax-color comments like elsewhere. 104 FormatText(&buf, []byte(text), -1, true, "", nil) 105 // Include the command as a comment. 106 text = fmt.Sprintf("<pre><!--{{%s}}\n-->%s</pre>", command, buf.Bytes()) 107 return text, nil 108 } 109 110 // parseArg returns the integer or string value of the argument and tells which it is. 111 func parseArg(arg interface{}, file string, max int) (ival int, sval string, isInt bool) { 112 switch n := arg.(type) { 113 case int: 114 if n <= 0 || n > max { 115 log.Panicf("%q:%d is out of range", file, n) 116 } 117 return n, "", true 118 case string: 119 return 0, n, false 120 } 121 log.Panicf("unrecognized argument %v type %T", arg, arg) 122 return 123 } 124 125 // oneLine returns the single line generated by a two-argument code invocation. 126 func oneLine(file, text string, arg interface{}) string { 127 lines := strings.SplitAfter(contents(file), "\n") 128 line, pattern, isInt := parseArg(arg, file, len(lines)) 129 if isInt { 130 return lines[line-1] 131 } 132 return lines[match(file, 0, lines, pattern)-1] 133 } 134 135 // multipleLines returns the text generated by a three-argument code invocation. 136 func multipleLines(file, text string, arg1, arg2 interface{}) string { 137 lines := strings.SplitAfter(contents(file), "\n") 138 line1, pattern1, isInt1 := parseArg(arg1, file, len(lines)) 139 line2, pattern2, isInt2 := parseArg(arg2, file, len(lines)) 140 if !isInt1 { 141 line1 = match(file, 0, lines, pattern1) 142 } 143 if !isInt2 { 144 line2 = match(file, line1, lines, pattern2) 145 } else if line2 < line1 { 146 log.Panicf("lines out of order for %q: %d %d", text, line1, line2) 147 } 148 for k := line1 - 1; k < line2; k++ { 149 if strings.HasSuffix(lines[k], "OMIT\n") { 150 lines[k] = "" 151 } 152 } 153 return strings.Join(lines[line1-1:line2], "") 154 } 155 156 // match identifies the input line that matches the pattern in a code invocation. 157 // If start>0, match lines starting there rather than at the beginning. 158 // The return value is 1-indexed. 159 func match(file string, start int, lines []string, pattern string) int { 160 // $ matches the end of the file. 161 if pattern == "$" { 162 if len(lines) == 0 { 163 log.Panicf("%q: empty file", file) 164 } 165 return len(lines) 166 } 167 // /regexp/ matches the line that matches the regexp. 168 if len(pattern) > 2 && pattern[0] == '/' && pattern[len(pattern)-1] == '/' { 169 re, err := regexp.Compile(pattern[1 : len(pattern)-1]) 170 if err != nil { 171 log.Panic(err) 172 } 173 for i := start; i < len(lines); i++ { 174 if re.MatchString(lines[i]) { 175 return i + 1 176 } 177 } 178 log.Panicf("%s: no match for %#q", file, pattern) 179 } 180 log.Panicf("unrecognized pattern: %q", pattern) 181 return 0 182 }