diff --git a/cmd/godoc/codewalk.go b/cmd/godoc/codewalk.go index 9be894f4..f9e161b8 100644 --- a/cmd/godoc/codewalk.go +++ b/cmd/godoc/codewalk.go @@ -20,6 +20,7 @@ import ( "log" "net/http" "os" + pathpkg "path" "regexp" "sort" "strconv" @@ -31,6 +32,8 @@ import ( "code.google.com/p/go.tools/godoc/vfs" ) +var codewalkHTML, codewalkdirHTML *template.Template + // Handler for /doc/codewalk/ and below. func codewalk(w http.ResponseWriter, r *http.Request) { relpath := r.URL.Path[len("/doc/codewalk/"):] @@ -78,6 +81,20 @@ func codewalk(w http.ResponseWriter, r *http.Request) { }) } +func redirect(w http.ResponseWriter, r *http.Request) (redirected bool) { + canonical := pathpkg.Clean(r.URL.Path) + if !strings.HasSuffix(canonical, "/") { + canonical += "/" + } + if r.URL.Path != canonical { + url := *r.URL + url.Path = canonical + http.Redirect(w, r, url.String(), http.StatusMovedPermanently) + redirected = true + } + return +} + // A Codewalk represents a single codewalk read from an XML file. type Codewalk struct { Title string `xml:"title,attr"` diff --git a/cmd/godoc/godoc.go b/cmd/godoc/godoc.go deleted file mode 100644 index 9fc2c8c1..00000000 --- a/cmd/godoc/godoc.go +++ /dev/null @@ -1,250 +0,0 @@ -// Copyright 2009 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package main - -import ( - "bytes" - "flag" - "fmt" - "log" - "net/http" - "net/url" - pathpkg "path" - "regexp" - "runtime" - "strings" - "text/template" - - "code.google.com/p/go.tools/godoc" - "code.google.com/p/go.tools/godoc/vfs" -) - -var ( - verbose = flag.Bool("v", false, "verbose mode") - - // file system roots - // TODO(gri) consider the invariant that goroot always end in '/' - goroot = flag.String("goroot", runtime.GOROOT(), "Go root directory") - - // layout control - tabWidth = flag.Int("tabwidth", 4, "tab width") - showTimestamps = flag.Bool("timestamps", false, "show timestamps with directory listings") - templateDir = flag.String("templates", "", "directory containing alternate template files") - showPlayground = flag.Bool("play", false, "enable playground in web interface") - showExamples = flag.Bool("ex", false, "show examples in command line mode") - declLinks = flag.Bool("links", true, "link identifiers to their declarations") - - // search index - indexEnabled = flag.Bool("index", false, "enable search index") - indexFiles = flag.String("index_files", "", "glob pattern specifying index files;"+ - "if not empty, the index is read from these files in sorted order") - maxResults = flag.Int("maxresults", 10000, "maximum number of full text search results shown") - indexThrottle = flag.Float64("index_throttle", 0.75, "index throttle value; 0.0 = no time allocated, 1.0 = full throttle") - - // source code notes - notesRx = flag.String("notes", "BUG", "regular expression matching note markers to show") -) - -var ( - pres *godoc.Presentation - fs = vfs.NameSpace{} -) - -func registerPublicHandlers(mux *http.ServeMux) { - if pres == nil { - panic("nil Presentation") - } - godoc.CmdHandler.RegisterWithMux(mux) - godoc.PkgHandler.RegisterWithMux(mux) - mux.HandleFunc("/doc/codewalk/", codewalk) - mux.Handle("/doc/play/", godoc.FileServer) - mux.HandleFunc("/search", search) - mux.Handle("/robots.txt", godoc.FileServer) - mux.HandleFunc("/opensearch.xml", serveSearchDesc) - mux.HandleFunc("/", pres.ServeFile) -} - -// ---------------------------------------------------------------------------- -// Templates - -func readTemplate(name string) *template.Template { - if pres == nil { - panic("no global Presentation set yet") - } - path := "lib/godoc/" + name - - // use underlying file system fs to read the template file - // (cannot use template ParseFile functions directly) - data, err := vfs.ReadFile(fs, path) - if err != nil { - log.Fatal("readTemplate: ", err) - } - // be explicit with errors (for app engine use) - t, err := template.New(name).Funcs(pres.FuncMap()).Parse(string(data)) - if err != nil { - log.Fatal("readTemplate: ", err) - } - return t -} - -var codewalkHTML, codewalkdirHTML *template.Template - -func readTemplates() { - // have to delay until after flags processing since paths depend on goroot - codewalkHTML = readTemplate("codewalk.html") - codewalkdirHTML = readTemplate("codewalkdir.html") - godoc.DirlistHTML = readTemplate("dirlist.html") - godoc.ErrorHTML = readTemplate("error.html") - godoc.ExampleHTML = readTemplate("example.html") - godoc.GodocHTML = readTemplate("godoc.html") - godoc.PackageHTML = readTemplate("package.html") - godoc.PackageText = readTemplate("package.txt") - godoc.SearchHTML = readTemplate("search.html") - godoc.SearchText = readTemplate("search.txt") - godoc.SearchDescXML = readTemplate("opensearch.xml") -} - -// ---------------------------------------------------------------------------- -// Files - -func applyTemplate(t *template.Template, name string, data interface{}) []byte { - var buf bytes.Buffer - if err := t.Execute(&buf, data); err != nil { - log.Printf("%s.Execute: %s", name, err) - } - return buf.Bytes() -} - -func redirect(w http.ResponseWriter, r *http.Request) (redirected bool) { - canonical := pathpkg.Clean(r.URL.Path) - if !strings.HasSuffix(canonical, "/") { - canonical += "/" - } - if r.URL.Path != canonical { - url := *r.URL - url.Path = canonical - http.Redirect(w, r, url.String(), http.StatusMovedPermanently) - redirected = true - } - return -} - -func serveSearchDesc(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/opensearchdescription+xml") - data := map[string]interface{}{ - "BaseURL": fmt.Sprintf("http://%s", r.Host), - } - if err := godoc.SearchDescXML.Execute(w, &data); err != nil && err != http.ErrBodyNotAllowed { - // Only log if there's an error that's not about writing on HEAD requests. - // See Issues 5451 and 5454. - log.Printf("searchDescXML.Execute: %s", err) - } -} - -// ---------------------------------------------------------------------------- -// Packages - -// remoteSearchURL returns the search URL for a given query as needed by -// remoteSearch. If html is set, an html result is requested; otherwise -// the result is in textual form. -// Adjust this function as necessary if modeNames or FormValue parameters -// change. -func remoteSearchURL(query string, html bool) string { - s := "/search?m=text&q=" - if html { - s = "/search?q=" - } - return s + url.QueryEscape(query) -} - -// ---------------------------------------------------------------------------- -// Search - -type SearchResult struct { - Query string - Alert string // error or warning message - - // identifier matches - Pak godoc.HitList // packages matching Query - Hit *godoc.LookupResult // identifier matches of Query - Alt *godoc.AltWords // alternative identifiers to look for - - // textual matches - Found int // number of textual occurrences found - Textual []godoc.FileLines // textual matches of Query - Complete bool // true if all textual occurrences of Query are reported -} - -func lookup(query string) (result SearchResult) { - result.Query = query - - corp := pres.Corpus - index, timestamp := corp.CurrentIndex() - if index != nil { - // identifier search - var err error - result.Pak, result.Hit, result.Alt, err = index.Lookup(query) - if err != nil && corp.MaxResults <= 0 { - // ignore the error if full text search is enabled - // since the query may be a valid regular expression - result.Alert = "Error in query string: " + err.Error() - return - } - - // full text search - if corp.MaxResults > 0 && query != "" { - rx, err := regexp.Compile(query) - if err != nil { - result.Alert = "Error in query regular expression: " + err.Error() - return - } - // If we get maxResults+1 results we know that there are more than - // maxResults results and thus the result may be incomplete (to be - // precise, we should remove one result from the result set, but - // nobody is going to count the results on the result page). - result.Found, result.Textual = index.LookupRegexp(rx, corp.MaxResults+1) - result.Complete = result.Found <= corp.MaxResults - if !result.Complete { - result.Found-- // since we looked for maxResults+1 - } - } - } - - // is the result accurate? - if pres.Corpus.IndexEnabled { - if ts := pres.Corpus.FSModifiedTime(); timestamp.Before(ts) { - // The index is older than the latest file system change under godoc's observation. - result.Alert = "Indexing in progress: result may be inaccurate" - } - } else { - result.Alert = "Search index disabled: no results available" - } - - return -} - -func search(w http.ResponseWriter, r *http.Request) { - query := strings.TrimSpace(r.FormValue("q")) - result := lookup(query) - - if godoc.GetPageInfoMode(r)&godoc.NoHTML != 0 { - pres.ServeText(w, applyTemplate(godoc.SearchText, "searchText", result)) - return - } - - var title string - if result.Hit != nil || len(result.Textual) > 0 { - title = fmt.Sprintf(`Results for query %q`, query) - } else { - title = fmt.Sprintf(`No results found for query %q`, query) - } - - pres.ServePage(w, godoc.Page{ - Title: title, - Tabtitle: query, - Query: query, - Body: applyTemplate(godoc.SearchHTML, "searchHTML", result), - }) -} diff --git a/cmd/godoc/main.go b/cmd/godoc/main.go index 4b48f273..521b496d 100644 --- a/cmd/godoc/main.go +++ b/cmd/godoc/main.go @@ -30,14 +30,12 @@ package main import ( "archive/zip" "bytes" - "errors" _ "expvar" // to serve /debug/vars "flag" "fmt" "go/ast" "go/build" "go/printer" - "io" "log" "net/http" "net/http/httptest" @@ -49,6 +47,7 @@ import ( "regexp" "runtime" "strings" + "text/template" "code.google.com/p/go.tools/godoc" "code.google.com/p/go.tools/godoc/vfs" @@ -76,8 +75,115 @@ var ( // command-line searches query = flag.Bool("q", false, "arguments are considered search queries") + + verbose = flag.Bool("v", false, "verbose mode") + + // file system roots + // TODO(gri) consider the invariant that goroot always end in '/' + goroot = flag.String("goroot", runtime.GOROOT(), "Go root directory") + + // layout control + tabWidth = flag.Int("tabwidth", 4, "tab width") + showTimestamps = flag.Bool("timestamps", false, "show timestamps with directory listings") + templateDir = flag.String("templates", "", "directory containing alternate template files") + showPlayground = flag.Bool("play", false, "enable playground in web interface") + showExamples = flag.Bool("ex", false, "show examples in command line mode") + declLinks = flag.Bool("links", true, "link identifiers to their declarations") + + // search index + indexEnabled = flag.Bool("index", false, "enable search index") + indexFiles = flag.String("index_files", "", "glob pattern specifying index files;"+ + "if not empty, the index is read from these files in sorted order") + maxResults = flag.Int("maxresults", 10000, "maximum number of full text search results shown") + indexThrottle = flag.Float64("index_throttle", 0.75, "index throttle value; 0.0 = no time allocated, 1.0 = full throttle") + + // source code notes + notesRx = flag.String("notes", "BUG", "regular expression matching note markers to show") ) +var ( + pres *godoc.Presentation + fs = vfs.NameSpace{} +) + +func registerPublicHandlers(mux *http.ServeMux) { + if pres == nil { + panic("nil Presentation") + } + godoc.CmdHandler.RegisterWithMux(mux) + godoc.PkgHandler.RegisterWithMux(mux) + mux.HandleFunc("/doc/codewalk/", codewalk) + mux.Handle("/doc/play/", godoc.FileServer) + mux.HandleFunc("/search", pres.HandleSearch) + mux.Handle("/robots.txt", godoc.FileServer) + mux.HandleFunc("/opensearch.xml", serveSearchDesc) + mux.HandleFunc("/", pres.ServeFile) +} + +// ---------------------------------------------------------------------------- +// Templates + +func readTemplate(name string) *template.Template { + if pres == nil { + panic("no global Presentation set yet") + } + path := "lib/godoc/" + name + + // use underlying file system fs to read the template file + // (cannot use template ParseFile functions directly) + data, err := vfs.ReadFile(fs, path) + if err != nil { + log.Fatal("readTemplate: ", err) + } + // be explicit with errors (for app engine use) + t, err := template.New(name).Funcs(pres.FuncMap()).Parse(string(data)) + if err != nil { + log.Fatal("readTemplate: ", err) + } + return t +} + +func readTemplates(p *godoc.Presentation) { + // have to delay until after flags processing since paths depend on goroot + codewalkHTML = readTemplate("codewalk.html") + codewalkdirHTML = readTemplate("codewalkdir.html") + p.DirlistHTML = readTemplate("dirlist.html") + p.ErrorHTML = readTemplate("error.html") + p.ExampleHTML = readTemplate("example.html") + p.GodocHTML = readTemplate("godoc.html") + p.PackageHTML = readTemplate("package.html") + p.PackageText = readTemplate("package.txt") + p.SearchHTML = readTemplate("search.html") + p.SearchText = readTemplate("search.txt") + p.SearchDescXML = readTemplate("opensearch.xml") +} + +// ---------------------------------------------------------------------------- +// Files + +func applyTemplate(t *template.Template, name string, data interface{}) []byte { + var buf bytes.Buffer + if err := t.Execute(&buf, data); err != nil { + log.Printf("%s.Execute: %s", name, err) + } + return buf.Bytes() +} + +func serveSearchDesc(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/opensearchdescription+xml") + data := map[string]interface{}{ + "BaseURL": fmt.Sprintf("http://%s", r.Host), + } + if err := pres.SearchDescXML.Execute(w, &data); err != nil && err != http.ErrBodyNotAllowed { + // Only log if there's an error that's not about writing on HEAD requests. + // See Issues 5451 and 5454. + log.Printf("searchDescXML.Execute: %s", err) + } +} + +// ---------------------------------------------------------------------------- +// Packages + func usage() { fmt.Fprintf(os.Stderr, "usage: godoc package [name ...]\n"+ @@ -93,36 +199,6 @@ func loggingHandler(h http.Handler) http.Handler { }) } -func remoteSearch(query string) (res *http.Response, err error) { - // list of addresses to try - var addrs []string - if *serverAddr != "" { - // explicit server address - only try this one - addrs = []string{*serverAddr} - } else { - addrs = []string{ - defaultAddr, - "golang.org", - } - } - - // remote search - search := remoteSearchURL(query, *html) - for _, addr := range addrs { - url := "http://" + addr + search - res, err = http.Get(url) - if err == nil && res.StatusCode == http.StatusOK { - break - } - } - - if err == nil && res.StatusCode != http.StatusOK { - err = errors.New(res.Status) - } - - return -} - // Does s look like a regular expression? func isRegexp(s string) bool { return strings.IndexAny(s, ".(|)*+?^$[]") >= 0 @@ -149,6 +225,44 @@ func makeRx(names []string) (rx *regexp.Regexp) { return } +func handleURLFlag() { + registerPublicHandlers(http.DefaultServeMux) + + // Try up to 10 fetches, following redirects. + urlstr := *urlFlag + for i := 0; i < 10; i++ { + // Prepare request. + u, err := url.Parse(urlstr) + if err != nil { + log.Fatal(err) + } + req := &http.Request{ + URL: u, + } + + // Invoke default HTTP handler to serve request + // to our buffering httpWriter. + w := httptest.NewRecorder() + http.DefaultServeMux.ServeHTTP(w, req) + + // Return data, error, or follow redirect. + switch w.Code { + case 200: // ok + os.Stdout.Write(w.Body.Bytes()) + return + case 301, 302, 303, 307: // redirect + redirect := w.HeaderMap.Get("Location") + if redirect == "" { + log.Fatalf("HTTP %d without Location header", w.Code) + } + urlstr = redirect + default: + log.Fatalf("HTTP error %d", w.Code) + } + } + log.Fatalf("too many redirects") +} + func main() { flag.Usage = usage flag.Parse() @@ -175,7 +289,7 @@ func main() { log.Fatalf("%s: %s\n", *zipfile, err) } defer rc.Close() // be nice (e.g., -writeIndex mode) - fs.Bind("/", zipvfs.New(rc, *zipfile), *goroot, vfs.BindReplace) + fs.Bind("/", zipfs.New(rc, *zipfile), *goroot, vfs.BindReplace) } // Bind $GOPATH trees into Go root. @@ -186,6 +300,12 @@ func main() { corpus := godoc.NewCorpus(fs) corpus.IndexEnabled = *indexEnabled corpus.IndexFiles = *indexFiles + if *writeIndex { + corpus.IndexThrottle = 1.0 + } + if err := corpus.Init(); err != nil { + log.Fatal(err) + } pres = godoc.NewPresentation(corpus) pres.TabWidth = *tabWidth @@ -193,8 +313,11 @@ func main() { pres.ShowPlayground = *showPlayground pres.ShowExamples = *showExamples pres.DeclLinks = *declLinks + if *notesRx != "" { + pres.NotesRx = regexp.MustCompile(*notesRx) + } - readTemplates() + readTemplates(pres) godoc.InitHandlers(pres) @@ -207,7 +330,6 @@ func main() { log.Println("initialize file systems") *verbose = true // want to see what happens - corpus.IndexThrottle = 1.0 corpus.UpdateIndex() log.Println("writing index file", *indexFiles) @@ -227,41 +349,8 @@ func main() { // Print content that would be served at the URL *urlFlag. if *urlFlag != "" { - registerPublicHandlers(http.DefaultServeMux) - - // Try up to 10 fetches, following redirects. - urlstr := *urlFlag - for i := 0; i < 10; i++ { - // Prepare request. - u, err := url.Parse(urlstr) - if err != nil { - log.Fatal(err) - } - req := &http.Request{ - URL: u, - } - - // Invoke default HTTP handler to serve request - // to our buffering httpWriter. - w := httptest.NewRecorder() - http.DefaultServeMux.ServeHTTP(w, req) - - // Return data, error, or follow redirect. - switch w.Code { - case 200: // ok - os.Stdout.Write(w.Body.Bytes()) - return - case 301, 302, 303, 307: // redirect - redirect := w.HeaderMap.Get("Location") - if redirect == "" { - log.Fatalf("HTTP %d without Location header", w.Code) - } - urlstr = redirect - default: - log.Fatalf("HTTP error %d", w.Code) - } - } - log.Fatalf("too many redirects") + handleURLFlag() + return } if *httpAddr != "" { @@ -300,22 +389,15 @@ func main() { return } - packageText := godoc.PackageText + packageText := pres.PackageText // Command line mode. if *html { - packageText = godoc.PackageHTML + packageText = pres.PackageHTML } if *query { - // Command-line queries. - for i := 0; i < flag.NArg(); i++ { - res, err := remoteSearch(flag.Arg(i)) - if err != nil { - log.Fatalf("remoteSearch: %s", err) - } - io.Copy(os.Stdout, res.Body) - } + handleRemoteSearch() return } diff --git a/cmd/godoc/remotesearch.go b/cmd/godoc/remotesearch.go new file mode 100644 index 00000000..3052e489 --- /dev/null +++ b/cmd/godoc/remotesearch.go @@ -0,0 +1,70 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "errors" + "flag" + "io" + "log" + "net/http" + "net/url" + "os" +) + +func handleRemoteSearch() { + // Command-line queries. + for i := 0; i < flag.NArg(); i++ { + res, err := remoteSearch(flag.Arg(i)) + if err != nil { + log.Fatalf("remoteSearch: %s", err) + } + io.Copy(os.Stdout, res.Body) + } + return +} + +// remoteSearchURL returns the search URL for a given query as needed by +// remoteSearch. If html is set, an html result is requested; otherwise +// the result is in textual form. +// Adjust this function as necessary if modeNames or FormValue parameters +// change. +func remoteSearchURL(query string, html bool) string { + s := "/search?m=text&q=" + if html { + s = "/search?q=" + } + return s + url.QueryEscape(query) +} + +func remoteSearch(query string) (res *http.Response, err error) { + // list of addresses to try + var addrs []string + if *serverAddr != "" { + // explicit server address - only try this one + addrs = []string{*serverAddr} + } else { + addrs = []string{ + defaultAddr, + "golang.org", + } + } + + // remote search + search := remoteSearchURL(query, *html) + for _, addr := range addrs { + url := "http://" + addr + search + res, err = http.Get(url) + if err == nil && res.StatusCode == http.StatusOK { + break + } + } + + if err == nil && res.StatusCode != http.StatusOK { + err = errors.New(res.Status) + } + + return +} diff --git a/godoc/godoc.go b/godoc/godoc.go index 093f007f..ee55355c 100644 --- a/godoc/godoc.go +++ b/godoc/godoc.go @@ -375,12 +375,12 @@ func (p *Presentation) example_htmlFunc(info *PageInfo, funcName string) string out = "" } - if ExampleHTML == nil { + if p.ExampleHTML == nil { out = "" return "" } - err := ExampleHTML.Execute(&buf, struct { + err := p.ExampleHTML.Execute(&buf, struct { Name, Doc, Code, Play, Output string }{eg.Name, eg.Doc, code, play, out}) if err != nil { diff --git a/godoc/page.go b/godoc/page.go index 18934113..96f0dbd6 100644 --- a/godoc/page.go +++ b/godoc/page.go @@ -8,7 +8,6 @@ import ( "log" "net/http" "runtime" - "text/template" ) // Page describes the contents of the top-level godoc webpage. @@ -25,18 +24,6 @@ type Page struct { Version string } -var ( - DirlistHTML, - ErrorHTML, - ExampleHTML, - GodocHTML, - PackageHTML, - PackageText, - SearchHTML, - SearchText, - SearchDescXML *template.Template -) - func (p *Presentation) ServePage(w http.ResponseWriter, page Page) { if page.Tabtitle == "" { page.Tabtitle = page.Title @@ -44,7 +31,7 @@ func (p *Presentation) ServePage(w http.ResponseWriter, page Page) { page.SearchBox = p.Corpus.IndexEnabled page.Playground = p.ShowPlayground page.Version = runtime.Version() - if err := GodocHTML.Execute(w, page); err != nil && err != http.ErrBodyNotAllowed { + if err := p.GodocHTML.Execute(w, page); err != nil && err != http.ErrBodyNotAllowed { // Only log if there's an error that's not about writing on HEAD requests. // See Issues 5451 and 5454. log.Printf("GodocHTML.Execute: %s", err) @@ -56,6 +43,6 @@ func (p *Presentation) ServeError(w http.ResponseWriter, r *http.Request, relpat p.ServePage(w, Page{ Title: "File " + relpath, Subtitle: relpath, - Body: applyTemplate(ErrorHTML, "errorHTML", err), // err may contain an absolute path! + Body: applyTemplate(p.ErrorHTML, "errorHTML", err), // err may contain an absolute path! }) } diff --git a/godoc/pres.go b/godoc/pres.go index 3191ab80..c97aa961 100644 --- a/godoc/pres.go +++ b/godoc/pres.go @@ -14,6 +14,16 @@ import ( type Presentation struct { Corpus *Corpus + DirlistHTML, + ErrorHTML, + ExampleHTML, + GodocHTML, + PackageHTML, + PackageText, + SearchHTML, + SearchText, + SearchDescXML *template.Template + // TabWidth optionally specifies the tab width. TabWidth int @@ -22,6 +32,8 @@ type Presentation struct { ShowExamples bool DeclLinks bool + // NotesRx optionally specifies a regexp to match + // notes to render in the output. NotesRx *regexp.Regexp initFuncMapOnce sync.Once diff --git a/godoc/search.go b/godoc/search.go new file mode 100644 index 00000000..8d5b4e3a --- /dev/null +++ b/godoc/search.go @@ -0,0 +1,99 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package godoc + +import ( + "fmt" + "net/http" + "regexp" + "strings" +) + +type SearchResult struct { + Query string + Alert string // error or warning message + + // identifier matches + Pak HitList // packages matching Query + Hit *LookupResult // identifier matches of Query + Alt *AltWords // alternative identifiers to look for + + // textual matches + Found int // number of textual occurrences found + Textual []FileLines // textual matches of Query + Complete bool // true if all textual occurrences of Query are reported +} + +func (c *Corpus) Lookup(query string) SearchResult { + var result SearchResult + result.Query = query + + index, timestamp := c.CurrentIndex() + if index != nil { + // identifier search + var err error + result.Pak, result.Hit, result.Alt, err = index.Lookup(query) + if err != nil && c.MaxResults <= 0 { + // ignore the error if full text search is enabled + // since the query may be a valid regular expression + result.Alert = "Error in query string: " + err.Error() + return result + } + + // full text search + if c.MaxResults > 0 && query != "" { + rx, err := regexp.Compile(query) + if err != nil { + result.Alert = "Error in query regular expression: " + err.Error() + return result + } + // If we get maxResults+1 results we know that there are more than + // maxResults results and thus the result may be incomplete (to be + // precise, we should remove one result from the result set, but + // nobody is going to count the results on the result page). + result.Found, result.Textual = index.LookupRegexp(rx, c.MaxResults+1) + result.Complete = result.Found <= c.MaxResults + if !result.Complete { + result.Found-- // since we looked for maxResults+1 + } + } + } + + // is the result accurate? + if c.IndexEnabled { + if ts := c.FSModifiedTime(); timestamp.Before(ts) { + // The index is older than the latest file system change under godoc's observation. + result.Alert = "Indexing in progress: result may be inaccurate" + } + } else { + result.Alert = "Search index disabled: no results available" + } + + return result +} + +func (p *Presentation) HandleSearch(w http.ResponseWriter, r *http.Request) { + query := strings.TrimSpace(r.FormValue("q")) + result := p.Corpus.Lookup(query) + + if GetPageInfoMode(r)&NoHTML != 0 { + p.ServeText(w, applyTemplate(p.SearchText, "searchText", result)) + return + } + + var title string + if result.Hit != nil || len(result.Textual) > 0 { + title = fmt.Sprintf(`Results for query %q`, query) + } else { + title = fmt.Sprintf(`No results found for query %q`, query) + } + + p.ServePage(w, Page{ + Title: title, + Tabtitle: query, + Query: query, + Body: applyTemplate(p.SearchHTML, "searchHTML", result), + }) +} diff --git a/godoc/server.go b/godoc/server.go index 9b2149b0..91b39600 100644 --- a/godoc/server.go +++ b/godoc/server.go @@ -213,7 +213,7 @@ func (h *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { } if mode&NoHTML != 0 { - h.p.ServeText(w, applyTemplate(PackageText, "packageText", info)) + h.p.ServeText(w, applyTemplate(h.p.PackageText, "packageText", info)) return } @@ -253,7 +253,7 @@ func (h *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { Title: title, Tabtitle: tabtitle, Subtitle: subtitle, - Body: applyTemplate(PackageHTML, "packageHTML", info), + Body: applyTemplate(h.p.PackageHTML, "packageHTML", info), }) } @@ -463,7 +463,7 @@ func (p *Presentation) serveDirectory(w http.ResponseWriter, r *http.Request, ab p.ServePage(w, Page{ Title: "Directory " + relpath, Tabtitle: relpath, - Body: applyTemplate(DirlistHTML, "dirlistHTML", list), + Body: applyTemplate(p.DirlistHTML, "dirlistHTML", list), }) } @@ -587,12 +587,12 @@ func (p *Presentation) serveFile(w http.ResponseWriter, r *http.Request) { FileServer.ServeHTTP(w, r) } -func serveSearchDesc(w http.ResponseWriter, r *http.Request) { +func (p *Presentation) serveSearchDesc(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/opensearchdescription+xml") data := map[string]interface{}{ "BaseURL": fmt.Sprintf("http://%s", r.Host), } - if err := SearchDescXML.Execute(w, &data); err != nil && err != http.ErrBodyNotAllowed { + if err := p.SearchDescXML.Execute(w, &data); err != nil && err != http.ErrBodyNotAllowed { // Only log if there's an error that's not about writing on HEAD requests. // See Issues 5451 and 5454. log.Printf("searchDescXML.Execute: %s", err) diff --git a/godoc/vfs/zipfs/zipfs.go b/godoc/vfs/zipfs/zipfs.go index 521bdfee..99869c5a 100644 --- a/godoc/vfs/zipfs/zipfs.go +++ b/godoc/vfs/zipfs/zipfs.go @@ -15,7 +15,7 @@ // like absolute paths w/o a leading '/'; i.e., the paths are considered // relative to the root of the file system. // - All path arguments to file system methods must be absolute paths. -package zipvfs +package zipfs import ( "archive/zip"