You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

802 lines
23 KiB

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