diff options
author | Michael Stapelberg <stapelberg@debian.org> | 2013-03-04 21:27:36 +0100 |
---|---|---|
committer | Michael Stapelberg <michael@stapelberg.de> | 2013-03-04 21:27:36 +0100 |
commit | 04b08da9af0c450d645ab7389d1467308cfc2db8 (patch) | |
tree | db247935fa4f2f94408edc3acd5d0d4f997aa0d8 /src/cmd/godoc | |
parent | 917c5fb8ec48e22459d77e3849e6d388f93d3260 (diff) | |
download | golang-upstream/1.1_hg20130304.tar.gz |
Imported Upstream version 1.1~hg20130304upstream/1.1_hg20130304
Diffstat (limited to 'src/cmd/godoc')
-rw-r--r-- | src/cmd/godoc/appinit.go | 9 | ||||
-rw-r--r-- | src/cmd/godoc/codewalk.go | 13 | ||||
-rw-r--r-- | src/cmd/godoc/dirtrees.go | 23 | ||||
-rw-r--r-- | src/cmd/godoc/doc.go | 20 | ||||
-rw-r--r-- | src/cmd/godoc/filesystem.go | 10 | ||||
-rw-r--r-- | src/cmd/godoc/format.go | 8 | ||||
-rw-r--r-- | src/cmd/godoc/godoc.go | 584 | ||||
-rw-r--r-- | src/cmd/godoc/index.go | 84 | ||||
-rw-r--r-- | src/cmd/godoc/main.go | 37 | ||||
-rw-r--r-- | src/cmd/godoc/parser.go | 51 | ||||
-rw-r--r-- | src/cmd/godoc/play-appengine.go | 35 | ||||
-rw-r--r-- | src/cmd/godoc/play-local.go | 41 | ||||
-rw-r--r-- | src/cmd/godoc/play.go | 52 | ||||
-rwxr-xr-x[-rw-r--r--] | src/cmd/godoc/setup-godoc-app.bash | 63 | ||||
-rw-r--r-- | src/cmd/godoc/template.go | 10 |
15 files changed, 617 insertions, 423 deletions
diff --git a/src/cmd/godoc/appinit.go b/src/cmd/godoc/appinit.go index 70da00110..996b2b850 100644 --- a/src/cmd/godoc/appinit.go +++ b/src/cmd/godoc/appinit.go @@ -17,9 +17,12 @@ import ( ) 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, relpath, "File "+relpath, "", "", contents) + servePage(w, Page{ + Title: "File " + relpath, + Subtitle: relpath, + Body: applyTemplate(errorHTML, "errorHTML", err), // err may contain an absolute path! + }) } func init() { @@ -34,6 +37,7 @@ func init() { *indexFiles = indexFilenames *maxResults = 100 // reduce latency by limiting the number of fulltext search results *indexThrottle = 0.3 // in case *indexFiles is empty (and thus the indexer is run) + *showPlayground = true // read .zip file and set up file systems const zipfile = zipFilename @@ -48,6 +52,7 @@ func init() { readTemplates() initHandlers() registerPublicHandlers(http.DefaultServeMux) + registerPlaygroundHandlers(http.DefaultServeMux) // initialize default directory tree with corresponding timestamp. initFSTree() diff --git a/src/cmd/godoc/codewalk.go b/src/cmd/godoc/codewalk.go index f7f51d0a0..e68c0fa6b 100644 --- a/src/cmd/godoc/codewalk.go +++ b/src/cmd/godoc/codewalk.go @@ -68,8 +68,11 @@ func codewalk(w http.ResponseWriter, r *http.Request) { return } - b := applyTemplate(codewalkHTML, "codewalk", cw) - servePage(w, cw.Title, "Codewalk: "+cw.Title, "", "", b) + servePage(w, Page{ + Title: "Codewalk: " + cw.Title, + Tabtitle: cw.Title, + Body: applyTemplate(codewalkHTML, "codewalk", cw), + }) } // A Codewalk represents a single codewalk read from an XML file. @@ -199,8 +202,10 @@ func codewalkDir(w http.ResponseWriter, r *http.Request, relpath, abspath string } } - b := applyTemplate(codewalkdirHTML, "codewalkdir", v) - servePage(w, "", "Codewalks", "", "", b) + servePage(w, Page{ + Title: "Codewalks", + Body: applyTemplate(codewalkdirHTML, "codewalkdir", v), + }) } // codewalkFileprint serves requests with ?fileprint=f&lo=lo&hi=hi. diff --git a/src/cmd/godoc/dirtrees.go b/src/cmd/godoc/dirtrees.go index b9b529f87..fda7adce5 100644 --- a/src/cmd/godoc/dirtrees.go +++ b/src/cmd/godoc/dirtrees.go @@ -74,7 +74,7 @@ func (b *treeBuilder) newDirTree(fset *token.FileSet, path, name string, depth i // determine number of subdirectories and if there are package files ndirs := 0 hasPkgFiles := false - var synopses [4]string // prioritized package documentation (0 == highest priority) + var synopses [3]string // prioritized package documentation (0 == highest priority) for _, d := range list { switch { case isPkgDir(d): @@ -95,12 +95,10 @@ func (b *treeBuilder) newDirTree(fset *token.FileSet, path, name string, depth i switch file.Name.Name { case name: i = 0 // normal case: directory name matches package name - case fakePkgName: - i = 1 // synopses for commands case "main": - i = 2 // directory contains a main package + i = 1 // directory contains a main package default: - i = 3 // none of the above + i = 2 // none of the above } if 0 <= i && i < len(synopses) && synopses[i] == "" { synopses[i] = doc.Synopsis(file.Doc.Text()) @@ -229,9 +227,7 @@ func (dir *Directory) lookupLocal(name string) *Directory { } func splitPath(p string) []string { - if strings.HasPrefix(p, "/") { - p = p[1:] - } + p = strings.TrimPrefix(p, "/") if p == "" { return nil } @@ -264,7 +260,7 @@ type DirEntry struct { Height int // = DirList.MaxHeight - Depth, > 0 Path string // directory path; includes Name, relative to DirList root Name string // directory name - HasPkg bool // true if the directory contains at least one package + HasPkg bool // true if the directory contains at least one package Synopsis string // package documentation, if any } @@ -310,14 +306,9 @@ func (root *Directory) listing(skipRoot bool) *DirList { // the path is relative to root.Path - remove the root.Path // prefix (the prefix should always be present but avoid // crashes and check) - path := d.Path - if strings.HasPrefix(d.Path, root.Path) { - path = d.Path[len(root.Path):] - } + path := strings.TrimPrefix(d.Path, root.Path) // remove leading separator if any - path must be relative - if len(path) > 0 && path[0] == '/' { - path = path[1:] - } + path = strings.TrimPrefix(path, "/") p.Path = path p.Name = d.Name p.HasPkg = d.HasPkg diff --git a/src/cmd/godoc/doc.go b/src/cmd/godoc/doc.go index 39ecc6e63..ddb6d2687 100644 --- a/src/cmd/godoc/doc.go +++ b/src/cmd/godoc/doc.go @@ -67,8 +67,6 @@ The flags are: -maxresults=10000 maximum number of full text search results shown (no full text index is built if maxresults <= 0) - -path="" - additional package directories (colon-separated) -html print HTML in command-line mode -goroot=$GOROOT @@ -88,20 +86,8 @@ The flags are: zip file providing the file system to serve; disabled if empty By default, godoc looks at the packages it finds via $GOROOT and $GOPATH (if set). -Additional directories may be specified via the -path flag which accepts a list -of colon-separated paths; unrooted paths are relative to the current working -directory. Each path is considered as an additional root for packages in order -of appearance. The last (absolute) path element is the prefix for the package -path. For instance, given the flag value: - - path=".:/home/bar:/public" - -for a godoc started in /home/user/godoc, absolute paths are mapped to package paths -as follows: - - /home/user/godoc/x -> godoc/x - /home/bar/x -> bar/x - /public/x -> public/x +This behavior can be altered by providing an alternative $GOROOT with the -goroot +flag. When godoc runs as a web server and -index is set, a search index is maintained. The index is created at startup. @@ -141,4 +127,4 @@ See "Godoc: documenting Go code" for how to write good comments for godoc: http://golang.org/doc/articles/godoc_documenting_go_code.html */ -package documentation +package main diff --git a/src/cmd/godoc/filesystem.go b/src/cmd/godoc/filesystem.go index 09d7b2463..0309d7cab 100644 --- a/src/cmd/godoc/filesystem.go +++ b/src/cmd/godoc/filesystem.go @@ -41,7 +41,7 @@ import ( // paths can assume they are slash-separated and should be using // package path (often imported as pathpkg) to manipulate them, // even on Windows. -// +// var fs = nameSpace{} // the underlying file system for godoc // Setting debugNS = true will enable debugging prints about @@ -138,7 +138,7 @@ func hasPathPrefix(x, y string) bool { // but we want to be able to mount multiple file systems on a single // mount point and have the system behave as if the union of those // file systems were present at the mount point. -// For example, if the OS file system has a Go installation in +// For example, if the OS file system has a Go installation in // c:\Go and additional Go path trees in d:\Work1 and d:\Work2, then // this name space creates the view we want for the godoc server: // @@ -179,7 +179,7 @@ func hasPathPrefix(x, y string) bool { // OS(`d:\Work1').ReadDir("/src/code") // OS(`d:\Work2').ReadDir("/src/code") // -// Note that the "/src/pkg" in "/src/pkg/code" has been replaced by +// Note that the "/src/pkg" in "/src/pkg/code" has been replaced by // just "/src" in the final two calls. // // OS is itself an implementation of a file system: it implements @@ -459,9 +459,7 @@ func (ns nameSpace) ReadDir(path string) ([]os.FileInfo, error) { if hasPathPrefix(old, path) && old != path { // Find next element after path in old. elem := old[len(path):] - if strings.HasPrefix(elem, "/") { - elem = elem[1:] - } + elem = strings.TrimPrefix(elem, "/") if i := strings.Index(elem, "/"); i >= 0 { elem = elem[:i] } diff --git a/src/cmd/godoc/format.go b/src/cmd/godoc/format.go index 3b1b9a822..122ddc7d6 100644 --- a/src/cmd/godoc/format.go +++ b/src/cmd/godoc/format.go @@ -54,6 +54,8 @@ type SegmentWriter func(w io.Writer, text []byte, selections int) // Selection is ignored. // func FormatSelections(w io.Writer, text []byte, lw LinkWriter, links Selection, sw SegmentWriter, selections ...Selection) { + // If we have a link writer, make the links + // selection the last entry in selections if lw != nil { selections = append(selections, links) } @@ -108,8 +110,8 @@ func FormatSelections(w io.Writer, text []byte, lw LinkWriter, links Selection, break } // determine the kind of segment change - if index == len(selections)-1 { - // we have a link segment change: + if lw != nil && index == len(selections)-1 { + // we have a link segment change (see start of this function): // format the previous selection segment, write the // link tag and start a new selection segment segment(offs) @@ -119,7 +121,7 @@ func FormatSelections(w io.Writer, text []byte, lw LinkWriter, links Selection, } else { // we have a selection change: // format the previous selection segment, determine - // the new selection bitset and start a new segment + // the new selection bitset and start a new segment segment(offs) lastOffs = offs mask := 1 << uint(index) diff --git a/src/cmd/godoc/godoc.go b/src/cmd/godoc/godoc.go index f6dc678b4..872b0dc1e 100644 --- a/src/cmd/godoc/godoc.go +++ b/src/cmd/godoc/godoc.go @@ -12,8 +12,10 @@ import ( "go/ast" "go/build" "go/doc" + "go/format" "go/printer" "go/token" + htmlpkg "html" "io" "io/ioutil" "log" @@ -57,12 +59,13 @@ var ( // TODO(gri) consider the invariant that goroot always end in '/' goroot = flag.String("goroot", runtime.GOROOT(), "Go root directory") testDir = flag.String("testdir", "", "Go root subdirectory - for testing only (faster startups)") - pkgPath = flag.String("path", "", "additional package directories (colon-separated)") // 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") // search index indexEnabled = flag.Bool("index", false, "enable search index") @@ -80,22 +83,19 @@ var ( fileServer http.Handler // default file server cmdHandler docServer pkgHandler docServer + + // which code 'Notes' to show + notes = flag.String("notes", "BUG", "comma separated list of Note markers as per pkg:go/doc") + // list of 'Notes' to show + notesToShow []string ) func initHandlers() { - // Add named directories in -path argument as - // subdirectories of src/pkg. - for _, p := range filepath.SplitList(*pkgPath) { - _, elem := filepath.Split(p) - if elem == "" { - log.Fatalf("invalid -path argument: %q has no final element", p) - } - fs.Bind("/src/pkg/"+elem, OS(p), "/", bindReplace) - } + notesToShow = strings.Split(*notes, ",") fileServer = http.FileServer(&httpFS{fs}) - cmdHandler = docServer{"/cmd/", "/src/cmd", false} - pkgHandler = docServer{"/pkg/", "/src/pkg", true} + cmdHandler = docServer{"/cmd/", "/src/cmd"} + pkgHandler = docServer{"/pkg/", "/src/pkg"} } func registerPublicHandlers(mux *http.ServeMux) { @@ -326,18 +326,62 @@ func startsWithUppercase(s string) bool { var exampleOutputRx = regexp.MustCompile(`(?i)//[[:space:]]*output:`) -func example_htmlFunc(funcName string, examples []*doc.Example, fset *token.FileSet) string { +// stripExampleSuffix strips lowercase braz in Foo_braz or Foo_Bar_braz from name +// while keeping uppercase Braz in Foo_Braz. +func stripExampleSuffix(name string) string { + if i := strings.LastIndex(name, "_"); i != -1 { + if i < len(name)-1 && !startsWithUppercase(name[i+1:]) { + name = name[:i] + } + } + return name +} + +func example_textFunc(funcName string, examples []*doc.Example, fset *token.FileSet, indent string) string { + if !*showExamples { + return "" + } + var buf bytes.Buffer + first := true for _, eg := range examples { - name := eg.Name + name := stripExampleSuffix(eg.Name) + if name != funcName { + continue + } - // strip lowercase braz in Foo_braz or Foo_Bar_braz from name - // while keeping uppercase Braz in Foo_Braz - if i := strings.LastIndex(name, "_"); i != -1 { - if i < len(name)-1 && !startsWithUppercase(name[i+1:]) { - name = name[:i] - } + if !first { + buf.WriteString("\n") } + first = false + + // print code + cnode := &printer.CommentedNode{Node: eg.Code, Comments: eg.Comments} + var buf1 bytes.Buffer + writeNode(&buf1, fset, cnode) + code := buf1.String() + // Additional formatting if this is a function body. + if n := len(code); n >= 2 && code[0] == '{' && code[n-1] == '}' { + // remove surrounding braces + code = code[1 : n-1] + // unindent + code = strings.Replace(code, "\n ", "\n", -1) + } + code = strings.Trim(code, "\n") + code = strings.Replace(code, "\n", "\n\t", -1) + + buf.WriteString(indent) + buf.WriteString("Example:\n\t") + buf.WriteString(code) + buf.WriteString("\n") + } + return buf.String() +} + +func example_htmlFunc(funcName string, examples []*doc.Example, fset *token.FileSet) string { + var buf bytes.Buffer + for _, eg := range examples { + name := stripExampleSuffix(eg.Name) if name != funcName { continue @@ -347,9 +391,11 @@ func example_htmlFunc(funcName string, examples []*doc.Example, fset *token.File cnode := &printer.CommentedNode{Node: eg.Code, Comments: eg.Comments} code := node_htmlFunc(cnode, fset) out := eg.Output + wholeFile := true - // 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] == '}' { + wholeFile = false // remove surrounding braces code = code[1 : n-1] // unindent @@ -358,14 +404,28 @@ func example_htmlFunc(funcName string, examples []*doc.Example, fset *token.File if loc := exampleOutputRx.FindStringIndex(code); loc != nil { code = strings.TrimSpace(code[:loc[0]]) } - } else { - // drop output, as the output comment will appear in the code + } + + // Write out the playground code in standard Go style + // (use tabs, no comment highlight, etc). + play := "" + if eg.Play != nil && *showPlayground { + var buf bytes.Buffer + if err := format.Node(&buf, fset, eg.Play); err != nil { + log.Print(err) + } else { + play = buf.String() + } + } + + // Drop output, as the output comment will appear in the code. + if wholeFile && play == "" { out = "" } err := exampleHTML.Execute(&buf, struct { - Name, Doc, Code, Output string - }{eg.Name, eg.Doc, code, out}) + Name, Doc, Code, Play, Output string + }{eg.Name, eg.Doc, code, play, out}) if err != nil { log.Print(err) } @@ -393,6 +453,10 @@ func example_suffixFunc(name string) string { return suffix } +func noteTitle(note string) string { + return strings.Title(strings.ToLower(note)) +} + func splitExampleName(s string) (name, suffix string) { i := strings.LastIndex(s, "_") if 0 <= i && i < len(s)-1 && !startsWithUppercase(s[i+1:]) { @@ -408,9 +472,7 @@ func pkgLinkFunc(path string) string { relpath := path[1:] // because of the irregular mapping under goroot // we need to correct certain relative paths - if strings.HasPrefix(relpath, "src/pkg/") { - relpath = relpath[len("src/pkg/"):] - } + relpath = strings.TrimPrefix(relpath, "src/pkg/") return pkgHandler.pattern[1:] + relpath // remove trailing '/' for relative URL } @@ -485,8 +547,12 @@ var fmap = template.FuncMap{ // formatting of Examples "example_html": example_htmlFunc, + "example_text": example_textFunc, "example_name": example_nameFunc, "example_suffix": example_suffixFunc, + + // formatting of Notes + "noteTitle": noteTitle, } func readTemplate(name string) *template.Template { @@ -538,31 +604,28 @@ func readTemplates() { // ---------------------------------------------------------------------------- // Generic HTML wrapper -func servePage(w http.ResponseWriter, tabtitle, title, subtitle, query string, content []byte) { - if tabtitle == "" { - tabtitle = title - } - d := struct { - Tabtitle string - Title string - Subtitle string - SearchBox bool - Query string - Version string - Menu []byte - Content []byte - }{ - tabtitle, - title, - subtitle, - *indexEnabled, - query, - runtime.Version(), - nil, - content, - } - - if err := godocHTML.Execute(w, &d); err != nil { +// Page describes the contents of the top-level godoc webpage. +type Page struct { + Title string + Tabtitle string + Subtitle string + Query string + Body []byte + + // filled in by servePage + SearchBox bool + Playground bool + Version string +} + +func servePage(w http.ResponseWriter, page Page) { + if page.Tabtitle == "" { + page.Tabtitle = page.Title + } + page.SearchBox = *indexEnabled + page.Playground = *showPlayground + page.Version = runtime.Version() + if err := godocHTML.Execute(w, page); err != nil { log.Printf("godocHTML.Execute: %s", err) } } @@ -627,7 +690,11 @@ func serveHTMLDoc(w http.ResponseWriter, r *http.Request, abspath, relpath strin src = buf.Bytes() } - servePage(w, "", meta.Title, meta.Subtitle, "", src) + servePage(w, Page{ + Title: meta.Title, + Subtitle: meta.Subtitle, + Body: src, + }) } func applyTemplate(t *template.Template, name string, data interface{}) []byte { @@ -640,11 +707,25 @@ func applyTemplate(t *template.Template, name string, data interface{}) []byte { func redirect(w http.ResponseWriter, r *http.Request) (redirected bool) { canonical := pathpkg.Clean(r.URL.Path) - if !strings.HasSuffix("/", canonical) { + if !strings.HasSuffix(canonical, "/") { canonical += "/" } if r.URL.Path != canonical { - http.Redirect(w, r, canonical, http.StatusMovedPermanently) + url := *r.URL + url.Path = canonical + http.Redirect(w, r, url.String(), http.StatusMovedPermanently) + redirected = true + } + 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 @@ -658,12 +739,22 @@ func serveTextFile(w http.ResponseWriter, r *http.Request, abspath, relpath, tit return } + if r.FormValue("m") == "text" { + serveText(w, src) + return + } + var buf bytes.Buffer buf.WriteString("<pre>") FormatText(&buf, src, 1, pathpkg.Ext(abspath) == ".go", r.FormValue("h"), 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)) - servePage(w, relpath, title+" "+relpath, "", "", buf.Bytes()) + servePage(w, Page{ + Title: title + " " + relpath, + Tabtitle: relpath, + Body: buf.Bytes(), + }) } func serveDirectory(w http.ResponseWriter, r *http.Request, abspath, relpath string) { @@ -677,8 +768,11 @@ func serveDirectory(w http.ResponseWriter, r *http.Request, abspath, relpath str return } - contents := applyTemplate(dirlistHTML, "dirlistHTML", list) - servePage(w, relpath, "Directory "+relpath, "", "", contents) + servePage(w, Page{ + Title: "Directory " + relpath, + Tabtitle: relpath, + Body: applyTemplate(dirlistHTML, "dirlistHTML", list), + }) } func serveFile(w http.ResponseWriter, r *http.Request) { @@ -734,6 +828,9 @@ func serveFile(w http.ResponseWriter, r *http.Request) { } if isTextFile(abspath) { + if redirectFile(w, r) { + return + } serveTextFile(w, r, abspath, relpath, "Text file") return } @@ -754,10 +851,6 @@ func serveSearchDesc(w http.ResponseWriter, r *http.Request) { // ---------------------------------------------------------------------------- // Packages -// Fake package file and name for commands. Contains the command documentation. -const fakePkgFile = "doc.go" -const fakePkgName = "documentation" - // Fake relative package path for built-ins. Documentation for all globals // (not just exported ones) will be shown for packages in this directory. const builtinPkgPath = "builtin" @@ -814,17 +907,21 @@ func remoteSearchURL(query string, html bool) string { } type PageInfo struct { - Dirname string // directory containing the package - PList []string // list of package names found - FSet *token.FileSet // corresponding file set - PAst *ast.File // nil if no single AST with package exports - PDoc *doc.Package // nil if no single package documentation - Examples []*doc.Example // nil if no example code - Dirs *DirList // nil if no directory information - DirTime time.Time // directory time stamp - DirFlat bool // if set, show directory in a flat (non-indented) manner - IsPkg bool // false if this is not documenting a real package - Err error // I/O error or nil + Dirname string // directory containing the package + Err error // error or nil + + // package info + FSet *token.FileSet // nil if no package documentation + PDoc *doc.Package // nil if no package documentation + Examples []*doc.Example // nil if no example code + Notes map[string][]string // nil if no package Notes + PAst *ast.File // nil if no AST with package exports + IsMain bool // true for package main + + // directory info + Dirs *DirList // nil if no directory information + DirTime time.Time // directory time stamp + DirFlat bool // if set, show directory in a flat (non-indented) manner } func (info *PageInfo) IsEmpty() bool { @@ -834,30 +931,97 @@ func (info *PageInfo) IsEmpty() bool { type docServer struct { pattern string // url pattern; e.g. "/pkg/" fsRoot string // file system root to which the pattern is mapped - isPkg bool // true if this handler serves real package documentation (as opposed to command documentation) } // fsReadDir implements ReadDir for the go/build package. func fsReadDir(dir string) ([]os.FileInfo, error) { - return fs.ReadDir(dir) + 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 := ReadFile(fs, name) + data, err := ReadFile(fs, filepath.ToSlash(name)) if err != nil { return nil, err } return ioutil.NopCloser(bytes.NewReader(data)), nil } -func inList(name string, list []string) bool { - for _, l := range list { - if name == l { - return true +// packageExports is a local implementation of ast.PackageExports +// which correctly updates each package file's comment list. +// (The ast.PackageExports signature is frozen, hence the local +// implementation). +// +func packageExports(fset *token.FileSet, pkg *ast.Package) { + for _, src := range pkg.Files { + cmap := ast.NewCommentMap(fset, src, src.Comments) + ast.FileExports(src) + src.Comments = cmap.Filter(src).Comments() + } +} + +// addNames adds the names declared by decl to the names set. +// Method names are added in the form ReceiverTypeName_Method. +func addNames(names map[string]bool, decl ast.Decl) { + switch d := decl.(type) { + case *ast.FuncDecl: + name := d.Name.Name + if d.Recv != nil { + var typeName string + switch r := d.Recv.List[0].Type.(type) { + case *ast.StarExpr: + typeName = r.X.(*ast.Ident).Name + case *ast.Ident: + typeName = r.Name + } + name = typeName + "_" + name + } + names[name] = true + case *ast.GenDecl: + for _, spec := range d.Specs { + switch s := spec.(type) { + case *ast.TypeSpec: + names[s.Name.Name] = true + case *ast.ValueSpec: + for _, id := range s.Names { + names[id.Name] = true + } + } + } + } +} + +// globalNames returns a set of the names declared by all package-level +// declarations. Method names are returned in the form Receiver_Method. +func globalNames(pkg *ast.Package) map[string]bool { + names := make(map[string]bool) + for _, file := range pkg.Files { + for _, decl := range file.Decls { + addNames(names, decl) } } - return false + return names +} + +// collectExamples collects examples for pkg from testfiles. +func collectExamples(pkg *ast.Package, testfiles map[string]*ast.File) []*doc.Example { + var files []*ast.File + for _, f := range testfiles { + files = append(files, f) + } + + var examples []*doc.Example + globals := globalNames(pkg) + for _, e := range doc.Examples(files...) { + name := stripExampleSuffix(e.Name) + if name == "" || globals[name] { + examples = append(examples, e) + } else { + log.Printf("skipping example 'Example%s' because '%s' is not a known function or type", e.Name, e.Name) + } + } + + return examples } // getPageInfo returns the PageInfo for a package directory abspath. If the @@ -865,132 +1029,56 @@ func inList(name string, list []string) bool { // computed (PageInfo.PAst), otherwise package documentation (PageInfo.Doc) // is extracted from the AST. If there is no corresponding package in the // directory, PageInfo.PAst and PageInfo.PDoc are nil. If there are no sub- -// directories, PageInfo.Dirs is nil. If a directory read error occurred, -// PageInfo.Err is set to the respective error but the error is not logged. +// directories, PageInfo.Dirs is nil. If an error occurred, PageInfo.Err is +// set to the respective error but the error is not logged. // -func (h *docServer) getPageInfo(abspath, relpath, pkgname string, mode PageInfoMode) PageInfo { - var pkgFiles []string +func (h *docServer) getPageInfo(abspath, relpath string, mode PageInfoMode) (info PageInfo) { + info.Dirname = abspath - // If we're showing the default package, restrict to the ones - // that would be used when building the package on this - // system. This makes sure that if there are separate - // implementations for, say, Windows vs Unix, we don't + // Restrict to the package files that would be used when building + // the package on this system. This makes sure that if there are + // separate implementations for, say, Windows vs Unix, we don't // jumble them all together. - if pkgname == "" { - // Note: Uses current binary's GOOS/GOARCH. - // To use different pair, such as if we allowed the user - // to choose, set ctxt.GOOS and ctxt.GOARCH before - // calling ctxt.ScanDir. - ctxt := build.Default - ctxt.IsAbsPath = pathpkg.IsAbs - ctxt.ReadDir = fsReadDir - ctxt.OpenFile = fsOpenFile - dir, err := ctxt.ImportDir(abspath, 0) - if err == nil { - pkgFiles = append(dir.GoFiles, dir.CgoFiles...) - } - } - - // filter function to select the desired .go files - filter := func(d os.FileInfo) bool { - // Only Go files. - if !isPkgFile(d) { - return false - } - // If we are looking at cmd documentation, only accept - // the special fakePkgFile containing the documentation. - if !h.isPkg { - return d.Name() == fakePkgFile - } - // Also restrict file list to pkgFiles. - return pkgFiles == nil || inList(d.Name(), pkgFiles) - } - - // get package ASTs - fset := token.NewFileSet() - pkgs, err := parseDir(fset, abspath, filter) - if err != nil && pkgs == nil { - // only report directory read errors, ignore parse errors - // (may be able to extract partial package information) - return PageInfo{Dirname: abspath, Err: err} - } - - // select package - var pkg *ast.Package // selected package - var plist []string // list of other package (names), if any - if len(pkgs) == 1 { - // Exactly one package - select it. - for _, p := range pkgs { - pkg = p - } - - } else if len(pkgs) > 1 { - // Multiple packages - select the best matching package: The - // 1st choice is the package with pkgname, the 2nd choice is - // the package with dirname, and the 3rd choice is a package - // that is not called "main" if there is exactly one such - // package. Otherwise, don't select a package. - dirpath, dirname := pathpkg.Split(abspath) - - // If the dirname is "go" we might be in a sub-directory for - // .go files - use the outer directory name instead for better - // results. - if dirname == "go" { - _, dirname = pathpkg.Split(pathpkg.Clean(dirpath)) - } - - var choice3 *ast.Package - loop: - for _, p := range pkgs { - switch { - case p.Name == pkgname: - pkg = p - break loop // 1st choice; we are done - case p.Name == dirname: - pkg = p // 2nd choice - case p.Name != "main": - choice3 = p - } - } - if pkg == nil && len(pkgs) == 2 { - pkg = choice3 - } - - // Compute the list of other packages - // (excluding the selected package, if any). - plist = make([]string, len(pkgs)) - i := 0 - for name := range pkgs { - if pkg == nil || name != pkg.Name { - plist[i] = name - i++ - } - } - plist = plist[0:i] - sort.Strings(plist) + // Note: Uses current binary's GOOS/GOARCH. + // To use different pair, such as if we allowed the user to choose, + // set ctxt.GOOS and ctxt.GOARCH before calling ctxt.ImportDir. + ctxt := build.Default + ctxt.IsAbsPath = pathpkg.IsAbs + ctxt.ReadDir = fsReadDir + ctxt.OpenFile = fsOpenFile + pkginfo, err := ctxt.ImportDir(abspath, 0) + // continue if there are no Go source files; we still want the directory info + if _, nogo := err.(*build.NoGoError); err != nil && !nogo { + info.Err = err + return } - // get examples from *_test.go files - var examples []*doc.Example - filter = func(d os.FileInfo) bool { - return isGoFile(d) && strings.HasSuffix(d.Name(), "_test.go") - } - if testpkgs, err := parseDir(fset, abspath, filter); err != nil { - log.Println("parsing test files:", err) - } else { - for _, testpkg := range testpkgs { - var files []*ast.File - for _, f := range testpkg.Files { - files = append(files, f) - } - examples = append(examples, doc.Examples(files...)...) + // collect package files + pkgname := pkginfo.Name + pkgfiles := append(pkginfo.GoFiles, pkginfo.CgoFiles...) + if len(pkgfiles) == 0 { + // Commands written in C have no .go files in the build. + // Instead, documentation may be found in an ignored file. + // The file may be ignored via an explicit +build ignore + // constraint (recommended), or by defining the package + // documentation (historic). + pkgname = "main" // assume package main since pkginfo.Name == "" + pkgfiles = pkginfo.IgnoredGoFiles + } + + // get package information, if any + if len(pkgfiles) > 0 { + // build package AST + fset := token.NewFileSet() + files, err := parseFiles(fset, abspath, pkgfiles) + if err != nil { + info.Err = err + return } - } + pkg := &ast.Package{Name: pkgname, Files: files} - // compute package documentation - var past *ast.File - var pdoc *doc.Package - if pkg != nil { + // extract package documentation + info.FSet = fset if mode&showSource == 0 { // show extracted documentation var m doc.Mode @@ -1000,19 +1088,39 @@ func (h *docServer) getPageInfo(abspath, relpath, pkgname string, mode PageInfoM if mode&allMethods != 0 { m |= doc.AllMethods } - pdoc = doc.New(pkg, pathpkg.Clean(relpath), m) // no trailing '/' in importpath + info.PDoc = doc.New(pkg, pathpkg.Clean(relpath), m) // no trailing '/' in importpath + + // collect examples + testfiles := append(pkginfo.TestGoFiles, pkginfo.XTestGoFiles...) + files, err = parseFiles(fset, abspath, testfiles) + if err != nil { + log.Println("parsing examples:", err) + } + info.Examples = collectExamples(pkg, files) + + // collect any notes that we want to show + if info.PDoc.Notes != nil { + info.Notes = make(map[string][]string) + for _, m := range notesToShow { + if n := info.PDoc.Notes[m]; n != nil { + info.Notes[m] = n + } + } + } + } else { // show source code // TODO(gri) Consider eliminating export filtering in this mode, // or perhaps eliminating the mode altogether. if mode&noFiltering == 0 { - ast.PackageExports(pkg) + packageExports(fset, pkg) } - past = ast.MergePackageFiles(pkg, ast.FilterUnassociatedComments) + info.PAst = ast.MergePackageFiles(pkg, 0) } + info.IsMain = pkgname == "main" } - // get directory information + // get directory information, if any var dir *Directory var timestamp time.Time if tree, ts := fsTree.get(); tree != nil && tree.(*Directory) != nil { @@ -1030,20 +1138,11 @@ func (h *docServer) getPageInfo(abspath, relpath, pkgname string, mode PageInfoM dir = newDirectory(abspath, 1) timestamp = time.Now() } + info.Dirs = dir.listing(true) + info.DirTime = timestamp + info.DirFlat = mode&flatDir != 0 - return PageInfo{ - Dirname: abspath, - PList: plist, - FSet: fset, - PAst: past, - PDoc: pdoc, - Examples: examples, - Dirs: dir.listing(true), - DirTime: timestamp, - DirFlat: mode&flatDir != 0, - IsPkg: h.isPkg, - Err: nil, - } + return } func (h *docServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { @@ -1057,7 +1156,7 @@ func (h *docServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { if relpath == builtinPkgPath { mode = noFiltering } - info := h.getPageInfo(abspath, relpath, r.FormValue("p"), mode) + info := h.getPageInfo(abspath, relpath, mode) if info.Err != nil { log.Print(info.Err) serveError(w, r, relpath, info.Err) @@ -1065,8 +1164,7 @@ func (h *docServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { } if mode&noHtml != 0 { - contents := applyTemplate(packageText, "packageText", info) - serveText(w, contents) + serveText(w, applyTemplate(packageText, "packageText", info)) return } @@ -1074,26 +1172,25 @@ func (h *docServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { switch { case info.PAst != nil: tabtitle = info.PAst.Name.Name - title = "Package " + tabtitle case info.PDoc != nil: - if info.PDoc.Name == fakePkgName { - // assume that the directory name is the command name - _, tabtitle = pathpkg.Split(relpath) - } else { - tabtitle = info.PDoc.Name - } - if info.IsPkg { - title = "Package " + tabtitle - } else { - title = "Command " + tabtitle - } + tabtitle = info.PDoc.Name default: tabtitle = info.Dirname - title = "Directory " + tabtitle + title = "Directory " if *showTimestamps { subtitle = "Last update: " + info.DirTime.String() } } + if title == "" { + if info.IsMain { + // assume that the directory name is the command name + _, tabtitle = pathpkg.Split(relpath) + title = "Command " + } else { + title = "Package " + } + } + title += tabtitle // special cases for top-level package/command directories switch tabtitle { @@ -1103,8 +1200,12 @@ func (h *docServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { tabtitle = "Commands" } - contents := applyTemplate(packageHTML, "packageHTML", info) - servePage(w, tabtitle, title, subtitle, "", contents) + servePage(w, Page{ + Title: title, + Tabtitle: tabtitle, + Subtitle: subtitle, + Body: applyTemplate(packageHTML, "packageHTML", info), + }) } // ---------------------------------------------------------------------------- @@ -1181,8 +1282,7 @@ func search(w http.ResponseWriter, r *http.Request) { result := lookup(query) if getPageInfoMode(r)&noHtml != 0 { - contents := applyTemplate(searchText, "searchText", result) - serveText(w, contents) + serveText(w, applyTemplate(searchText, "searchText", result)) return } @@ -1193,8 +1293,12 @@ func search(w http.ResponseWriter, r *http.Request) { title = fmt.Sprintf(`No results found for query %q`, query) } - contents := applyTemplate(searchHTML, "searchHTML", result) - servePage(w, query, title, "", query, contents) + servePage(w, Page{ + Title: title, + Tabtitle: query, + Query: query, + Body: applyTemplate(searchHTML, "searchHTML", result), + }) } // ---------------------------------------------------------------------------- diff --git a/src/cmd/godoc/index.go b/src/cmd/godoc/index.go index 1bef79693..91c56461a 100644 --- a/src/cmd/godoc/index.go +++ b/src/cmd/godoc/index.go @@ -148,7 +148,7 @@ func init() { // sanity check: if nKinds is too large, the SpotInfo // accessor functions may need to be updated if nKinds > 8 { - panic("nKinds > 8") + panic("internal error: nKinds > 8") } } @@ -457,12 +457,6 @@ func (x *Indexer) addSnippet(s *Snippet) int { return index } -func (x *Indexer) visitComment(c *ast.CommentGroup) { - if c != nil { - ast.Walk(x, c) - } -} - func (x *Indexer) visitIdent(kind SpotKind, id *ast.Ident) { if id != nil { lists, found := x.words[id.Name] @@ -486,20 +480,24 @@ func (x *Indexer) visitIdent(kind SpotKind, id *ast.Ident) { } } -func (x *Indexer) visitSpec(spec ast.Spec, isVarDecl bool) { +func (x *Indexer) visitFieldList(kind SpotKind, list *ast.FieldList) { + for _, f := range list.List { + x.decl = nil // no snippets for fields + for _, name := range f.Names { + x.visitIdent(kind, name) + } + ast.Walk(x, f.Type) + // ignore tag - not indexed at the moment + } +} + +func (x *Indexer) visitSpec(kind SpotKind, spec ast.Spec) { switch n := spec.(type) { case *ast.ImportSpec: - x.visitComment(n.Doc) x.visitIdent(ImportDecl, n.Name) - ast.Walk(x, n.Path) - x.visitComment(n.Comment) + // ignore path - not indexed at the moment case *ast.ValueSpec: - x.visitComment(n.Doc) - kind := ConstDecl - if isVarDecl { - kind = VarDecl - } for _, n := range n.Names { x.visitIdent(kind, n) } @@ -507,57 +505,51 @@ func (x *Indexer) visitSpec(spec ast.Spec, isVarDecl bool) { for _, v := range n.Values { ast.Walk(x, v) } - x.visitComment(n.Comment) case *ast.TypeSpec: - x.visitComment(n.Doc) x.visitIdent(TypeDecl, n.Name) ast.Walk(x, n.Type) - x.visitComment(n.Comment) + } +} + +func (x *Indexer) visitGenDecl(decl *ast.GenDecl) { + kind := VarDecl + if decl.Tok == token.CONST { + kind = ConstDecl + } + x.decl = decl + for _, s := range decl.Specs { + x.visitSpec(kind, s) } } func (x *Indexer) Visit(node ast.Node) ast.Visitor { - // TODO(gri): methods in interface types are categorized as VarDecl switch n := node.(type) { case nil: - return nil + // nothing to do case *ast.Ident: x.visitIdent(Use, n) - case *ast.Field: - x.decl = nil // no snippets for fields - x.visitComment(n.Doc) - for _, m := range n.Names { - x.visitIdent(VarDecl, m) - } - ast.Walk(x, n.Type) - ast.Walk(x, n.Tag) - x.visitComment(n.Comment) + case *ast.FieldList: + x.visitFieldList(VarDecl, n) + + case *ast.InterfaceType: + x.visitFieldList(MethodDecl, n.Methods) case *ast.DeclStmt: + // local declarations should only be *ast.GenDecls; + // ignore incorrect ASTs if decl, ok := n.Decl.(*ast.GenDecl); ok { - // local declarations can only be *ast.GenDecls x.decl = nil // no snippets for local declarations - x.visitComment(decl.Doc) - for _, s := range decl.Specs { - x.visitSpec(s, decl.Tok == token.VAR) - } - } else { - // handle error case gracefully - ast.Walk(x, n.Decl) + x.visitGenDecl(decl) } case *ast.GenDecl: x.decl = n - x.visitComment(n.Doc) - for _, s := range n.Specs { - x.visitSpec(s, n.Tok == token.VAR) - } + x.visitGenDecl(n) case *ast.FuncDecl: - x.visitComment(n.Doc) kind := FuncDecl if n.Recv != nil { kind = MethodDecl @@ -571,15 +563,11 @@ func (x *Indexer) Visit(node ast.Node) ast.Visitor { } case *ast.File: - x.visitComment(n.Doc) x.decl = nil x.visitIdent(PackageClause, n.Name) for _, d := range n.Decls { ast.Walk(x, d) } - // don't visit package level comments for now - // to avoid duplicate visiting from individual - // nodes default: return x @@ -622,7 +610,7 @@ func (x *Indexer) addFile(filename string, goFile bool) (file *token.File, ast * // the file set implementation changed or we have another error. base := x.fset.Base() if x.sources.Len() != base { - panic("internal error - file base incorrect") + panic("internal error: file base incorrect") } // append file contents (src) to x.sources diff --git a/src/cmd/godoc/main.go b/src/cmd/godoc/main.go index 2e2889ed3..134410090 100644 --- a/src/cmd/godoc/main.go +++ b/src/cmd/godoc/main.go @@ -36,6 +36,7 @@ import ( "fmt" "go/ast" "go/build" + "go/printer" "io" "log" "net/http" @@ -73,9 +74,12 @@ 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, relpath, "File "+relpath, "", "", contents) + servePage(w, Page{ + Title: "File " + relpath, + Subtitle: relpath, + Body: applyTemplate(errorHTML, "errorHTML", err), // err may contain an absolute path! + }) } func usage() { @@ -221,6 +225,8 @@ func main() { // Print content that would be served at the URL *urlFlag. if *urlFlag != "" { registerPublicHandlers(http.DefaultServeMux) + initFSTree() + updateMetadata() // Try up to 10 fetches, following redirects. urlstr := *urlFlag for i := 0; i < 10; i++ { @@ -278,10 +284,7 @@ func main() { } registerPublicHandlers(http.DefaultServeMux) - - // Playground handlers are not available in local godoc. - http.HandleFunc("/compile", disabledHandler) - http.HandleFunc("/share", disabledHandler) + registerPlaygroundHandlers(http.DefaultServeMux) // Initialize default directory tree with corresponding timestamp. // (Do it in a goroutine so that launch is quick.) @@ -344,7 +347,7 @@ func main() { fs.Bind(target, OS(path), "/", bindReplace) abspath = target } else if strings.HasPrefix(path, cmdPrefix) { - path = path[len(cmdPrefix):] + path = strings.TrimPrefix(path, cmdPrefix) forceCmd = true } else if bp, _ := build.Import(path, "", build.FindOnly); bp.Dir != "" && bp.ImportPath != "" { fs.Bind(target, OS(bp.Dir), "/", bindReplace) @@ -369,13 +372,11 @@ func main() { } mode |= showSource } - // TODO(gri): Provide a mechanism (flag?) to select a package - // if there are multiple packages in a directory. // first, try as package unless forced as command var info PageInfo if !forceCmd { - info = pkgHandler.getPageInfo(abspath, relpath, "", mode) + info = pkgHandler.getPageInfo(abspath, relpath, mode) } // second, try as command unless the path is absolute @@ -383,7 +384,7 @@ func main() { var cinfo PageInfo if !filepath.IsAbs(path) { abspath = pathpkg.Join(cmdHandler.fsRoot, path) - cinfo = cmdHandler.getPageInfo(abspath, relpath, "", mode) + cinfo = cmdHandler.getPageInfo(abspath, relpath, mode) } // determine what to use @@ -421,20 +422,24 @@ func main() { filter := func(s string) bool { return rx.MatchString(s) } switch { case info.PAst != nil: + cmap := ast.NewCommentMap(info.FSet, info.PAst, info.PAst.Comments) ast.FilterFile(info.PAst, filter) // Special case: Don't use templates for printing // so we only get the filtered declarations without // package clause or extra whitespace. for i, d := range info.PAst.Decls { + // determine the comments associated with d only + comments := cmap.Filter(d).Comments() + cn := &printer.CommentedNode{Node: d, Comments: comments} if i > 0 { fmt.Println() } if *html { var buf bytes.Buffer - writeNode(&buf, info.FSet, d) + writeNode(&buf, info.FSet, cn) FormatText(os.Stdout, buf.Bytes(), -1, true, "", nil) } else { - writeNode(os.Stdout, info.FSet, d) + writeNode(os.Stdout, info.FSet, cn) } fmt.Println() } @@ -459,9 +464,3 @@ type httpWriter struct { 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.") -} diff --git a/src/cmd/godoc/parser.go b/src/cmd/godoc/parser.go index c6b7c2dc8..42a5d2d98 100644 --- a/src/cmd/godoc/parser.go +++ b/src/cmd/godoc/parser.go @@ -2,10 +2,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// This file contains support functions for parsing .go files. -// Similar functionality is found in package go/parser but the -// functions here operate using godoc's file system fs instead -// of calling the OS's file operations directly. +// This file contains support functions for parsing .go files +// accessed via godoc's file system fs. package main @@ -13,7 +11,6 @@ import ( "go/ast" "go/parser" "go/token" - "os" pathpkg "path" ) @@ -25,44 +22,16 @@ func parseFile(fset *token.FileSet, filename string, mode parser.Mode) (*ast.Fil return parser.ParseFile(fset, filename, src, mode) } -func parseFiles(fset *token.FileSet, filenames []string) (pkgs map[string]*ast.Package, first error) { - pkgs = make(map[string]*ast.Package) - for _, filename := range filenames { - file, err := parseFile(fset, filename, parser.ParseComments) +func parseFiles(fset *token.FileSet, abspath string, localnames []string) (map[string]*ast.File, error) { + files := make(map[string]*ast.File) + for _, f := range localnames { + absname := pathpkg.Join(abspath, f) + file, err := parseFile(fset, absname, parser.ParseComments) if err != nil { - if first == nil { - first = err - } - continue - } - - name := file.Name.Name - pkg, found := pkgs[name] - if !found { - // TODO(gri) Use NewPackage here; reconsider ParseFiles API. - pkg = &ast.Package{Name: name, Files: make(map[string]*ast.File)} - pkgs[name] = pkg - } - pkg.Files[filename] = file - } - return -} - -func parseDir(fset *token.FileSet, path string, filter func(os.FileInfo) bool) (map[string]*ast.Package, error) { - list, err := fs.ReadDir(path) - if err != nil { - return nil, err - } - - filenames := make([]string, len(list)) - i := 0 - for _, d := range list { - if filter == nil || filter(d) { - filenames[i] = pathpkg.Join(path, d.Name()) - i++ + return nil, err } + files[absname] = file } - filenames = filenames[0:i] - return parseFiles(fset, filenames) + return files, nil } diff --git a/src/cmd/godoc/play-appengine.go b/src/cmd/godoc/play-appengine.go new file mode 100644 index 000000000..9e351d1a2 --- /dev/null +++ b/src/cmd/godoc/play-appengine.go @@ -0,0 +1,35 @@ +// Copyright 2012 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. + +// App Engine godoc Playground functionality. + +// +build appengine + +package main + +import ( + "io" + "net/http" + + "appengine" + "appengine/urlfetch" +) + +func bounceToPlayground(w http.ResponseWriter, req *http.Request) { + c := appengine.NewContext(req) + client := urlfetch.Client(c) + url := playgroundBaseURL + req.URL.Path + defer req.Body.Close() + resp, err := client.Post(url, req.Header.Get("Content-type"), req.Body) + if err != nil { + http.Error(w, "Internal Server Error", 500) + c.Errorf("making POST request: %v", err) + return + } + defer resp.Body.Close() + if _, err := io.Copy(w, resp.Body); err != nil { + http.Error(w, "Internal Server Error", 500) + c.Errorf("making POST request: %v", err) + } +} diff --git a/src/cmd/godoc/play-local.go b/src/cmd/godoc/play-local.go new file mode 100644 index 000000000..637ce5e1a --- /dev/null +++ b/src/cmd/godoc/play-local.go @@ -0,0 +1,41 @@ +// Copyright 2012 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. + +// Stand-alone godoc Playground functionality. + +// +build !appengine + +package main + +import ( + "io" + "net/http" + "net/url" +) + +var playgroundScheme, playgroundHost string + +func init() { + u, err := url.Parse(playgroundBaseURL) + if err != nil { + panic(err) + } + playgroundScheme = u.Scheme + playgroundHost = u.Host +} + +// bounceToPlayground forwards the request to play.golang.org. +func bounceToPlayground(w http.ResponseWriter, req *http.Request) { + defer req.Body.Close() + req.URL.Scheme = playgroundScheme + req.URL.Host = playgroundHost + resp, err := http.Post(req.URL.String(), req.Header.Get("Content-type"), req.Body) + if err != nil { + http.Error(w, err.Error(), 500) + return + } + w.WriteHeader(resp.StatusCode) + io.Copy(w, resp.Body) + resp.Body.Close() +} diff --git a/src/cmd/godoc/play.go b/src/cmd/godoc/play.go new file mode 100644 index 000000000..47a11f6c0 --- /dev/null +++ b/src/cmd/godoc/play.go @@ -0,0 +1,52 @@ +// Copyright 2012 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. + +// Common Playground functionality. + +package main + +import ( + "encoding/json" + "fmt" + "go/format" + "net/http" +) + +// The server that will service compile and share requests. +const playgroundBaseURL = "http://play.golang.org" + +func registerPlaygroundHandlers(mux *http.ServeMux) { + if *showPlayground { + mux.HandleFunc("/compile", bounceToPlayground) + mux.HandleFunc("/share", bounceToPlayground) + } else { + mux.HandleFunc("/compile", disabledHandler) + mux.HandleFunc("/share", disabledHandler) + } + http.HandleFunc("/fmt", fmtHandler) +} + +type fmtResponse struct { + Body string + Error string +} + +// fmtHandler takes a Go program in its "body" form value, formats it with +// standard gofmt formatting, and writes a fmtResponse as a JSON object. +func fmtHandler(w http.ResponseWriter, r *http.Request) { + resp := new(fmtResponse) + body, err := format.Source([]byte(r.FormValue("body"))) + if err != nil { + resp.Error = err.Error() + } else { + resp.Body = string(body) + } + json.NewEncoder(w).Encode(resp) +} + +// 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.") +} diff --git a/src/cmd/godoc/setup-godoc-app.bash b/src/cmd/godoc/setup-godoc-app.bash index b8dc4dcf9..792e0d450 100644..100755 --- a/src/cmd/godoc/setup-godoc-app.bash +++ b/src/cmd/godoc/setup-godoc-app.bash @@ -4,13 +4,14 @@ # Use of this source code is governed by a BSD-style # license that can be found in the LICENSE file. -# This script creates the .zip, index, and configuration files for running -# godoc on app-engine. +# This script creates a complete godoc app in $APPDIR. +# It copies the cmd/godoc and src/pkg/go/... sources from GOROOT, +# synthesizes an app.yaml file, and creates the .zip, index, and +# configuration files. # # If an argument is provided it is assumed to be the app-engine godoc directory. -# Without an argument, $APPDIR is used instead. If GOROOT is not set, the -# current working directory is assumed to be $GOROOT. Various sanity checks -# prevent accidents. +# Without an argument, $APPDIR is used instead. If GOROOT is not set, "go env" +# is consulted to find the $GOROOT. # # The script creates a .zip file representing the $GOROOT file system # and computes the correspondig search index files. These files are then @@ -29,8 +30,8 @@ error() { getArgs() { if [ -z $GOROOT ]; then - GOROOT=$(pwd) - echo "GOROOT not set, using cwd instead" + GOROOT=$(go env GOROOT) + echo "GOROOT not set explicitly, using $GOROOT instead" fi if [ -z $APPDIR ]; then if [ $# == 0 ]; then @@ -47,14 +48,8 @@ getArgs() { if [ ! -x $GOROOT/bin/godoc ]; then error "$GOROOT/bin/godoc does not exist or is not executable" fi - if [ ! -d $APPDIR ]; then - error "$APPDIR is not a directory" - fi - if [ ! -e $APPDIR/app.yaml ]; then - error "$APPDIR is not an app-engine directory; missing file app.yaml" - fi - if [ ! -d $APPDIR/godoc ]; then - error "$APPDIR is missing directory godoc" + if [ -e $APPDIR ]; then + error "$APPDIR exists; check and remove it before trying again" fi # reporting @@ -62,12 +57,32 @@ getArgs() { echo "APPDIR = $APPDIR" } -cleanup() { - echo "*** cleanup $APPDIR" - rm $APPDIR/$ZIPFILE - rm $APPDIR/$INDEXFILE - rm $APPDIR/$SPLITFILES* - rm $APPDIR/$CONFIGFILE +copyGodoc() { + echo "*** copy $GOROOT/src/cmd/godoc to $APPDIR/godoc" + cp -r $GOROOT/src/cmd/godoc $APPDIR/godoc +} + +copyGoPackages() { + echo "*** copy $GOROOT/src/pkg/go to $APPDIR/newgo and rewrite imports" + cp -r $GOROOT/src/pkg/go $APPDIR/newgo + find $APPDIR/newgo -type d -name testdata | xargs rm -r + gofiles=$(find $APPDIR -name '*.go') + sed -i '' 's_^\(."\)\(go/[a-z]*\)"$_\1new\2"_' $gofiles + sed -i '' 's_^\(import "\)\(go/[a-z]*\)"$_\1new\2"_' $gofiles +} + +makeAppYaml() { + echo "*** make $APPDIR/app.yaml" + cat > $APPDIR/app.yaml <<EOF +application: godoc +version: 1 +runtime: go +api_version: go1 + +handlers: +- url: /.* + script: _go_app +EOF } makeZipfile() { @@ -112,7 +127,11 @@ EOF } getArgs "$@" -cleanup +set -e +mkdir $APPDIR +copyGodoc +copyGoPackages +makeAppYaml makeZipfile makeIndexfile splitIndexfile diff --git a/src/cmd/godoc/template.go b/src/cmd/godoc/template.go index d709baef4..7b9b9cfeb 100644 --- a/src/cmd/godoc/template.go +++ b/src/cmd/godoc/template.go @@ -3,7 +3,7 @@ // license that can be found in the LICENSE file. // Template support for writing HTML documents. -// Documents that include Template: true in their +// Documents that include Template: true in their // metadata are executed as input to text/template. // // This file defines functions for those templates to invoke. @@ -57,8 +57,8 @@ func contents(name string) string { return string(file) } -// format returns a textual representation of the arg, formatted according to its nature. -func format(arg interface{}) string { +// stringFor returns a textual representation of the arg, formatted according to its nature. +func stringFor(arg interface{}) string { switch arg := arg.(type) { case int: return fmt.Sprintf("%d", arg) @@ -87,10 +87,10 @@ func code(file string, arg ...interface{}) (s string, err error) { // text is already whole file. command = fmt.Sprintf("code %q", file) case 1: - command = fmt.Sprintf("code %q %s", file, format(arg[0])) + command = fmt.Sprintf("code %q %s", file, stringFor(arg[0])) text = oneLine(file, text, arg[0]) case 2: - command = fmt.Sprintf("code %q %s %s", file, format(arg[0]), format(arg[1])) + command = fmt.Sprintf("code %q %s %s", file, stringFor(arg[0]), stringFor(arg[1])) text = multipleLines(file, text, arg[0], arg[1]) default: return "", fmt.Errorf("incorrect code invocation: code %q %q", file, arg) |