~john-koepi/ubuntu/trusty/golang/default

« back to all changes in this revision

Viewing changes to src/cmd/godoc/main.go

  • Committer: Bazaar Package Importer
  • Author(s): Ondřej Surý
  • Date: 2011-04-20 17:36:48 UTC
  • Revision ID: james.westby@ubuntu.com-20110420173648-ifergoxyrm832trd
Tags: upstream-2011.03.07.1
Import upstream version 2011.03.07.1

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
// Copyright 2009 The Go Authors. All rights reserved.
 
2
// Use of this source code is governed by a BSD-style
 
3
// license that can be found in the LICENSE file.
 
4
 
 
5
// godoc: Go Documentation Server
 
6
 
 
7
// Web server tree:
 
8
//
 
9
//      http://godoc/           main landing page
 
10
//      http://godoc/doc/       serve from $GOROOT/doc - spec, mem, tutorial, etc.
 
11
//      http://godoc/src/       serve files from $GOROOT/src; .go gets pretty-printed
 
12
//      http://godoc/cmd/       serve documentation about commands
 
13
//      http://godoc/pkg/       serve documentation about packages
 
14
//                              (idea is if you say import "compress/zlib", you go to
 
15
//                              http://godoc/pkg/compress/zlib)
 
16
//
 
17
// Command-line interface:
 
18
//
 
19
//      godoc packagepath [name ...]
 
20
//
 
21
//      godoc compress/zlib
 
22
//              - prints doc for package compress/zlib
 
23
//      godoc crypto/block Cipher NewCMAC
 
24
//              - prints doc for Cipher and NewCMAC in package crypto/block
 
25
 
 
26
package main
 
27
 
 
28
import (
 
29
        "bytes"
 
30
        _ "expvar" // to serve /debug/vars
 
31
        "flag"
 
32
        "fmt"
 
33
        "go/ast"
 
34
        "http"
 
35
        _ "http/pprof" // to serve /debug/pprof/*
 
36
        "io"
 
37
        "log"
 
38
        "os"
 
39
        "path/filepath"
 
40
        "regexp"
 
41
        "runtime"
 
42
        "strings"
 
43
        "time"
 
44
)
 
45
 
 
46
const defaultAddr = ":6060" // default webserver address
 
47
 
 
48
var (
 
49
        // periodic sync
 
50
        syncCmd   = flag.String("sync", "", "sync command; disabled if empty")
 
51
        syncMin   = flag.Int("sync_minutes", 0, "sync interval in minutes; disabled if <= 0")
 
52
        syncDelay delayTime // actual sync interval in minutes; usually syncDelay == syncMin, but syncDelay may back off exponentially
 
53
 
 
54
        // network
 
55
        httpAddr   = flag.String("http", "", "HTTP service address (e.g., '"+defaultAddr+"')")
 
56
        serverAddr = flag.String("server", "", "webserver address for command line searches")
 
57
 
 
58
        // layout control
 
59
        html    = flag.Bool("html", false, "print HTML in command-line mode")
 
60
        srcMode = flag.Bool("src", false, "print (exported) source in command-line mode")
 
61
 
 
62
        // command-line searches
 
63
        query = flag.Bool("q", false, "arguments are considered search queries")
 
64
)
 
65
 
 
66
 
 
67
func serveError(w http.ResponseWriter, r *http.Request, relpath string, err os.Error) {
 
68
        contents := applyTemplate(errorHTML, "errorHTML", err) // err may contain an absolute path!
 
69
        w.WriteHeader(http.StatusNotFound)
 
70
        servePage(w, "File "+relpath, "", "", contents)
 
71
}
 
72
 
 
73
 
 
74
func exec(rw http.ResponseWriter, args []string) (status int) {
 
75
        r, w, err := os.Pipe()
 
76
        if err != nil {
 
77
                log.Printf("os.Pipe(): %v", err)
 
78
                return 2
 
79
        }
 
80
 
 
81
        bin := args[0]
 
82
        fds := []*os.File{nil, w, w}
 
83
        if *verbose {
 
84
                log.Printf("executing %v", args)
 
85
        }
 
86
        p, err := os.StartProcess(bin, args, os.Environ(), *goroot, fds)
 
87
        defer r.Close()
 
88
        w.Close()
 
89
        if err != nil {
 
90
                log.Printf("os.StartProcess(%q): %v", bin, err)
 
91
                return 2
 
92
        }
 
93
        defer p.Release()
 
94
 
 
95
        var buf bytes.Buffer
 
96
        io.Copy(&buf, r)
 
97
        wait, err := p.Wait(0)
 
98
        if err != nil {
 
99
                os.Stderr.Write(buf.Bytes())
 
100
                log.Printf("os.Wait(%d, 0): %v", p.Pid, err)
 
101
                return 2
 
102
        }
 
103
        status = wait.ExitStatus()
 
104
        if !wait.Exited() || status > 1 {
 
105
                os.Stderr.Write(buf.Bytes())
 
106
                log.Printf("executing %v failed (exit status = %d)", args, status)
 
107
                return
 
108
        }
 
109
 
 
110
        if *verbose {
 
111
                os.Stderr.Write(buf.Bytes())
 
112
        }
 
113
        if rw != nil {
 
114
                rw.SetHeader("content-type", "text/plain; charset=utf-8")
 
115
                rw.Write(buf.Bytes())
 
116
        }
 
117
 
 
118
        return
 
119
}
 
120
 
 
121
 
 
122
func dosync(w http.ResponseWriter, r *http.Request) {
 
123
        args := []string{"/bin/sh", "-c", *syncCmd}
 
124
        switch exec(w, args) {
 
125
        case 0:
 
126
                // sync succeeded and some files have changed;
 
127
                // update package tree.
 
128
                // TODO(gri): The directory tree may be temporarily out-of-sync.
 
129
                //            Consider keeping separate time stamps so the web-
 
130
                //            page can indicate this discrepancy.
 
131
                initFSTree()
 
132
                fallthrough
 
133
        case 1:
 
134
                // sync failed because no files changed;
 
135
                // don't change the package tree
 
136
                syncDelay.set(*syncMin) //  revert to regular sync schedule
 
137
        default:
 
138
                // sync failed because of an error - back off exponentially, but try at least once a day
 
139
                syncDelay.backoff(24 * 60)
 
140
        }
 
141
}
 
142
 
 
143
 
 
144
func usage() {
 
145
        fmt.Fprintf(os.Stderr,
 
146
                "usage: godoc package [name ...]\n"+
 
147
                        "       godoc -http="+defaultAddr+"\n")
 
148
        flag.PrintDefaults()
 
149
        os.Exit(2)
 
150
}
 
151
 
 
152
 
 
153
func loggingHandler(h http.Handler) http.Handler {
 
154
        return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
 
155
                log.Printf("%s\t%s", w.RemoteAddr(), req.URL)
 
156
                h.ServeHTTP(w, req)
 
157
        })
 
158
}
 
159
 
 
160
 
 
161
func remoteSearch(query string) (res *http.Response, err os.Error) {
 
162
        search := "/search?f=text&q=" + http.URLEscape(query)
 
163
 
 
164
        // list of addresses to try
 
165
        var addrs []string
 
166
        if *serverAddr != "" {
 
167
                // explicit server address - only try this one
 
168
                addrs = []string{*serverAddr}
 
169
        } else {
 
170
                addrs = []string{
 
171
                        defaultAddr,
 
172
                        "golang.org",
 
173
                }
 
174
        }
 
175
 
 
176
        // remote search
 
177
        for _, addr := range addrs {
 
178
                url := "http://" + addr + search
 
179
                res, _, err = http.Get(url)
 
180
                if err == nil && res.StatusCode == http.StatusOK {
 
181
                        break
 
182
                }
 
183
        }
 
184
 
 
185
        if err == nil && res.StatusCode != http.StatusOK {
 
186
                err = os.NewError(res.Status)
 
187
        }
 
188
 
 
189
        return
 
190
}
 
191
 
 
192
 
 
193
// Does s look like a regular expression?
 
194
func isRegexp(s string) bool {
 
195
        return strings.IndexAny(s, ".(|)*+?^$[]") >= 0
 
196
}
 
197
 
 
198
 
 
199
// Make a regular expression of the form
 
200
// names[0]|names[1]|...names[len(names)-1].
 
201
// Returns nil if the regular expression is illegal.
 
202
func makeRx(names []string) (rx *regexp.Regexp) {
 
203
        if len(names) > 0 {
 
204
                s := ""
 
205
                for i, name := range names {
 
206
                        if i > 0 {
 
207
                                s += "|"
 
208
                        }
 
209
                        if isRegexp(name) {
 
210
                                s += name
 
211
                        } else {
 
212
                                s += "^" + name + "$" // must match exactly
 
213
                        }
 
214
                }
 
215
                rx, _ = regexp.Compile(s) // rx is nil if there's a compilation error
 
216
        }
 
217
        return
 
218
}
 
219
 
 
220
 
 
221
func main() {
 
222
        flag.Usage = usage
 
223
        flag.Parse()
 
224
 
 
225
        // Check usage: either server and no args, or command line and args
 
226
        if (*httpAddr != "") != (flag.NArg() == 0) {
 
227
                usage()
 
228
        }
 
229
 
 
230
        if *tabwidth < 0 {
 
231
                log.Fatalf("negative tabwidth %d", *tabwidth)
 
232
        }
 
233
 
 
234
        initHandlers()
 
235
        readTemplates()
 
236
 
 
237
        if *httpAddr != "" {
 
238
                // HTTP server mode.
 
239
                var handler http.Handler = http.DefaultServeMux
 
240
                if *verbose {
 
241
                        log.Printf("Go Documentation Server")
 
242
                        log.Printf("version = %s", runtime.Version())
 
243
                        log.Printf("address = %s", *httpAddr)
 
244
                        log.Printf("goroot = %s", *goroot)
 
245
                        log.Printf("tabwidth = %d", *tabwidth)
 
246
                        if *maxResults > 0 {
 
247
                                log.Printf("maxresults = %d (full text index enabled)", *maxResults)
 
248
                        }
 
249
                        if !fsMap.IsEmpty() {
 
250
                                log.Print("user-defined mapping:")
 
251
                                fsMap.Fprint(os.Stderr)
 
252
                        }
 
253
                        handler = loggingHandler(handler)
 
254
                }
 
255
 
 
256
                registerPublicHandlers(http.DefaultServeMux)
 
257
                if *syncCmd != "" {
 
258
                        http.Handle("/debug/sync", http.HandlerFunc(dosync))
 
259
                }
 
260
 
 
261
                // Initialize default directory tree with corresponding timestamp.
 
262
                // (Do it in a goroutine so that launch is quick.)
 
263
                go initFSTree()
 
264
 
 
265
                // Initialize directory trees for user-defined file systems (-path flag).
 
266
                initDirTrees()
 
267
 
 
268
                // Start sync goroutine, if enabled.
 
269
                if *syncCmd != "" && *syncMin > 0 {
 
270
                        syncDelay.set(*syncMin) // initial sync delay
 
271
                        go func() {
 
272
                                for {
 
273
                                        dosync(nil, nil)
 
274
                                        delay, _ := syncDelay.get()
 
275
                                        if *verbose {
 
276
                                                log.Printf("next sync in %dmin", delay.(int))
 
277
                                        }
 
278
                                        time.Sleep(int64(delay.(int)) * 60e9)
 
279
                                }
 
280
                        }()
 
281
                }
 
282
 
 
283
                // Start indexing goroutine.
 
284
                go indexer()
 
285
 
 
286
                // Start http server.
 
287
                if err := http.ListenAndServe(*httpAddr, handler); err != nil {
 
288
                        log.Fatalf("ListenAndServe %s: %v", *httpAddr, err)
 
289
                }
 
290
 
 
291
                return
 
292
        }
 
293
 
 
294
        // Command line mode.
 
295
        if *html {
 
296
                packageText = packageHTML
 
297
                searchText = packageHTML
 
298
        }
 
299
 
 
300
        if *query {
 
301
                // Command-line queries.
 
302
                for i := 0; i < flag.NArg(); i++ {
 
303
                        res, err := remoteSearch(flag.Arg(i))
 
304
                        if err != nil {
 
305
                                log.Fatalf("remoteSearch: %s", err)
 
306
                        }
 
307
                        io.Copy(os.Stdout, res.Body)
 
308
                }
 
309
                return
 
310
        }
 
311
 
 
312
        // determine paths
 
313
        path := flag.Arg(0)
 
314
        if len(path) > 0 && path[0] == '.' {
 
315
                // assume cwd; don't assume -goroot
 
316
                cwd, _ := os.Getwd() // ignore errors
 
317
                path = filepath.Join(cwd, path)
 
318
        }
 
319
        relpath := path
 
320
        abspath := path
 
321
        if !filepath.IsAbs(path) {
 
322
                abspath = absolutePath(path, pkgHandler.fsRoot)
 
323
        } else {
 
324
                relpath = relativeURL(path)
 
325
        }
 
326
 
 
327
        var mode PageInfoMode
 
328
        if *srcMode {
 
329
                // only filter exports if we don't have explicit command-line filter arguments
 
330
                if flag.NArg() == 1 {
 
331
                        mode |= exportsOnly
 
332
                }
 
333
        } else {
 
334
                mode = exportsOnly | genDoc
 
335
        }
 
336
        // TODO(gri): Provide a mechanism (flag?) to select a package
 
337
        //            if there are multiple packages in a directory.
 
338
        info := pkgHandler.getPageInfo(abspath, relpath, "", mode)
 
339
 
 
340
        if info.IsEmpty() {
 
341
                // try again, this time assume it's a command
 
342
                if !filepath.IsAbs(path) {
 
343
                        abspath = absolutePath(path, cmdHandler.fsRoot)
 
344
                }
 
345
                cmdInfo := cmdHandler.getPageInfo(abspath, relpath, "", mode)
 
346
                // only use the cmdInfo if it actually contains a result
 
347
                // (don't hide errors reported from looking up a package)
 
348
                if !cmdInfo.IsEmpty() {
 
349
                        info = cmdInfo
 
350
                }
 
351
        }
 
352
        if info.Err != nil {
 
353
                log.Fatalf("%v", info.Err)
 
354
        }
 
355
 
 
356
        // If we have more than one argument, use the remaining arguments for filtering
 
357
        if flag.NArg() > 1 {
 
358
                args := flag.Args()[1:]
 
359
                rx := makeRx(args)
 
360
                if rx == nil {
 
361
                        log.Fatalf("illegal regular expression from %v", args)
 
362
                }
 
363
 
 
364
                filter := func(s string) bool { return rx.MatchString(s) }
 
365
                switch {
 
366
                case info.PAst != nil:
 
367
                        ast.FilterFile(info.PAst, filter)
 
368
                        // Special case: Don't use templates for printing
 
369
                        // so we only get the filtered declarations without
 
370
                        // package clause or extra whitespace.
 
371
                        for i, d := range info.PAst.Decls {
 
372
                                if i > 0 {
 
373
                                        fmt.Println()
 
374
                                }
 
375
                                if *html {
 
376
                                        writeAnyHTML(os.Stdout, info.FSet, d)
 
377
                                } else {
 
378
                                        writeAny(os.Stdout, info.FSet, d)
 
379
                                }
 
380
                                fmt.Println()
 
381
                        }
 
382
                        return
 
383
 
 
384
                case info.PDoc != nil:
 
385
                        info.PDoc.Filter(filter)
 
386
                }
 
387
        }
 
388
 
 
389
        if err := packageText.Execute(os.Stdout, info); err != nil {
 
390
                log.Printf("packageText.Execute: %s", err)
 
391
        }
 
392
}