mirror of
https://github.com/golang/go
synced 2024-11-18 09:04:49 -07:00
godoc: more cleanup
cmd/godoc/godoc.go is now merged into main.go, which is now only 530 lines. App Engine mode is still broken, but should be easy to fix up. (just needs a global *godoc.Presentation created in init) R=golang-dev, adg CC=golang-dev https://golang.org/cl/11498044
This commit is contained in:
parent
5395cfe05a
commit
66f0d6e92e
@ -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"`
|
||||
|
@ -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),
|
||||
})
|
||||
}
|
@ -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
|
||||
}
|
||||
|
||||
|
70
cmd/godoc/remotesearch.go
Normal file
70
cmd/godoc/remotesearch.go
Normal file
@ -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
|
||||
}
|
@ -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 {
|
||||
|
@ -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!
|
||||
})
|
||||
}
|
||||
|
@ -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
|
||||
|
99
godoc/search.go
Normal file
99
godoc/search.go
Normal file
@ -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),
|
||||
})
|
||||
}
|
@ -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)
|
||||
|
@ -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"
|
||||
|
Loading…
Reference in New Issue
Block a user