Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code | Sign in
(1419)

Delta Between Two Patch Sets: pkg/present/code.go

Issue 10539043: code review 10539043: go.talks/pkg/present: include line numbers in output HTML (Closed)
Left Patch Set: diff -r 27c84def09a6 https://code.google.com/p/go.talks Created 10 years, 9 months ago
Right Patch Set: diff -r 27c84def09a6 https://code.google.com/p/go.talks Created 10 years, 9 months ago
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments. Please Sign in to add in-line comments.
Jump to:
Left: Side by side diff | Download
Right: Side by side diff | Download
« no previous file with change/comment | « no previous file | present/js/play.js » ('j') | no next file with change/comment »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
LEFTRIGHT
1 // Copyright 2012 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
1 package present 5 package present
2 6
3 import ( 7 import (
8 "bufio"
9 "bytes"
4 "fmt" 10 "fmt"
5 "html/template" 11 "html/template"
6 "path/filepath" 12 "path/filepath"
13 "regexp"
14 "strconv"
15 "strings"
16 )
17
18 // Is the playground available?
19 var PlayEnabled = false
20
21 // TOOD(adg): replace the PlayEnabled flag with something less spaghetti-like.
22 // Instead this will probably be determined by a template execution Context
23 // value that contains various global metadata required when rendering
24 // templates.
25
26 func init() {
27 Register("code", parseCode)
28 Register("play", parseCode)
29 }
30
31 type Code struct {
32 Text template.HTML
33 Play bool // runnable code
34 }
35
36 func (c Code) TemplateName() string { return "code" }
37
38 // The input line is a .code or .play entry with a file name and an optional HLf oo marker on the end.
39 // Anything between the file and HL (if any) is an address expression, which we treat as a string here.
40 // We pick off the HL first, for easy parsing.
41 var (
42 highlightRE = regexp.MustCompile(`\s+HL([a-zA-Z0-9_]+)?$`)
43 hlCommentRE = regexp.MustCompile(`(.+) // HL(.*)$`)
44 codeRE = regexp.MustCompile(`\.(code|play)\s+([^\s]+)(\s+)?(.*)?$`)
45 )
46
47 func parseCode(ctx *Context, sourceFile string, sourceLine int, cmd string) (Ele m, error) {
48 cmd = strings.TrimSpace(cmd)
49
50 // Pull off the HL, if any, from the end of the input line.
51 highlight := ""
52 if hl := highlightRE.FindStringSubmatchIndex(cmd); len(hl) == 4 {
53 highlight = cmd[hl[2]:hl[3]]
54 cmd = cmd[:hl[2]-2]
55 }
56
57 // Parse the remaining command line.
58 // Arguments:
59 // args[0]: whole match
60 // args[1]: .code/.play
61 // args[2]: file name
62 // args[3]: space, if any, before optional address
63 // args[4]: optional address
64 args := codeRE.FindStringSubmatch(cmd)
65 if len(args) != 5 {
66 return nil, fmt.Errorf("%s:%d: syntax error for .code/.play invo cation", sourceFile, sourceLine)
67 }
68 command, file, addr := args[1], args[2], strings.TrimSpace(args[4])
69 play := command == "play" && PlayEnabled
70
71 // Read in code file and (optionally) match address.
72 filename := filepath.Join(filepath.Dir(sourceFile), file)
73 textBytes, err := ctx.ReadFile(filename)
74 if err != nil {
75 return nil, fmt.Errorf("%s:%d: %v", sourceFile, sourceLine, err)
76 }
77 lo, hi, err := addrToByteRange(addr, 0, textBytes)
78 if err != nil {
79 return nil, fmt.Errorf("%s:%d: %v", sourceFile, sourceLine, err)
80 }
81
82 // Acme pattern matches can stop mid-line,
83 // so run to end of line in both directions if not at line start/end.
84 for lo > 0 && textBytes[lo-1] != '\n' {
85 lo--
86 }
87 if hi > 0 {
88 for hi < len(textBytes) && textBytes[hi-1] != '\n' {
89 hi++
90 }
91 }
92
93 lines := codeLines(textBytes, lo, hi)
94
95 for i, line := range lines {
96 // Replace tabs by spaces, which work better in HTML.
97 line.L = strings.Replace(line.L, "\t", " ", -1)
98
99 // Highlight lines that end with "// HL[highlight]"
100 // and strip the magic comment.
101 if m := hlCommentRE.FindStringSubmatch(line.L); m != nil {
102 line.L = m[1]
103 line.HL = m[2] == highlight
104 }
105
106 lines[i] = line
107 }
108
109 data := &codeTemplateData{Lines: lines}
110
111 // Include before and after in a hidden span for playground code.
112 if play {
113 data.Prefix = textBytes[:lo]
114 data.Suffix = textBytes[hi:]
115 }
116
117 var buf bytes.Buffer
118 if err := codeTemplate.Execute(&buf, data); err != nil {
119 return nil, err
120 }
121 return Code{Text: template.HTML(buf.String()), Play: play}, nil
122 }
123
124 type codeTemplateData struct {
125 Lines []codeLine
126 Prefix, Suffix []byte
127 }
128
129 var leadingSpaceRE = regexp.MustCompile(`^[ \t]*`)
130
131 var codeTemplate = template.Must(template.New("code").Funcs(template.FuncMap{
132 "trimSpace": strings.TrimSpace,
133 "leadingSpace": leadingSpaceRE.FindString,
134 }).Parse(codeTemplateHTML))
135
136 const codeTemplateHTML = `
137 {{with .Prefix}}<pre style="display: none"><span>{{printf "%s" .}}</span></pre>{ {end}}
138
139 <pre>{{range .Lines}}<span num="{{.N}}">{{/*
140 */}}{{if .HL}}{{leadingSpace .L}}<b>{{trimSpace .L}}</b>{{/*
141 */}}{{else}}{{.L}}{{end}}{{/*
142 */}}</span>
143 {{end}}</pre>
144
145 {{with .Suffix}}<pre style="display: none"><span>{{printf "%s" .}}</span></pre>{ {end}}
146 `
147
148 // codeLine represents a line of code extracted from a source file.
149 type codeLine struct {
150 L string // The line of code.
151 N int // The line number from the source file.
152 HL bool // Whether the line should be highlighted.
153 }
154
155 // codeLines takes a source file and returns the lines that
156 // span the byte range specified by start and end.
157 // It discards lines that end in "OMIT".
158 func codeLines(src []byte, start, end int) (lines []codeLine) {
159 startLine := 1
160 for i, b := range src {
161 if i == start {
162 break
163 }
164 if b == '\n' {
165 startLine++
166 }
167 }
168 s := bufio.NewScanner(bytes.NewReader(src[start:end]))
169 for n := startLine; s.Scan(); n++ {
170 l := s.Text()
171 if strings.HasSuffix(l, "OMIT") {
172 continue
173 }
174 lines = append(lines, codeLine{L: l, N: n})
175 }
176 return
177 }
178
179 func parseArgs(name string, line int, args []string) (res []interface{}, err err or) {
180 res = make([]interface{}, len(args))
181 for i, v := range args {
182 if len(v) == 0 {
183 return nil, fmt.Errorf("%s:%d bad code argument %q", nam e, line, v)
184 }
185 switch v[0] {
186 case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
187 n, err := strconv.Atoi(v)
188 if err != nil {
189 return nil, fmt.Errorf("%s:%d bad code argument %q", name, line, v)
190 }
191 res[i] = n
192 case '/':
193 if len(v) < 2 || v[len(v)-1] != '/' {
194 return nil, fmt.Errorf("%s:%d bad code argument %q", name, line, v)
195 }
196 res[i] = v
197 case '$':
198 res[i] = "$"
199 default:
200 return nil, fmt.Errorf("%s:%d bad code argument %q", nam e, line, v)
201 }
202 }
203 return
204 }
205
206 // parseArg returns the integer or string value of the argument and tells which it is.
207 func parseArg(arg interface{}, max int) (ival int, sval string, isInt bool, err error) {
208 switch n := arg.(type) {
209 case int:
210 if n <= 0 || n > max {
211 return 0, "", false, fmt.Errorf("%d is out of range", n)
212 }
213 return n, "", true, nil
214 case string:
215 return 0, n, false, nil
216 }
217 return 0, "", false, fmt.Errorf("unrecognized argument %v type %T", arg, arg)
218 }
219
220 // oneLine returns the single line generated by a two-argument code invocation.
221 func oneLine(ctx *Context, file, text string, arg interface{}) (line, before, af ter string, err error) {
222 contentBytes, err := ctx.ReadFile(file)
223 if err != nil {
224 return "", "", "", err
225 }
226 lines := strings.SplitAfter(string(contentBytes), "\n")
227 lineNum, pattern, isInt, err := parseArg(arg, len(lines))
228 if err != nil {
229 return "", "", "", err
230 }
231 var n int
232 if isInt {
233 n = lineNum - 1
234 } else {
235 n, err = match(file, 0, lines, pattern)
236 n -= 1
237 }
238 if err != nil {
239 return "", "", "", err
240 }
241 return lines[n],
242 strings.Join(lines[:n], ""),
243 strings.Join(lines[n+1:], ""),
244 nil
245 }
246
247 // multipleLines returns the text generated by a three-argument code invocation.
248 func multipleLines(ctx *Context, file string, arg1, arg2 interface{}) (line, bef ore, after string, err error) {
249 contentBytes, err := ctx.ReadFile(file)
250 lines := strings.SplitAfter(string(contentBytes), "\n")
251 if err != nil {
252 return "", "", "", err
253 }
254 line1, pattern1, isInt1, err := parseArg(arg1, len(lines))
255 if err != nil {
256 return "", "", "", err
257 }
258 line2, pattern2, isInt2, err := parseArg(arg2, len(lines))
259 if err != nil {
260 return "", "", "", err
261 }
262 if !isInt1 {
263 line1, err = match(file, 0, lines, pattern1)
264 }
265 if !isInt2 {
266 line2, err = match(file, line1, lines, pattern2)
267 } else if line2 < line1 {
268 return "", "", "", fmt.Errorf("lines out of order for %q: %d %d" , file, line1, line2)
269 }
270 if err != nil {
271 return "", "", "", err
272 }
273 for k := line1 - 1; k < line2; k++ {
274 if strings.HasSuffix(lines[k], "OMIT\n") {
275 lines[k] = ""
276 }
277 }
278 return strings.Join(lines[line1-1:line2], ""),
279 strings.Join(lines[:line1-1], ""),
280 strings.Join(lines[line2:], ""),
281 nil
282 }
283
284 // match identifies the input line that matches the pattern in a code invocation .
285 // If start>0, match lines starting there rather than at the beginning.
286 // The return value is 1-indexed.
287 func match(file string, start int, lines []string, pattern string) (int, error) {
288 // $ matches the end of the file.
289 if pattern == "$" {
290 if len(lines) == 0 {
291 return 0, fmt.Errorf("%q: empty file", file)
292 }
293 return len(lines), nil
294 }
295 // /regexp/ matches the line that matches the regexp.
296 if len(pattern) > 2 && pattern[0] == '/' && pattern[len(pattern)-1] == ' /' {
297 re, err := regexp.Compile(pattern[1 : len(pattern)-1])
298 if err != nil {
299 return 0, err
300 }
301 for i := start; i < len(lines); i++ {
302 if re.MatchString(lines[i]) {
303 return i + 1, nil
304 }
305 }
306 return 0, fmt.Errorf("%s: no match for %#q", file, pattern)
307 }
308 return 0, fmt.Errorf("unrecognized pattern: %q", pattern)
309 }
LEFTRIGHT
« no previous file | present/js/play.js » ('j') | Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Toggle Comments ('s')

Powered by Google App Engine
RSS Feeds Recent Issues | This issue
This is Rietveld f62528b