summaryrefslogtreecommitdiff
path: root/src/cmd/godoc/main.go
diff options
context:
space:
mode:
Diffstat (limited to 'src/cmd/godoc/main.go')
-rw-r--r--src/cmd/godoc/main.go223
1 files changed, 99 insertions, 124 deletions
diff --git a/src/cmd/godoc/main.go b/src/cmd/godoc/main.go
index 5f4210539..23f712ab3 100644
--- a/src/cmd/godoc/main.go
+++ b/src/cmd/godoc/main.go
@@ -38,13 +38,13 @@ import (
"log"
"net/http"
_ "net/http/pprof" // to serve /debug/pprof/*
+ "net/url"
"os"
- "path"
+ pathpkg "path"
"path/filepath"
"regexp"
"runtime"
"strings"
- "time"
)
const defaultAddr = ":6060" // default webserver address
@@ -57,11 +57,6 @@ var (
// file-based index
writeIndex = flag.Bool("write_index", false, "write index to a file; the file name must be specified with -index_files")
- // periodic sync
- syncCmd = flag.String("sync", "", "sync command; disabled if empty")
- syncMin = flag.Int("sync_minutes", 0, "sync interval in minutes; disabled if <= 0")
- syncDelay delayTime // actual sync interval in minutes; usually syncDelay == syncMin, but syncDelay may back off exponentially
-
// network
httpAddr = flag.String("http", "", "HTTP service address (e.g., '"+defaultAddr+"')")
serverAddr = flag.String("server", "", "webserver address for command line searches")
@@ -69,6 +64,7 @@ var (
// layout control
html = flag.Bool("html", false, "print HTML in command-line mode")
srcMode = flag.Bool("src", false, "print (exported) source in command-line mode")
+ urlFlag = flag.String("url", "", "print HTML for named URL")
// command-line searches
query = flag.Bool("q", false, "arguments are considered search queries")
@@ -77,76 +73,7 @@ var (
func serveError(w http.ResponseWriter, r *http.Request, relpath string, err error) {
contents := applyTemplate(errorHTML, "errorHTML", err) // err may contain an absolute path!
w.WriteHeader(http.StatusNotFound)
- servePage(w, "File "+relpath, "", "", contents)
-}
-
-func exec(rw http.ResponseWriter, args []string) (status int) {
- r, w, err := os.Pipe()
- if err != nil {
- log.Printf("os.Pipe(): %v", err)
- return 2
- }
-
- bin := args[0]
- fds := []*os.File{nil, w, w}
- if *verbose {
- log.Printf("executing %v", args)
- }
- p, err := os.StartProcess(bin, args, &os.ProcAttr{Files: fds, Dir: *goroot})
- defer r.Close()
- w.Close()
- if err != nil {
- log.Printf("os.StartProcess(%q): %v", bin, err)
- return 2
- }
-
- var buf bytes.Buffer
- io.Copy(&buf, r)
- wait, err := p.Wait()
- if err != nil {
- os.Stderr.Write(buf.Bytes())
- log.Printf("os.Wait(%d, 0): %v", p.Pid, err)
- return 2
- }
- if !wait.Success() {
- os.Stderr.Write(buf.Bytes())
- log.Printf("executing %v failed", args)
- status = 1 // See comment in default case in dosync.
- return
- }
-
- if *verbose {
- os.Stderr.Write(buf.Bytes())
- }
- if rw != nil {
- rw.Header().Set("Content-Type", "text/plain; charset=utf-8")
- rw.Write(buf.Bytes())
- }
-
- return
-}
-
-func dosync(w http.ResponseWriter, r *http.Request) {
- args := []string{"/bin/sh", "-c", *syncCmd}
- switch exec(w, args) {
- case 0:
- // sync succeeded and some files have changed;
- // update package tree.
- // TODO(gri): The directory tree may be temporarily out-of-sync.
- // Consider keeping separate time stamps so the web-
- // page can indicate this discrepancy.
- initFSTree()
- fallthrough
- case 1:
- // sync failed because no files changed;
- // don't change the package tree
- syncDelay.set(time.Duration(*syncMin) * time.Minute) // revert to regular sync schedule
- default:
- // TODO(r): this cannot happen now, since Wait has a boolean exit condition,
- // not an integer.
- // sync failed because of an error - back off exponentially, but try at least once a day
- syncDelay.backoff(24 * time.Hour)
- }
+ servePage(w, relpath, "File "+relpath, "", "", contents)
}
func usage() {
@@ -225,7 +152,7 @@ func main() {
flag.Parse()
// Check usage: either server and no args, command line and args, or index creation mode
- if (*httpAddr != "") != (flag.NArg() == 0) && !*writeIndex {
+ if (*httpAddr != "" || *urlFlag != "") != (flag.NArg() == 0) && !*writeIndex {
usage()
}
@@ -239,19 +166,20 @@ func main() {
// same is true for the http handlers in initHandlers.
if *zipfile == "" {
// use file system of underlying OS
- *goroot = filepath.Clean(*goroot) // normalize path separator
- fs = OS
- fsHttp = http.Dir(*goroot)
+ fs.Bind("/", OS(*goroot), "/", bindReplace)
} else {
// use file system specified via .zip file (path separator must be '/')
rc, err := zip.OpenReader(*zipfile)
if err != nil {
log.Fatalf("%s: %s\n", *zipfile, err)
}
- defer rc.Close() // be nice (e.g., -writeIndex mode)
- *goroot = path.Join("/", *goroot) // fsHttp paths are relative to '/'
- fs = NewZipFS(rc)
- fsHttp = NewHttpZipFS(rc, *goroot)
+ defer rc.Close() // be nice (e.g., -writeIndex mode)
+ fs.Bind("/", NewZipFS(rc, *zipfile), *goroot, bindReplace)
+ }
+
+ // Bind $GOPATH trees into Go root.
+ for _, p := range filepath.SplitList(build.Default.GOPATH) {
+ fs.Bind("/src/pkg", OS(p), "/src", bindAfter)
}
readTemplates()
@@ -266,7 +194,6 @@ func main() {
log.Println("initialize file systems")
*verbose = true // want to see what happens
initFSTree()
- initDirTrees()
*indexThrottle = 1
updateIndex()
@@ -286,6 +213,44 @@ func main() {
return
}
+ // 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 := &httpWriter{h: http.Header{}, code: 200}
+ http.DefaultServeMux.ServeHTTP(w, req)
+
+ // Return data, error, or follow redirect.
+ switch w.code {
+ case 200: // ok
+ os.Stdout.Write(w.Bytes())
+ return
+ case 301, 302, 303, 307: // redirect
+ redirect := w.h.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")
+ }
+
if *httpAddr != "" {
// HTTP server mode.
var handler http.Handler = http.DefaultServeMux
@@ -303,41 +268,20 @@ func main() {
default:
log.Print("identifier search index enabled")
}
- if !fsMap.IsEmpty() {
- log.Print("user-defined mapping:")
- fsMap.Fprint(os.Stderr)
- }
+ fs.Fprint(os.Stderr)
handler = loggingHandler(handler)
}
registerPublicHandlers(http.DefaultServeMux)
- if *syncCmd != "" {
- http.Handle("/debug/sync", http.HandlerFunc(dosync))
- }
+
+ // Playground handlers are not available in local godoc.
+ http.HandleFunc("/compile", disabledHandler)
+ http.HandleFunc("/share", disabledHandler)
// Initialize default directory tree with corresponding timestamp.
// (Do it in a goroutine so that launch is quick.)
go initFSTree()
- // Initialize directory trees for user-defined file systems (-path flag).
- initDirTrees()
-
- // Start sync goroutine, if enabled.
- if *syncCmd != "" && *syncMin > 0 {
- syncDelay.set(*syncMin) // initial sync delay
- go func() {
- for {
- dosync(nil, nil)
- delay, _ := syncDelay.get()
- dt := delay.(time.Duration)
- if *verbose {
- log.Printf("next sync in %s", dt)
- }
- time.Sleep(dt)
- }
- }()
- }
-
// Immediately update metadata.
updateMetadata()
// Periodically refresh metadata.
@@ -374,27 +318,38 @@ func main() {
return
}
- // determine paths
+ // Determine paths.
+ //
+ // If we are passed an operating system path like . or ./foo or /foo/bar or c:\mysrc,
+ // we need to map that path somewhere in the fs name space so that routines
+ // like getPageInfo will see it. We use the arbitrarily-chosen virtual path "/target"
+ // for this. That is, if we get passed a directory like the above, we map that
+ // directory so that getPageInfo sees it as /target.
+ const target = "/target"
const cmdPrefix = "cmd/"
path := flag.Arg(0)
var forceCmd bool
- if strings.HasPrefix(path, ".") {
- // assume cwd; don't assume -goroot
+ var abspath, relpath string
+ if filepath.IsAbs(path) {
+ fs.Bind(target, OS(path), "/", bindReplace)
+ abspath = target
+ } else if build.IsLocalImport(path) {
cwd, _ := os.Getwd() // ignore errors
path = filepath.Join(cwd, path)
+ fs.Bind(target, OS(path), "/", bindReplace)
+ abspath = target
} else if strings.HasPrefix(path, cmdPrefix) {
path = path[len(cmdPrefix):]
forceCmd = true
- }
- relpath := path
- abspath := path
- if bp, _ := build.Import(path, "", build.FindOnly); bp.Dir != "" && bp.ImportPath != "" {
+ } else if bp, _ := build.Import(path, "", build.FindOnly); bp.Dir != "" && bp.ImportPath != "" {
+ fs.Bind(target, OS(bp.Dir), "/", bindReplace)
+ abspath = target
relpath = bp.ImportPath
- abspath = bp.Dir
- } else if !filepath.IsAbs(path) {
- abspath = absolutePath(path, pkgHandler.fsRoot)
} else {
- relpath = relativeURL(path)
+ abspath = pathpkg.Join(pkgHandler.fsRoot, path)
+ }
+ if relpath == "" {
+ relpath = abspath
}
var mode PageInfoMode
@@ -422,7 +377,7 @@ func main() {
// (the go command invokes godoc w/ absolute paths; don't override)
var cinfo PageInfo
if !filepath.IsAbs(path) {
- abspath = absolutePath(path, cmdHandler.fsRoot)
+ abspath = pathpkg.Join(cmdHandler.fsRoot, path)
cinfo = cmdHandler.getPageInfo(abspath, relpath, "", mode)
}
@@ -445,6 +400,10 @@ func main() {
if info.Err != nil {
log.Fatalf("%v", info.Err)
}
+ if info.PDoc != nil && info.PDoc.ImportPath == target {
+ // Replace virtual /target with actual argument from command line.
+ info.PDoc.ImportPath = flag.Arg(0)
+ }
// If we have more than one argument, use the remaining arguments for filtering
if flag.NArg() > 1 {
@@ -485,3 +444,19 @@ func main() {
log.Printf("packageText.Execute: %s", err)
}
}
+
+// An httpWriter is an http.ResponseWriter writing to a bytes.Buffer.
+type httpWriter struct {
+ bytes.Buffer
+ h http.Header
+ code int
+}
+
+func (w *httpWriter) Header() http.Header { return w.h }
+func (w *httpWriter) WriteHeader(code int) { w.code = code }
+
+// disabledHandler serves a 501 "Not Implemented" response.
+func disabledHandler(w http.ResponseWriter, r *http.Request) {
+ w.WriteHeader(http.StatusNotImplemented)
+ fmt.Fprint(w, "This functionality is not available via local godoc.")
+}