godoc: remove more global variables
More moves into Corpus and Presentation. R=golang-dev, adg CC=golang-dev https://golang.org/cl/11492043
This commit is contained in:
parent
4fc6323385
commit
5395cfe05a
|
|
@ -43,7 +43,7 @@ func codewalk(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// If directory exists, serve list of code walks.
|
// If directory exists, serve list of code walks.
|
||||||
dir, err := godoc.FS.Lstat(abspath)
|
dir, err := fs.Lstat(abspath)
|
||||||
if err == nil && dir.IsDir() {
|
if err == nil && dir.IsDir() {
|
||||||
codewalkDir(w, r, relpath, abspath)
|
codewalkDir(w, r, relpath, abspath)
|
||||||
return
|
return
|
||||||
|
|
@ -51,7 +51,7 @@ func codewalk(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
// If file exists, serve using standard file server.
|
// If file exists, serve using standard file server.
|
||||||
if err == nil {
|
if err == nil {
|
||||||
serveFile(w, r)
|
pres.ServeFile(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -117,7 +117,7 @@ func (st *Codestep) String() string {
|
||||||
|
|
||||||
// loadCodewalk reads a codewalk from the named XML file.
|
// loadCodewalk reads a codewalk from the named XML file.
|
||||||
func loadCodewalk(filename string) (*Codewalk, error) {
|
func loadCodewalk(filename string) (*Codewalk, error) {
|
||||||
f, err := godoc.FS.Open(filename)
|
f, err := fs.Open(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -138,7 +138,7 @@ func loadCodewalk(filename string) (*Codewalk, error) {
|
||||||
i = len(st.Src)
|
i = len(st.Src)
|
||||||
}
|
}
|
||||||
filename := st.Src[0:i]
|
filename := st.Src[0:i]
|
||||||
data, err := vfs.ReadFile(godoc.FS, filename)
|
data, err := vfs.ReadFile(fs, filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
st.Err = err
|
st.Err = err
|
||||||
continue
|
continue
|
||||||
|
|
@ -185,7 +185,7 @@ func codewalkDir(w http.ResponseWriter, r *http.Request, relpath, abspath string
|
||||||
Title string
|
Title string
|
||||||
}
|
}
|
||||||
|
|
||||||
dir, err := godoc.FS.ReadDir(abspath)
|
dir, err := fs.ReadDir(abspath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
log.Print(err)
|
||||||
pres.ServeError(w, r, relpath, err)
|
pres.ServeError(w, r, relpath, err)
|
||||||
|
|
@ -219,7 +219,7 @@ func codewalkDir(w http.ResponseWriter, r *http.Request, relpath, abspath string
|
||||||
// the usual godoc HTML wrapper.
|
// the usual godoc HTML wrapper.
|
||||||
func codewalkFileprint(w http.ResponseWriter, r *http.Request, f string) {
|
func codewalkFileprint(w http.ResponseWriter, r *http.Request, f string) {
|
||||||
abspath := f
|
abspath := f
|
||||||
data, err := vfs.ReadFile(godoc.FS, abspath)
|
data, err := vfs.ReadFile(fs, abspath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
log.Print(err)
|
||||||
pres.ServeError(w, r, f, err)
|
pres.ServeError(w, r, f, err)
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
htmlpkg "html"
|
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
|
@ -19,33 +18,9 @@ import (
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
"code.google.com/p/go.tools/godoc"
|
"code.google.com/p/go.tools/godoc"
|
||||||
"code.google.com/p/go.tools/godoc/util"
|
|
||||||
"code.google.com/p/go.tools/godoc/vfs"
|
"code.google.com/p/go.tools/godoc/vfs"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
// Globals
|
|
||||||
|
|
||||||
func flagBool(b *bool, name string, value bool, usage string) interface{} {
|
|
||||||
flag.BoolVar(b, name, value, usage)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func flagInt(v *int, name string, value int, usage string) interface{} {
|
|
||||||
flag.IntVar(v, name, value, usage)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func flagString(v *string, name string, value string, usage string) interface{} {
|
|
||||||
flag.StringVar(v, name, value, usage)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func flagFloat64(v *float64, name string, value float64, usage string) interface{} {
|
|
||||||
flag.Float64Var(v, name, value, usage)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
verbose = flag.Bool("v", false, "verbose mode")
|
verbose = flag.Bool("v", false, "verbose mode")
|
||||||
|
|
||||||
|
|
@ -54,27 +29,33 @@ var (
|
||||||
goroot = flag.String("goroot", runtime.GOROOT(), "Go root directory")
|
goroot = flag.String("goroot", runtime.GOROOT(), "Go root directory")
|
||||||
|
|
||||||
// layout control
|
// layout control
|
||||||
_ = flagInt(&godoc.TabWidth, "tabwidth", 4, "tab width")
|
tabWidth = flag.Int("tabwidth", 4, "tab width")
|
||||||
_ = flagBool(&godoc.ShowTimestamps, "timestamps", false, "show timestamps with directory listings")
|
showTimestamps = flag.Bool("timestamps", false, "show timestamps with directory listings")
|
||||||
templateDir = flag.String("templates", "", "directory containing alternate template files")
|
templateDir = flag.String("templates", "", "directory containing alternate template files")
|
||||||
_ = flagBool(&godoc.ShowPlayground, "play", false, "enable playground in web interface")
|
showPlayground = flag.Bool("play", false, "enable playground in web interface")
|
||||||
_ = flagBool(&godoc.ShowExamples, "ex", false, "show examples in command line mode")
|
showExamples = flag.Bool("ex", false, "show examples in command line mode")
|
||||||
_ = flagBool(&godoc.DeclLinks, "links", true, "link identifiers to their declarations")
|
declLinks = flag.Bool("links", true, "link identifiers to their declarations")
|
||||||
|
|
||||||
// search index
|
// search index
|
||||||
indexEnabled = flag.Bool("index", false, "enable search index")
|
indexEnabled = flag.Bool("index", false, "enable search index")
|
||||||
indexFiles = flag.String("index_files", "", "glob pattern specifying index files;"+
|
indexFiles = flag.String("index_files", "", "glob pattern specifying index files;"+
|
||||||
"if not empty, the index is read from these files in sorted order")
|
"if not empty, the index is read from these files in sorted order")
|
||||||
_ = flagInt(&godoc.MaxResults, "maxresults", 10000, "maximum number of full text search results shown")
|
maxResults = flag.Int("maxresults", 10000, "maximum number of full text search results shown")
|
||||||
_ = flagFloat64(&godoc.IndexThrottle, "index_throttle", 0.75, "index throttle value; 0.0 = no time allocated, 1.0 = full throttle")
|
indexThrottle = flag.Float64("index_throttle", 0.75, "index throttle value; 0.0 = no time allocated, 1.0 = full throttle")
|
||||||
|
|
||||||
// source code notes
|
// source code notes
|
||||||
_ = flagString(&godoc.NotesRx, "notes", "BUG", "regular expression matching note markers to show")
|
notesRx = flag.String("notes", "BUG", "regular expression matching note markers to show")
|
||||||
)
|
)
|
||||||
|
|
||||||
var pres *godoc.Presentation
|
var (
|
||||||
|
pres *godoc.Presentation
|
||||||
|
fs = vfs.NameSpace{}
|
||||||
|
)
|
||||||
|
|
||||||
func registerPublicHandlers(mux *http.ServeMux) {
|
func registerPublicHandlers(mux *http.ServeMux) {
|
||||||
|
if pres == nil {
|
||||||
|
panic("nil Presentation")
|
||||||
|
}
|
||||||
godoc.CmdHandler.RegisterWithMux(mux)
|
godoc.CmdHandler.RegisterWithMux(mux)
|
||||||
godoc.PkgHandler.RegisterWithMux(mux)
|
godoc.PkgHandler.RegisterWithMux(mux)
|
||||||
mux.HandleFunc("/doc/codewalk/", codewalk)
|
mux.HandleFunc("/doc/codewalk/", codewalk)
|
||||||
|
|
@ -82,23 +63,26 @@ func registerPublicHandlers(mux *http.ServeMux) {
|
||||||
mux.HandleFunc("/search", search)
|
mux.HandleFunc("/search", search)
|
||||||
mux.Handle("/robots.txt", godoc.FileServer)
|
mux.Handle("/robots.txt", godoc.FileServer)
|
||||||
mux.HandleFunc("/opensearch.xml", serveSearchDesc)
|
mux.HandleFunc("/opensearch.xml", serveSearchDesc)
|
||||||
mux.HandleFunc("/", serveFile)
|
mux.HandleFunc("/", pres.ServeFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
// Templates
|
// Templates
|
||||||
|
|
||||||
func readTemplate(name string) *template.Template {
|
func readTemplate(name string) *template.Template {
|
||||||
|
if pres == nil {
|
||||||
|
panic("no global Presentation set yet")
|
||||||
|
}
|
||||||
path := "lib/godoc/" + name
|
path := "lib/godoc/" + name
|
||||||
|
|
||||||
// use underlying file system fs to read the template file
|
// use underlying file system fs to read the template file
|
||||||
// (cannot use template ParseFile functions directly)
|
// (cannot use template ParseFile functions directly)
|
||||||
data, err := vfs.ReadFile(godoc.FS, path)
|
data, err := vfs.ReadFile(fs, path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("readTemplate: ", err)
|
log.Fatal("readTemplate: ", err)
|
||||||
}
|
}
|
||||||
// be explicit with errors (for app engine use)
|
// be explicit with errors (for app engine use)
|
||||||
t, err := template.New(name).Funcs(godoc.FuncMap).Parse(string(data))
|
t, err := template.New(name).Funcs(pres.FuncMap()).Parse(string(data))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("readTemplate: ", err)
|
log.Fatal("readTemplate: ", err)
|
||||||
}
|
}
|
||||||
|
|
@ -147,125 +131,6 @@ func redirect(w http.ResponseWriter, r *http.Request) (redirected bool) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func redirectFile(w http.ResponseWriter, r *http.Request) (redirected bool) {
|
|
||||||
c := pathpkg.Clean(r.URL.Path)
|
|
||||||
c = strings.TrimRight(c, "/")
|
|
||||||
if r.URL.Path != c {
|
|
||||||
url := *r.URL
|
|
||||||
url.Path = c
|
|
||||||
http.Redirect(w, r, url.String(), http.StatusMovedPermanently)
|
|
||||||
redirected = true
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func serveTextFile(w http.ResponseWriter, r *http.Request, abspath, relpath, title string) {
|
|
||||||
src, err := vfs.ReadFile(godoc.FS, abspath)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("ReadFile: %s", err)
|
|
||||||
pres.ServeError(w, r, relpath, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.FormValue("m") == "text" {
|
|
||||||
pres.ServeText(w, src)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var buf bytes.Buffer
|
|
||||||
buf.WriteString("<pre>")
|
|
||||||
godoc.FormatText(&buf, src, 1, pathpkg.Ext(abspath) == ".go", r.FormValue("h"), godoc.RangeSelection(r.FormValue("s")))
|
|
||||||
buf.WriteString("</pre>")
|
|
||||||
fmt.Fprintf(&buf, `<p><a href="/%s?m=text">View as plain text</a></p>`, htmlpkg.EscapeString(relpath))
|
|
||||||
|
|
||||||
pres.ServePage(w, godoc.Page{
|
|
||||||
Title: title + " " + relpath,
|
|
||||||
Tabtitle: relpath,
|
|
||||||
Body: buf.Bytes(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func serveDirectory(w http.ResponseWriter, r *http.Request, abspath, relpath string) {
|
|
||||||
if redirect(w, r) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
list, err := godoc.FS.ReadDir(abspath)
|
|
||||||
if err != nil {
|
|
||||||
pres.ServeError(w, r, relpath, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
pres.ServePage(w, godoc.Page{
|
|
||||||
Title: "Directory " + relpath,
|
|
||||||
Tabtitle: relpath,
|
|
||||||
Body: applyTemplate(godoc.DirlistHTML, "dirlistHTML", list),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func serveFile(w http.ResponseWriter, r *http.Request) {
|
|
||||||
relpath := r.URL.Path
|
|
||||||
|
|
||||||
// Check to see if we need to redirect or serve another file.
|
|
||||||
if m := godoc.MetadataFor(relpath); m != nil {
|
|
||||||
if m.Path != relpath {
|
|
||||||
// Redirect to canonical path.
|
|
||||||
http.Redirect(w, r, m.Path, http.StatusMovedPermanently)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Serve from the actual filesystem path.
|
|
||||||
relpath = m.FilePath()
|
|
||||||
}
|
|
||||||
|
|
||||||
abspath := relpath
|
|
||||||
relpath = relpath[1:] // strip leading slash
|
|
||||||
|
|
||||||
switch pathpkg.Ext(relpath) {
|
|
||||||
case ".html":
|
|
||||||
if strings.HasSuffix(relpath, "/index.html") {
|
|
||||||
// We'll show index.html for the directory.
|
|
||||||
// Use the dir/ version as canonical instead of dir/index.html.
|
|
||||||
http.Redirect(w, r, r.URL.Path[0:len(r.URL.Path)-len("index.html")], http.StatusMovedPermanently)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
pres.ServeHTMLDoc(w, r, abspath, relpath)
|
|
||||||
return
|
|
||||||
|
|
||||||
case ".go":
|
|
||||||
serveTextFile(w, r, abspath, relpath, "Source file")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
dir, err := godoc.FS.Lstat(abspath)
|
|
||||||
if err != nil {
|
|
||||||
log.Print(err)
|
|
||||||
pres.ServeError(w, r, relpath, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if dir != nil && dir.IsDir() {
|
|
||||||
if redirect(w, r) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if index := pathpkg.Join(abspath, "index.html"); util.IsTextFile(godoc.FS, index) {
|
|
||||||
pres.ServeHTMLDoc(w, r, index, index)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
serveDirectory(w, r, abspath, relpath)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if util.IsTextFile(godoc.FS, abspath) {
|
|
||||||
if redirectFile(w, r) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
serveTextFile(w, r, abspath, relpath, "Text file")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
godoc.FileServer.ServeHTTP(w, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
func serveSearchDesc(w http.ResponseWriter, r *http.Request) {
|
func serveSearchDesc(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Content-Type", "application/opensearchdescription+xml")
|
w.Header().Set("Content-Type", "application/opensearchdescription+xml")
|
||||||
data := map[string]interface{}{
|
data := map[string]interface{}{
|
||||||
|
|
@ -315,14 +180,13 @@ type SearchResult struct {
|
||||||
func lookup(query string) (result SearchResult) {
|
func lookup(query string) (result SearchResult) {
|
||||||
result.Query = query
|
result.Query = query
|
||||||
|
|
||||||
index, timestamp := godoc.SearchIndex.Get()
|
corp := pres.Corpus
|
||||||
|
index, timestamp := corp.CurrentIndex()
|
||||||
if index != nil {
|
if index != nil {
|
||||||
index := index.(*godoc.Index)
|
|
||||||
|
|
||||||
// identifier search
|
// identifier search
|
||||||
var err error
|
var err error
|
||||||
result.Pak, result.Hit, result.Alt, err = index.Lookup(query)
|
result.Pak, result.Hit, result.Alt, err = index.Lookup(query)
|
||||||
if err != nil && godoc.MaxResults <= 0 {
|
if err != nil && corp.MaxResults <= 0 {
|
||||||
// ignore the error if full text search is enabled
|
// ignore the error if full text search is enabled
|
||||||
// since the query may be a valid regular expression
|
// since the query may be a valid regular expression
|
||||||
result.Alert = "Error in query string: " + err.Error()
|
result.Alert = "Error in query string: " + err.Error()
|
||||||
|
|
@ -330,7 +194,7 @@ func lookup(query string) (result SearchResult) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// full text search
|
// full text search
|
||||||
if godoc.MaxResults > 0 && query != "" {
|
if corp.MaxResults > 0 && query != "" {
|
||||||
rx, err := regexp.Compile(query)
|
rx, err := regexp.Compile(query)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
result.Alert = "Error in query regular expression: " + err.Error()
|
result.Alert = "Error in query regular expression: " + err.Error()
|
||||||
|
|
@ -340,8 +204,8 @@ func lookup(query string) (result SearchResult) {
|
||||||
// maxResults results and thus the result may be incomplete (to be
|
// maxResults results and thus the result may be incomplete (to be
|
||||||
// precise, we should remove one result from the result set, but
|
// precise, we should remove one result from the result set, but
|
||||||
// nobody is going to count the results on the result page).
|
// nobody is going to count the results on the result page).
|
||||||
result.Found, result.Textual = index.LookupRegexp(rx, godoc.MaxResults+1)
|
result.Found, result.Textual = index.LookupRegexp(rx, corp.MaxResults+1)
|
||||||
result.Complete = result.Found <= godoc.MaxResults
|
result.Complete = result.Found <= corp.MaxResults
|
||||||
if !result.Complete {
|
if !result.Complete {
|
||||||
result.Found-- // since we looked for maxResults+1
|
result.Found-- // since we looked for maxResults+1
|
||||||
}
|
}
|
||||||
|
|
@ -350,7 +214,7 @@ func lookup(query string) (result SearchResult) {
|
||||||
|
|
||||||
// is the result accurate?
|
// is the result accurate?
|
||||||
if pres.Corpus.IndexEnabled {
|
if pres.Corpus.IndexEnabled {
|
||||||
if _, ts := godoc.FSModified.Get(); timestamp.Before(ts) {
|
if ts := pres.Corpus.FSModifiedTime(); timestamp.Before(ts) {
|
||||||
// The index is older than the latest file system change under godoc's observation.
|
// The index is older than the latest file system change under godoc's observation.
|
||||||
result.Alert = "Indexing in progress: result may be inaccurate"
|
result.Alert = "Indexing in progress: result may be inaccurate"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -158,9 +158,6 @@ func main() {
|
||||||
usage()
|
usage()
|
||||||
}
|
}
|
||||||
|
|
||||||
fs := vfs.NameSpace{}
|
|
||||||
godoc.FS = fs // temp hack
|
|
||||||
|
|
||||||
// Determine file system to use.
|
// Determine file system to use.
|
||||||
// TODO(gri) - fs and fsHttp should really be the same. Try to unify.
|
// TODO(gri) - fs and fsHttp should really be the same. Try to unify.
|
||||||
// - fsHttp doesn't need to be set up in command-line mode,
|
// - fsHttp doesn't need to be set up in command-line mode,
|
||||||
|
|
@ -186,35 +183,40 @@ func main() {
|
||||||
fs.Bind("/src/pkg", vfs.OS(p), "/src", vfs.BindAfter)
|
fs.Bind("/src/pkg", vfs.OS(p), "/src", vfs.BindAfter)
|
||||||
}
|
}
|
||||||
|
|
||||||
readTemplates()
|
|
||||||
|
|
||||||
corpus := godoc.NewCorpus(fs)
|
corpus := godoc.NewCorpus(fs)
|
||||||
corpus.IndexEnabled = *indexEnabled
|
corpus.IndexEnabled = *indexEnabled
|
||||||
corpus.IndexFiles = *indexFiles
|
corpus.IndexFiles = *indexFiles
|
||||||
|
|
||||||
pres = godoc.NewPresentation(corpus)
|
pres = godoc.NewPresentation(corpus)
|
||||||
// ...
|
pres.TabWidth = *tabWidth
|
||||||
|
pres.ShowTimestamps = *showTimestamps
|
||||||
|
pres.ShowPlayground = *showPlayground
|
||||||
|
pres.ShowExamples = *showExamples
|
||||||
|
pres.DeclLinks = *declLinks
|
||||||
|
|
||||||
|
readTemplates()
|
||||||
|
|
||||||
godoc.InitHandlers(pres)
|
godoc.InitHandlers(pres)
|
||||||
|
|
||||||
if *writeIndex {
|
if *writeIndex {
|
||||||
// Write search index and exit.
|
// Write search index and exit.
|
||||||
if godoc.IndexFiles == "" {
|
if *indexFiles == "" {
|
||||||
log.Fatal("no index file specified")
|
log.Fatal("no index file specified")
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Println("initialize file systems")
|
log.Println("initialize file systems")
|
||||||
*verbose = true // want to see what happens
|
*verbose = true // want to see what happens
|
||||||
|
|
||||||
godoc.IndexThrottle = 1.0
|
corpus.IndexThrottle = 1.0
|
||||||
godoc.UpdateIndex()
|
corpus.UpdateIndex()
|
||||||
|
|
||||||
log.Println("writing index file", godoc.IndexFiles)
|
log.Println("writing index file", *indexFiles)
|
||||||
f, err := os.Create(godoc.IndexFiles)
|
f, err := os.Create(*indexFiles)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
index, _ := godoc.SearchIndex.Get()
|
index, _ := corpus.CurrentIndex()
|
||||||
err = index.(*godoc.Index).Write(f)
|
err = index.Write(f)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
@ -270,12 +272,12 @@ func main() {
|
||||||
log.Printf("version = %s", runtime.Version())
|
log.Printf("version = %s", runtime.Version())
|
||||||
log.Printf("address = %s", *httpAddr)
|
log.Printf("address = %s", *httpAddr)
|
||||||
log.Printf("goroot = %s", *goroot)
|
log.Printf("goroot = %s", *goroot)
|
||||||
log.Printf("tabwidth = %d", godoc.TabWidth)
|
log.Printf("tabwidth = %d", *tabWidth)
|
||||||
switch {
|
switch {
|
||||||
case !*indexEnabled:
|
case !*indexEnabled:
|
||||||
log.Print("search index disabled")
|
log.Print("search index disabled")
|
||||||
case godoc.MaxResults > 0:
|
case *maxResults > 0:
|
||||||
log.Printf("full text index enabled (maxresults = %d)", godoc.MaxResults)
|
log.Printf("full text index enabled (maxresults = %d)", *maxResults)
|
||||||
default:
|
default:
|
||||||
log.Print("identifier search index enabled")
|
log.Print("identifier search index enabled")
|
||||||
}
|
}
|
||||||
|
|
@ -287,7 +289,7 @@ func main() {
|
||||||
|
|
||||||
// Initialize search index.
|
// Initialize search index.
|
||||||
if *indexEnabled {
|
if *indexEnabled {
|
||||||
go godoc.RunIndexer()
|
go corpus.RunIndexer()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start http server.
|
// Start http server.
|
||||||
|
|
@ -431,10 +433,10 @@ func main() {
|
||||||
}
|
}
|
||||||
if *html {
|
if *html {
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
godoc.WriteNode(&buf, info.FSet, cn)
|
pres.WriteNode(&buf, info.FSet, cn)
|
||||||
godoc.FormatText(os.Stdout, buf.Bytes(), -1, true, "", nil)
|
godoc.FormatText(os.Stdout, buf.Bytes(), -1, true, "", nil)
|
||||||
} else {
|
} else {
|
||||||
godoc.WriteNode(os.Stdout, info.FSet, cn)
|
pres.WriteNode(os.Stdout, info.FSet, cn)
|
||||||
}
|
}
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ package godoc
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
pathpkg "path"
|
pathpkg "path"
|
||||||
|
"time"
|
||||||
|
|
||||||
"code.google.com/p/go.tools/godoc/util"
|
"code.google.com/p/go.tools/godoc/util"
|
||||||
"code.google.com/p/go.tools/godoc/vfs"
|
"code.google.com/p/go.tools/godoc/vfs"
|
||||||
|
|
@ -46,6 +47,8 @@ type Corpus struct {
|
||||||
fsModified util.RWValue // timestamp of last call to invalidateIndex
|
fsModified util.RWValue // timestamp of last call to invalidateIndex
|
||||||
docMetadata util.RWValue // mapping from paths to *Metadata
|
docMetadata util.RWValue // mapping from paths to *Metadata
|
||||||
|
|
||||||
|
// SearchIndex is the search index in use.
|
||||||
|
searchIndex util.RWValue
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewCorpus returns a new Corpus from a filesystem.
|
// NewCorpus returns a new Corpus from a filesystem.
|
||||||
|
|
@ -61,6 +64,17 @@ func NewCorpus(fs vfs.FileSystem) *Corpus {
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Corpus) CurrentIndex() (*Index, time.Time) {
|
||||||
|
v, t := c.searchIndex.Get()
|
||||||
|
idx, _ := v.(*Index)
|
||||||
|
return idx, t
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Corpus) FSModifiedTime() time.Time {
|
||||||
|
_, ts := c.fsModified.Get()
|
||||||
|
return ts
|
||||||
|
}
|
||||||
|
|
||||||
// Init initializes Corpus, once options on Corpus are set.
|
// Init initializes Corpus, once options on Corpus are set.
|
||||||
// It must be called before any subsequent method calls.
|
// It must be called before any subsequent method calls.
|
||||||
func (c *Corpus) Init() error {
|
func (c *Corpus) Init() error {
|
||||||
|
|
|
||||||
|
|
@ -86,7 +86,7 @@ func (b *treeBuilder) newDirTree(fset *token.FileSet, path, name string, depth i
|
||||||
// though the directory doesn't contain any real package files - was bug)
|
// though the directory doesn't contain any real package files - was bug)
|
||||||
if synopses[0] == "" {
|
if synopses[0] == "" {
|
||||||
// no "optimal" package synopsis yet; continue to collect synopses
|
// no "optimal" package synopsis yet; continue to collect synopses
|
||||||
file, err := parseFile(fset, pathpkg.Join(path, d.Name()),
|
file, err := b.c.parseFile(fset, pathpkg.Join(path, d.Name()),
|
||||||
parser.ParseComments|parser.PackageClauseOnly)
|
parser.ParseComments|parser.PackageClauseOnly)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
hasPkgFiles = true
|
hasPkgFiles = true
|
||||||
|
|
|
||||||
155
godoc/godoc.go
155
godoc/godoc.go
|
|
@ -27,46 +27,10 @@ import (
|
||||||
"time"
|
"time"
|
||||||
"unicode"
|
"unicode"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
"code.google.com/p/go.tools/godoc/util"
|
|
||||||
"code.google.com/p/go.tools/godoc/vfs"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// FS is the file system that godoc reads from and serves.
|
// Verbose controls logging verbosity.
|
||||||
// It is a virtual file system that operates on slash-separated paths,
|
var Verbose = false
|
||||||
// and its root corresponds to the Go distribution root: /src/pkg
|
|
||||||
// holds the source tree, and so on. This means that the URLs served by
|
|
||||||
// the godoc server are the same as the paths in the virtual file
|
|
||||||
// system, which helps keep things simple.
|
|
||||||
// TODO(bradfitz): delete this global
|
|
||||||
var FS = vfs.NameSpace{}
|
|
||||||
|
|
||||||
// Old flags
|
|
||||||
var (
|
|
||||||
// DeclLinks controls whether identifers are linked to their declaration.
|
|
||||||
DeclLinks = true
|
|
||||||
|
|
||||||
// ShowExamples controls whether to show examples in command-line mode.
|
|
||||||
// TODO(bradfitz,adg): delete this flag
|
|
||||||
ShowExamples = false
|
|
||||||
|
|
||||||
// ShowPlayground controls whether to enable the playground in
|
|
||||||
// the web interface.
|
|
||||||
// TODO(bradfitz,adg): delete this flag
|
|
||||||
ShowPlayground = false
|
|
||||||
|
|
||||||
ShowTimestamps = false
|
|
||||||
|
|
||||||
Verbose = false
|
|
||||||
|
|
||||||
TabWidth = 4
|
|
||||||
|
|
||||||
// regular expression matching note markers to show
|
|
||||||
NotesRx = "BUG"
|
|
||||||
)
|
|
||||||
|
|
||||||
// SearchIndex is the search index in use.
|
|
||||||
var SearchIndex util.RWValue
|
|
||||||
|
|
||||||
// Fake relative package path for built-ins. Documentation for all globals
|
// Fake relative package path for built-ins. Documentation for all globals
|
||||||
// (not just exported ones) will be shown for packages in this directory.
|
// (not just exported ones) will be shown for packages in this directory.
|
||||||
|
|
@ -77,39 +41,57 @@ const BuiltinPkgPath = "builtin"
|
||||||
// Convention: template function names ending in "_html" or "_url" produce
|
// Convention: template function names ending in "_html" or "_url" produce
|
||||||
// HTML- or URL-escaped strings; all other function results may
|
// HTML- or URL-escaped strings; all other function results may
|
||||||
// require explicit escaping in the template.
|
// require explicit escaping in the template.
|
||||||
var FuncMap = template.FuncMap{
|
func (p *Presentation) FuncMap() template.FuncMap {
|
||||||
// various helpers
|
p.initFuncMapOnce.Do(p.initFuncMap)
|
||||||
"filename": filenameFunc,
|
return p.funcMap
|
||||||
"repeat": strings.Repeat,
|
}
|
||||||
|
|
||||||
// access to FileInfos (directory listings)
|
func (p *Presentation) TemplateFuncs() template.FuncMap {
|
||||||
"fileInfoName": fileInfoNameFunc,
|
p.initFuncMapOnce.Do(p.initFuncMap)
|
||||||
"fileInfoTime": fileInfoTimeFunc,
|
return p.templateFuncs
|
||||||
|
}
|
||||||
|
|
||||||
// access to search result information
|
func (p *Presentation) initFuncMap() {
|
||||||
"infoKind_html": infoKind_htmlFunc,
|
if p.Corpus == nil {
|
||||||
"infoLine": infoLineFunc,
|
panic("nil Presentation.Corpus")
|
||||||
"infoSnippet_html": infoSnippet_htmlFunc,
|
}
|
||||||
|
p.templateFuncs = template.FuncMap{
|
||||||
|
"code": p.code,
|
||||||
|
}
|
||||||
|
p.funcMap = template.FuncMap{
|
||||||
|
// various helpers
|
||||||
|
"filename": filenameFunc,
|
||||||
|
"repeat": strings.Repeat,
|
||||||
|
|
||||||
// formatting of AST nodes
|
// access to FileInfos (directory listings)
|
||||||
"node": nodeFunc,
|
"fileInfoName": fileInfoNameFunc,
|
||||||
"node_html": node_htmlFunc,
|
"fileInfoTime": fileInfoTimeFunc,
|
||||||
"comment_html": comment_htmlFunc,
|
|
||||||
"comment_text": comment_textFunc,
|
|
||||||
|
|
||||||
// support for URL attributes
|
// access to search result information
|
||||||
"pkgLink": pkgLinkFunc,
|
"infoKind_html": infoKind_htmlFunc,
|
||||||
"srcLink": srcLinkFunc,
|
"infoLine": p.infoLineFunc,
|
||||||
"posLink_url": posLink_urlFunc,
|
"infoSnippet_html": p.infoSnippet_htmlFunc,
|
||||||
|
|
||||||
// formatting of Examples
|
// formatting of AST nodes
|
||||||
"example_html": example_htmlFunc,
|
"node": p.nodeFunc,
|
||||||
"example_text": example_textFunc,
|
"node_html": p.node_htmlFunc,
|
||||||
"example_name": example_nameFunc,
|
"comment_html": comment_htmlFunc,
|
||||||
"example_suffix": example_suffixFunc,
|
"comment_text": comment_textFunc,
|
||||||
|
|
||||||
// formatting of Notes
|
// support for URL attributes
|
||||||
"noteTitle": noteTitle,
|
"pkgLink": pkgLinkFunc,
|
||||||
|
"srcLink": srcLinkFunc,
|
||||||
|
"posLink_url": posLink_urlFunc,
|
||||||
|
|
||||||
|
// formatting of Examples
|
||||||
|
"example_html": p.example_htmlFunc,
|
||||||
|
"example_text": p.example_textFunc,
|
||||||
|
"example_name": p.example_nameFunc,
|
||||||
|
"example_suffix": p.example_suffixFunc,
|
||||||
|
|
||||||
|
// formatting of Notes
|
||||||
|
"noteTitle": noteTitle,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func filenameFunc(path string) string {
|
func filenameFunc(path string) string {
|
||||||
|
|
@ -148,10 +130,10 @@ func infoKind_htmlFunc(info SpotInfo) string {
|
||||||
return infoKinds[info.Kind()] // infoKind entries are html-escaped
|
return infoKinds[info.Kind()] // infoKind entries are html-escaped
|
||||||
}
|
}
|
||||||
|
|
||||||
func infoLineFunc(info SpotInfo) int {
|
func (p *Presentation) infoLineFunc(info SpotInfo) int {
|
||||||
line := info.Lori()
|
line := info.Lori()
|
||||||
if info.IsIndex() {
|
if info.IsIndex() {
|
||||||
index, _ := SearchIndex.Get()
|
index, _ := p.Corpus.searchIndex.Get()
|
||||||
if index != nil {
|
if index != nil {
|
||||||
line = index.(*Index).Snippet(line).Line
|
line = index.(*Index).Snippet(line).Line
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -165,27 +147,27 @@ func infoLineFunc(info SpotInfo) int {
|
||||||
return line
|
return line
|
||||||
}
|
}
|
||||||
|
|
||||||
func infoSnippet_htmlFunc(info SpotInfo) string {
|
func (p *Presentation) infoSnippet_htmlFunc(info SpotInfo) string {
|
||||||
if info.IsIndex() {
|
if info.IsIndex() {
|
||||||
index, _ := SearchIndex.Get()
|
index, _ := p.Corpus.searchIndex.Get()
|
||||||
// Snippet.Text was HTML-escaped when it was generated
|
// Snippet.Text was HTML-escaped when it was generated
|
||||||
return index.(*Index).Snippet(info.Lori()).Text
|
return index.(*Index).Snippet(info.Lori()).Text
|
||||||
}
|
}
|
||||||
return `<span class="alert">no snippet text available</span>`
|
return `<span class="alert">no snippet text available</span>`
|
||||||
}
|
}
|
||||||
|
|
||||||
func nodeFunc(info *PageInfo, node interface{}) string {
|
func (p *Presentation) nodeFunc(info *PageInfo, node interface{}) string {
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
writeNode(&buf, info.FSet, node)
|
p.writeNode(&buf, info.FSet, node)
|
||||||
return buf.String()
|
return buf.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func node_htmlFunc(info *PageInfo, node interface{}, linkify bool) string {
|
func (p *Presentation) node_htmlFunc(info *PageInfo, node interface{}, linkify bool) string {
|
||||||
var buf1 bytes.Buffer
|
var buf1 bytes.Buffer
|
||||||
writeNode(&buf1, info.FSet, node)
|
p.writeNode(&buf1, info.FSet, node)
|
||||||
|
|
||||||
var buf2 bytes.Buffer
|
var buf2 bytes.Buffer
|
||||||
if n, _ := node.(ast.Node); n != nil && linkify && DeclLinks {
|
if n, _ := node.(ast.Node); n != nil && linkify && p.DeclLinks {
|
||||||
LinkifyText(&buf2, buf1.Bytes(), n)
|
LinkifyText(&buf2, buf1.Bytes(), n)
|
||||||
} else {
|
} else {
|
||||||
FormatText(&buf2, buf1.Bytes(), -1, true, "", nil)
|
FormatText(&buf2, buf1.Bytes(), -1, true, "", nil)
|
||||||
|
|
@ -307,8 +289,8 @@ func srcLinkFunc(s string) string {
|
||||||
return pathpkg.Clean("/" + s)
|
return pathpkg.Clean("/" + s)
|
||||||
}
|
}
|
||||||
|
|
||||||
func example_textFunc(info *PageInfo, funcName, indent string) string {
|
func (p *Presentation) example_textFunc(info *PageInfo, funcName, indent string) string {
|
||||||
if !ShowExamples {
|
if !p.ShowExamples {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -328,7 +310,7 @@ func example_textFunc(info *PageInfo, funcName, indent string) string {
|
||||||
// print code
|
// print code
|
||||||
cnode := &printer.CommentedNode{Node: eg.Code, Comments: eg.Comments}
|
cnode := &printer.CommentedNode{Node: eg.Code, Comments: eg.Comments}
|
||||||
var buf1 bytes.Buffer
|
var buf1 bytes.Buffer
|
||||||
writeNode(&buf1, info.FSet, cnode)
|
p.writeNode(&buf1, info.FSet, cnode)
|
||||||
code := buf1.String()
|
code := buf1.String()
|
||||||
// Additional formatting if this is a function body.
|
// Additional formatting if this is a function body.
|
||||||
if n := len(code); n >= 2 && code[0] == '{' && code[n-1] == '}' {
|
if n := len(code); n >= 2 && code[0] == '{' && code[n-1] == '}' {
|
||||||
|
|
@ -348,7 +330,7 @@ func example_textFunc(info *PageInfo, funcName, indent string) string {
|
||||||
return buf.String()
|
return buf.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func example_htmlFunc(info *PageInfo, funcName string) string {
|
func (p *Presentation) example_htmlFunc(info *PageInfo, funcName string) string {
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
for _, eg := range info.Examples {
|
for _, eg := range info.Examples {
|
||||||
name := stripExampleSuffix(eg.Name)
|
name := stripExampleSuffix(eg.Name)
|
||||||
|
|
@ -359,7 +341,7 @@ func example_htmlFunc(info *PageInfo, funcName string) string {
|
||||||
|
|
||||||
// print code
|
// print code
|
||||||
cnode := &printer.CommentedNode{Node: eg.Code, Comments: eg.Comments}
|
cnode := &printer.CommentedNode{Node: eg.Code, Comments: eg.Comments}
|
||||||
code := node_htmlFunc(info, cnode, true)
|
code := p.node_htmlFunc(info, cnode, true)
|
||||||
out := eg.Output
|
out := eg.Output
|
||||||
wholeFile := true
|
wholeFile := true
|
||||||
|
|
||||||
|
|
@ -379,7 +361,7 @@ func example_htmlFunc(info *PageInfo, funcName string) string {
|
||||||
// Write out the playground code in standard Go style
|
// Write out the playground code in standard Go style
|
||||||
// (use tabs, no comment highlight, etc).
|
// (use tabs, no comment highlight, etc).
|
||||||
play := ""
|
play := ""
|
||||||
if eg.Play != nil && ShowPlayground {
|
if eg.Play != nil && p.ShowPlayground {
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
if err := format.Node(&buf, info.FSet, eg.Play); err != nil {
|
if err := format.Node(&buf, info.FSet, eg.Play); err != nil {
|
||||||
log.Print(err)
|
log.Print(err)
|
||||||
|
|
@ -410,7 +392,7 @@ func example_htmlFunc(info *PageInfo, funcName string) string {
|
||||||
|
|
||||||
// example_nameFunc takes an example function name and returns its display
|
// example_nameFunc takes an example function name and returns its display
|
||||||
// name. For example, "Foo_Bar_quux" becomes "Foo.Bar (Quux)".
|
// name. For example, "Foo_Bar_quux" becomes "Foo.Bar (Quux)".
|
||||||
func example_nameFunc(s string) string {
|
func (p *Presentation) example_nameFunc(s string) string {
|
||||||
name, suffix := splitExampleName(s)
|
name, suffix := splitExampleName(s)
|
||||||
// replace _ with . for method names
|
// replace _ with . for method names
|
||||||
name = strings.Replace(name, "_", ".", 1)
|
name = strings.Replace(name, "_", ".", 1)
|
||||||
|
|
@ -423,7 +405,7 @@ func example_nameFunc(s string) string {
|
||||||
|
|
||||||
// example_suffixFunc takes an example function name and returns its suffix in
|
// example_suffixFunc takes an example function name and returns its suffix in
|
||||||
// parenthesized form. For example, "Foo_Bar_quux" becomes " (Quux)".
|
// parenthesized form. For example, "Foo_Bar_quux" becomes " (Quux)".
|
||||||
func example_suffixFunc(name string) string {
|
func (p *Presentation) example_suffixFunc(name string) string {
|
||||||
_, suffix := splitExampleName(name)
|
_, suffix := splitExampleName(name)
|
||||||
return suffix
|
return suffix
|
||||||
}
|
}
|
||||||
|
|
@ -462,7 +444,7 @@ func splitExampleName(s string) (name, suffix string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write an AST node to w.
|
// Write an AST node to w.
|
||||||
func writeNode(w io.Writer, fset *token.FileSet, x interface{}) {
|
func (p *Presentation) writeNode(w io.Writer, fset *token.FileSet, x interface{}) {
|
||||||
// convert trailing tabs into spaces using a tconv filter
|
// convert trailing tabs into spaces using a tconv filter
|
||||||
// to ensure a good outcome in most browsers (there may still
|
// to ensure a good outcome in most browsers (there may still
|
||||||
// be tabs in comments and strings, but converting those into
|
// be tabs in comments and strings, but converting those into
|
||||||
|
|
@ -472,10 +454,13 @@ func writeNode(w io.Writer, fset *token.FileSet, x interface{}) {
|
||||||
// with an another printer mode (which is more efficiently
|
// with an another printer mode (which is more efficiently
|
||||||
// implemented in the printer than here with another layer)
|
// implemented in the printer than here with another layer)
|
||||||
mode := printer.TabIndent | printer.UseSpaces
|
mode := printer.TabIndent | printer.UseSpaces
|
||||||
err := (&printer.Config{Mode: mode, Tabwidth: TabWidth}).Fprint(&tconv{output: w}, fset, x)
|
err := (&printer.Config{Mode: mode, Tabwidth: p.TabWidth}).Fprint(&tconv{p: p, output: w}, fset, x)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
log.Print(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var WriteNode = writeNode
|
// WriteNote writes x to w.
|
||||||
|
func (p *Presentation) WriteNode(w io.Writer, fset *token.FileSet, x interface{}) {
|
||||||
|
p.writeNode(w, fset, x)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -62,19 +62,6 @@ import (
|
||||||
"code.google.com/p/go.tools/godoc/util"
|
"code.google.com/p/go.tools/godoc/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO(bradfitz,adg): legacy flag vars. clean up.
|
|
||||||
var (
|
|
||||||
MaxResults = 1000
|
|
||||||
|
|
||||||
// index throttle value; 0.0 = no time allocated, 1.0 = full throttle
|
|
||||||
IndexThrottle float64 = 0.75
|
|
||||||
|
|
||||||
// IndexFiles is a glob pattern specifying index files; if
|
|
||||||
// not empty, the index is read from these files in sorted
|
|
||||||
// order")
|
|
||||||
IndexFiles string
|
|
||||||
)
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
// InterfaceSlice is a helper type for sorting interface
|
// InterfaceSlice is a helper type for sorting interface
|
||||||
// slices according to some slice-specific sort criteria.
|
// slices according to some slice-specific sort criteria.
|
||||||
|
|
@ -384,6 +371,7 @@ type Statistics struct {
|
||||||
// interface for walking file trees, and the ast.Visitor interface for
|
// interface for walking file trees, and the ast.Visitor interface for
|
||||||
// walking Go ASTs.
|
// walking Go ASTs.
|
||||||
type Indexer struct {
|
type Indexer struct {
|
||||||
|
c *Corpus
|
||||||
fset *token.FileSet // file set for all indexed files
|
fset *token.FileSet // file set for all indexed files
|
||||||
sources bytes.Buffer // concatenated sources
|
sources bytes.Buffer // concatenated sources
|
||||||
packages map[string]*Pak // map of canonicalized *Paks
|
packages map[string]*Pak // map of canonicalized *Paks
|
||||||
|
|
@ -549,7 +537,7 @@ func pkgName(filename string) string {
|
||||||
// failed (that is, if the file was not added), it returns file == nil.
|
// failed (that is, if the file was not added), it returns file == nil.
|
||||||
func (x *Indexer) addFile(filename string, goFile bool) (file *token.File, ast *ast.File) {
|
func (x *Indexer) addFile(filename string, goFile bool) (file *token.File, ast *ast.File) {
|
||||||
// open file
|
// open file
|
||||||
f, err := FS.Open(filename)
|
f, err := x.c.fs.Open(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -716,19 +704,20 @@ func canonical(w string) string { return strings.ToLower(w) }
|
||||||
// NewIndex creates a new index for the .go files
|
// NewIndex creates a new index for the .go files
|
||||||
// in the directories given by dirnames.
|
// in the directories given by dirnames.
|
||||||
//
|
//
|
||||||
func NewIndex(dirnames <-chan string, fulltextIndex bool, throttle float64) *Index {
|
func NewIndex(c *Corpus, dirnames <-chan string, fulltextIndex bool, throttle float64) *Index {
|
||||||
var x Indexer
|
var x Indexer
|
||||||
th := util.NewThrottle(throttle, 100*time.Millisecond) // run at least 0.1s at a time
|
th := util.NewThrottle(throttle, 100*time.Millisecond) // run at least 0.1s at a time
|
||||||
|
|
||||||
// initialize Indexer
|
// initialize Indexer
|
||||||
// (use some reasonably sized maps to start)
|
// (use some reasonably sized maps to start)
|
||||||
|
x.c = c
|
||||||
x.fset = token.NewFileSet()
|
x.fset = token.NewFileSet()
|
||||||
x.packages = make(map[string]*Pak, 256)
|
x.packages = make(map[string]*Pak, 256)
|
||||||
x.words = make(map[string]*IndexResult, 8192)
|
x.words = make(map[string]*IndexResult, 8192)
|
||||||
|
|
||||||
// index all files in the directories given by dirnames
|
// index all files in the directories given by dirnames
|
||||||
for dirname := range dirnames {
|
for dirname := range dirnames {
|
||||||
list, err := FS.ReadDir(dirname)
|
list, err := c.fs.ReadDir(dirname)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue // ignore this directory
|
continue // ignore this directory
|
||||||
}
|
}
|
||||||
|
|
@ -1046,19 +1035,19 @@ func (c *Corpus) invalidateIndex() {
|
||||||
// indexUpToDate() returns true if the search index is not older
|
// indexUpToDate() returns true if the search index is not older
|
||||||
// than any of the file systems under godoc's observation.
|
// than any of the file systems under godoc's observation.
|
||||||
//
|
//
|
||||||
func indexUpToDate() bool {
|
func (c *Corpus) indexUpToDate() bool {
|
||||||
_, fsTime := FSModified.Get()
|
_, fsTime := c.fsModified.Get()
|
||||||
_, siTime := SearchIndex.Get()
|
_, siTime := c.searchIndex.Get()
|
||||||
return !fsTime.After(siTime)
|
return !fsTime.After(siTime)
|
||||||
}
|
}
|
||||||
|
|
||||||
// feedDirnames feeds the directory names of all directories
|
// feedDirnames feeds the directory names of all directories
|
||||||
// under the file system given by root to channel c.
|
// under the file system given by root to channel c.
|
||||||
//
|
//
|
||||||
func feedDirnames(root *util.RWValue, c chan<- string) {
|
func (c *Corpus) feedDirnames(ch chan<- string) {
|
||||||
if dir, _ := root.Get(); dir != nil {
|
if dir, _ := c.fsTree.Get(); dir != nil {
|
||||||
for d := range dir.(*Directory).iter(false) {
|
for d := range dir.(*Directory).iter(false) {
|
||||||
c <- d.Path
|
ch <- d.Path
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1066,16 +1055,16 @@ func feedDirnames(root *util.RWValue, c chan<- string) {
|
||||||
// fsDirnames() returns a channel sending all directory names
|
// fsDirnames() returns a channel sending all directory names
|
||||||
// of all the file systems under godoc's observation.
|
// of all the file systems under godoc's observation.
|
||||||
//
|
//
|
||||||
func fsDirnames() <-chan string {
|
func (c *Corpus) fsDirnames() <-chan string {
|
||||||
c := make(chan string, 256) // buffered for fewer context switches
|
ch := make(chan string, 256) // buffered for fewer context switches
|
||||||
go func() {
|
go func() {
|
||||||
feedDirnames(&FSTree, c)
|
c.feedDirnames(ch)
|
||||||
close(c)
|
close(ch)
|
||||||
}()
|
}()
|
||||||
return c
|
return ch
|
||||||
}
|
}
|
||||||
|
|
||||||
func readIndex(filenames string) error {
|
func (c *Corpus) readIndex(filenames string) error {
|
||||||
matches, err := filepath.Glob(filenames)
|
matches, err := filepath.Glob(filenames)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
@ -1096,18 +1085,18 @@ func readIndex(filenames string) error {
|
||||||
if err := x.Read(io.MultiReader(files...)); err != nil {
|
if err := x.Read(io.MultiReader(files...)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
SearchIndex.Set(x)
|
c.searchIndex.Set(x)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func UpdateIndex() {
|
func (c *Corpus) UpdateIndex() {
|
||||||
if Verbose {
|
if Verbose {
|
||||||
log.Printf("updating index...")
|
log.Printf("updating index...")
|
||||||
}
|
}
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
index := NewIndex(fsDirnames(), MaxResults > 0, IndexThrottle)
|
index := NewIndex(c, c.fsDirnames(), c.MaxResults > 0, c.IndexThrottle)
|
||||||
stop := time.Now()
|
stop := time.Now()
|
||||||
SearchIndex.Set(index)
|
c.searchIndex.Set(index)
|
||||||
if Verbose {
|
if Verbose {
|
||||||
secs := stop.Sub(start).Seconds()
|
secs := stop.Sub(start).Seconds()
|
||||||
stats := index.Stats()
|
stats := index.Stats()
|
||||||
|
|
@ -1123,19 +1112,19 @@ func UpdateIndex() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// RunIndexer runs forever, indexing.
|
// RunIndexer runs forever, indexing.
|
||||||
func RunIndexer() {
|
func (c *Corpus) RunIndexer() {
|
||||||
// initialize the index from disk if possible
|
// initialize the index from disk if possible
|
||||||
if IndexFiles != "" {
|
if c.IndexFiles != "" {
|
||||||
if err := readIndex(IndexFiles); err != nil {
|
if err := c.readIndex(c.IndexFiles); err != nil {
|
||||||
log.Printf("error reading index: %s", err)
|
log.Printf("error reading index: %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// repeatedly update the index when it goes out of date
|
// repeatedly update the index when it goes out of date
|
||||||
for {
|
for {
|
||||||
if !indexUpToDate() {
|
if !c.indexUpToDate() {
|
||||||
// index possibly out of date - make a new one
|
// index possibly out of date - make a new one
|
||||||
UpdateIndex()
|
c.UpdateIndex()
|
||||||
}
|
}
|
||||||
delay := 60 * time.Second // by default, try every 60s
|
delay := 60 * time.Second // by default, try every 60s
|
||||||
if false { // TODO(bradfitz): was: *testDir != "" {
|
if false { // TODO(bradfitz): was: *testDir != "" {
|
||||||
|
|
|
||||||
|
|
@ -99,14 +99,14 @@ func (c *Corpus) updateMetadata() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
scan("/doc")
|
scan("/doc")
|
||||||
DocMetadata.Set(metadata)
|
c.docMetadata.Set(metadata)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MetadataFor returns the *Metadata for a given relative path or nil if none
|
// MetadataFor returns the *Metadata for a given relative path or nil if none
|
||||||
// exists.
|
// exists.
|
||||||
//
|
//
|
||||||
func MetadataFor(relpath string) *Metadata {
|
func (c *Corpus) MetadataFor(relpath string) *Metadata {
|
||||||
if m, _ := DocMetadata.Get(); m != nil {
|
if m, _ := c.docMetadata.Get(); m != nil {
|
||||||
meta := m.(map[string]*Metadata)
|
meta := m.(map[string]*Metadata)
|
||||||
// If metadata for this relpath exists, return it.
|
// If metadata for this relpath exists, return it.
|
||||||
if p := meta[relpath]; p != nil {
|
if p := meta[relpath]; p != nil {
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ func (p *Presentation) ServePage(w http.ResponseWriter, page Page) {
|
||||||
page.Tabtitle = page.Title
|
page.Tabtitle = page.Title
|
||||||
}
|
}
|
||||||
page.SearchBox = p.Corpus.IndexEnabled
|
page.SearchBox = p.Corpus.IndexEnabled
|
||||||
page.Playground = ShowPlayground
|
page.Playground = p.ShowPlayground
|
||||||
page.Version = runtime.Version()
|
page.Version = runtime.Version()
|
||||||
if err := GodocHTML.Execute(w, page); err != nil && err != http.ErrBodyNotAllowed {
|
if err := GodocHTML.Execute(w, page); err != nil && err != http.ErrBodyNotAllowed {
|
||||||
// Only log if there's an error that's not about writing on HEAD requests.
|
// Only log if there's an error that's not about writing on HEAD requests.
|
||||||
|
|
|
||||||
|
|
@ -16,19 +16,19 @@ import (
|
||||||
"code.google.com/p/go.tools/godoc/vfs"
|
"code.google.com/p/go.tools/godoc/vfs"
|
||||||
)
|
)
|
||||||
|
|
||||||
func parseFile(fset *token.FileSet, filename string, mode parser.Mode) (*ast.File, error) {
|
func (c *Corpus) parseFile(fset *token.FileSet, filename string, mode parser.Mode) (*ast.File, error) {
|
||||||
src, err := vfs.ReadFile(FS, filename)
|
src, err := vfs.ReadFile(c.fs, filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return parser.ParseFile(fset, filename, src, mode)
|
return parser.ParseFile(fset, filename, src, mode)
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseFiles(fset *token.FileSet, abspath string, localnames []string) (map[string]*ast.File, error) {
|
func (c *Corpus) parseFiles(fset *token.FileSet, abspath string, localnames []string) (map[string]*ast.File, error) {
|
||||||
files := make(map[string]*ast.File)
|
files := make(map[string]*ast.File)
|
||||||
for _, f := range localnames {
|
for _, f := range localnames {
|
||||||
absname := pathpkg.Join(abspath, f)
|
absname := pathpkg.Join(abspath, f)
|
||||||
file, err := parseFile(fset, absname, parser.ParseComments)
|
file, err := c.parseFile(fset, absname, parser.ParseComments)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,8 @@
|
||||||
package godoc
|
package godoc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"regexp"
|
||||||
|
"sync"
|
||||||
"text/template"
|
"text/template"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -19,6 +21,12 @@ type Presentation struct {
|
||||||
ShowPlayground bool
|
ShowPlayground bool
|
||||||
ShowExamples bool
|
ShowExamples bool
|
||||||
DeclLinks bool
|
DeclLinks bool
|
||||||
|
|
||||||
|
NotesRx *regexp.Regexp
|
||||||
|
|
||||||
|
initFuncMapOnce sync.Once
|
||||||
|
funcMap template.FuncMap
|
||||||
|
templateFuncs template.FuncMap
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewPresentation returns a new Presentation from a corpus.
|
// NewPresentation returns a new Presentation from a corpus.
|
||||||
|
|
@ -33,7 +41,3 @@ func NewPresentation(c *Corpus) *Presentation {
|
||||||
DeclLinks: true,
|
DeclLinks: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Presentation) FuncMap() template.FuncMap {
|
|
||||||
panic("")
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,6 @@ import (
|
||||||
"os"
|
"os"
|
||||||
pathpkg "path"
|
pathpkg "path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
|
||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
"text/template"
|
||||||
"time"
|
"time"
|
||||||
|
|
@ -35,11 +34,6 @@ var (
|
||||||
FileServer http.Handler // default file server
|
FileServer http.Handler // default file server
|
||||||
CmdHandler Server
|
CmdHandler Server
|
||||||
PkgHandler Server
|
PkgHandler Server
|
||||||
|
|
||||||
// file system information
|
|
||||||
FSTree util.RWValue // *Directory tree of packages, updated with each sync (but sync code is removed now)
|
|
||||||
FSModified util.RWValue // timestamp of last call to invalidateIndex
|
|
||||||
DocMetadata util.RWValue // mapping from paths to *Metadata
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func InitHandlers(p *Presentation) {
|
func InitHandlers(p *Presentation) {
|
||||||
|
|
@ -83,8 +77,17 @@ func (h *Server) GetPageInfo(abspath, relpath string, mode PageInfoMode) *PageIn
|
||||||
// set ctxt.GOOS and ctxt.GOARCH before calling ctxt.ImportDir.
|
// set ctxt.GOOS and ctxt.GOARCH before calling ctxt.ImportDir.
|
||||||
ctxt := build.Default
|
ctxt := build.Default
|
||||||
ctxt.IsAbsPath = pathpkg.IsAbs
|
ctxt.IsAbsPath = pathpkg.IsAbs
|
||||||
ctxt.ReadDir = fsReadDir
|
ctxt.ReadDir = func(dir string) ([]os.FileInfo, error) {
|
||||||
ctxt.OpenFile = fsOpenFile
|
return h.c.fs.ReadDir(filepath.ToSlash(dir))
|
||||||
|
}
|
||||||
|
ctxt.OpenFile = func(name string) (r io.ReadCloser, err error) {
|
||||||
|
data, err := vfs.ReadFile(h.c.fs, filepath.ToSlash(name))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return ioutil.NopCloser(bytes.NewReader(data)), nil
|
||||||
|
}
|
||||||
|
|
||||||
pkginfo, err := ctxt.ImportDir(abspath, 0)
|
pkginfo, err := ctxt.ImportDir(abspath, 0)
|
||||||
// continue if there are no Go source files; we still want the directory info
|
// continue if there are no Go source files; we still want the directory info
|
||||||
if _, nogo := err.(*build.NoGoError); err != nil && !nogo {
|
if _, nogo := err.(*build.NoGoError); err != nil && !nogo {
|
||||||
|
|
@ -109,7 +112,7 @@ func (h *Server) GetPageInfo(abspath, relpath string, mode PageInfoMode) *PageIn
|
||||||
if len(pkgfiles) > 0 {
|
if len(pkgfiles) > 0 {
|
||||||
// build package AST
|
// build package AST
|
||||||
fset := token.NewFileSet()
|
fset := token.NewFileSet()
|
||||||
files, err := parseFiles(fset, abspath, pkgfiles)
|
files, err := h.c.parseFiles(fset, abspath, pkgfiles)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
info.Err = err
|
info.Err = err
|
||||||
return info
|
return info
|
||||||
|
|
@ -133,7 +136,7 @@ func (h *Server) GetPageInfo(abspath, relpath string, mode PageInfoMode) *PageIn
|
||||||
|
|
||||||
// collect examples
|
// collect examples
|
||||||
testfiles := append(pkginfo.TestGoFiles, pkginfo.XTestGoFiles...)
|
testfiles := append(pkginfo.TestGoFiles, pkginfo.XTestGoFiles...)
|
||||||
files, err = parseFiles(fset, abspath, testfiles)
|
files, err = h.c.parseFiles(fset, abspath, testfiles)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("parsing examples:", err)
|
log.Println("parsing examples:", err)
|
||||||
}
|
}
|
||||||
|
|
@ -142,7 +145,7 @@ func (h *Server) GetPageInfo(abspath, relpath string, mode PageInfoMode) *PageIn
|
||||||
// collect any notes that we want to show
|
// collect any notes that we want to show
|
||||||
if info.PDoc.Notes != nil {
|
if info.PDoc.Notes != nil {
|
||||||
// could regexp.Compile only once per godoc, but probably not worth it
|
// could regexp.Compile only once per godoc, but probably not worth it
|
||||||
if rx, err := regexp.Compile(NotesRx); err == nil {
|
if rx := h.p.NotesRx; rx != nil {
|
||||||
for m, n := range info.PDoc.Notes {
|
for m, n := range info.PDoc.Notes {
|
||||||
if rx.MatchString(m) {
|
if rx.MatchString(m) {
|
||||||
if info.Notes == nil {
|
if info.Notes == nil {
|
||||||
|
|
@ -169,7 +172,7 @@ func (h *Server) GetPageInfo(abspath, relpath string, mode PageInfoMode) *PageIn
|
||||||
// get directory information, if any
|
// get directory information, if any
|
||||||
var dir *Directory
|
var dir *Directory
|
||||||
var timestamp time.Time
|
var timestamp time.Time
|
||||||
if tree, ts := FSTree.Get(); tree != nil && tree.(*Directory) != nil {
|
if tree, ts := h.c.fsTree.Get(); tree != nil && tree.(*Directory) != nil {
|
||||||
// directory tree is present; lookup respective directory
|
// directory tree is present; lookup respective directory
|
||||||
// (may still fail if the file system was updated and the
|
// (may still fail if the file system was updated and the
|
||||||
// new directory tree has not yet been computed)
|
// new directory tree has not yet been computed)
|
||||||
|
|
@ -223,7 +226,7 @@ func (h *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
default:
|
default:
|
||||||
tabtitle = info.Dirname
|
tabtitle = info.Dirname
|
||||||
title = "Directory "
|
title = "Directory "
|
||||||
if ShowTimestamps {
|
if h.p.ShowTimestamps {
|
||||||
subtitle = "Last update: " + info.DirTime.String()
|
subtitle = "Last update: " + info.DirTime.String()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -292,20 +295,6 @@ var AdjustPageInfoMode = func(_ *http.Request, mode PageInfoMode) PageInfoMode {
|
||||||
return mode
|
return mode
|
||||||
}
|
}
|
||||||
|
|
||||||
// fsReadDir implements ReadDir for the go/build package.
|
|
||||||
func fsReadDir(dir string) ([]os.FileInfo, error) {
|
|
||||||
return FS.ReadDir(filepath.ToSlash(dir))
|
|
||||||
}
|
|
||||||
|
|
||||||
// fsOpenFile implements OpenFile for the go/build package.
|
|
||||||
func fsOpenFile(name string) (r io.ReadCloser, err error) {
|
|
||||||
data, err := vfs.ReadFile(FS, filepath.ToSlash(name))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return ioutil.NopCloser(bytes.NewReader(data)), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// poorMansImporter returns a (dummy) package object named
|
// poorMansImporter returns a (dummy) package object named
|
||||||
// by the last path component of the provided package path
|
// by the last path component of the provided package path
|
||||||
// (as is the convention for packages). This is sufficient
|
// (as is the convention for packages). This is sufficient
|
||||||
|
|
@ -465,7 +454,7 @@ func (p *Presentation) serveDirectory(w http.ResponseWriter, r *http.Request, ab
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
list, err := FS.ReadDir(abspath)
|
list, err := p.Corpus.fs.ReadDir(abspath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.ServeError(w, r, relpath, err)
|
p.ServeError(w, r, relpath, err)
|
||||||
return
|
return
|
||||||
|
|
@ -480,7 +469,7 @@ func (p *Presentation) serveDirectory(w http.ResponseWriter, r *http.Request, ab
|
||||||
|
|
||||||
func (p *Presentation) ServeHTMLDoc(w http.ResponseWriter, r *http.Request, abspath, relpath string) {
|
func (p *Presentation) ServeHTMLDoc(w http.ResponseWriter, r *http.Request, abspath, relpath string) {
|
||||||
// get HTML body contents
|
// get HTML body contents
|
||||||
src, err := vfs.ReadFile(FS, abspath)
|
src, err := vfs.ReadFile(p.Corpus.fs, abspath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("ReadFile: %s", err)
|
log.Printf("ReadFile: %s", err)
|
||||||
p.ServeError(w, r, relpath, err)
|
p.ServeError(w, r, relpath, err)
|
||||||
|
|
@ -502,7 +491,7 @@ func (p *Presentation) ServeHTMLDoc(w http.ResponseWriter, r *http.Request, absp
|
||||||
|
|
||||||
// evaluate as template if indicated
|
// evaluate as template if indicated
|
||||||
if meta.Template {
|
if meta.Template {
|
||||||
tmpl, err := template.New("main").Funcs(TemplateFuncs).Parse(string(src))
|
tmpl, err := template.New("main").Funcs(p.TemplateFuncs()).Parse(string(src))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("parsing template %s: %v", relpath, err)
|
log.Printf("parsing template %s: %v", relpath, err)
|
||||||
p.ServeError(w, r, relpath, err)
|
p.ServeError(w, r, relpath, err)
|
||||||
|
|
@ -531,11 +520,15 @@ func (p *Presentation) ServeHTMLDoc(w http.ResponseWriter, r *http.Request, absp
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *Presentation) ServeFile(w http.ResponseWriter, r *http.Request) {
|
||||||
|
p.serveFile(w, r)
|
||||||
|
}
|
||||||
|
|
||||||
func (p *Presentation) serveFile(w http.ResponseWriter, r *http.Request) {
|
func (p *Presentation) serveFile(w http.ResponseWriter, r *http.Request) {
|
||||||
relpath := r.URL.Path
|
relpath := r.URL.Path
|
||||||
|
|
||||||
// Check to see if we need to redirect or serve another file.
|
// Check to see if we need to redirect or serve another file.
|
||||||
if m := MetadataFor(relpath); m != nil {
|
if m := p.Corpus.MetadataFor(relpath); m != nil {
|
||||||
if m.Path != relpath {
|
if m.Path != relpath {
|
||||||
// Redirect to canonical path.
|
// Redirect to canonical path.
|
||||||
http.Redirect(w, r, m.Path, http.StatusMovedPermanently)
|
http.Redirect(w, r, m.Path, http.StatusMovedPermanently)
|
||||||
|
|
@ -564,7 +557,7 @@ func (p *Presentation) serveFile(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
dir, err := FS.Lstat(abspath)
|
dir, err := p.Corpus.fs.Lstat(abspath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
log.Print(err)
|
||||||
p.ServeError(w, r, relpath, err)
|
p.ServeError(w, r, relpath, err)
|
||||||
|
|
@ -575,7 +568,7 @@ func (p *Presentation) serveFile(w http.ResponseWriter, r *http.Request) {
|
||||||
if redirect(w, r) {
|
if redirect(w, r) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if index := pathpkg.Join(abspath, "index.html"); util.IsTextFile(FS, index) {
|
if index := pathpkg.Join(abspath, "index.html"); util.IsTextFile(p.Corpus.fs, index) {
|
||||||
p.ServeHTMLDoc(w, r, index, index)
|
p.ServeHTMLDoc(w, r, index, index)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -583,7 +576,7 @@ func (p *Presentation) serveFile(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if util.IsTextFile(FS, abspath) {
|
if util.IsTextFile(p.Corpus.fs, abspath) {
|
||||||
if redirectFile(w, r) {
|
if redirectFile(w, r) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,10 +21,10 @@ type Snippet struct {
|
||||||
Text string // HTML-escaped
|
Text string // HTML-escaped
|
||||||
}
|
}
|
||||||
|
|
||||||
func newSnippet(fset *token.FileSet, decl ast.Decl, id *ast.Ident) *Snippet {
|
func (p *Presentation) newSnippet(fset *token.FileSet, decl ast.Decl, id *ast.Ident) *Snippet {
|
||||||
// TODO instead of pretty-printing the node, should use the original source instead
|
// TODO instead of pretty-printing the node, should use the original source instead
|
||||||
var buf1 bytes.Buffer
|
var buf1 bytes.Buffer
|
||||||
writeNode(&buf1, fset, decl)
|
p.writeNode(&buf1, fset, decl)
|
||||||
// wrap text with <pre> tag
|
// wrap text with <pre> tag
|
||||||
var buf2 bytes.Buffer
|
var buf2 bytes.Buffer
|
||||||
buf2.WriteString("<pre>")
|
buf2.WriteString("<pre>")
|
||||||
|
|
@ -55,7 +55,7 @@ func findSpec(list []ast.Spec, id *ast.Ident) ast.Spec {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func genSnippet(fset *token.FileSet, d *ast.GenDecl, id *ast.Ident) *Snippet {
|
func (p *Presentation) genSnippet(fset *token.FileSet, d *ast.GenDecl, id *ast.Ident) *Snippet {
|
||||||
s := findSpec(d.Specs, id)
|
s := findSpec(d.Specs, id)
|
||||||
if s == nil {
|
if s == nil {
|
||||||
return nil // declaration doesn't contain id - exit gracefully
|
return nil // declaration doesn't contain id - exit gracefully
|
||||||
|
|
@ -71,10 +71,10 @@ func genSnippet(fset *token.FileSet, d *ast.GenDecl, id *ast.Ident) *Snippet {
|
||||||
Rparen: d.Rparen,
|
Rparen: d.Rparen,
|
||||||
}
|
}
|
||||||
|
|
||||||
return newSnippet(fset, dd, id)
|
return p.newSnippet(fset, dd, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func funcSnippet(fset *token.FileSet, d *ast.FuncDecl, id *ast.Ident) *Snippet {
|
func (p *Presentation) funcSnippet(fset *token.FileSet, d *ast.FuncDecl, id *ast.Ident) *Snippet {
|
||||||
if d.Name != id {
|
if d.Name != id {
|
||||||
return nil // declaration doesn't contain id - exit gracefully
|
return nil // declaration doesn't contain id - exit gracefully
|
||||||
}
|
}
|
||||||
|
|
@ -87,19 +87,30 @@ func funcSnippet(fset *token.FileSet, d *ast.FuncDecl, id *ast.Ident) *Snippet {
|
||||||
Type: d.Type,
|
Type: d.Type,
|
||||||
}
|
}
|
||||||
|
|
||||||
return newSnippet(fset, dd, id)
|
return p.newSnippet(fset, dd, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSnippet creates a text snippet from a declaration decl containing an
|
// NewSnippet creates a text snippet from a declaration decl containing an
|
||||||
// identifier id. Parts of the declaration not containing the identifier
|
// identifier id. Parts of the declaration not containing the identifier
|
||||||
// may be removed for a more compact snippet.
|
// may be removed for a more compact snippet.
|
||||||
//
|
func NewSnippet(fset *token.FileSet, decl ast.Decl, id *ast.Ident) *Snippet {
|
||||||
func NewSnippet(fset *token.FileSet, decl ast.Decl, id *ast.Ident) (s *Snippet) {
|
// TODO(bradfitz, adg): remove this function. But it's used by indexer, which
|
||||||
|
// doesn't have a *Presentation, and NewSnippet needs a TabWidth.
|
||||||
|
var p Presentation
|
||||||
|
p.TabWidth = 4
|
||||||
|
return p.NewSnippet(fset, decl, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSnippet creates a text snippet from a declaration decl containing an
|
||||||
|
// identifier id. Parts of the declaration not containing the identifier
|
||||||
|
// may be removed for a more compact snippet.
|
||||||
|
func (p *Presentation) NewSnippet(fset *token.FileSet, decl ast.Decl, id *ast.Ident) *Snippet {
|
||||||
|
var s *Snippet
|
||||||
switch d := decl.(type) {
|
switch d := decl.(type) {
|
||||||
case *ast.GenDecl:
|
case *ast.GenDecl:
|
||||||
s = genSnippet(fset, d, id)
|
s = p.genSnippet(fset, d, id)
|
||||||
case *ast.FuncDecl:
|
case *ast.FuncDecl:
|
||||||
s = funcSnippet(fset, d, id)
|
s = p.funcSnippet(fset, d, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
// handle failure gracefully
|
// handle failure gracefully
|
||||||
|
|
@ -108,5 +119,5 @@ func NewSnippet(fset *token.FileSet, decl ast.Decl, id *ast.Ident) (s *Snippet)
|
||||||
fmt.Fprintf(&buf, `<span class="alert">could not generate a snippet for <span class="highlight">%s</span></span>`, id.Name)
|
fmt.Fprintf(&buf, `<span class="alert">could not generate a snippet for <span class="highlight">%s</span></span>`, id.Name)
|
||||||
s = &Snippet{fset.Position(id.Pos()).Line, buf.String()}
|
s = &Snippet{fset.Position(id.Pos()).Line, buf.String()}
|
||||||
}
|
}
|
||||||
return
|
return s
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ type tconv struct {
|
||||||
output io.Writer
|
output io.Writer
|
||||||
state int // indenting or collecting
|
state int // indenting or collecting
|
||||||
indent int // valid if state == indenting
|
indent int // valid if state == indenting
|
||||||
|
p *Presentation
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *tconv) writeIndent() (err error) {
|
func (p *tconv) writeIndent() (err error) {
|
||||||
|
|
@ -44,7 +45,7 @@ func (p *tconv) Write(data []byte) (n int, err error) {
|
||||||
case indenting:
|
case indenting:
|
||||||
switch b {
|
switch b {
|
||||||
case '\t':
|
case '\t':
|
||||||
p.indent += TabWidth
|
p.indent += p.p.TabWidth
|
||||||
case '\n':
|
case '\n':
|
||||||
p.indent = 0
|
p.indent = 0
|
||||||
if _, err = p.output.Write(data[n : n+1]); err != nil {
|
if _, err = p.output.Write(data[n : n+1]); err != nil {
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,6 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
|
||||||
|
|
||||||
"code.google.com/p/go.tools/godoc/vfs"
|
"code.google.com/p/go.tools/godoc/vfs"
|
||||||
)
|
)
|
||||||
|
|
@ -45,14 +44,10 @@ import (
|
||||||
// Functions in this file panic on error, but the panic is recovered
|
// Functions in this file panic on error, but the panic is recovered
|
||||||
// to an error by 'code'.
|
// to an error by 'code'.
|
||||||
|
|
||||||
var TemplateFuncs = template.FuncMap{
|
|
||||||
"code": code,
|
|
||||||
}
|
|
||||||
|
|
||||||
// contents reads and returns the content of the named file
|
// contents reads and returns the content of the named file
|
||||||
// (from the virtual file system, so for example /doc refers to $GOROOT/doc).
|
// (from the virtual file system, so for example /doc refers to $GOROOT/doc).
|
||||||
func contents(name string) string {
|
func (c *Corpus) contents(name string) string {
|
||||||
file, err := vfs.ReadFile(FS, name)
|
file, err := vfs.ReadFile(c.fs, name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Panic(err)
|
log.Panic(err)
|
||||||
}
|
}
|
||||||
|
|
@ -75,14 +70,14 @@ func stringFor(arg interface{}) string {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func code(file string, arg ...interface{}) (s string, err error) {
|
func (p *Presentation) code(file string, arg ...interface{}) (s string, err error) {
|
||||||
defer func() {
|
defer func() {
|
||||||
if r := recover(); r != nil {
|
if r := recover(); r != nil {
|
||||||
err = fmt.Errorf("%v", r)
|
err = fmt.Errorf("%v", r)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
text := contents(file)
|
text := p.Corpus.contents(file)
|
||||||
var command string
|
var command string
|
||||||
switch len(arg) {
|
switch len(arg) {
|
||||||
case 0:
|
case 0:
|
||||||
|
|
@ -90,10 +85,10 @@ func code(file string, arg ...interface{}) (s string, err error) {
|
||||||
command = fmt.Sprintf("code %q", file)
|
command = fmt.Sprintf("code %q", file)
|
||||||
case 1:
|
case 1:
|
||||||
command = fmt.Sprintf("code %q %s", file, stringFor(arg[0]))
|
command = fmt.Sprintf("code %q %s", file, stringFor(arg[0]))
|
||||||
text = oneLine(file, text, arg[0])
|
text = p.Corpus.oneLine(file, text, arg[0])
|
||||||
case 2:
|
case 2:
|
||||||
command = fmt.Sprintf("code %q %s %s", file, stringFor(arg[0]), stringFor(arg[1]))
|
command = fmt.Sprintf("code %q %s %s", file, stringFor(arg[0]), stringFor(arg[1]))
|
||||||
text = multipleLines(file, text, arg[0], arg[1])
|
text = p.Corpus.multipleLines(file, text, arg[0], arg[1])
|
||||||
default:
|
default:
|
||||||
return "", fmt.Errorf("incorrect code invocation: code %q %q", file, arg)
|
return "", fmt.Errorf("incorrect code invocation: code %q %q", file, arg)
|
||||||
}
|
}
|
||||||
|
|
@ -125,8 +120,8 @@ func parseArg(arg interface{}, file string, max int) (ival int, sval string, isI
|
||||||
}
|
}
|
||||||
|
|
||||||
// oneLine returns the single line generated by a two-argument code invocation.
|
// oneLine returns the single line generated by a two-argument code invocation.
|
||||||
func oneLine(file, text string, arg interface{}) string {
|
func (c *Corpus) oneLine(file, text string, arg interface{}) string {
|
||||||
lines := strings.SplitAfter(contents(file), "\n")
|
lines := strings.SplitAfter(c.contents(file), "\n")
|
||||||
line, pattern, isInt := parseArg(arg, file, len(lines))
|
line, pattern, isInt := parseArg(arg, file, len(lines))
|
||||||
if isInt {
|
if isInt {
|
||||||
return lines[line-1]
|
return lines[line-1]
|
||||||
|
|
@ -135,8 +130,8 @@ func oneLine(file, text string, arg interface{}) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
// multipleLines returns the text generated by a three-argument code invocation.
|
// multipleLines returns the text generated by a three-argument code invocation.
|
||||||
func multipleLines(file, text string, arg1, arg2 interface{}) string {
|
func (c *Corpus) multipleLines(file, text string, arg1, arg2 interface{}) string {
|
||||||
lines := strings.SplitAfter(contents(file), "\n")
|
lines := strings.SplitAfter(c.contents(file), "\n")
|
||||||
line1, pattern1, isInt1 := parseArg(arg1, file, len(lines))
|
line1, pattern1, isInt1 := parseArg(arg1, file, len(lines))
|
||||||
line2, pattern2, isInt2 := parseArg(arg2, file, len(lines))
|
line2, pattern2, isInt2 := parseArg(arg2, file, len(lines))
|
||||||
if !isInt1 {
|
if !isInt1 {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue