GoPLS Viewer

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