Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.
 
 
 

576 lignes
15 KiB

  1. // Copyright 2013 The Go Authors. All rights reserved.
  2. //
  3. // Use of this source code is governed by a BSD-style
  4. // license that can be found in the LICENSE file or at
  5. // https://developers.google.com/open-source/licenses/bsd.
  6. package main
  7. import (
  8. "bytes"
  9. "encoding/base64"
  10. "errors"
  11. "fmt"
  12. godoc "go/doc"
  13. htemp "html/template"
  14. "io"
  15. "net/http"
  16. "net/url"
  17. "path"
  18. "path/filepath"
  19. "reflect"
  20. "regexp"
  21. "sort"
  22. "strings"
  23. ttemp "text/template"
  24. "time"
  25. "github.com/spf13/viper"
  26. "github.com/golang/gddo/doc"
  27. "github.com/golang/gddo/gosrc"
  28. "github.com/golang/gddo/httputil"
  29. )
  30. var cacheBusters httputil.CacheBusters
  31. type flashMessage struct {
  32. ID string
  33. Args []string
  34. }
  35. // getFlashMessages retrieves flash messages from the request and clears the flash cookie if needed.
  36. func getFlashMessages(resp http.ResponseWriter, req *http.Request) []flashMessage {
  37. c, err := req.Cookie("flash")
  38. if err == http.ErrNoCookie {
  39. return nil
  40. }
  41. http.SetCookie(resp, &http.Cookie{Name: "flash", Path: "/", MaxAge: -1, Expires: time.Now().Add(-100 * 24 * time.Hour)})
  42. if err != nil {
  43. return nil
  44. }
  45. p, err := base64.URLEncoding.DecodeString(c.Value)
  46. if err != nil {
  47. return nil
  48. }
  49. var messages []flashMessage
  50. for _, s := range strings.Split(string(p), "\000") {
  51. idArgs := strings.Split(s, "\001")
  52. messages = append(messages, flashMessage{ID: idArgs[0], Args: idArgs[1:]})
  53. }
  54. return messages
  55. }
  56. // setFlashMessages sets a cookie with the given flash messages.
  57. func setFlashMessages(resp http.ResponseWriter, messages []flashMessage) {
  58. var buf []byte
  59. for i, message := range messages {
  60. if i > 0 {
  61. buf = append(buf, '\000')
  62. }
  63. buf = append(buf, message.ID...)
  64. for _, arg := range message.Args {
  65. buf = append(buf, '\001')
  66. buf = append(buf, arg...)
  67. }
  68. }
  69. value := base64.URLEncoding.EncodeToString(buf)
  70. http.SetCookie(resp, &http.Cookie{Name: "flash", Value: value, Path: "/"})
  71. }
  72. type tdoc struct {
  73. *doc.Package
  74. allExamples []*texample
  75. }
  76. type texample struct {
  77. ID string
  78. Label string
  79. Example *doc.Example
  80. Play bool
  81. obj interface{}
  82. }
  83. func newTDoc(pdoc *doc.Package) *tdoc {
  84. return &tdoc{Package: pdoc}
  85. }
  86. func (pdoc *tdoc) SourceLink(pos doc.Pos, text string, textOnlyOK bool) htemp.HTML {
  87. if pos.Line == 0 || pdoc.LineFmt == "" || pdoc.Files[pos.File].URL == "" {
  88. if textOnlyOK {
  89. return htemp.HTML(htemp.HTMLEscapeString(text))
  90. }
  91. return ""
  92. }
  93. return htemp.HTML(fmt.Sprintf(`<a title="View Source" href="%s">%s</a>`,
  94. htemp.HTMLEscapeString(fmt.Sprintf(pdoc.LineFmt, pdoc.Files[pos.File].URL, pos.Line)),
  95. htemp.HTMLEscapeString(text)))
  96. }
  97. // UsesLink generates a link to uses of a symbol definition.
  98. // title is used as the tooltip. defParts are parts of the symbol definition name.
  99. func (pdoc *tdoc) UsesLink(title string, defParts ...string) htemp.HTML {
  100. if viper.GetString(ConfigSourcegraphURL) == "" {
  101. return ""
  102. }
  103. var def string
  104. switch len(defParts) {
  105. case 1:
  106. // Funcs and types have one def part.
  107. def = defParts[0]
  108. case 3:
  109. // Methods have three def parts, the original receiver name, actual receiver name and method name.
  110. orig, recv, methodName := defParts[0], defParts[1], defParts[2]
  111. if orig == "" {
  112. // TODO: Remove this fallback after 2016-08-05. It's only needed temporarily to backfill data.
  113. // Actual receiver is not needed, it's only used because original receiver value
  114. // was recently added to gddo/doc package and will be blank until next package rebuild.
  115. //
  116. // Use actual receiver as fallback.
  117. orig = recv
  118. }
  119. // Trim "*" from "*T" if it's a pointer receiver method.
  120. typeName := strings.TrimPrefix(orig, "*")
  121. def = typeName + "/" + methodName
  122. default:
  123. panic(fmt.Errorf("%v defParts, want 1 or 3", len(defParts)))
  124. }
  125. q := url.Values{
  126. "repo": {pdoc.ProjectRoot},
  127. "pkg": {pdoc.ImportPath},
  128. "def": {def},
  129. }
  130. u := viper.GetString(ConfigSourcegraphURL) + "/-/godoc/refs?" + q.Encode()
  131. return htemp.HTML(fmt.Sprintf(`<a class="uses" title="%s" href="%s">Uses</a>`, htemp.HTMLEscapeString(title), htemp.HTMLEscapeString(u)))
  132. }
  133. func (pdoc *tdoc) PageName() string {
  134. if pdoc.Name != "" && !pdoc.IsCmd {
  135. return pdoc.Name
  136. }
  137. _, name := path.Split(pdoc.ImportPath)
  138. return name
  139. }
  140. func (pdoc *tdoc) addExamples(obj interface{}, export, method string, examples []*doc.Example) {
  141. label := export
  142. id := export
  143. if method != "" {
  144. label += "." + method
  145. id += "-" + method
  146. }
  147. for _, e := range examples {
  148. te := &texample{
  149. Label: label,
  150. ID: id,
  151. Example: e,
  152. obj: obj,
  153. // Only show play links for packages within the standard library.
  154. Play: e.Play != "" && gosrc.IsGoRepoPath(pdoc.ImportPath),
  155. }
  156. if e.Name != "" {
  157. te.Label += " (" + e.Name + ")"
  158. if method == "" {
  159. te.ID += "-"
  160. }
  161. te.ID += "-" + e.Name
  162. }
  163. pdoc.allExamples = append(pdoc.allExamples, te)
  164. }
  165. }
  166. type byExampleID []*texample
  167. func (e byExampleID) Len() int { return len(e) }
  168. func (e byExampleID) Less(i, j int) bool { return e[i].ID < e[j].ID }
  169. func (e byExampleID) Swap(i, j int) { e[i], e[j] = e[j], e[i] }
  170. func (pdoc *tdoc) AllExamples() []*texample {
  171. if pdoc.allExamples != nil {
  172. return pdoc.allExamples
  173. }
  174. pdoc.allExamples = make([]*texample, 0)
  175. pdoc.addExamples(pdoc, "package", "", pdoc.Examples)
  176. for _, f := range pdoc.Funcs {
  177. pdoc.addExamples(f, f.Name, "", f.Examples)
  178. }
  179. for _, t := range pdoc.Types {
  180. pdoc.addExamples(t, t.Name, "", t.Examples)
  181. for _, f := range t.Funcs {
  182. pdoc.addExamples(f, f.Name, "", f.Examples)
  183. }
  184. for _, m := range t.Methods {
  185. if len(m.Examples) > 0 {
  186. pdoc.addExamples(m, t.Name, m.Name, m.Examples)
  187. }
  188. }
  189. }
  190. sort.Sort(byExampleID(pdoc.allExamples))
  191. return pdoc.allExamples
  192. }
  193. func (pdoc *tdoc) ObjExamples(obj interface{}) []*texample {
  194. var examples []*texample
  195. for _, e := range pdoc.allExamples {
  196. if e.obj == obj {
  197. examples = append(examples, e)
  198. }
  199. }
  200. return examples
  201. }
  202. func (pdoc *tdoc) Breadcrumbs(templateName string) htemp.HTML {
  203. if !strings.HasPrefix(pdoc.ImportPath, pdoc.ProjectRoot) {
  204. return ""
  205. }
  206. var buf bytes.Buffer
  207. i := 0
  208. j := len(pdoc.ProjectRoot)
  209. if j == 0 {
  210. j = strings.IndexRune(pdoc.ImportPath, '/')
  211. if j < 0 {
  212. j = len(pdoc.ImportPath)
  213. }
  214. }
  215. for {
  216. if i != 0 {
  217. buf.WriteString(`<span class="text-muted">/</span>`)
  218. }
  219. link := j < len(pdoc.ImportPath) ||
  220. (templateName != "dir.html" && templateName != "cmd.html" && templateName != "pkg.html")
  221. if link {
  222. buf.WriteString(`<a href="`)
  223. buf.WriteString(formatPathFrag(pdoc.ImportPath[:j], ""))
  224. buf.WriteString(`">`)
  225. } else {
  226. buf.WriteString(`<span class="text-muted">`)
  227. }
  228. buf.WriteString(htemp.HTMLEscapeString(pdoc.ImportPath[i:j]))
  229. if link {
  230. buf.WriteString("</a>")
  231. } else {
  232. buf.WriteString("</span>")
  233. }
  234. i = j + 1
  235. if i >= len(pdoc.ImportPath) {
  236. break
  237. }
  238. j = strings.IndexRune(pdoc.ImportPath[i:], '/')
  239. if j < 0 {
  240. j = len(pdoc.ImportPath)
  241. } else {
  242. j += i
  243. }
  244. }
  245. return htemp.HTML(buf.String())
  246. }
  247. func (pdoc *tdoc) StatusDescription() htemp.HTML {
  248. desc := ""
  249. switch pdoc.Package.Status {
  250. case gosrc.DeadEndFork:
  251. desc = "This is a dead-end fork (no commits since the fork)."
  252. case gosrc.QuickFork:
  253. desc = "This is a quick bug-fix fork (has fewer than three commits, and only during the week it was created)."
  254. case gosrc.Inactive:
  255. desc = "This is an inactive package (no imports and no commits in at least two years)."
  256. }
  257. return htemp.HTML(desc)
  258. }
  259. func formatPathFrag(path, fragment string) string {
  260. if len(path) > 0 && path[0] != '/' {
  261. path = "/" + path
  262. }
  263. u := url.URL{Path: path, Fragment: fragment}
  264. return u.String()
  265. }
  266. func hostFn(urlStr string) string {
  267. u, err := url.Parse(urlStr)
  268. if err != nil {
  269. return ""
  270. }
  271. return u.Host
  272. }
  273. func mapFn(kvs ...interface{}) (map[string]interface{}, error) {
  274. if len(kvs)%2 != 0 {
  275. return nil, errors.New("map requires even number of arguments")
  276. }
  277. m := make(map[string]interface{})
  278. for i := 0; i < len(kvs); i += 2 {
  279. s, ok := kvs[i].(string)
  280. if !ok {
  281. return nil, errors.New("even args to map must be strings")
  282. }
  283. m[s] = kvs[i+1]
  284. }
  285. return m, nil
  286. }
  287. // relativePathFn formats an import path as HTML.
  288. func relativePathFn(path string, parentPath interface{}) string {
  289. if p, ok := parentPath.(string); ok && p != "" && strings.HasPrefix(path, p) {
  290. path = path[len(p)+1:]
  291. }
  292. return path
  293. }
  294. // importPathFn formats an import with zero width space characters to allow for breaks.
  295. func importPathFn(path string) htemp.HTML {
  296. path = htemp.HTMLEscapeString(path)
  297. if len(path) > 45 {
  298. // Allow long import paths to break following "/"
  299. path = strings.Replace(path, "/", "/&#8203;", -1)
  300. }
  301. return htemp.HTML(path)
  302. }
  303. var (
  304. h3Pat = regexp.MustCompile(`<h3 id="([^"]+)">([^<]+)</h3>`)
  305. rfcPat = regexp.MustCompile(`RFC\s+(\d{3,4})(,?\s+[Ss]ection\s+(\d+(\.\d+)*))?`)
  306. packagePat = regexp.MustCompile(`\s+package\s+([-a-z0-9]\S+)`)
  307. )
  308. func replaceAll(src []byte, re *regexp.Regexp, replace func(out, src []byte, m []int) []byte) []byte {
  309. var out []byte
  310. for len(src) > 0 {
  311. m := re.FindSubmatchIndex(src)
  312. if m == nil {
  313. break
  314. }
  315. out = append(out, src[:m[0]]...)
  316. out = replace(out, src, m)
  317. src = src[m[1]:]
  318. }
  319. if out == nil {
  320. return src
  321. }
  322. return append(out, src...)
  323. }
  324. // commentFn formats a source code comment as HTML.
  325. func commentFn(v string) htemp.HTML {
  326. var buf bytes.Buffer
  327. godoc.ToHTML(&buf, v, nil)
  328. p := buf.Bytes()
  329. p = replaceAll(p, h3Pat, func(out, src []byte, m []int) []byte {
  330. out = append(out, `<h4 id="`...)
  331. out = append(out, src[m[2]:m[3]]...)
  332. out = append(out, `">`...)
  333. out = append(out, src[m[4]:m[5]]...)
  334. out = append(out, ` <a class="permalink" href="#`...)
  335. out = append(out, src[m[2]:m[3]]...)
  336. out = append(out, `">&para</a></h4>`...)
  337. return out
  338. })
  339. p = replaceAll(p, rfcPat, func(out, src []byte, m []int) []byte {
  340. out = append(out, `<a href="http://tools.ietf.org/html/rfc`...)
  341. out = append(out, src[m[2]:m[3]]...)
  342. // If available, add section fragment
  343. if m[4] != -1 {
  344. out = append(out, `#section-`...)
  345. out = append(out, src[m[6]:m[7]]...)
  346. }
  347. out = append(out, `">`...)
  348. out = append(out, src[m[0]:m[1]]...)
  349. out = append(out, `</a>`...)
  350. return out
  351. })
  352. p = replaceAll(p, packagePat, func(out, src []byte, m []int) []byte {
  353. path := bytes.TrimRight(src[m[2]:m[3]], ".!?:")
  354. if !gosrc.IsValidPath(string(path)) {
  355. return append(out, src[m[0]:m[1]]...)
  356. }
  357. out = append(out, src[m[0]:m[2]]...)
  358. out = append(out, `<a href="/`...)
  359. out = append(out, path...)
  360. out = append(out, `">`...)
  361. out = append(out, path...)
  362. out = append(out, `</a>`...)
  363. out = append(out, src[m[2]+len(path):m[1]]...)
  364. return out
  365. })
  366. return htemp.HTML(p)
  367. }
  368. // commentTextFn formats a source code comment as text.
  369. func commentTextFn(v string) string {
  370. const indent = " "
  371. var buf bytes.Buffer
  372. godoc.ToText(&buf, v, indent, "\t", 80-2*len(indent))
  373. p := buf.Bytes()
  374. return string(p)
  375. }
  376. var period = []byte{'.'}
  377. func codeFn(c doc.Code, typ *doc.Type) htemp.HTML {
  378. var buf bytes.Buffer
  379. last := 0
  380. src := []byte(c.Text)
  381. buf.WriteString("<pre>")
  382. for _, a := range c.Annotations {
  383. htemp.HTMLEscape(&buf, src[last:a.Pos])
  384. switch a.Kind {
  385. case doc.PackageLinkAnnotation:
  386. buf.WriteString(`<a href="`)
  387. buf.WriteString(formatPathFrag(c.Paths[a.PathIndex], ""))
  388. buf.WriteString(`">`)
  389. htemp.HTMLEscape(&buf, src[a.Pos:a.End])
  390. buf.WriteString(`</a>`)
  391. case doc.LinkAnnotation, doc.BuiltinAnnotation:
  392. var p string
  393. if a.Kind == doc.BuiltinAnnotation {
  394. p = "builtin"
  395. } else if a.PathIndex >= 0 {
  396. p = c.Paths[a.PathIndex]
  397. }
  398. n := src[a.Pos:a.End]
  399. n = n[bytes.LastIndex(n, period)+1:]
  400. buf.WriteString(`<a href="`)
  401. buf.WriteString(formatPathFrag(p, string(n)))
  402. buf.WriteString(`">`)
  403. htemp.HTMLEscape(&buf, src[a.Pos:a.End])
  404. buf.WriteString(`</a>`)
  405. case doc.CommentAnnotation:
  406. buf.WriteString(`<span class="com">`)
  407. htemp.HTMLEscape(&buf, src[a.Pos:a.End])
  408. buf.WriteString(`</span>`)
  409. case doc.AnchorAnnotation:
  410. buf.WriteString(`<span id="`)
  411. if typ != nil {
  412. htemp.HTMLEscape(&buf, []byte(typ.Name))
  413. buf.WriteByte('.')
  414. }
  415. htemp.HTMLEscape(&buf, src[a.Pos:a.End])
  416. buf.WriteString(`">`)
  417. htemp.HTMLEscape(&buf, src[a.Pos:a.End])
  418. buf.WriteString(`</span>`)
  419. default:
  420. htemp.HTMLEscape(&buf, src[a.Pos:a.End])
  421. }
  422. last = int(a.End)
  423. }
  424. htemp.HTMLEscape(&buf, src[last:])
  425. buf.WriteString("</pre>")
  426. return htemp.HTML(buf.String())
  427. }
  428. var isInterfacePat = regexp.MustCompile(`^type [^ ]+ interface`)
  429. func isInterfaceFn(t *doc.Type) bool {
  430. return isInterfacePat.MatchString(t.Decl.Text)
  431. }
  432. var gaAccount string
  433. func gaAccountFn() string {
  434. return gaAccount
  435. }
  436. func noteTitleFn(s string) string {
  437. return strings.Title(strings.ToLower(s))
  438. }
  439. func htmlCommentFn(s string) htemp.HTML {
  440. return htemp.HTML("<!-- " + s + " -->")
  441. }
  442. var mimeTypes = map[string]string{
  443. ".html": htmlMIMEType,
  444. ".txt": textMIMEType,
  445. }
  446. func executeTemplate(resp http.ResponseWriter, name string, status int, header http.Header, data interface{}) error {
  447. for k, v := range header {
  448. resp.Header()[k] = v
  449. }
  450. mimeType, ok := mimeTypes[path.Ext(name)]
  451. if !ok {
  452. mimeType = textMIMEType
  453. }
  454. resp.Header().Set("Content-Type", mimeType)
  455. t := templates[name]
  456. if t == nil {
  457. return fmt.Errorf("template %s not found", name)
  458. }
  459. resp.WriteHeader(status)
  460. if status == http.StatusNotModified {
  461. return nil
  462. }
  463. return t.Execute(resp, data)
  464. }
  465. var templates = map[string]interface {
  466. Execute(io.Writer, interface{}) error
  467. }{}
  468. func joinTemplateDir(base string, files []string) []string {
  469. result := make([]string, len(files))
  470. for i := range files {
  471. result[i] = filepath.Join(base, "templates", files[i])
  472. }
  473. return result
  474. }
  475. func parseHTMLTemplates(sets [][]string) error {
  476. for _, set := range sets {
  477. templateName := set[0]
  478. t := htemp.New("")
  479. t.Funcs(htemp.FuncMap{
  480. "code": codeFn,
  481. "comment": commentFn,
  482. "equal": reflect.DeepEqual,
  483. "gaAccount": gaAccountFn,
  484. "host": hostFn,
  485. "htmlComment": htmlCommentFn,
  486. "importPath": importPathFn,
  487. "isInterface": isInterfaceFn,
  488. "isValidImportPath": gosrc.IsValidPath,
  489. "map": mapFn,
  490. "noteTitle": noteTitleFn,
  491. "relativePath": relativePathFn,
  492. "sidebarEnabled": func() bool { return viper.GetBool(ConfigSidebar) },
  493. "staticPath": func(p string) string { return cacheBusters.AppendQueryParam(p, "v") },
  494. "templateName": func() string { return templateName },
  495. })
  496. if _, err := t.ParseFiles(joinTemplateDir(viper.GetString(ConfigAssetsDir), set)...); err != nil {
  497. return err
  498. }
  499. t = t.Lookup("ROOT")
  500. if t == nil {
  501. return fmt.Errorf("ROOT template not found in %v", set)
  502. }
  503. templates[set[0]] = t
  504. }
  505. return nil
  506. }
  507. func parseTextTemplates(sets [][]string) error {
  508. for _, set := range sets {
  509. t := ttemp.New("")
  510. t.Funcs(ttemp.FuncMap{
  511. "comment": commentTextFn,
  512. })
  513. if _, err := t.ParseFiles(joinTemplateDir(viper.GetString(ConfigAssetsDir), set)...); err != nil {
  514. return err
  515. }
  516. t = t.Lookup("ROOT")
  517. if t == nil {
  518. return fmt.Errorf("ROOT template not found in %v", set)
  519. }
  520. templates[set[0]] = t
  521. }
  522. return nil
  523. }