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

Side by Side Diff: src/cmd/godoc/codewalk.go

Issue 1008042: code review 1008042: godoc: add codewalk support (Closed)
Patch Set: code review 1008042: godoc: add codewalk support Created 14 years, 10 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:
View unified diff | Download patch
« no previous file with comments | « src/cmd/godoc/Makefile ('k') | src/cmd/godoc/godoc.go » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright 2010 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 // The /doc/codewalk/ tree is synthesized from codewalk descriptions,
6 // files named $GOROOT/doc/codewalk/*.xml.
7 // For an example and a description of the format, see
8 // http://golang.org/doc/codewalk/codewalk or run godoc -http=:6060
9 // and see http://localhost:6060/doc/codewalk/codewalk .
10 // That page is itself a codewalk; the source code for it is
11 // $GOROOT/doc/codewalk/codewalk.xml.
12
13 package main
14
15 import (
16 "container/vector"
17 "fmt"
18 "http"
19 "io"
20 "io/ioutil"
21 "log"
22 "os"
23 "regexp"
24 "sort"
25 "strconv"
26 "strings"
27 "template"
28 "utf8"
29 "xml"
30 )
31
32
33 // Handler for /doc/codewalk/ and below.
34 func codewalk(c *http.Conn, r *http.Request) {
35 relpath := r.URL.Path[len("/doc/codewalk/"):]
36 abspath := absolutePath(r.URL.Path[1:], *goroot)
37
38 r.ParseForm()
39 if f := r.FormValue("fileprint"); f != "" {
40 codewalkFileprint(c, r, f)
41 return
42 }
43
44 // If directory exists, serve list of code walks.
45 dir, err := os.Lstat(abspath)
46 if err == nil && dir.IsDirectory() {
47 codewalkDir(c, r, relpath, abspath)
48 return
49 }
50
51 // If file exists, serve using standard file server.
52 if err == nil {
53 serveFile(c, r)
54 return
55 }
56
57 // Otherwise append .xml and hope to find
58 // a codewalk description.
59 cw, err := loadCodewalk(abspath + ".xml")
60 if err != nil {
61 log.Stderr(err)
62 serveError(c, r, relpath, err)
63 return
64 }
65
66 b := applyTemplate(codewalkHTML, "codewalk", cw)
67 servePage(c, "Codewalk: "+cw.Title, "", "", b)
68 }
69
70
71 // A Codewalk represents a single codewalk read from an XML file.
72 type Codewalk struct {
73 Title string "attr"
74 File []string
75 Step []*Codestep
76 }
77
78
79 // A Codestep is a single step in a codewalk.
80 type Codestep struct {
81 // Filled in from XML
82 Src string "attr"
83 Title string "attr"
84 XML string "innerxml"
85
86 // Derived from Src; not in XML.
87 Err os.Error
88 File string
89 Lo int
90 LoByte int
91 Hi int
92 HiByte int
93 Data []byte
94 }
95
96
97 // String method for printing in template.
98 // Formats file address nicely.
99 func (st *Codestep) String() string {
100 s := st.File
101 if st.Lo != 0 || st.Hi != 0 {
102 s += fmt.Sprintf(":%d", st.Lo)
103 if st.Lo != st.Hi {
104 s += fmt.Sprintf(",%d", st.Hi)
105 }
106 }
107 return s
108 }
109
110
111 // loadCodewalk reads a codewalk from the named XML file.
112 func loadCodewalk(file string) (*Codewalk, os.Error) {
113 f, err := os.Open(file, os.O_RDONLY, 0)
114 if err != nil {
115 return nil, err
116 }
117 defer f.Close()
118 cw := new(Codewalk)
119 p := xml.NewParser(f)
120 p.Entity = xml.HTMLEntity
121 err = p.Unmarshal(cw, nil)
122 if err != nil {
123 return nil, &os.PathError{"parsing", file, err}
124 }
125
126 // Compute file list, evaluate line numbers for addresses.
127 m := make(map[string]bool)
128 for _, st := range cw.Step {
129 i := strings.Index(st.Src, ":")
130 if i < 0 {
131 i = len(st.Src)
132 }
133 file := st.Src[0:i]
134 data, err := ioutil.ReadFile(absolutePath(file, *goroot))
135 if err != nil {
136 st.Err = err
137 continue
138 }
139 if i < len(st.Src) {
140 lo, hi, err := addrToByteRange(st.Src[i+1:], 0, data)
141 if err != nil {
142 st.Err = err
143 continue
144 }
145 // Expand match to line boundaries.
146 for lo > 0 && data[lo-1] != '\n' {
147 lo--
148 }
149 for hi < len(data) && (hi == 0 || data[hi-1] != '\n') {
150 hi++
151 }
152 st.Lo = byteToLine(data, lo)
153 st.Hi = byteToLine(data, hi-1)
154 }
155 st.Data = data
156 st.File = file
157 m[file] = true
158 }
159
160 // Make list of files
161 cw.File = make([]string, len(m))
162 i := 0
163 for f := range m {
164 cw.File[i] = f
165 i++
166 }
167 sort.SortStrings(cw.File)
168
169 return cw, nil
170 }
171
172
173 // codewalkDir serves the codewalk directory listing.
174 // It scans the directory for subdirectories or files named *.xml
175 // and prepares a table.
176 func codewalkDir(c *http.Conn, r *http.Request, relpath, abspath string) {
177 type elem struct {
178 Name string
179 Title string
180 }
181
182 dir, err := ioutil.ReadDir(abspath)
183 if err != nil {
184 log.Stderr(err)
185 serveError(c, r, relpath, err)
186 return
187 }
188 var v vector.Vector
189 for _, fi := range dir {
190 if fi.IsDirectory() {
191 v.Push(&elem{fi.Name + "/", ""})
192 } else if strings.HasSuffix(fi.Name, ".xml") {
193 cw, err := loadCodewalk(abspath + "/" + fi.Name)
194 if err != nil {
195 continue
196 }
197 v.Push(&elem{fi.Name[0 : len(fi.Name)-len(".xml")], cw.T itle})
198 }
199 }
200
201 b := applyTemplate(codewalkdirHTML, "codewalkdir", v)
202 servePage(c, "Codewalks", "", "", b)
203 }
204
205
206 // codewalkFileprint serves requests with ?fileprint=f&lo=lo&hi=hi.
207 // The filename f has already been retrieved and is passed as an argument.
208 // Lo and hi are the numbers of the first and last line to highlight
209 // in the response. This format is used for the middle window pane
210 // of the codewalk pages. It is a separate iframe and does not get
211 // the usual godoc HTML wrapper.
212 func codewalkFileprint(c *http.Conn, r *http.Request, f string) {
213 abspath := absolutePath(f, *goroot)
214 data, err := ioutil.ReadFile(abspath)
215 if err != nil {
216 serveError(c, r, f, err)
217 return
218 }
219 lo, _ := strconv.Atoi(r.FormValue("lo"))
220 hi, _ := strconv.Atoi(r.FormValue("hi"))
221 if hi < lo {
222 hi = lo
223 }
224 lo = lineToByte(data, lo)
225 hi = lineToByte(data, hi+1)
226
227 // Put the mark 4 lines before lo, so that the iframe
228 // shows a few lines of context before the highlighted
229 // section.
230 n := 4
231 mark := lo
232 for ; mark > 0 && n > 0; mark-- {
233 if data[mark-1] == '\n' {
234 if n--; n == 0 {
235 break
236 }
237 }
238 }
239
240 io.WriteString(c, `<style type="text/css">@import "/doc/codewalk/codewal k.css";</style><pre>`)
241 template.HTMLEscape(c, data[0:mark])
242 io.WriteString(c, "<a name='mark'></a>")
243 template.HTMLEscape(c, data[mark:lo])
244 if lo < hi {
245 io.WriteString(c, "<div class='codewalkhighlight'>")
246 template.HTMLEscape(c, data[lo:hi])
247 io.WriteString(c, "</div>")
248 }
249 template.HTMLEscape(c, data[hi:])
250 io.WriteString(c, "</pre>")
251 }
252
253
254 // addrToByte evaluates the given address starting at offset start in data.
255 // It returns the lo and hi byte offset of the matched region within data.
256 // See http://plan9.bell-labs.com/sys/doc/sam/sam.html Table II
257 // for details on the syntax.
258 func addrToByteRange(addr string, start int, data []byte) (lo, hi int, err os.Er ror) {
259 var (
260 dir byte
261 prevc byte
262 charOffset bool
263 )
264 lo = start
265 hi = start
266 for addr != "" && err == nil {
267 c := addr[0]
268 switch c {
269 default:
270 err = os.NewError("invalid address syntax near " + strin g(c))
271 case ',':
272 if len(addr) == 1 {
273 hi = len(data)
274 } else {
275 _, hi, err = addrToByteRange(addr[1:], hi, data)
276 }
277 return
278
279 case '+', '-':
280 if prevc == '+' || prevc == '-' {
281 lo, hi, err = addrNumber(data, lo, hi, prevc, 1, charOffset)
282 }
283 dir = c
284
285 case '$':
286 lo = len(data)
287 hi = len(data)
288 if len(addr) > 1 {
289 dir = '+'
290 }
291
292 case '#':
293 charOffset = true
294
295 case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
296 var i int
297 for i = 1; i < len(addr); i++ {
298 if addr[i] < '0' || addr[i] > '9' {
299 break
300 }
301 }
302 var n int
303 n, err = strconv.Atoi(addr[0:i])
304 if err != nil {
305 break
306 }
307 lo, hi, err = addrNumber(data, lo, hi, dir, n, charOffse t)
308 dir = 0
309 charOffset = false
310 prevc = c
311 addr = addr[i:]
312 continue
313
314 case '/':
315 var i, j int
316 Regexp:
317 for i = 1; i < len(addr); i++ {
318 switch addr[i] {
319 case '\\':
320 i++
321 case '/':
322 j = i + 1
323 break Regexp
324 }
325 }
326 if j == 0 {
327 j = i
328 }
329 pattern := addr[1:i]
330 lo, hi, err = addrRegexp(data, lo, hi, dir, pattern)
331 prevc = c
332 addr = addr[j:]
333 continue
334 }
335 prevc = c
336 addr = addr[1:]
337 }
338
339 if err == nil && dir != 0 {
340 lo, hi, err = addrNumber(data, lo, hi, dir, 1, charOffset)
341 }
342 if err != nil {
343 return 0, 0, err
344 }
345 return lo, hi, nil
346 }
347
348
349 // addrNumber applies the given dir, n, and charOffset to the address lo, hi.
350 // dir is '+' or '-', n is the count, and charOffset is true if the syntax
351 // used was #n. Applying +n (or +#n) means to advance n lines
352 // (or characters) after hi. Applying -n (or -#n) means to back up n lines
353 // (or characters) before lo.
354 // The return value is the new lo, hi.
355 func addrNumber(data []byte, lo, hi int, dir byte, n int, charOffset bool) (int, int, os.Error) {
356 switch dir {
357 case 0:
358 lo = 0
359 hi = 0
360 fallthrough
361
362 case '+':
363 if charOffset {
364 pos := hi
365 for ; n > 0 && pos < len(data); n-- {
366 _, size := utf8.DecodeRune(data[pos:])
367 pos += size
368 }
369 if n == 0 {
370 return pos, pos, nil
371 }
372 break
373 }
374 // find next beginning of line
375 if hi > 0 {
376 for hi < len(data) && data[hi-1] != '\n' {
377 hi++
378 }
379 }
380 lo = hi
381 if n == 0 {
382 return lo, hi, nil
383 }
384 for ; hi < len(data); hi++ {
385 if data[hi] != '\n' {
386 continue
387 }
388 switch n--; n {
389 case 1:
390 lo = hi + 1
391 case 0:
392 return lo, hi + 1, nil
393 }
394 }
395
396 case '-':
397 if charOffset {
398 // Scan backward for bytes that are not UTF-8 continuati on bytes.
399 pos := lo
400 for ; pos > 0 && n > 0; pos-- {
401 if data[pos]&0xc0 != 0x80 {
402 n--
403 }
404 }
405 if n == 0 {
406 return pos, pos, nil
407 }
408 break
409 }
410 // find earlier beginning of line
411 for lo > 0 && data[lo-1] != '\n' {
412 lo--
413 }
414 hi = lo
415 if n == 0 {
416 return lo, hi, nil
417 }
418 for ; lo >= 0; lo-- {
419 if lo > 0 && data[lo-1] != '\n' {
420 continue
421 }
422 switch n--; n {
423 case 1:
424 hi = lo
425 case 0:
426 return lo, hi, nil
427 }
428 }
429 }
430
431 return 0, 0, os.NewError("address out of range")
432 }
433
434
435 // addrRegexp searches for pattern in the given direction starting at lo, hi.
436 // The direction dir is '+' (search forward from hi) or '-' (search backward fro m lo).
437 // Backward searches are unimplemented.
438 func addrRegexp(data []byte, lo, hi int, dir byte, pattern string) (int, int, os .Error) {
439 re, err := regexp.Compile(pattern)
440 if err != nil {
441 return 0, 0, err
442 }
443 if dir == '-' {
444 // Could implement reverse search using binary search
445 // through file, but that seems like overkill.
446 return 0, 0, os.NewError("reverse search not implemented")
447 }
448 m := re.Execute(data[hi:])
449 if len(m) > 0 {
450 m[0] += hi
451 m[1] += hi
452 } else if hi > 0 {
453 // No match. Wrap to beginning of data.
454 m = re.Execute(data)
455 }
456 if len(m) == 0 {
457 return 0, 0, os.NewError("no match for " + pattern)
458 }
459 return m[0], m[1], nil
460 }
461
462
463 // lineToByte returns the byte index of the first byte of line n.
464 // Line numbers begin at 1.
465 func lineToByte(data []byte, n int) int {
466 if n <= 1 {
467 return 0
468 }
469 n--
470 for i, c := range data {
471 if c == '\n' {
472 if n--; n == 0 {
473 return i + 1
474 }
475 }
476 }
477 return len(data)
478 }
479
480
481 // byteToLine returns the number of the line containing the byte at index i.
482 func byteToLine(data []byte, i int) int {
483 l := 1
484 for j, c := range data {
485 if j == i {
486 return l
487 }
488 if c == '\n' {
489 l++
490 }
491 }
492 return l
493 }
OLDNEW
« no previous file with comments | « src/cmd/godoc/Makefile ('k') | src/cmd/godoc/godoc.go » ('j') | no next file with comments »

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