|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307 |
- // Copyright 2013 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 or at
- // https://developers.google.com/open-source/licenses/bsd.
-
- // Package lintapp implements the go-lint.appspot.com server.
- package lintapp
-
- import (
- "bytes"
- "encoding/gob"
- "fmt"
- "html/template"
- "net/http"
- "os"
- "path/filepath"
- "strconv"
- "strings"
- "time"
-
- "golang.org/x/net/context"
- "google.golang.org/appengine"
- "google.golang.org/appengine/datastore"
- "google.golang.org/appengine/log"
- "google.golang.org/appengine/urlfetch"
-
- "github.com/golang/gddo/gosrc"
- "github.com/golang/gddo/httputil"
-
- "github.com/golang/lint"
- )
-
- func init() {
- http.Handle("/", handlerFunc(serveRoot))
- http.Handle("/-/bot", handlerFunc(serveBot))
- http.Handle("/-/refresh", handlerFunc(serveRefresh))
- if s := os.Getenv("CONTACT_EMAIL"); s != "" {
- contactEmail = s
- }
- }
-
- var (
- contactEmail = "golang-dev@googlegroups.com"
- homeTemplate = parseTemplate("common.html", "index.html")
- packageTemplate = parseTemplate("common.html", "package.html")
- errorTemplate = parseTemplate("common.html", "error.html")
- templateFuncs = template.FuncMap{
- "timeago": timeagoFn,
- "contactEmail": contactEmailFn,
- }
- github = httputil.NewAuthTransportFromEnvironment(nil)
- )
-
- func parseTemplate(fnames ...string) *template.Template {
- paths := make([]string, len(fnames))
- for i := range fnames {
- paths[i] = filepath.Join("assets/templates", fnames[i])
- }
- t, err := template.New("").Funcs(templateFuncs).ParseFiles(paths...)
- if err != nil {
- panic(err)
- }
- t = t.Lookup("ROOT")
- if t == nil {
- panic(fmt.Sprintf("ROOT template not found in %v", fnames))
- }
- return t
- }
-
- func contactEmailFn() string {
- return contactEmail
- }
-
- func timeagoFn(t time.Time) string {
- d := time.Since(t)
- switch {
- case d < time.Second:
- return "just now"
- case d < 2*time.Second:
- return "one second ago"
- case d < time.Minute:
- return fmt.Sprintf("%d seconds ago", d/time.Second)
- case d < 2*time.Minute:
- return "one minute ago"
- case d < time.Hour:
- return fmt.Sprintf("%d minutes ago", d/time.Minute)
- case d < 2*time.Hour:
- return "one hour ago"
- case d < 48*time.Hour:
- return fmt.Sprintf("%d hours ago", d/time.Hour)
- default:
- return fmt.Sprintf("%d days ago", d/(time.Hour*24))
- }
- }
-
- func writeResponse(w http.ResponseWriter, status int, t *template.Template, v interface{}) error {
- var buf bytes.Buffer
- if err := t.Execute(&buf, v); err != nil {
- return err
- }
- w.Header().Set("Content-Type", "text/html; charset=utf-8")
- w.Header().Set("Content-Length", strconv.Itoa(buf.Len()))
- w.WriteHeader(status)
- _, err := w.Write(buf.Bytes())
- return err
- }
-
- func writeErrorResponse(w http.ResponseWriter, status int) error {
- return writeResponse(w, status, errorTemplate, http.StatusText(status))
- }
-
- func httpClient(r *http.Request) *http.Client {
- c := appengine.NewContext(r)
- return &http.Client{
- Transport: &httputil.Transport{
- Token: github.Token,
- ClientID: github.ClientID,
- ClientSecret: github.ClientSecret,
- Base: &urlfetch.Transport{Context: c, Deadline: 10 * time.Second},
- UserAgent: fmt.Sprintf("%s (+http://%s/-/bot)", appengine.AppID(c), r.Host),
- },
- }
- }
-
- const version = 1
-
- type storePackage struct {
- Data []byte
- Version int
- }
-
- type lintPackage struct {
- Files []*lintFile
- Path string
- Updated time.Time
- LineFmt string
- URL string
- }
-
- type lintFile struct {
- Name string
- Problems []*lintProblem
- URL string
- }
-
- type lintProblem struct {
- Line int
- Text string
- LineText string
- Confidence float64
- Link string
- }
-
- func putPackage(c context.Context, importPath string, pkg *lintPackage) error {
- var buf bytes.Buffer
- if err := gob.NewEncoder(&buf).Encode(pkg); err != nil {
- return err
- }
- _, err := datastore.Put(c,
- datastore.NewKey(c, "Package", importPath, 0, nil),
- &storePackage{Data: buf.Bytes(), Version: version})
- return err
- }
-
- func getPackage(c context.Context, importPath string) (*lintPackage, error) {
- var spkg storePackage
- if err := datastore.Get(c, datastore.NewKey(c, "Package", importPath, 0, nil), &spkg); err != nil {
- if err == datastore.ErrNoSuchEntity {
- err = nil
- }
- return nil, err
- }
- if spkg.Version != version {
- return nil, nil
- }
- var pkg lintPackage
- if err := gob.NewDecoder(bytes.NewReader(spkg.Data)).Decode(&pkg); err != nil {
- return nil, err
- }
- return &pkg, nil
- }
-
- func runLint(r *http.Request, importPath string) (*lintPackage, error) {
- dir, err := gosrc.Get(httpClient(r), importPath, "")
- if err != nil {
- return nil, err
- }
-
- pkg := lintPackage{
- Path: importPath,
- Updated: time.Now(),
- LineFmt: dir.LineFmt,
- URL: dir.BrowseURL,
- }
- linter := lint.Linter{}
- for _, f := range dir.Files {
- if !strings.HasSuffix(f.Name, ".go") {
- continue
- }
- problems, err := linter.Lint(f.Name, f.Data)
- if err == nil && len(problems) == 0 {
- continue
- }
- file := lintFile{Name: f.Name, URL: f.BrowseURL}
- if err != nil {
- file.Problems = []*lintProblem{{Text: err.Error()}}
- } else {
- for _, p := range problems {
- file.Problems = append(file.Problems, &lintProblem{
- Line: p.Position.Line,
- Text: p.Text,
- LineText: p.LineText,
- Confidence: p.Confidence,
- Link: p.Link,
- })
- }
- }
- if len(file.Problems) > 0 {
- pkg.Files = append(pkg.Files, &file)
- }
- }
-
- if err := putPackage(appengine.NewContext(r), importPath, &pkg); err != nil {
- return nil, err
- }
-
- return &pkg, nil
- }
-
- func filterByConfidence(r *http.Request, pkg *lintPackage) {
- minConfidence, err := strconv.ParseFloat(r.FormValue("minConfidence"), 64)
- if err != nil {
- minConfidence = 0.8
- }
- for _, f := range pkg.Files {
- j := 0
- for i := range f.Problems {
- if f.Problems[i].Confidence >= minConfidence {
- f.Problems[j] = f.Problems[i]
- j++
- }
- }
- f.Problems = f.Problems[:j]
- }
- }
-
- type handlerFunc func(http.ResponseWriter, *http.Request) error
-
- func (f handlerFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) {
- c := appengine.NewContext(r)
- err := f(w, r)
- if err == nil {
- return
- } else if gosrc.IsNotFound(err) {
- writeErrorResponse(w, 404)
- } else if e, ok := err.(*gosrc.RemoteError); ok {
- log.Infof(c, "Remote error %s: %v", e.Host, e)
- writeResponse(w, 500, errorTemplate, fmt.Sprintf("Error accessing %s.", e.Host))
- } else if err != nil {
- log.Errorf(c, "Internal error %v", err)
- writeErrorResponse(w, 500)
- }
- }
-
- func serveRoot(w http.ResponseWriter, r *http.Request) error {
- switch {
- case r.Method != "GET" && r.Method != "HEAD":
- return writeErrorResponse(w, 405)
- case r.URL.Path == "/":
- return writeResponse(w, 200, homeTemplate, nil)
- default:
- importPath := r.URL.Path[1:]
- if !gosrc.IsValidPath(importPath) {
- return gosrc.NotFoundError{Message: "bad path"}
- }
- c := appengine.NewContext(r)
- pkg, err := getPackage(c, importPath)
- if pkg == nil && err == nil {
- pkg, err = runLint(r, importPath)
- }
- if err != nil {
- return err
- }
- filterByConfidence(r, pkg)
- return writeResponse(w, 200, packageTemplate, pkg)
- }
- }
-
- func serveRefresh(w http.ResponseWriter, r *http.Request) error {
- if r.Method != "POST" {
- return writeErrorResponse(w, 405)
- }
- importPath := r.FormValue("importPath")
- pkg, err := runLint(r, importPath)
- if err != nil {
- return err
- }
- http.Redirect(w, r, "/"+pkg.Path, 301)
- return nil
- }
-
- func serveBot(w http.ResponseWriter, r *http.Request) error {
- c := appengine.NewContext(r)
- _, err := fmt.Fprintf(w, "Contact %s for help with the %s bot.", contactEmail, appengine.AppID(c))
- return err
- }
|