LEFT | RIGHT |
1 // Copyright 2009 The Go Authors. All rights reserved. | 1 // Copyright 2009 The Go Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style | 2 // Use of this source code is governed by a BSD-style |
3 // license that can be found in the LICENSE file. | 3 // license that can be found in the LICENSE file. |
4 | 4 |
5 package main | 5 package main |
6 | 6 |
7 import ( | 7 import ( |
8 "bytes" | 8 "bytes" |
9 "flag" | 9 "flag" |
10 "fmt" | 10 "fmt" |
11 "go/ast" | 11 "go/ast" |
12 "go/doc" | 12 "go/doc" |
13 "go/parser" | 13 "go/parser" |
14 "go/printer" | 14 "go/printer" |
15 "go/scanner" | |
16 "go/token" | 15 "go/token" |
17 "http" | 16 "http" |
18 "io" | 17 "io" |
19 "io/ioutil" | 18 "io/ioutil" |
20 "log" | 19 "log" |
21 "os" | 20 "os" |
22 pathutil "path" | 21 pathutil "path" |
23 "regexp" | 22 "regexp" |
24 "runtime" | 23 "runtime" |
25 "sort" | 24 "sort" |
26 "strconv" | |
27 "strings" | 25 "strings" |
28 "template" | 26 "template" |
29 "time" | 27 "time" |
30 "utf8" | 28 "utf8" |
31 ) | 29 ) |
32 | 30 |
33 | 31 |
34 // ---------------------------------------------------------------------------- | 32 // ---------------------------------------------------------------------------- |
35 // Globals | 33 // Globals |
36 | 34 |
(...skipping 22 matching lines...) Expand all Loading... |
59 goroot = flag.String("goroot", runtime.GOROOT(), "Go root directory
") | 57 goroot = flag.String("goroot", runtime.GOROOT(), "Go root directory
") |
60 testDir = flag.String("testdir", "", "Go root subdirectory - for tes
ting only (faster startups)") | 58 testDir = flag.String("testdir", "", "Go root subdirectory - for tes
ting only (faster startups)") |
61 path = flag.String("path", "", "additional package directories (c
olon-separated)") | 59 path = flag.String("path", "", "additional package directories (c
olon-separated)") |
62 filter = flag.String("filter", "", "filter file containing permitte
d package directory paths") | 60 filter = flag.String("filter", "", "filter file containing permitte
d package directory paths") |
63 filterMin = flag.Int("filter_minutes", 0, "filter file update interval
in minutes; disabled if <= 0") | 61 filterMin = flag.Int("filter_minutes", 0, "filter file update interval
in minutes; disabled if <= 0") |
64 filterDelay delayTime // actual filter update interval in minutes; usual
ly filterDelay == filterMin, but filterDelay may back off exponentially | 62 filterDelay delayTime // actual filter update interval in minutes; usual
ly filterDelay == filterMin, but filterDelay may back off exponentially |
65 | 63 |
66 // layout control | 64 // layout control |
67 tabwidth = flag.Int("tabwidth", 4, "tab width") | 65 tabwidth = flag.Int("tabwidth", 4, "tab width") |
68 showTimestamps = flag.Bool("timestamps", true, "show timestamps with dir
ectory listings") | 66 showTimestamps = flag.Bool("timestamps", true, "show timestamps with dir
ectory listings") |
69 » fulltextIndex = flag.Bool("fulltext", false, "build full text index for
search queries") | 67 » fulltextIndex = flag.Bool("fulltext", false, "build full text index for
regular expression queries") |
70 » regexpQueries = flag.Bool("regexp", false, "interpret queries as regula
r expressions for full text search") | |
71 | 68 |
72 // file system mapping | 69 // file system mapping |
73 fsMap Mapping // user-defined mapping | 70 fsMap Mapping // user-defined mapping |
74 fsTree RWValue // *Directory tree of packages, updated with each syn
c | 71 fsTree RWValue // *Directory tree of packages, updated with each syn
c |
75 pathFilter RWValue // filter used when building fsMap directory trees | 72 pathFilter RWValue // filter used when building fsMap directory trees |
76 fsModified RWValue // timestamp of last call to invalidateIndex | 73 fsModified RWValue // timestamp of last call to invalidateIndex |
77 | 74 |
78 // http handlers | 75 // http handlers |
79 fileServer http.Handler // default file server | 76 fileServer http.Handler // default file server |
80 cmdHandler httpHandler | 77 cmdHandler httpHandler |
(...skipping 184 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
265 // no user-defined mapping found; use default mapping | 262 // no user-defined mapping found; use default mapping |
266 relpath = path[len(prefix):] | 263 relpath = path[len(prefix):] |
267 } | 264 } |
268 } | 265 } |
269 // Only if path is an invalid absolute path is relpath == "" | 266 // Only if path is an invalid absolute path is relpath == "" |
270 // at this point. This should never happen since absolute paths | 267 // at this point. This should never happen since absolute paths |
271 // are only created via godoc for files that do exist. However, | 268 // are only created via godoc for files that do exist. However, |
272 // it is ok to return ""; it will simply provide a link to the | 269 // it is ok to return ""; it will simply provide a link to the |
273 // top of the pkg or src directories. | 270 // top of the pkg or src directories. |
274 return relpath | 271 return relpath |
275 } | |
276 | |
277 | |
278 // ---------------------------------------------------------------------------- | |
279 // HTML formatting support | |
280 | |
281 // aposescaper implements an io.Writer that escapes single quotes: | |
282 // ' is written as \' . It is used to escape text such that it can | |
283 // be used as the content of single-quoted string literals. | |
284 type aposescaper struct { | |
285 w io.Writer | |
286 } | |
287 | |
288 | |
289 func (e *aposescaper) Write(p []byte) (n int, err os.Error) { | |
290 backslash := []byte{'\\'} | |
291 var i, m int | |
292 for j, b := range p { | |
293 if b == '\'' { | |
294 m, err = e.w.Write(p[i:j]) | |
295 n += m | |
296 if err != nil { | |
297 return | |
298 } | |
299 _, err = e.w.Write(backslash) | |
300 if err != nil { | |
301 return | |
302 } | |
303 i = j | |
304 } | |
305 } | |
306 m, err = e.w.Write(p[i:]) | |
307 n += m | |
308 return | |
309 } | |
310 | |
311 | |
312 // Styler implements a printer.Styler. | |
313 type Styler struct { | |
314 linetags bool | |
315 highlight string | |
316 objmap map[*ast.Object]int | |
317 idcount int | |
318 } | |
319 | |
320 | |
321 func newStyler(highlight string) *Styler { | |
322 return &Styler{true, highlight, make(map[*ast.Object]int), 0} | |
323 } | |
324 | |
325 | |
326 // identId returns a number >= 0 identifying the *ast.Object | |
327 // denoted by name. If no object is denoted, the result is < 0. | |
328 // | |
329 // TODO(gri): Consider making this a mapping from popup info | |
330 // (for that name) to id, instead of *ast.Object | |
331 // to id. If a lot of the popup info is the same | |
332 // (e.g. type information), this will reduce the | |
333 // size of the html generated. | |
334 func (s *Styler) identId(name *ast.Ident) int { | |
335 obj := name.Obj | |
336 if obj == nil || s.objmap == nil { | |
337 return -1 | |
338 } | |
339 id, found := s.objmap[obj] | |
340 if !found { | |
341 // first occurence | |
342 id = s.idcount | |
343 s.objmap[obj] = id | |
344 s.idcount++ | |
345 } | |
346 return id | |
347 } | |
348 | |
349 | |
350 // writeObjInfo writes the popup info corresponding to obj to w. | |
351 // The text is HTML-escaped and does not contain single quotes. | |
352 func writeObjInfo(w io.Writer, fset *token.FileSet, obj *ast.Object) { | |
353 // for now, show object kind and name; eventually | |
354 // do something more interesting (show declaration, | |
355 // for instance) | |
356 if obj.Kind != ast.Bad { | |
357 fmt.Fprintf(w, "%s ", obj.Kind) | |
358 } | |
359 template.HTMLEscape(w, []byte(obj.Name)) | |
360 // show type if we know it | |
361 if obj.Type != nil && obj.Type.Expr != nil { | |
362 fmt.Fprint(w, " ") | |
363 writeNode(&aposescaper{w}, fset, obj.Type.Expr, true, &defaultSt
yler) | |
364 } | |
365 } | |
366 | |
367 | |
368 // idList returns a Javascript array (source) with identifier popup | |
369 // information: The i'th array entry is a single-quoted string with | |
370 // the popup information for an identifier x with s.identId(x) == i, | |
371 // for 0 <= i < s.idcount. | |
372 func (s *Styler) idList(fset *token.FileSet) []byte { | |
373 var buf bytes.Buffer | |
374 buf.WriteString("[\n") | |
375 | |
376 if s.idcount > 0 { | |
377 // invert objmap: create an array [id]obj from map[obj]id | |
378 a := make([]*ast.Object, s.idcount) | |
379 for obj, id := range s.objmap { | |
380 a[id] = obj | |
381 } | |
382 | |
383 // for each id, print object info as single-quoted Javascript st
ring | |
384 for id, obj := range a { | |
385 printIndex := false // enable for debugging (but longer
html) | |
386 if printIndex { | |
387 fmt.Fprintf(&buf, "/* %4d */ ", id) | |
388 } | |
389 fmt.Fprint(&buf, "'") | |
390 writeObjInfo(&buf, fset, obj) | |
391 fmt.Fprint(&buf, "',\n") | |
392 } | |
393 } | |
394 | |
395 buf.WriteString("]\n") | |
396 return buf.Bytes() | |
397 } | |
398 | |
399 | |
400 // Use the defaultStyler when there is no specific styler. | |
401 // The defaultStyler does not emit line tags since they may | |
402 // interfere with tags emitted by templates. | |
403 // TODO(gri): Should emit line tags at the beginning of a line; | |
404 // never in the middle of code. | |
405 var defaultStyler Styler | |
406 | |
407 | |
408 func (s *Styler) LineTag(line int) (text []byte, tag printer.HTMLTag) { | |
409 if s.linetags { | |
410 tag = printer.HTMLTag{fmt.Sprintf(`<a id="L%d">`, line), "</a>"} | |
411 } | |
412 return | |
413 } | |
414 | |
415 | |
416 func (s *Styler) Comment(c *ast.Comment, line []byte) (text []byte, tag printer.
HTMLTag) { | |
417 text = line | |
418 // minimal syntax-coloring of comments for now - people will want more | |
419 // (don't do anything more until there's a button to turn it on/off) | |
420 tag = printer.HTMLTag{`<span class="comment">`, "</span>"} | |
421 return | |
422 } | |
423 | |
424 | |
425 func (s *Styler) BasicLit(x *ast.BasicLit) (text []byte, tag printer.HTMLTag) { | |
426 text = x.Value | |
427 return | |
428 } | |
429 | |
430 | |
431 func (s *Styler) Ident(name *ast.Ident) (text []byte, tag printer.HTMLTag) { | |
432 text = []byte(name.Name) | |
433 var str string | |
434 if id := s.identId(name); id >= 0 { | |
435 str = fmt.Sprintf(` id="%d"`, id) | |
436 } | |
437 if s.highlight == name.Name { | |
438 str += ` class="highlight"` | |
439 } | |
440 if str != "" { | |
441 tag = printer.HTMLTag{"<span" + str + ">", "</span>"} | |
442 } | |
443 return | |
444 } | |
445 | |
446 | |
447 func (s *Styler) Token(tok token.Token) (text []byte, tag printer.HTMLTag) { | |
448 text = []byte(tok.String()) | |
449 return | |
450 } | 272 } |
451 | 273 |
452 | 274 |
453 // ---------------------------------------------------------------------------- | 275 // ---------------------------------------------------------------------------- |
454 // Tab conversion | 276 // Tab conversion |
455 | 277 |
456 var spaces = []byte(" ") // 16 spaces seems like a good number | 278 var spaces = []byte(" ") // 16 spaces seems like a good number |
457 | 279 |
458 const ( | 280 const ( |
459 indenting = iota | 281 indenting = iota |
(...skipping 59 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
519 _, err = p.output.Write(data[pos:]) | 341 _, err = p.output.Write(data[pos:]) |
520 } | 342 } |
521 return | 343 return |
522 } | 344 } |
523 | 345 |
524 | 346 |
525 // ---------------------------------------------------------------------------- | 347 // ---------------------------------------------------------------------------- |
526 // Templates | 348 // Templates |
527 | 349 |
528 // Write an AST-node to w; optionally html-escaped. | 350 // Write an AST-node to w; optionally html-escaped. |
529 func writeNode(w io.Writer, fset *token.FileSet, node interface{}, html bool, st
yler printer.Styler) { | 351 func writeNode(w io.Writer, fset *token.FileSet, node interface{}, html bool) { |
530 mode := printer.TabIndent | printer.UseSpaces | 352 mode := printer.TabIndent | printer.UseSpaces |
531 if html { | 353 if html { |
532 mode |= printer.GenHTML | 354 mode |= printer.GenHTML |
533 } | 355 } |
534 // convert trailing tabs into spaces using a tconv filter | 356 // convert trailing tabs into spaces using a tconv filter |
535 // to ensure a good outcome in most browsers (there may still | 357 // to ensure a good outcome in most browsers (there may still |
536 // be tabs in comments and strings, but converting those into | 358 // be tabs in comments and strings, but converting those into |
537 // the right number of spaces is much harder) | 359 // the right number of spaces is much harder) |
538 » (&printer.Config{mode, *tabwidth, styler}).Fprint(&tconv{output: w}, fse
t, node) | 360 » (&printer.Config{mode, *tabwidth, nil}).Fprint(&tconv{output: w}, fset,
node) |
539 } | 361 } |
540 | 362 |
541 | 363 |
542 // Write text to w; optionally html-escaped. | 364 // Write text to w; optionally html-escaped. |
543 func writeText(w io.Writer, text []byte, html bool) { | 365 func writeText(w io.Writer, text []byte, html bool) { |
544 if html { | 366 if html { |
545 template.HTMLEscape(w, text) | 367 template.HTMLEscape(w, text) |
546 return | 368 return |
547 } | 369 } |
548 w.Write(text) | 370 w.Write(text) |
549 } | 371 } |
550 | 372 |
551 | 373 |
552 // Write anything to w; optionally html-escaped. | 374 // Write anything to w; optionally html-escaped. |
553 func writeAny(w io.Writer, fset *token.FileSet, html bool, x interface{}) { | 375 func writeAny(w io.Writer, fset *token.FileSet, html bool, x interface{}) { |
554 switch v := x.(type) { | 376 switch v := x.(type) { |
555 case []byte: | 377 case []byte: |
556 writeText(w, v, html) | 378 writeText(w, v, html) |
557 case string: | 379 case string: |
558 writeText(w, []byte(v), html) | 380 writeText(w, []byte(v), html) |
559 case ast.Decl, ast.Expr, ast.Stmt, *ast.File: | 381 case ast.Decl, ast.Expr, ast.Stmt, *ast.File: |
560 » » writeNode(w, fset, x, html, &defaultStyler) | 382 » » writeNode(w, fset, x, html) |
561 default: | 383 default: |
562 if html { | 384 if html { |
563 var buf bytes.Buffer | 385 var buf bytes.Buffer |
564 fmt.Fprint(&buf, x) | 386 fmt.Fprint(&buf, x) |
565 writeText(w, buf.Bytes(), true) | 387 writeText(w, buf.Bytes(), true) |
566 } else { | 388 } else { |
567 fmt.Fprint(w, x) | 389 fmt.Fprint(w, x) |
568 } | 390 } |
569 } | 391 } |
570 } | 392 } |
(...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
615 var buf bytes.Buffer | 437 var buf bytes.Buffer |
616 writeAny(&buf, fileset(x), false, x[0]) | 438 writeAny(&buf, fileset(x), false, x[0]) |
617 template.HTMLEscape(w, []byte(http.URLEscape(string(buf.Bytes())))) | 439 template.HTMLEscape(w, []byte(http.URLEscape(string(buf.Bytes())))) |
618 } | 440 } |
619 | 441 |
620 | 442 |
621 // Template formatter for the various "url-xxx" formats excluding url-esc. | 443 // Template formatter for the various "url-xxx" formats excluding url-esc. |
622 func urlFmt(w io.Writer, format string, x ...interface{}) { | 444 func urlFmt(w io.Writer, format string, x ...interface{}) { |
623 var path string | 445 var path string |
624 var line int | 446 var line int |
| 447 var low, high int // selection |
625 | 448 |
626 // determine path and position info, if any | 449 // determine path and position info, if any |
627 type positioner interface { | 450 type positioner interface { |
628 Pos() token.Pos | 451 Pos() token.Pos |
| 452 End() token.Pos |
629 } | 453 } |
630 switch t := x[0].(type) { | 454 switch t := x[0].(type) { |
631 case string: | 455 case string: |
632 path = t | 456 path = t |
633 case positioner: | 457 case positioner: |
634 » » pos := t.Pos() | 458 » » fset := fileset(x) |
635 » » if pos.IsValid() { | 459 » » if p := t.Pos(); p.IsValid() { |
636 » » » pos := fileset(x).Position(pos) | 460 » » » pos := fset.Position(p) |
637 path = pos.Filename | 461 path = pos.Filename |
638 line = pos.Line | 462 line = pos.Line |
| 463 low = pos.Offset |
| 464 } |
| 465 if p := t.End(); p.IsValid() { |
| 466 high = fset.Position(p).Offset |
639 } | 467 } |
640 default: | 468 default: |
641 // we should never reach here, but be resilient | 469 // we should never reach here, but be resilient |
642 // and assume the position is invalid (empty path, | 470 // and assume the position is invalid (empty path, |
643 // and line 0) | 471 // and line 0) |
644 log.Printf("INTERNAL ERROR: urlFmt(%s) without a string or posit
ioner", format) | 472 log.Printf("INTERNAL ERROR: urlFmt(%s) without a string or posit
ioner", format) |
645 } | 473 } |
646 | 474 |
647 // map path | 475 // map path |
648 relpath := relativePath(path) | 476 relpath := relativePath(path) |
649 | 477 |
650 // convert to relative URLs so that they can also | 478 // convert to relative URLs so that they can also |
651 // be used as relative file names in .txt templates | 479 // be used as relative file names in .txt templates |
652 switch format { | 480 switch format { |
653 default: | 481 default: |
654 // we should never reach here, but be resilient | 482 // we should never reach here, but be resilient |
655 // and assume the url-pkg format instead | 483 // and assume the url-pkg format instead |
656 log.Printf("INTERNAL ERROR: urlFmt(%s)", format) | 484 log.Printf("INTERNAL ERROR: urlFmt(%s)", format) |
657 fallthrough | 485 fallthrough |
658 case "url-pkg": | 486 case "url-pkg": |
659 // because of the irregular mapping under goroot | 487 // because of the irregular mapping under goroot |
660 // we need to correct certain relative paths | 488 // we need to correct certain relative paths |
661 if strings.HasPrefix(relpath, "src/pkg/") { | 489 if strings.HasPrefix(relpath, "src/pkg/") { |
662 relpath = relpath[len("src/pkg/"):] | 490 relpath = relpath[len("src/pkg/"):] |
663 } | 491 } |
664 template.HTMLEscape(w, []byte(pkgHandler.pattern[1:]+relpath)) /
/ remove trailing '/' for relative URL | 492 template.HTMLEscape(w, []byte(pkgHandler.pattern[1:]+relpath)) /
/ remove trailing '/' for relative URL |
665 case "url-src": | 493 case "url-src": |
666 template.HTMLEscape(w, []byte(relpath)) | 494 template.HTMLEscape(w, []byte(relpath)) |
667 case "url-pos": | 495 case "url-pos": |
| 496 template.HTMLEscape(w, []byte(relpath)) |
| 497 // selection ranges are of form "s=low:high" |
| 498 if low < high { |
| 499 fmt.Fprintf(w, "?s=%d:%d", low, high) |
| 500 // if we have a selection, position the page |
| 501 // such that the selection is a bit below the top |
| 502 line -= 10 |
| 503 if line < 1 { |
| 504 line = 1 |
| 505 } |
| 506 } |
668 // line id's in html-printed source are of the | 507 // line id's in html-printed source are of the |
669 // form "L%d" where %d stands for the line number | 508 // form "L%d" where %d stands for the line number |
670 » » template.HTMLEscape(w, []byte(relpath)) | 509 » » if line > 0 { |
671 » » fmt.Fprintf(w, "#L%d", line) | 510 » » » fmt.Fprintf(w, "#L%d", line) |
| 511 » » } |
672 } | 512 } |
673 } | 513 } |
674 | 514 |
675 | 515 |
676 // The strings in infoKinds must be properly html-escaped. | 516 // The strings in infoKinds must be properly html-escaped. |
677 var infoKinds = [nKinds]string{ | 517 var infoKinds = [nKinds]string{ |
678 PackageClause: "package clause", | 518 PackageClause: "package clause", |
679 ImportDecl: "import decl", | 519 ImportDecl: "import decl", |
680 ConstDecl: "const decl", | 520 ConstDecl: "const decl", |
681 TypeDecl: "type decl", | 521 TypeDecl: "type decl", |
(...skipping 26 matching lines...) Expand all Loading... |
708 line = 0 | 548 line = 0 |
709 } | 549 } |
710 } | 550 } |
711 fmt.Fprintf(w, "%d", line) | 551 fmt.Fprintf(w, "%d", line) |
712 } | 552 } |
713 | 553 |
714 | 554 |
715 // Template formatter for "infoSnippet" format. | 555 // Template formatter for "infoSnippet" format. |
716 func infoSnippetFmt(w io.Writer, format string, x ...interface{}) { | 556 func infoSnippetFmt(w io.Writer, format string, x ...interface{}) { |
717 info := x[0].(SpotInfo) | 557 info := x[0].(SpotInfo) |
718 » text := `<span class="alert">no snippet text available</span>` | 558 » text := []byte(`<span class="alert">no snippet text available</span>`) |
719 if info.IsIndex() { | 559 if info.IsIndex() { |
720 index, _ := searchIndex.get() | 560 index, _ := searchIndex.get() |
721 // no escaping of snippet text needed; | 561 // no escaping of snippet text needed; |
722 // snippet text is escaped when generated | 562 // snippet text is escaped when generated |
723 text = index.(*Index).Snippet(info.Lori()).Text | 563 text = index.(*Index).Snippet(info.Lori()).Text |
724 } | 564 } |
725 » fmt.Fprint(w, text) | 565 » w.Write(text) |
726 } | 566 } |
727 | 567 |
728 | 568 |
729 // Template formatter for "padding" format. | 569 // Template formatter for "padding" format. |
730 func paddingFmt(w io.Writer, format string, x ...interface{}) { | 570 func paddingFmt(w io.Writer, format string, x ...interface{}) { |
731 for i := x[0].(int); i > 0; i-- { | 571 for i := x[0].(int); i > 0; i-- { |
732 fmt.Fprint(w, `<td width="25"></td>`) | 572 fmt.Fprint(w, `<td width="25"></td>`) |
733 } | 573 } |
734 } | 574 } |
735 | 575 |
(...skipping 62 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
798 | 638 |
799 var ( | 639 var ( |
800 codewalkHTML, | 640 codewalkHTML, |
801 codewalkdirHTML, | 641 codewalkdirHTML, |
802 dirlistHTML, | 642 dirlistHTML, |
803 errorHTML, | 643 errorHTML, |
804 godocHTML, | 644 godocHTML, |
805 packageHTML, | 645 packageHTML, |
806 packageText, | 646 packageText, |
807 searchHTML, | 647 searchHTML, |
808 » searchText, | 648 » searchText *template.Template |
809 » sourceHTML *template.Template | |
810 ) | 649 ) |
811 | 650 |
812 func readTemplates() { | 651 func readTemplates() { |
813 // have to delay until after flags processing since paths depend on goro
ot | 652 // have to delay until after flags processing since paths depend on goro
ot |
814 codewalkHTML = readTemplate("codewalk.html") | 653 codewalkHTML = readTemplate("codewalk.html") |
815 codewalkdirHTML = readTemplate("codewalkdir.html") | 654 codewalkdirHTML = readTemplate("codewalkdir.html") |
816 dirlistHTML = readTemplate("dirlist.html") | 655 dirlistHTML = readTemplate("dirlist.html") |
817 errorHTML = readTemplate("error.html") | 656 errorHTML = readTemplate("error.html") |
818 godocHTML = readTemplate("godoc.html") | 657 godocHTML = readTemplate("godoc.html") |
819 packageHTML = readTemplate("package.html") | 658 packageHTML = readTemplate("package.html") |
820 packageText = readTemplate("package.txt") | 659 packageText = readTemplate("package.txt") |
821 searchHTML = readTemplate("search.html") | 660 searchHTML = readTemplate("search.html") |
822 searchText = readTemplate("search.txt") | 661 searchText = readTemplate("search.txt") |
823 sourceHTML = readTemplate("source.html") | |
824 } | 662 } |
825 | 663 |
826 | 664 |
827 // ---------------------------------------------------------------------------- | 665 // ---------------------------------------------------------------------------- |
828 // Generic HTML wrapper | 666 // Generic HTML wrapper |
829 | 667 |
830 func servePage(w http.ResponseWriter, title, subtitle, query string, content []b
yte) { | 668 func servePage(w http.ResponseWriter, title, subtitle, query string, content []b
yte) { |
831 d := struct { | 669 d := struct { |
832 Title string | 670 Title string |
833 Subtitle string | 671 Subtitle string |
(...skipping 80 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
914 | 752 |
915 func applyTemplate(t *template.Template, name string, data interface{}) []byte { | 753 func applyTemplate(t *template.Template, name string, data interface{}) []byte { |
916 var buf bytes.Buffer | 754 var buf bytes.Buffer |
917 if err := t.Execute(data, &buf); err != nil { | 755 if err := t.Execute(data, &buf); err != nil { |
918 log.Printf("%s.Execute: %s", name, err) | 756 log.Printf("%s.Execute: %s", name, err) |
919 } | 757 } |
920 return buf.Bytes() | 758 return buf.Bytes() |
921 } | 759 } |
922 | 760 |
923 | 761 |
924 func serveGoSource(w http.ResponseWriter, r *http.Request, abspath, relpath stri
ng) { | |
925 fset := token.NewFileSet() | |
926 file, err := parser.ParseFile(fset, abspath, nil, parser.ParseComments) | |
927 if err != nil { | |
928 log.Printf("parser.ParseFile: %s", err) | |
929 serveError(w, r, relpath, err) | |
930 return | |
931 } | |
932 | |
933 // TODO(gri) enable once we are confident it works for all files | |
934 // augment AST with types; ignore errors (partial type information ok) | |
935 // typechecker.CheckFile(file, nil) | |
936 | |
937 var buf bytes.Buffer | |
938 styler := newStyler(r.FormValue("h")) | |
939 writeNode(&buf, fset, file, true, styler) | |
940 | |
941 type SourceInfo struct { | |
942 IdList []byte | |
943 Source []byte | |
944 } | |
945 info := &SourceInfo{styler.idList(fset), buf.Bytes()} | |
946 | |
947 contents := applyTemplate(sourceHTML, "sourceHTML", info) | |
948 servePage(w, "Source file "+relpath, "", "", contents) | |
949 } | |
950 | |
951 | |
952 func redirect(w http.ResponseWriter, r *http.Request) (redirected bool) { | 762 func redirect(w http.ResponseWriter, r *http.Request) (redirected bool) { |
953 if canonical := pathutil.Clean(r.URL.Path) + "/"; r.URL.Path != canonica
l { | 763 if canonical := pathutil.Clean(r.URL.Path) + "/"; r.URL.Path != canonica
l { |
954 http.Redirect(w, r, canonical, http.StatusMovedPermanently) | 764 http.Redirect(w, r, canonical, http.StatusMovedPermanently) |
955 redirected = true | 765 redirected = true |
956 } | 766 } |
957 return | 767 return |
958 } | 768 } |
959 | 769 |
960 | 770 |
961 // TODO(gri): Should have a mapping from extension to handler, eventually. | 771 // TODO(gri): Should have a mapping from extension to handler, eventually. |
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
998 // decoding error or control character - not a text file | 808 // decoding error or control character - not a text file |
999 return false | 809 return false |
1000 } | 810 } |
1001 } | 811 } |
1002 | 812 |
1003 // likely a text file | 813 // likely a text file |
1004 return true | 814 return true |
1005 } | 815 } |
1006 | 816 |
1007 | 817 |
1008 // commentSelection computes the Selection for all Go comments in src. | 818 func serveTextFile(w http.ResponseWriter, r *http.Request, abspath, relpath, tit
le string) { |
1009 func commentSelection(src []byte) Selection { | |
1010 » var s scanner.Scanner | |
1011 » file := s.Init(token.NewFileSet(), "", src, nil, scanner.ScanComments+sc
anner.InsertSemis) | |
1012 » return func() (seg []int) { | |
1013 » » for { | |
1014 » » » pos, tok, lit := s.Scan() | |
1015 » » » if tok == token.EOF { | |
1016 » » » » break | |
1017 » » » } | |
1018 » » » offs := file.Offset(pos) | |
1019 » » » if tok == token.COMMENT { | |
1020 » » » » seg = []int{offs, offs + len(lit)} | |
1021 » » » » break | |
1022 » » » } | |
1023 » » } | |
1024 » » return | |
1025 » } | |
1026 } | |
1027 | |
1028 | |
1029 func makeSelection(matches [][]int) Selection { | |
1030 » return func() (seg []int) { | |
1031 » » if len(matches) > 0 { | |
1032 » » » seg = matches[0] | |
1033 » » » matches = matches[1:] | |
1034 » » } | |
1035 » » return | |
1036 » } | |
1037 } | |
1038 | |
1039 | |
1040 // regexpSelection computes the Selection for the regular expression expr in tex
t. | |
1041 func regexpSelection(text []byte, expr string) Selection { | |
1042 » var matches [][]int | |
1043 » if rx, err := regexp.Compile(expr); err == nil { | |
1044 » » matches = rx.FindAllIndex(text, -1) | |
1045 » } | |
1046 » return makeSelection(matches) | |
1047 } | |
1048 | |
1049 | |
1050 var selRx = regexp.MustCompile(`^([0-9]+):([0-9]+)`) | |
1051 | |
1052 func rangeSelection(str string, max int) Selection { | |
1053 » m := selRx.FindStringSubmatch(str) | |
1054 » if len(m) >= 2 { | |
1055 » » from, _ := strconv.Atoi(m[1]) | |
1056 » » to, _ := strconv.Atoi(m[2]) | |
1057 » » if from < to { | |
1058 » » » return makeSelection([][]int{[]int{from, to}}) | |
1059 » » } | |
1060 » } | |
1061 » return nil | |
1062 } | |
1063 | |
1064 | |
1065 // bit 0 (2^0 == 1): comments | |
1066 // bit 1 (2^1 == 2): highlights | |
1067 // bit 2 (2^2 == 4): selections | |
1068 // | |
1069 var startTags = []string{ | |
1070 » /* 0 */ ``, | |
1071 » /* 1 */ `<span class ="comment">`, | |
1072 » /* 2 */ `<span class="highlight">`, | |
1073 » /* 3 */ `<span class="highlight-comment">`, | |
1074 » /* 4 */ `<span class="selection">`, | |
1075 » /* 5 */ `<span class="selection-comment">`, | |
1076 » /* 6 */ `<span class="selection-highlight">`, | |
1077 » /* 7 */ `<span class="selection-highlight-comment">`, | |
1078 } | |
1079 | |
1080 | |
1081 func formatText(text []byte, isGoFile bool, pattern, rangeStr string) []byte { | |
1082 » var buf bytes.Buffer | |
1083 » buf.WriteString("<pre>\n") | |
1084 | |
1085 » var comments, highlights, ranges Selection | |
1086 » if isGoFile { | |
1087 » » comments = commentSelection(text) | |
1088 » } | |
1089 » if pattern != "" { | |
1090 » » highlights = regexpSelection(text, pattern) | |
1091 » } | |
1092 » if rangeStr != "" { | |
1093 » » ranges = rangeSelection(rangeStr, len(text)) | |
1094 » } | |
1095 » if comments != nil || highlights != nil || ranges != nil { | |
1096 » » FormatSelections(&buf, text, startTags, comments, highlights, ra
nges) | |
1097 » } else { | |
1098 » » template.HTMLEscape(&buf, text) | |
1099 » } | |
1100 | |
1101 » buf.WriteString("</pre>\n") | |
1102 » return buf.Bytes() | |
1103 } | |
1104 | |
1105 | |
1106 func serveTextFile(w http.ResponseWriter, r *http.Request, abspath, relpath stri
ng) { | |
1107 src, err := ioutil.ReadFile(abspath) | 819 src, err := ioutil.ReadFile(abspath) |
1108 if err != nil { | 820 if err != nil { |
1109 log.Printf("ioutil.ReadFile: %s", err) | 821 log.Printf("ioutil.ReadFile: %s", err) |
1110 serveError(w, r, relpath, err) | 822 serveError(w, r, relpath, err) |
1111 return | 823 return |
1112 } | 824 } |
1113 | 825 |
1114 » contents := formatText(src, pathutil.Ext(abspath) == ".go", r.FormValue(
"g"), r.FormValue("s")) | 826 » contents := FormatText(src, 1, pathutil.Ext(abspath) == ".go", r.FormVal
ue("h"), rangeSelection(r.FormValue("s"))) |
1115 » servePage(w, "Text file "+relpath, "", "", contents) | 827 » servePage(w, title+" "+relpath, "", "", contents) |
1116 } | 828 } |
1117 | 829 |
1118 | 830 |
1119 func serveDirectory(w http.ResponseWriter, r *http.Request, abspath, relpath str
ing) { | 831 func serveDirectory(w http.ResponseWriter, r *http.Request, abspath, relpath str
ing) { |
1120 if redirect(w, r) { | 832 if redirect(w, r) { |
1121 return | 833 return |
1122 } | 834 } |
1123 | 835 |
1124 list, err := ioutil.ReadDir(abspath) | 836 list, err := ioutil.ReadDir(abspath) |
1125 if err != nil { | 837 if err != nil { |
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1160 if strings.HasSuffix(abspath, "/index.html") { | 872 if strings.HasSuffix(abspath, "/index.html") { |
1161 // We'll show index.html for the directory. | 873 // We'll show index.html for the directory. |
1162 // Use the dir/ version as canonical instead of dir/inde
x.html. | 874 // Use the dir/ version as canonical instead of dir/inde
x.html. |
1163 http.Redirect(w, r, r.URL.Path[0:len(r.URL.Path)-len("in
dex.html")], http.StatusMovedPermanently) | 875 http.Redirect(w, r, r.URL.Path[0:len(r.URL.Path)-len("in
dex.html")], http.StatusMovedPermanently) |
1164 return | 876 return |
1165 } | 877 } |
1166 serveHTMLDoc(w, r, abspath, relpath) | 878 serveHTMLDoc(w, r, abspath, relpath) |
1167 return | 879 return |
1168 | 880 |
1169 case ".go": | 881 case ".go": |
1170 » » if r.FormValue("g") != "" { | 882 » » serveTextFile(w, r, abspath, relpath, "Source file") |
1171 » » » serveTextFile(w, r, abspath, relpath) | |
1172 » » » return | |
1173 » » } | |
1174 » » serveGoSource(w, r, abspath, relpath) | |
1175 return | 883 return |
1176 } | 884 } |
1177 | 885 |
1178 dir, err := os.Lstat(abspath) | 886 dir, err := os.Lstat(abspath) |
1179 if err != nil { | 887 if err != nil { |
1180 log.Print(err) | 888 log.Print(err) |
1181 serveError(w, r, relpath, err) | 889 serveError(w, r, relpath, err) |
1182 return | 890 return |
1183 } | 891 } |
1184 | 892 |
1185 if dir != nil && dir.IsDirectory() { | 893 if dir != nil && dir.IsDirectory() { |
1186 if redirect(w, r) { | 894 if redirect(w, r) { |
1187 return | 895 return |
1188 } | 896 } |
1189 if index := abspath + "/index.html"; isTextFile(index) { | 897 if index := abspath + "/index.html"; isTextFile(index) { |
1190 serveHTMLDoc(w, r, index, relativePath(index)) | 898 serveHTMLDoc(w, r, index, relativePath(index)) |
1191 return | 899 return |
1192 } | 900 } |
1193 serveDirectory(w, r, abspath, relpath) | 901 serveDirectory(w, r, abspath, relpath) |
1194 return | 902 return |
1195 } | 903 } |
1196 | 904 |
1197 if isTextFile(abspath) { | 905 if isTextFile(abspath) { |
1198 » » serveTextFile(w, r, abspath, relpath) | 906 » » serveTextFile(w, r, abspath, relpath, "Text file") |
1199 return | 907 return |
1200 } | 908 } |
1201 | 909 |
1202 fileServer.ServeHTTP(w, r) | 910 fileServer.ServeHTTP(w, r) |
1203 } | 911 } |
1204 | 912 |
1205 | 913 |
1206 // ---------------------------------------------------------------------------- | 914 // ---------------------------------------------------------------------------- |
1207 // Packages | 915 // Packages |
1208 | 916 |
(...skipping 27 matching lines...) Expand all Loading... |
1236 fsRoot string // file system root to which the pattern is mapped | 944 fsRoot string // file system root to which the pattern is mapped |
1237 isPkg bool // true if this handler serves real package documentation
(as opposed to command documentation) | 945 isPkg bool // true if this handler serves real package documentation
(as opposed to command documentation) |
1238 } | 946 } |
1239 | 947 |
1240 | 948 |
1241 // getPageInfo returns the PageInfo for a package directory abspath. If the | 949 // getPageInfo returns the PageInfo for a package directory abspath. If the |
1242 // parameter genAST is set, an AST containing only the package exports is | 950 // parameter genAST is set, an AST containing only the package exports is |
1243 // computed (PageInfo.PAst), otherwise package documentation (PageInfo.Doc) | 951 // computed (PageInfo.PAst), otherwise package documentation (PageInfo.Doc) |
1244 // is extracted from the AST. If there is no corresponding package in the | 952 // is extracted from the AST. If there is no corresponding package in the |
1245 // directory, PageInfo.PAst and PageInfo.PDoc are nil. If there are no sub- | 953 // directory, PageInfo.PAst and PageInfo.PDoc are nil. If there are no sub- |
1246 // directories, PageInfo.Dirs is nil. If a directory read error occured, | 954 // directories, PageInfo.Dirs is nil. If a directory read error occurred, |
1247 // PageInfo.Err is set to the respective error but the error is not logged. | 955 // PageInfo.Err is set to the respective error but the error is not logged. |
1248 // | 956 // |
1249 func (h *httpHandler) getPageInfo(abspath, relpath, pkgname string, mode PageInf
oMode) PageInfo { | 957 func (h *httpHandler) getPageInfo(abspath, relpath, pkgname string, mode PageInf
oMode) PageInfo { |
1250 // filter function to select the desired .go files | 958 // filter function to select the desired .go files |
1251 filter := func(d *os.FileInfo) bool { | 959 filter := func(d *os.FileInfo) bool { |
1252 // If we are looking at cmd documentation, only accept | 960 // If we are looking at cmd documentation, only accept |
1253 // the special fakePkgFile containing the documentation. | 961 // the special fakePkgFile containing the documentation. |
1254 return isPkgFile(d) && (h.isPkg || d.Name == fakePkgFile) | 962 return isPkgFile(d) && (h.isPkg || d.Name == fakePkgFile) |
1255 } | 963 } |
1256 | 964 |
(...skipping 178 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1435 | 1143 |
1436 type SearchResult struct { | 1144 type SearchResult struct { |
1437 Query string | 1145 Query string |
1438 Alert string // error or warning message | 1146 Alert string // error or warning message |
1439 | 1147 |
1440 // identifier matches | 1148 // identifier matches |
1441 Hit *LookupResult // identifier matches of Query | 1149 Hit *LookupResult // identifier matches of Query |
1442 Alt *AltWords // alternative identifiers to look for | 1150 Alt *AltWords // alternative identifiers to look for |
1443 | 1151 |
1444 // textual matches | 1152 // textual matches |
1445 » Found int // number of textual occurences found | 1153 » Found int // number of textual occurrences found |
1446 Textual []FileLines // textual matches of Query | 1154 Textual []FileLines // textual matches of Query |
1447 » Complete bool // true if all textual occurences of Query are repo
rted | 1155 » Complete bool // true if all textual occurrences of Query are rep
orted |
1448 } | 1156 } |
1449 | 1157 |
1450 | 1158 |
1451 func lookup(query string) (result SearchResult) { | 1159 func lookup(query string) (result SearchResult) { |
1452 result.Query = query | 1160 result.Query = query |
1453 | |
1454 // if regexp queries are disabled, quote the query string: | |
1455 // it will always compile and look like a literal | |
1456 if !*regexpQueries { | |
1457 query = regexp.QuoteMeta(query) | |
1458 } | |
1459 | 1161 |
1460 // determine identifier lookup string and full text regexp | 1162 // determine identifier lookup string and full text regexp |
1461 lookupStr := "" | 1163 lookupStr := "" |
1462 lookupRx, err := regexp.Compile(query) | 1164 lookupRx, err := regexp.Compile(query) |
1463 if err != nil { | 1165 if err != nil { |
1464 result.Alert = "Error in query regular expression: " + err.Strin
g() | 1166 result.Alert = "Error in query regular expression: " + err.Strin
g() |
1465 return | 1167 return |
1466 } | 1168 } |
1467 if prefix, complete := lookupRx.LiteralPrefix(); complete { | 1169 if prefix, complete := lookupRx.LiteralPrefix(); complete { |
1468 // otherwise we lookup "" (with no result) because | 1170 // otherwise we lookup "" (with no result) because |
(...skipping 121 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1590 log.Printf("after GC: bytes = %d footprint = %d", runti
me.MemStats.HeapAlloc, runtime.MemStats.Sys) | 1292 log.Printf("after GC: bytes = %d footprint = %d", runti
me.MemStats.HeapAlloc, runtime.MemStats.Sys) |
1591 } | 1293 } |
1592 var delay int64 = 60 * 1e9 // by default, try every 60s | 1294 var delay int64 = 60 * 1e9 // by default, try every 60s |
1593 if *testDir != "" { | 1295 if *testDir != "" { |
1594 // in test mode, try once a second for fast startup | 1296 // in test mode, try once a second for fast startup |
1595 delay = 1 * 1e9 | 1297 delay = 1 * 1e9 |
1596 } | 1298 } |
1597 time.Sleep(delay) | 1299 time.Sleep(delay) |
1598 } | 1300 } |
1599 } | 1301 } |
LEFT | RIGHT |