github.com/jgarto/itcv@v0.0.0-20180826224514-4eea09c1aa0d/_vendor/src/golang.org/x/tools/godoc/server.go (about) 1 // Copyright 2013 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 package godoc 6 7 import ( 8 "bytes" 9 "encoding/json" 10 "errors" 11 "fmt" 12 "go/ast" 13 "go/build" 14 "go/doc" 15 "go/token" 16 htmlpkg "html" 17 htmltemplate "html/template" 18 "io" 19 "io/ioutil" 20 "log" 21 "net/http" 22 "os" 23 pathpkg "path" 24 "path/filepath" 25 "sort" 26 "strings" 27 "text/template" 28 "time" 29 30 "golang.org/x/tools/godoc/analysis" 31 "golang.org/x/tools/godoc/util" 32 "golang.org/x/tools/godoc/vfs" 33 ) 34 35 // handlerServer is a migration from an old godoc http Handler type. 36 // This should probably merge into something else. 37 type handlerServer struct { 38 p *Presentation 39 c *Corpus // copy of p.Corpus 40 pattern string // url pattern; e.g. "/pkg/" 41 stripPrefix string // prefix to strip from import path; e.g. "pkg/" 42 fsRoot string // file system root to which the pattern is mapped; e.g. "/src" 43 exclude []string // file system paths to exclude; e.g. "/src/cmd" 44 } 45 46 func (s *handlerServer) registerWithMux(mux *http.ServeMux) { 47 mux.Handle(s.pattern, s) 48 } 49 50 // getPageInfo returns the PageInfo for a package directory abspath. If the 51 // parameter genAST is set, an AST containing only the package exports is 52 // computed (PageInfo.PAst), otherwise package documentation (PageInfo.Doc) 53 // is extracted from the AST. If there is no corresponding package in the 54 // directory, PageInfo.PAst and PageInfo.PDoc are nil. If there are no sub- 55 // directories, PageInfo.Dirs is nil. If an error occurred, PageInfo.Err is 56 // set to the respective error but the error is not logged. 57 // 58 func (h *handlerServer) GetPageInfo(abspath, relpath string, mode PageInfoMode, goos, goarch string) *PageInfo { 59 info := &PageInfo{Dirname: abspath, Mode: mode} 60 61 // Restrict to the package files that would be used when building 62 // the package on this system. This makes sure that if there are 63 // separate implementations for, say, Windows vs Unix, we don't 64 // jumble them all together. 65 // Note: If goos/goarch aren't set, the current binary's GOOS/GOARCH 66 // are used. 67 ctxt := build.Default 68 ctxt.IsAbsPath = pathpkg.IsAbs 69 ctxt.IsDir = func(path string) bool { 70 fi, err := h.c.fs.Stat(filepath.ToSlash(path)) 71 return err == nil && fi.IsDir() 72 } 73 ctxt.ReadDir = func(dir string) ([]os.FileInfo, error) { 74 f, err := h.c.fs.ReadDir(filepath.ToSlash(dir)) 75 filtered := make([]os.FileInfo, 0, len(f)) 76 for _, i := range f { 77 if mode&NoFiltering != 0 || i.Name() != "internal" { 78 filtered = append(filtered, i) 79 } 80 } 81 return filtered, err 82 } 83 ctxt.OpenFile = func(name string) (r io.ReadCloser, err error) { 84 data, err := vfs.ReadFile(h.c.fs, filepath.ToSlash(name)) 85 if err != nil { 86 return nil, err 87 } 88 return ioutil.NopCloser(bytes.NewReader(data)), nil 89 } 90 91 if goos != "" { 92 ctxt.GOOS = goos 93 } 94 if goarch != "" { 95 ctxt.GOARCH = goarch 96 } 97 98 pkginfo, err := ctxt.ImportDir(abspath, 0) 99 // continue if there are no Go source files; we still want the directory info 100 if _, nogo := err.(*build.NoGoError); err != nil && !nogo { 101 info.Err = err 102 return info 103 } 104 105 // collect package files 106 pkgname := pkginfo.Name 107 pkgfiles := append(pkginfo.GoFiles, pkginfo.CgoFiles...) 108 if len(pkgfiles) == 0 { 109 // Commands written in C have no .go files in the build. 110 // Instead, documentation may be found in an ignored file. 111 // The file may be ignored via an explicit +build ignore 112 // constraint (recommended), or by defining the package 113 // documentation (historic). 114 pkgname = "main" // assume package main since pkginfo.Name == "" 115 pkgfiles = pkginfo.IgnoredGoFiles 116 } 117 118 // get package information, if any 119 if len(pkgfiles) > 0 { 120 // build package AST 121 fset := token.NewFileSet() 122 files, err := h.c.parseFiles(fset, relpath, abspath, pkgfiles) 123 if err != nil { 124 info.Err = err 125 return info 126 } 127 128 // ignore any errors - they are due to unresolved identifiers 129 pkg, _ := ast.NewPackage(fset, files, poorMansImporter, nil) 130 131 // extract package documentation 132 info.FSet = fset 133 if mode&ShowSource == 0 { 134 // show extracted documentation 135 var m doc.Mode 136 if mode&NoFiltering != 0 { 137 m |= doc.AllDecls 138 } 139 if mode&AllMethods != 0 { 140 m |= doc.AllMethods 141 } 142 info.PDoc = doc.New(pkg, pathpkg.Clean(relpath), m) // no trailing '/' in importpath 143 if mode&NoTypeAssoc != 0 { 144 for _, t := range info.PDoc.Types { 145 info.PDoc.Consts = append(info.PDoc.Consts, t.Consts...) 146 info.PDoc.Vars = append(info.PDoc.Vars, t.Vars...) 147 info.PDoc.Funcs = append(info.PDoc.Funcs, t.Funcs...) 148 t.Consts = nil 149 t.Vars = nil 150 t.Funcs = nil 151 } 152 // for now we cannot easily sort consts and vars since 153 // go/doc.Value doesn't export the order information 154 sort.Sort(funcsByName(info.PDoc.Funcs)) 155 } 156 157 // collect examples 158 testfiles := append(pkginfo.TestGoFiles, pkginfo.XTestGoFiles...) 159 files, err = h.c.parseFiles(fset, relpath, abspath, testfiles) 160 if err != nil { 161 log.Println("parsing examples:", err) 162 } 163 info.Examples = collectExamples(h.c, pkg, files) 164 165 // collect any notes that we want to show 166 if info.PDoc.Notes != nil { 167 // could regexp.Compile only once per godoc, but probably not worth it 168 if rx := h.p.NotesRx; rx != nil { 169 for m, n := range info.PDoc.Notes { 170 if rx.MatchString(m) { 171 if info.Notes == nil { 172 info.Notes = make(map[string][]*doc.Note) 173 } 174 info.Notes[m] = n 175 } 176 } 177 } 178 } 179 180 } else { 181 // show source code 182 // TODO(gri) Consider eliminating export filtering in this mode, 183 // or perhaps eliminating the mode altogether. 184 if mode&NoFiltering == 0 { 185 packageExports(fset, pkg) 186 } 187 info.PAst = files 188 } 189 info.IsMain = pkgname == "main" 190 } 191 192 // get directory information, if any 193 var dir *Directory 194 var timestamp time.Time 195 if tree, ts := h.c.fsTree.Get(); tree != nil && tree.(*Directory) != nil { 196 // directory tree is present; lookup respective directory 197 // (may still fail if the file system was updated and the 198 // new directory tree has not yet been computed) 199 dir = tree.(*Directory).lookup(abspath) 200 timestamp = ts 201 } 202 if dir == nil { 203 // no directory tree present (happens in command-line mode); 204 // compute 2 levels for this page. The second level is to 205 // get the synopses of sub-directories. 206 // note: cannot use path filter here because in general 207 // it doesn't contain the FSTree path 208 dir = h.c.newDirectory(abspath, 2) 209 timestamp = time.Now() 210 } 211 info.Dirs = dir.listing(true, func(path string) bool { return h.includePath(path, mode) }) 212 213 info.DirTime = timestamp 214 info.DirFlat = mode&FlatDir != 0 215 216 return info 217 } 218 219 func (h *handlerServer) includePath(path string, mode PageInfoMode) (r bool) { 220 // if the path is under one of the exclusion paths, don't list. 221 for _, e := range h.exclude { 222 if strings.HasPrefix(path, e) { 223 return false 224 } 225 } 226 227 // if the path includes 'internal', don't list unless we are in the NoFiltering mode. 228 if mode&NoFiltering != 0 { 229 return true 230 } 231 if strings.Contains(path, "internal") || strings.Contains(path, "vendor") { 232 for _, c := range strings.Split(filepath.Clean(path), string(os.PathSeparator)) { 233 if c == "internal" || c == "vendor" { 234 return false 235 } 236 } 237 } 238 return true 239 } 240 241 type funcsByName []*doc.Func 242 243 func (s funcsByName) Len() int { return len(s) } 244 func (s funcsByName) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 245 func (s funcsByName) Less(i, j int) bool { return s[i].Name < s[j].Name } 246 247 func (h *handlerServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { 248 if redirect(w, r) { 249 return 250 } 251 252 relpath := pathpkg.Clean(r.URL.Path[len(h.stripPrefix)+1:]) 253 254 if !h.corpusInitialized() { 255 h.p.ServeError(w, r, relpath, errors.New("Scan is not yet complete. Please retry after a few moments")) 256 return 257 } 258 259 abspath := pathpkg.Join(h.fsRoot, relpath) 260 mode := h.p.GetPageInfoMode(r) 261 if relpath == builtinPkgPath { 262 mode = NoFiltering | NoTypeAssoc 263 } 264 info := h.GetPageInfo(abspath, relpath, mode, r.FormValue("GOOS"), r.FormValue("GOARCH")) 265 if info.Err != nil { 266 log.Print(info.Err) 267 h.p.ServeError(w, r, relpath, info.Err) 268 return 269 } 270 271 if mode&NoHTML != 0 { 272 h.p.ServeText(w, applyTemplate(h.p.PackageText, "packageText", info)) 273 return 274 } 275 276 var tabtitle, title, subtitle string 277 switch { 278 case info.PAst != nil: 279 for _, ast := range info.PAst { 280 tabtitle = ast.Name.Name 281 break 282 } 283 case info.PDoc != nil: 284 tabtitle = info.PDoc.Name 285 default: 286 tabtitle = info.Dirname 287 title = "Directory " 288 if h.p.ShowTimestamps { 289 subtitle = "Last update: " + info.DirTime.String() 290 } 291 } 292 if title == "" { 293 if info.IsMain { 294 // assume that the directory name is the command name 295 _, tabtitle = pathpkg.Split(relpath) 296 title = "Command " 297 } else { 298 title = "Package " 299 } 300 } 301 title += tabtitle 302 303 // special cases for top-level package/command directories 304 switch tabtitle { 305 case "/src": 306 title = "Packages" 307 tabtitle = "Packages" 308 case "/src/cmd": 309 title = "Commands" 310 tabtitle = "Commands" 311 } 312 313 // Emit JSON array for type information. 314 pi := h.c.Analysis.PackageInfo(relpath) 315 info.CallGraphIndex = pi.CallGraphIndex 316 info.CallGraph = htmltemplate.JS(marshalJSON(pi.CallGraph)) 317 info.AnalysisData = htmltemplate.JS(marshalJSON(pi.Types)) 318 info.TypeInfoIndex = make(map[string]int) 319 for i, ti := range pi.Types { 320 info.TypeInfoIndex[ti.Name] = i 321 } 322 323 info.GoogleCN = googleCN(r) 324 var body []byte 325 if info.Dirname == "/src" { 326 body = applyTemplate(h.p.PackageRootHTML, "packageRootHTML", info) 327 } else { 328 body = applyTemplate(h.p.PackageHTML, "packageHTML", info) 329 } 330 h.p.ServePage(w, Page{ 331 Title: title, 332 Tabtitle: tabtitle, 333 Subtitle: subtitle, 334 Body: body, 335 GoogleCN: info.GoogleCN, 336 }) 337 } 338 339 func (h *handlerServer) corpusInitialized() bool { 340 h.c.initMu.RLock() 341 defer h.c.initMu.RUnlock() 342 return h.c.initDone 343 } 344 345 type PageInfoMode uint 346 347 const ( 348 PageInfoModeQueryString = "m" // query string where PageInfoMode is stored 349 350 NoFiltering PageInfoMode = 1 << iota // do not filter exports 351 AllMethods // show all embedded methods 352 ShowSource // show source code, do not extract documentation 353 NoHTML // show result in textual form, do not generate HTML 354 FlatDir // show directory in a flat (non-indented) manner 355 NoTypeAssoc // don't associate consts, vars, and factory functions with types 356 ) 357 358 // modeNames defines names for each PageInfoMode flag. 359 var modeNames = map[string]PageInfoMode{ 360 "all": NoFiltering, 361 "methods": AllMethods, 362 "src": ShowSource, 363 "text": NoHTML, 364 "flat": FlatDir, 365 } 366 367 // generate a query string for persisting PageInfoMode between pages. 368 func modeQueryString(mode PageInfoMode) string { 369 if modeNames := mode.names(); len(modeNames) > 0 { 370 return "?m=" + strings.Join(modeNames, ",") 371 } 372 return "" 373 } 374 375 // alphabetically sorted names of active flags for a PageInfoMode. 376 func (m PageInfoMode) names() []string { 377 var names []string 378 for name, mode := range modeNames { 379 if m&mode != 0 { 380 names = append(names, name) 381 } 382 } 383 sort.Strings(names) 384 return names 385 } 386 387 // GetPageInfoMode computes the PageInfoMode flags by analyzing the request 388 // URL form value "m". It is value is a comma-separated list of mode names 389 // as defined by modeNames (e.g.: m=src,text). 390 func (p *Presentation) GetPageInfoMode(r *http.Request) PageInfoMode { 391 var mode PageInfoMode 392 for _, k := range strings.Split(r.FormValue(PageInfoModeQueryString), ",") { 393 if m, found := modeNames[strings.TrimSpace(k)]; found { 394 mode |= m 395 } 396 } 397 if p.AdjustPageInfoMode != nil { 398 mode = p.AdjustPageInfoMode(r, mode) 399 } 400 return mode 401 } 402 403 // poorMansImporter returns a (dummy) package object named 404 // by the last path component of the provided package path 405 // (as is the convention for packages). This is sufficient 406 // to resolve package identifiers without doing an actual 407 // import. It never returns an error. 408 // 409 func poorMansImporter(imports map[string]*ast.Object, path string) (*ast.Object, error) { 410 pkg := imports[path] 411 if pkg == nil { 412 // note that strings.LastIndex returns -1 if there is no "/" 413 pkg = ast.NewObj(ast.Pkg, path[strings.LastIndex(path, "/")+1:]) 414 pkg.Data = ast.NewScope(nil) // required by ast.NewPackage for dot-import 415 imports[path] = pkg 416 } 417 return pkg, nil 418 } 419 420 // globalNames returns a set of the names declared by all package-level 421 // declarations. Method names are returned in the form Receiver_Method. 422 func globalNames(pkg *ast.Package) map[string]bool { 423 names := make(map[string]bool) 424 for _, file := range pkg.Files { 425 for _, decl := range file.Decls { 426 addNames(names, decl) 427 } 428 } 429 return names 430 } 431 432 // collectExamples collects examples for pkg from testfiles. 433 func collectExamples(c *Corpus, pkg *ast.Package, testfiles map[string]*ast.File) []*doc.Example { 434 var files []*ast.File 435 for _, f := range testfiles { 436 files = append(files, f) 437 } 438 439 var examples []*doc.Example 440 globals := globalNames(pkg) 441 for _, e := range doc.Examples(files...) { 442 name := stripExampleSuffix(e.Name) 443 if name == "" || globals[name] { 444 examples = append(examples, e) 445 } else if c.Verbose { 446 log.Printf("skipping example 'Example%s' because '%s' is not a known function or type", e.Name, e.Name) 447 } 448 } 449 450 return examples 451 } 452 453 // addNames adds the names declared by decl to the names set. 454 // Method names are added in the form ReceiverTypeName_Method. 455 func addNames(names map[string]bool, decl ast.Decl) { 456 switch d := decl.(type) { 457 case *ast.FuncDecl: 458 name := d.Name.Name 459 if d.Recv != nil { 460 var typeName string 461 switch r := d.Recv.List[0].Type.(type) { 462 case *ast.StarExpr: 463 typeName = r.X.(*ast.Ident).Name 464 case *ast.Ident: 465 typeName = r.Name 466 } 467 name = typeName + "_" + name 468 } 469 names[name] = true 470 case *ast.GenDecl: 471 for _, spec := range d.Specs { 472 switch s := spec.(type) { 473 case *ast.TypeSpec: 474 names[s.Name.Name] = true 475 case *ast.ValueSpec: 476 for _, id := range s.Names { 477 names[id.Name] = true 478 } 479 } 480 } 481 } 482 } 483 484 // packageExports is a local implementation of ast.PackageExports 485 // which correctly updates each package file's comment list. 486 // (The ast.PackageExports signature is frozen, hence the local 487 // implementation). 488 // 489 func packageExports(fset *token.FileSet, pkg *ast.Package) { 490 for _, src := range pkg.Files { 491 cmap := ast.NewCommentMap(fset, src, src.Comments) 492 ast.FileExports(src) 493 src.Comments = cmap.Filter(src).Comments() 494 } 495 } 496 497 func applyTemplate(t *template.Template, name string, data interface{}) []byte { 498 var buf bytes.Buffer 499 if err := t.Execute(&buf, data); err != nil { 500 log.Printf("%s.Execute: %s", name, err) 501 } 502 return buf.Bytes() 503 } 504 505 type writerCapturesErr struct { 506 w io.Writer 507 err error 508 } 509 510 func (w *writerCapturesErr) Write(p []byte) (int, error) { 511 n, err := w.w.Write(p) 512 if err != nil { 513 w.err = err 514 } 515 return n, err 516 } 517 518 // applyTemplateToResponseWriter uses an http.ResponseWriter as the io.Writer 519 // for the call to template.Execute. It uses an io.Writer wrapper to capture 520 // errors from the underlying http.ResponseWriter. Errors are logged only when 521 // they come from the template processing and not the Writer; this avoid 522 // polluting log files with error messages due to networking issues, such as 523 // client disconnects and http HEAD protocol violations. 524 func applyTemplateToResponseWriter(rw http.ResponseWriter, t *template.Template, data interface{}) { 525 w := &writerCapturesErr{w: rw} 526 err := t.Execute(w, data) 527 // There are some cases where template.Execute does not return an error when 528 // rw returns an error, and some where it does. So check w.err first. 529 if w.err == nil && err != nil { 530 // Log template errors. 531 log.Printf("%s.Execute: %s", t.Name(), err) 532 } 533 } 534 535 func redirect(w http.ResponseWriter, r *http.Request) (redirected bool) { 536 canonical := pathpkg.Clean(r.URL.Path) 537 if !strings.HasSuffix(canonical, "/") { 538 canonical += "/" 539 } 540 if r.URL.Path != canonical { 541 url := *r.URL 542 url.Path = canonical 543 http.Redirect(w, r, url.String(), http.StatusMovedPermanently) 544 redirected = true 545 } 546 return 547 } 548 549 func redirectFile(w http.ResponseWriter, r *http.Request) (redirected bool) { 550 c := pathpkg.Clean(r.URL.Path) 551 c = strings.TrimRight(c, "/") 552 if r.URL.Path != c { 553 url := *r.URL 554 url.Path = c 555 http.Redirect(w, r, url.String(), http.StatusMovedPermanently) 556 redirected = true 557 } 558 return 559 } 560 561 func (p *Presentation) serveTextFile(w http.ResponseWriter, r *http.Request, abspath, relpath, title string) { 562 src, err := vfs.ReadFile(p.Corpus.fs, abspath) 563 if err != nil { 564 log.Printf("ReadFile: %s", err) 565 p.ServeError(w, r, relpath, err) 566 return 567 } 568 569 if r.FormValue(PageInfoModeQueryString) == "text" { 570 p.ServeText(w, src) 571 return 572 } 573 574 h := r.FormValue("h") 575 s := RangeSelection(r.FormValue("s")) 576 577 var buf bytes.Buffer 578 if pathpkg.Ext(abspath) == ".go" { 579 // Find markup links for this file (e.g. "/src/fmt/print.go"). 580 fi := p.Corpus.Analysis.FileInfo(abspath) 581 buf.WriteString("<script type='text/javascript'>document.ANALYSIS_DATA = ") 582 buf.Write(marshalJSON(fi.Data)) 583 buf.WriteString(";</script>\n") 584 585 if status := p.Corpus.Analysis.Status(); status != "" { 586 buf.WriteString("<a href='/lib/godoc/analysis/help.html'>Static analysis features</a> ") 587 // TODO(adonovan): show analysis status at per-file granularity. 588 fmt.Fprintf(&buf, "<span style='color: grey'>[%s]</span><br/>", htmlpkg.EscapeString(status)) 589 } 590 591 buf.WriteString("<pre>") 592 formatGoSource(&buf, src, fi.Links, h, s) 593 buf.WriteString("</pre>") 594 } else { 595 buf.WriteString("<pre>") 596 FormatText(&buf, src, 1, false, h, s) 597 buf.WriteString("</pre>") 598 } 599 fmt.Fprintf(&buf, `<p><a href="/%s?m=text">View as plain text</a></p>`, htmlpkg.EscapeString(relpath)) 600 601 p.ServePage(w, Page{ 602 Title: title, 603 SrcPath: relpath, 604 Tabtitle: relpath, 605 Body: buf.Bytes(), 606 GoogleCN: googleCN(r), 607 }) 608 } 609 610 // formatGoSource HTML-escapes Go source text and writes it to w, 611 // decorating it with the specified analysis links. 612 // 613 func formatGoSource(buf *bytes.Buffer, text []byte, links []analysis.Link, pattern string, selection Selection) { 614 // Emit to a temp buffer so that we can add line anchors at the end. 615 saved, buf := buf, new(bytes.Buffer) 616 617 var i int 618 var link analysis.Link // shared state of the two funcs below 619 segmentIter := func() (seg Segment) { 620 if i < len(links) { 621 link = links[i] 622 i++ 623 seg = Segment{link.Start(), link.End()} 624 } 625 return 626 } 627 linkWriter := func(w io.Writer, offs int, start bool) { 628 link.Write(w, offs, start) 629 } 630 631 comments := tokenSelection(text, token.COMMENT) 632 var highlights Selection 633 if pattern != "" { 634 highlights = regexpSelection(text, pattern) 635 } 636 637 FormatSelections(buf, text, linkWriter, segmentIter, selectionTag, comments, highlights, selection) 638 639 // Now copy buf to saved, adding line anchors. 640 641 // The lineSelection mechanism can't be composed with our 642 // linkWriter, so we have to add line spans as another pass. 643 n := 1 644 for _, line := range bytes.Split(buf.Bytes(), []byte("\n")) { 645 // The line numbers are inserted into the document via a CSS ::before 646 // pseudo-element. This prevents them from being copied when users 647 // highlight and copy text. 648 // ::before is supported in 98% of browsers: https://caniuse.com/#feat=css-gencontent 649 // This is also the trick Github uses to hide line numbers. 650 // 651 // The first tab for the code snippet needs to start in column 9, so 652 // it indents a full 8 spaces, hence the two nbsp's. Otherwise the tab 653 // character only indents about two spaces. 654 fmt.Fprintf(saved, `<span id="L%d" class="ln">%6d </span>`, n, n) 655 n++ 656 saved.Write(line) 657 saved.WriteByte('\n') 658 } 659 } 660 661 func (p *Presentation) serveDirectory(w http.ResponseWriter, r *http.Request, abspath, relpath string) { 662 if redirect(w, r) { 663 return 664 } 665 666 list, err := p.Corpus.fs.ReadDir(abspath) 667 if err != nil { 668 p.ServeError(w, r, relpath, err) 669 return 670 } 671 672 p.ServePage(w, Page{ 673 Title: "Directory", 674 SrcPath: relpath, 675 Tabtitle: relpath, 676 Body: applyTemplate(p.DirlistHTML, "dirlistHTML", list), 677 GoogleCN: googleCN(r), 678 }) 679 } 680 681 func (p *Presentation) ServeHTMLDoc(w http.ResponseWriter, r *http.Request, abspath, relpath string) { 682 // get HTML body contents 683 src, err := vfs.ReadFile(p.Corpus.fs, abspath) 684 if err != nil { 685 log.Printf("ReadFile: %s", err) 686 p.ServeError(w, r, relpath, err) 687 return 688 } 689 690 // if it begins with "<!DOCTYPE " assume it is standalone 691 // html that doesn't need the template wrapping. 692 if bytes.HasPrefix(src, doctype) { 693 w.Write(src) 694 return 695 } 696 697 // if it begins with a JSON blob, read in the metadata. 698 meta, src, err := extractMetadata(src) 699 if err != nil { 700 log.Printf("decoding metadata %s: %v", relpath, err) 701 } 702 703 page := Page{ 704 Title: meta.Title, 705 Subtitle: meta.Subtitle, 706 GoogleCN: googleCN(r), 707 } 708 709 // evaluate as template if indicated 710 if meta.Template { 711 tmpl, err := template.New("main").Funcs(p.TemplateFuncs()).Parse(string(src)) 712 if err != nil { 713 log.Printf("parsing template %s: %v", relpath, err) 714 p.ServeError(w, r, relpath, err) 715 return 716 } 717 var buf bytes.Buffer 718 if err := tmpl.Execute(&buf, page); err != nil { 719 log.Printf("executing template %s: %v", relpath, err) 720 p.ServeError(w, r, relpath, err) 721 return 722 } 723 src = buf.Bytes() 724 } 725 726 // if it's the language spec, add tags to EBNF productions 727 if strings.HasSuffix(abspath, "go_spec.html") { 728 var buf bytes.Buffer 729 Linkify(&buf, src) 730 src = buf.Bytes() 731 } 732 733 page.Body = src 734 p.ServePage(w, page) 735 } 736 737 func (p *Presentation) ServeFile(w http.ResponseWriter, r *http.Request) { 738 p.serveFile(w, r) 739 } 740 741 func (p *Presentation) serveFile(w http.ResponseWriter, r *http.Request) { 742 relpath := r.URL.Path 743 744 // Check to see if we need to redirect or serve another file. 745 if m := p.Corpus.MetadataFor(relpath); m != nil { 746 if m.Path != relpath { 747 // Redirect to canonical path. 748 http.Redirect(w, r, m.Path, http.StatusMovedPermanently) 749 return 750 } 751 // Serve from the actual filesystem path. 752 relpath = m.filePath 753 } 754 755 abspath := relpath 756 relpath = relpath[1:] // strip leading slash 757 758 switch pathpkg.Ext(relpath) { 759 case ".html": 760 if strings.HasSuffix(relpath, "/index.html") { 761 // We'll show index.html for the directory. 762 // Use the dir/ version as canonical instead of dir/index.html. 763 http.Redirect(w, r, r.URL.Path[0:len(r.URL.Path)-len("index.html")], http.StatusMovedPermanently) 764 return 765 } 766 p.ServeHTMLDoc(w, r, abspath, relpath) 767 return 768 769 case ".go": 770 p.serveTextFile(w, r, abspath, relpath, "Source file") 771 return 772 } 773 774 dir, err := p.Corpus.fs.Lstat(abspath) 775 if err != nil { 776 log.Print(err) 777 p.ServeError(w, r, relpath, err) 778 return 779 } 780 781 if dir != nil && dir.IsDir() { 782 if redirect(w, r) { 783 return 784 } 785 if index := pathpkg.Join(abspath, "index.html"); util.IsTextFile(p.Corpus.fs, index) { 786 p.ServeHTMLDoc(w, r, index, index) 787 return 788 } 789 p.serveDirectory(w, r, abspath, relpath) 790 return 791 } 792 793 if util.IsTextFile(p.Corpus.fs, abspath) { 794 if redirectFile(w, r) { 795 return 796 } 797 p.serveTextFile(w, r, abspath, relpath, "Text file") 798 return 799 } 800 801 p.fileServer.ServeHTTP(w, r) 802 } 803 804 func (p *Presentation) ServeText(w http.ResponseWriter, text []byte) { 805 w.Header().Set("Content-Type", "text/plain; charset=utf-8") 806 w.Write(text) 807 } 808 809 func marshalJSON(x interface{}) []byte { 810 var data []byte 811 var err error 812 const indentJSON = false // for easier debugging 813 if indentJSON { 814 data, err = json.MarshalIndent(x, "", " ") 815 } else { 816 data, err = json.Marshal(x) 817 } 818 if err != nil { 819 panic(fmt.Sprintf("json.Marshal failed: %s", err)) 820 } 821 return data 822 }