2013-07-16 22:02:35 -06:00
// 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.
// godoc: Go Documentation Server
// Web server tree:
//
// http://godoc/ main landing page
// http://godoc/doc/ serve from $GOROOT/doc - spec, mem, etc.
// http://godoc/src/ serve files from $GOROOT/src; .go gets pretty-printed
// http://godoc/cmd/ serve documentation about commands
// http://godoc/pkg/ serve documentation about packages
// (idea is if you say import "compress/zlib", you go to
// http://godoc/pkg/compress/zlib)
//
// Command-line interface:
//
// godoc packagepath [name ...]
//
// godoc compress/zlib
// - prints doc for package compress/zlib
// godoc crypto/block Cipher NewCMAC
// - prints doc for Cipher and NewCMAC in package crypto/block
// +build !appengine
package main
import (
"archive/zip"
_ "expvar" // to serve /debug/vars
"flag"
"fmt"
"go/build"
"log"
"net/http"
2013-07-17 01:25:54 -06:00
"net/http/httptest"
2013-07-16 22:02:35 -06:00
_ "net/http/pprof" // to serve /debug/pprof/*
"net/url"
"os"
"path/filepath"
"regexp"
"runtime"
2014-03-14 16:58:22 -06:00
"strings"
2013-07-16 23:02:27 -06:00
2013-07-17 01:09:54 -06:00
"code.google.com/p/go.tools/godoc"
2014-03-14 16:58:22 -06:00
"code.google.com/p/go.tools/godoc/analysis"
2013-08-20 21:49:05 -06:00
"code.google.com/p/go.tools/godoc/static"
2013-07-16 23:02:27 -06:00
"code.google.com/p/go.tools/godoc/vfs"
2013-12-16 12:25:50 -07:00
"code.google.com/p/go.tools/godoc/vfs/gatefs"
2013-08-15 19:44:27 -06:00
"code.google.com/p/go.tools/godoc/vfs/mapfs"
2013-07-16 23:02:27 -06:00
"code.google.com/p/go.tools/godoc/vfs/zipfs"
2013-07-16 22:02:35 -06:00
)
2013-07-31 19:01:10 -06:00
const (
2013-10-10 19:36:50 -06:00
defaultAddr = ":6060" // default webserver address
toolsPath = "code.google.com/p/go.tools/cmd/"
2013-07-31 19:01:10 -06:00
)
2013-07-16 22:02:35 -06:00
var (
// file system to serve
// (with e.g.: zip -r go.zip $GOROOT -i \*.go -i \*.html -i \*.css -i \*.js -i \*.txt -i \*.c -i \*.h -i \*.s -i \*.png -i \*.jpg -i \*.sh -i favicon.ico)
zipfile = flag . String ( "zip" , "" , "zip file providing the file system to serve; disabled if empty" )
// file-based index
writeIndex = flag . Bool ( "write_index" , false , "write index to a file; the file name must be specified with -index_files" )
2014-04-16 14:35:08 -06:00
analysisFlag = flag . String ( "analysis" , "" , ` comma-separated list of analyses to perform (supported: type, pointer). See http://golang.org/lib/godoc/analysis/help.html ` )
2014-03-14 16:58:22 -06:00
2013-07-16 22:02:35 -06:00
// network
httpAddr = flag . String ( "http" , "" , "HTTP service address (e.g., '" + defaultAddr + "')" )
serverAddr = flag . String ( "server" , "" , "webserver address for command line searches" )
// 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" )
2013-07-17 21:51:17 -06:00
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" )
2013-07-16 22:02:35 -06:00
)
func usage ( ) {
fmt . Fprintf ( os . Stderr ,
"usage: godoc package [name ...]\n" +
" godoc -http=" + defaultAddr + "\n" )
flag . PrintDefaults ( )
os . Exit ( 2 )
}
func loggingHandler ( h http . Handler ) http . Handler {
return http . HandlerFunc ( func ( w http . ResponseWriter , req * http . Request ) {
log . Printf ( "%s\t%s" , req . RemoteAddr , req . URL )
h . ServeHTTP ( w , req )
} )
}
2013-07-17 21:51:17 -06:00
func handleURLFlag ( ) {
// 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" )
}
2013-07-16 22:02:35 -06:00
func main ( ) {
flag . Usage = usage
flag . Parse ( )
2013-10-02 22:29:16 -06:00
playEnabled = * showPlayground
2013-07-16 22:02:35 -06:00
// Check usage: either server and no args, command line and args, or index creation mode
if ( * httpAddr != "" || * urlFlag != "" ) != ( flag . NArg ( ) == 0 ) && ! * writeIndex {
usage ( )
}
2013-12-16 12:25:50 -07:00
var fsGate chan bool
fsGate = make ( chan bool , 20 )
2013-07-16 22:02:35 -06:00
// Determine file system to use.
if * zipfile == "" {
// use file system of underlying OS
2014-09-10 07:02:54 -06:00
rootfs := gatefs . New ( vfs . OS ( * goroot ) , fsGate )
fs . Bind ( "/" , rootfs , "/" , vfs . BindReplace )
2013-07-16 22:02:35 -06:00
} 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)
2013-07-17 21:51:17 -06:00
fs . Bind ( "/" , zipfs . New ( rc , * zipfile ) , * goroot , vfs . BindReplace )
2013-07-16 22:02:35 -06:00
}
2013-10-02 22:29:16 -06:00
if * templateDir != "" {
fs . Bind ( "/lib/godoc" , vfs . OS ( * templateDir ) , "/" , vfs . BindBefore )
} else {
fs . Bind ( "/lib/godoc" , mapfs . New ( static . Files ) , "/" , vfs . BindReplace )
}
2013-07-16 22:02:35 -06:00
// Bind $GOPATH trees into Go root.
for _ , p := range filepath . SplitList ( build . Default . GOPATH ) {
2014-09-10 07:02:54 -06:00
fs . Bind ( "/src" , gatefs . New ( vfs . OS ( p ) , fsGate ) , "/src" , vfs . BindAfter )
2013-07-16 22:02:35 -06:00
}
2013-08-16 20:49:24 -06:00
httpMode := * httpAddr != ""
2014-03-14 16:58:22 -06:00
var typeAnalysis , pointerAnalysis bool
if * analysisFlag != "" {
for _ , a := range strings . Split ( * analysisFlag , "," ) {
switch a {
case "type" :
typeAnalysis = true
case "pointer" :
pointerAnalysis = true
default :
log . Fatalf ( "unknown analysis: %s" , a )
}
}
}
2013-07-17 17:52:45 -06:00
corpus := godoc . NewCorpus ( fs )
2013-07-18 18:27:53 -06:00
corpus . Verbose = * verbose
2013-11-21 09:55:42 -07:00
corpus . MaxResults = * maxResults
2013-08-16 20:49:24 -06:00
corpus . IndexEnabled = * indexEnabled && httpMode
2013-11-21 09:55:42 -07:00
if * maxResults == 0 {
corpus . IndexFullText = false
}
2013-07-17 17:52:45 -06:00
corpus . IndexFiles = * indexFiles
2013-08-27 16:16:31 -06:00
corpus . IndexThrottle = * indexThrottle
2013-07-17 21:51:17 -06:00
if * writeIndex {
corpus . IndexThrottle = 1.0
2014-06-13 00:49:32 -06:00
corpus . IndexEnabled = true
2013-07-17 21:51:17 -06:00
}
2013-08-16 20:49:24 -06:00
if * writeIndex || httpMode || * urlFlag != "" {
if err := corpus . Init ( ) ; err != nil {
log . Fatal ( err )
}
2013-07-17 21:51:17 -06:00
}
2013-07-17 17:52:45 -06:00
pres = godoc . NewPresentation ( corpus )
2013-07-17 21:14:09 -06:00
pres . TabWidth = * tabWidth
pres . ShowTimestamps = * showTimestamps
pres . ShowPlayground = * showPlayground
pres . ShowExamples = * showExamples
pres . DeclLinks = * declLinks
2013-11-20 07:56:10 -07:00
pres . SrcMode = * srcMode
pres . HTMLMode = * html
2013-07-17 21:51:17 -06:00
if * notesRx != "" {
pres . NotesRx = regexp . MustCompile ( * notesRx )
}
2013-07-17 21:14:09 -06:00
2013-11-20 07:56:10 -07:00
readTemplates ( pres , httpMode || * urlFlag != "" )
2013-07-18 22:02:03 -06:00
registerHandlers ( pres )
2013-07-17 21:14:09 -06:00
2013-07-16 22:02:35 -06:00
if * writeIndex {
// Write search index and exit.
2013-07-17 21:14:09 -06:00
if * indexFiles == "" {
2013-07-16 22:02:35 -06:00
log . Fatal ( "no index file specified" )
}
log . Println ( "initialize file systems" )
* verbose = true // want to see what happens
2013-07-17 21:14:09 -06:00
corpus . UpdateIndex ( )
2013-07-16 22:02:35 -06:00
2013-07-17 21:14:09 -06:00
log . Println ( "writing index file" , * indexFiles )
f , err := os . Create ( * indexFiles )
2013-07-16 22:02:35 -06:00
if err != nil {
log . Fatal ( err )
}
2013-07-17 21:14:09 -06:00
index , _ := corpus . CurrentIndex ( )
2013-11-12 16:46:25 -07:00
_ , err = index . WriteTo ( f )
2013-07-16 22:02:35 -06:00
if err != nil {
log . Fatal ( err )
}
log . Println ( "done" )
return
}
// Print content that would be served at the URL *urlFlag.
if * urlFlag != "" {
2013-07-17 21:51:17 -06:00
handleURLFlag ( )
return
2013-07-16 22:02:35 -06:00
}
2013-08-16 20:49:24 -06:00
if httpMode {
2013-07-16 22:02:35 -06:00
// HTTP server mode.
var handler http . Handler = http . DefaultServeMux
if * verbose {
log . Printf ( "Go Documentation Server" )
log . Printf ( "version = %s" , runtime . Version ( ) )
log . Printf ( "address = %s" , * httpAddr )
log . Printf ( "goroot = %s" , * goroot )
2013-07-17 21:14:09 -06:00
log . Printf ( "tabwidth = %d" , * tabWidth )
2013-07-16 22:02:35 -06:00
switch {
2013-07-17 17:52:45 -06:00
case ! * indexEnabled :
2013-07-16 22:02:35 -06:00
log . Print ( "search index disabled" )
2013-07-17 21:14:09 -06:00
case * maxResults > 0 :
log . Printf ( "full text index enabled (maxresults = %d)" , * maxResults )
2013-07-16 22:02:35 -06:00
default :
log . Print ( "identifier search index enabled" )
}
2013-07-17 17:52:45 -06:00
fs . Fprint ( os . Stderr )
2013-07-16 22:02:35 -06:00
handler = loggingHandler ( handler )
}
// Initialize search index.
2013-07-17 17:52:45 -06:00
if * indexEnabled {
2013-07-17 21:14:09 -06:00
go corpus . RunIndexer ( )
2013-07-16 22:02:35 -06:00
}
2014-03-14 16:58:22 -06:00
// Start type/pointer analysis.
if typeAnalysis || pointerAnalysis {
go analysis . Run ( pointerAnalysis , & corpus . Analysis )
}
2013-07-16 22:02:35 -06:00
// Start http server.
if err := http . ListenAndServe ( * httpAddr , handler ) ; err != nil {
log . Fatalf ( "ListenAndServe %s: %v" , * httpAddr , err )
}
return
}
if * query {
2013-07-17 21:51:17 -06:00
handleRemoteSearch ( )
2013-07-16 22:02:35 -06:00
return
}
2013-11-20 07:56:10 -07:00
if err := godoc . CommandLine ( os . Stdout , fs , pres , flag . Args ( ) ) ; err != nil {
log . Print ( err )
2013-07-16 22:02:35 -06:00
}
}