You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

911 lines
25 KiB

  1. // Copyright 2013 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. // Package godoc is a work-in-progress (2013-07-17) package to
  5. // begin splitting up the godoc binary into multiple pieces.
  6. //
  7. // This package comment will evolve over time as this package splits
  8. // into smaller pieces.
  9. package godoc // import "golang.org/x/tools/godoc"
  10. import (
  11. "bytes"
  12. "fmt"
  13. "go/ast"
  14. "go/doc"
  15. "go/format"
  16. "go/printer"
  17. "go/token"
  18. htmltemplate "html/template"
  19. "io"
  20. "log"
  21. "os"
  22. pathpkg "path"
  23. "regexp"
  24. "strconv"
  25. "strings"
  26. "text/template"
  27. "time"
  28. "unicode"
  29. "unicode/utf8"
  30. )
  31. // Fake relative package path for built-ins. Documentation for all globals
  32. // (not just exported ones) will be shown for packages in this directory.
  33. const builtinPkgPath = "builtin"
  34. // FuncMap defines template functions used in godoc templates.
  35. //
  36. // Convention: template function names ending in "_html" or "_url" produce
  37. // HTML- or URL-escaped strings; all other function results may
  38. // require explicit escaping in the template.
  39. func (p *Presentation) FuncMap() template.FuncMap {
  40. p.initFuncMapOnce.Do(p.initFuncMap)
  41. return p.funcMap
  42. }
  43. func (p *Presentation) TemplateFuncs() template.FuncMap {
  44. p.initFuncMapOnce.Do(p.initFuncMap)
  45. return p.templateFuncs
  46. }
  47. func (p *Presentation) initFuncMap() {
  48. if p.Corpus == nil {
  49. panic("nil Presentation.Corpus")
  50. }
  51. p.templateFuncs = template.FuncMap{
  52. "code": p.code,
  53. }
  54. p.funcMap = template.FuncMap{
  55. // various helpers
  56. "filename": filenameFunc,
  57. "repeat": strings.Repeat,
  58. // access to FileInfos (directory listings)
  59. "fileInfoName": fileInfoNameFunc,
  60. "fileInfoTime": fileInfoTimeFunc,
  61. // access to search result information
  62. "infoKind_html": infoKind_htmlFunc,
  63. "infoLine": p.infoLineFunc,
  64. "infoSnippet_html": p.infoSnippet_htmlFunc,
  65. // formatting of AST nodes
  66. "node": p.nodeFunc,
  67. "node_html": p.node_htmlFunc,
  68. "comment_html": comment_htmlFunc,
  69. "comment_text": comment_textFunc,
  70. "sanitize": sanitizeFunc,
  71. // support for URL attributes
  72. "pkgLink": pkgLinkFunc,
  73. "srcLink": srcLinkFunc,
  74. "posLink_url": newPosLink_urlFunc(srcPosLinkFunc),
  75. "docLink": docLinkFunc,
  76. "queryLink": queryLinkFunc,
  77. "srcBreadcrumb": srcBreadcrumbFunc,
  78. "srcToPkgLink": srcToPkgLinkFunc,
  79. // formatting of Examples
  80. "example_html": p.example_htmlFunc,
  81. "example_text": p.example_textFunc,
  82. "example_name": p.example_nameFunc,
  83. "example_suffix": p.example_suffixFunc,
  84. // formatting of analysis information
  85. "callgraph_html": p.callgraph_htmlFunc,
  86. "implements_html": p.implements_htmlFunc,
  87. "methodset_html": p.methodset_htmlFunc,
  88. // formatting of Notes
  89. "noteTitle": noteTitle,
  90. // Number operation
  91. "multiply": multiply,
  92. // formatting of PageInfoMode query string
  93. "modeQueryString": modeQueryString,
  94. }
  95. if p.URLForSrc != nil {
  96. p.funcMap["srcLink"] = p.URLForSrc
  97. }
  98. if p.URLForSrcPos != nil {
  99. p.funcMap["posLink_url"] = newPosLink_urlFunc(p.URLForSrcPos)
  100. }
  101. if p.URLForSrcQuery != nil {
  102. p.funcMap["queryLink"] = p.URLForSrcQuery
  103. }
  104. }
  105. func multiply(a, b int) int { return a * b }
  106. func filenameFunc(path string) string {
  107. _, localname := pathpkg.Split(path)
  108. return localname
  109. }
  110. func fileInfoNameFunc(fi os.FileInfo) string {
  111. name := fi.Name()
  112. if fi.IsDir() {
  113. name += "/"
  114. }
  115. return name
  116. }
  117. func fileInfoTimeFunc(fi os.FileInfo) string {
  118. if t := fi.ModTime(); t.Unix() != 0 {
  119. return t.Local().String()
  120. }
  121. return "" // don't return epoch if time is obviously not set
  122. }
  123. // The strings in infoKinds must be properly html-escaped.
  124. var infoKinds = [nKinds]string{
  125. PackageClause: "package clause",
  126. ImportDecl: "import decl",
  127. ConstDecl: "const decl",
  128. TypeDecl: "type decl",
  129. VarDecl: "var decl",
  130. FuncDecl: "func decl",
  131. MethodDecl: "method decl",
  132. Use: "use",
  133. }
  134. func infoKind_htmlFunc(info SpotInfo) string {
  135. return infoKinds[info.Kind()] // infoKind entries are html-escaped
  136. }
  137. func (p *Presentation) infoLineFunc(info SpotInfo) int {
  138. line := info.Lori()
  139. if info.IsIndex() {
  140. index, _ := p.Corpus.searchIndex.Get()
  141. if index != nil {
  142. line = index.(*Index).Snippet(line).Line
  143. } else {
  144. // no line information available because
  145. // we don't have an index - this should
  146. // never happen; be conservative and don't
  147. // crash
  148. line = 0
  149. }
  150. }
  151. return line
  152. }
  153. func (p *Presentation) infoSnippet_htmlFunc(info SpotInfo) string {
  154. if info.IsIndex() {
  155. index, _ := p.Corpus.searchIndex.Get()
  156. // Snippet.Text was HTML-escaped when it was generated
  157. return index.(*Index).Snippet(info.Lori()).Text
  158. }
  159. return `<span class="alert">no snippet text available</span>`
  160. }
  161. func (p *Presentation) nodeFunc(info *PageInfo, node interface{}) string {
  162. var buf bytes.Buffer
  163. p.writeNode(&buf, info.FSet, node)
  164. return buf.String()
  165. }
  166. func (p *Presentation) node_htmlFunc(info *PageInfo, node interface{}, linkify bool) string {
  167. var buf1 bytes.Buffer
  168. p.writeNode(&buf1, info.FSet, node)
  169. var buf2 bytes.Buffer
  170. if n, _ := node.(ast.Node); n != nil && linkify && p.DeclLinks {
  171. LinkifyText(&buf2, buf1.Bytes(), n)
  172. if st, name := isStructTypeDecl(n); st != nil {
  173. addStructFieldIDAttributes(&buf2, name, st)
  174. }
  175. } else {
  176. FormatText(&buf2, buf1.Bytes(), -1, true, "", nil)
  177. }
  178. return buf2.String()
  179. }
  180. // isStructTypeDecl checks whether n is a struct declaration.
  181. // It either returns a non-nil StructType and its name, or zero values.
  182. func isStructTypeDecl(n ast.Node) (st *ast.StructType, name string) {
  183. gd, ok := n.(*ast.GenDecl)
  184. if !ok || gd.Tok != token.TYPE {
  185. return nil, ""
  186. }
  187. if gd.Lparen > 0 {
  188. // Parenthesized type. Who does that, anyway?
  189. // TODO: Reportedly gri does. Fix this to handle that too.
  190. return nil, ""
  191. }
  192. if len(gd.Specs) != 1 {
  193. return nil, ""
  194. }
  195. ts, ok := gd.Specs[0].(*ast.TypeSpec)
  196. if !ok {
  197. return nil, ""
  198. }
  199. st, ok = ts.Type.(*ast.StructType)
  200. if !ok {
  201. return nil, ""
  202. }
  203. return st, ts.Name.Name
  204. }
  205. // addStructFieldIDAttributes modifies the contents of buf such that
  206. // all struct fields of the named struct have <span id='name.Field'>
  207. // in them, so people can link to /#Struct.Field.
  208. func addStructFieldIDAttributes(buf *bytes.Buffer, name string, st *ast.StructType) {
  209. if st.Fields == nil {
  210. return
  211. }
  212. // needsLink is a set of identifiers that still need to be
  213. // linked, where value == key, to avoid an allocation in func
  214. // linkedField.
  215. needsLink := make(map[string]string)
  216. for _, f := range st.Fields.List {
  217. if len(f.Names) == 0 {
  218. continue
  219. }
  220. fieldName := f.Names[0].Name
  221. needsLink[fieldName] = fieldName
  222. }
  223. var newBuf bytes.Buffer
  224. foreachLine(buf.Bytes(), func(line []byte) {
  225. if fieldName := linkedField(line, needsLink); fieldName != "" {
  226. fmt.Fprintf(&newBuf, `<span id="%s.%s"></span>`, name, fieldName)
  227. delete(needsLink, fieldName)
  228. }
  229. newBuf.Write(line)
  230. })
  231. buf.Reset()
  232. buf.Write(newBuf.Bytes())
  233. }
  234. // foreachLine calls fn for each line of in, where a line includes
  235. // the trailing "\n", except on the last line, if it doesn't exist.
  236. func foreachLine(in []byte, fn func(line []byte)) {
  237. for len(in) > 0 {
  238. nl := bytes.IndexByte(in, '\n')
  239. if nl == -1 {
  240. fn(in)
  241. return
  242. }
  243. fn(in[:nl+1])
  244. in = in[nl+1:]
  245. }
  246. }
  247. // commentPrefix is the line prefix for comments after they've been HTMLified.
  248. var commentPrefix = []byte(`<span class="comment">// `)
  249. // linkedField determines whether the given line starts with an
  250. // identifer in the provided ids map (mapping from identifier to the
  251. // same identifier). The line can start with either an identifier or
  252. // an identifier in a comment. If one matches, it returns the
  253. // identifier that matched. Otherwise it returns the empty string.
  254. func linkedField(line []byte, ids map[string]string) string {
  255. line = bytes.TrimSpace(line)
  256. // For fields with a doc string of the
  257. // conventional form, we put the new span into
  258. // the comment instead of the field.
  259. // The "conventional" form is a complete sentence
  260. // per https://golang.org/s/style#comment-sentences like:
  261. //
  262. // // Foo is an optional Fooer to foo the foos.
  263. // Foo Fooer
  264. //
  265. // In this case, we want the #StructName.Foo
  266. // link to make the browser go to the comment
  267. // line "Foo is an optional Fooer" instead of
  268. // the "Foo Fooer" line, which could otherwise
  269. // obscure the docs above the browser's "fold".
  270. //
  271. // TODO: do this better, so it works for all
  272. // comments, including unconventional ones.
  273. if bytes.HasPrefix(line, commentPrefix) {
  274. line = line[len(commentPrefix):]
  275. }
  276. id := scanIdentifier(line)
  277. if len(id) == 0 {
  278. // No leading identifier. Avoid map lookup for
  279. // somewhat common case.
  280. return ""
  281. }
  282. return ids[string(id)]
  283. }
  284. // scanIdentifier scans a valid Go identifier off the front of v and
  285. // either returns a subslice of v if there's a valid identifier, or
  286. // returns a zero-length slice.
  287. func scanIdentifier(v []byte) []byte {
  288. var n int // number of leading bytes of v belonging to an identifier
  289. for {
  290. r, width := utf8.DecodeRune(v[n:])
  291. if !(isLetter(r) || n > 0 && isDigit(r)) {
  292. break
  293. }
  294. n += width
  295. }
  296. return v[:n]
  297. }
  298. func isLetter(ch rune) bool {
  299. return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_' || ch >= utf8.RuneSelf && unicode.IsLetter(ch)
  300. }
  301. func isDigit(ch rune) bool {
  302. return '0' <= ch && ch <= '9' || ch >= utf8.RuneSelf && unicode.IsDigit(ch)
  303. }
  304. func comment_htmlFunc(comment string) string {
  305. var buf bytes.Buffer
  306. // TODO(gri) Provide list of words (e.g. function parameters)
  307. // to be emphasized by ToHTML.
  308. doc.ToHTML(&buf, comment, nil) // does html-escaping
  309. return buf.String()
  310. }
  311. // punchCardWidth is the number of columns of fixed-width
  312. // characters to assume when wrapping text. Very few people
  313. // use terminals or cards smaller than 80 characters, so 80 it is.
  314. // We do not try to sniff the environment or the tty to adapt to
  315. // the situation; instead, by using a constant we make sure that
  316. // godoc always produces the same output regardless of context,
  317. // a consistency that is lost otherwise. For example, if we sniffed
  318. // the environment or tty, then http://golang.org/pkg/math/?m=text
  319. // would depend on the width of the terminal where godoc started,
  320. // which is clearly bogus. More generally, the Unix tools that behave
  321. // differently when writing to a tty than when writing to a file have
  322. // a history of causing confusion (compare `ls` and `ls | cat`), and we
  323. // want to avoid that mistake here.
  324. const punchCardWidth = 80
  325. func containsOnlySpace(buf []byte) bool {
  326. isNotSpace := func(r rune) bool { return !unicode.IsSpace(r) }
  327. return bytes.IndexFunc(buf, isNotSpace) == -1
  328. }
  329. func comment_textFunc(comment, indent, preIndent string) string {
  330. var buf bytes.Buffer
  331. doc.ToText(&buf, comment, indent, preIndent, punchCardWidth-2*len(indent))
  332. if containsOnlySpace(buf.Bytes()) {
  333. return ""
  334. }
  335. return buf.String()
  336. }
  337. // sanitizeFunc sanitizes the argument src by replacing newlines with
  338. // blanks, removing extra blanks, and by removing trailing whitespace
  339. // and commas before closing parentheses.
  340. func sanitizeFunc(src string) string {
  341. buf := make([]byte, len(src))
  342. j := 0 // buf index
  343. comma := -1 // comma index if >= 0
  344. for i := 0; i < len(src); i++ {
  345. ch := src[i]
  346. switch ch {
  347. case '\t', '\n', ' ':
  348. // ignore whitespace at the beginning, after a blank, or after opening parentheses
  349. if j == 0 {
  350. continue
  351. }
  352. if p := buf[j-1]; p == ' ' || p == '(' || p == '{' || p == '[' {
  353. continue
  354. }
  355. // replace all whitespace with blanks
  356. ch = ' '
  357. case ',':
  358. comma = j
  359. case ')', '}', ']':
  360. // remove any trailing comma
  361. if comma >= 0 {
  362. j = comma
  363. }
  364. // remove any trailing whitespace
  365. if j > 0 && buf[j-1] == ' ' {
  366. j--
  367. }
  368. default:
  369. comma = -1
  370. }
  371. buf[j] = ch
  372. j++
  373. }
  374. // remove trailing blank, if any
  375. if j > 0 && buf[j-1] == ' ' {
  376. j--
  377. }
  378. return string(buf[:j])
  379. }
  380. type PageInfo struct {
  381. Dirname string // directory containing the package
  382. Err error // error or nil
  383. GoogleCN bool // page is being served from golang.google.cn
  384. Mode PageInfoMode // display metadata from query string
  385. // package info
  386. FSet *token.FileSet // nil if no package documentation
  387. PDoc *doc.Package // nil if no package documentation
  388. Examples []*doc.Example // nil if no example code
  389. Notes map[string][]*doc.Note // nil if no package Notes
  390. PAst map[string]*ast.File // nil if no AST with package exports
  391. IsMain bool // true for package main
  392. IsFiltered bool // true if results were filtered
  393. // analysis info
  394. TypeInfoIndex map[string]int // index of JSON datum for type T (if -analysis=type)
  395. AnalysisData htmltemplate.JS // array of TypeInfoJSON values
  396. CallGraph htmltemplate.JS // array of PCGNodeJSON values (if -analysis=pointer)
  397. CallGraphIndex map[string]int // maps func name to index in CallGraph
  398. // directory info
  399. Dirs *DirList // nil if no directory information
  400. DirTime time.Time // directory time stamp
  401. DirFlat bool // if set, show directory in a flat (non-indented) manner
  402. }
  403. func (info *PageInfo) IsEmpty() bool {
  404. return info.Err != nil || info.PAst == nil && info.PDoc == nil && info.Dirs == nil
  405. }
  406. func pkgLinkFunc(path string) string {
  407. // because of the irregular mapping under goroot
  408. // we need to correct certain relative paths
  409. path = strings.TrimPrefix(path, "/")
  410. path = strings.TrimPrefix(path, "src/")
  411. path = strings.TrimPrefix(path, "pkg/")
  412. return "pkg/" + path
  413. }
  414. // srcToPkgLinkFunc builds an <a> tag linking to the package
  415. // documentation of relpath.
  416. func srcToPkgLinkFunc(relpath string) string {
  417. relpath = pkgLinkFunc(relpath)
  418. relpath = pathpkg.Dir(relpath)
  419. if relpath == "pkg" {
  420. return `<a href="/pkg">Index</a>`
  421. }
  422. return fmt.Sprintf(`<a href="/%s">%s</a>`, relpath, relpath[len("pkg/"):])
  423. }
  424. // srcBreadcrumbFun converts each segment of relpath to a HTML <a>.
  425. // Each segment links to its corresponding src directories.
  426. func srcBreadcrumbFunc(relpath string) string {
  427. segments := strings.Split(relpath, "/")
  428. var buf bytes.Buffer
  429. var selectedSegment string
  430. var selectedIndex int
  431. if strings.HasSuffix(relpath, "/") {
  432. // relpath is a directory ending with a "/".
  433. // Selected segment is the segment before the last slash.
  434. selectedIndex = len(segments) - 2
  435. selectedSegment = segments[selectedIndex] + "/"
  436. } else {
  437. selectedIndex = len(segments) - 1
  438. selectedSegment = segments[selectedIndex]
  439. }
  440. for i := range segments[:selectedIndex] {
  441. buf.WriteString(fmt.Sprintf(`<a href="/%s">%s</a>/`,
  442. strings.Join(segments[:i+1], "/"),
  443. segments[i],
  444. ))
  445. }
  446. buf.WriteString(`<span class="text-muted">`)
  447. buf.WriteString(selectedSegment)
  448. buf.WriteString(`</span>`)
  449. return buf.String()
  450. }
  451. func newPosLink_urlFunc(srcPosLinkFunc func(s string, line, low, high int) string) func(info *PageInfo, n interface{}) string {
  452. // n must be an ast.Node or a *doc.Note
  453. return func(info *PageInfo, n interface{}) string {
  454. var pos, end token.Pos
  455. switch n := n.(type) {
  456. case ast.Node:
  457. pos = n.Pos()
  458. end = n.End()
  459. case *doc.Note:
  460. pos = n.Pos
  461. end = n.End
  462. default:
  463. panic(fmt.Sprintf("wrong type for posLink_url template formatter: %T", n))
  464. }
  465. var relpath string
  466. var line int
  467. var low, high int // selection offset range
  468. if pos.IsValid() {
  469. p := info.FSet.Position(pos)
  470. relpath = p.Filename
  471. line = p.Line
  472. low = p.Offset
  473. }
  474. if end.IsValid() {
  475. high = info.FSet.Position(end).Offset
  476. }
  477. return srcPosLinkFunc(relpath, line, low, high)
  478. }
  479. }
  480. func srcPosLinkFunc(s string, line, low, high int) string {
  481. s = srcLinkFunc(s)
  482. var buf bytes.Buffer
  483. template.HTMLEscape(&buf, []byte(s))
  484. // selection ranges are of form "s=low:high"
  485. if low < high {
  486. fmt.Fprintf(&buf, "?s=%d:%d", low, high) // no need for URL escaping
  487. // if we have a selection, position the page
  488. // such that the selection is a bit below the top
  489. line -= 10
  490. if line < 1 {
  491. line = 1
  492. }
  493. }
  494. // line id's in html-printed source are of the
  495. // form "L%d" where %d stands for the line number
  496. if line > 0 {
  497. fmt.Fprintf(&buf, "#L%d", line) // no need for URL escaping
  498. }
  499. return buf.String()
  500. }
  501. func srcLinkFunc(s string) string {
  502. s = pathpkg.Clean("/" + s)
  503. if !strings.HasPrefix(s, "/src/") {
  504. s = "/src" + s
  505. }
  506. return s
  507. }
  508. // queryLinkFunc returns a URL for a line in a source file with a highlighted
  509. // query term.
  510. // s is expected to be a path to a source file.
  511. // query is expected to be a string that has already been appropriately escaped
  512. // for use in a URL query.
  513. func queryLinkFunc(s, query string, line int) string {
  514. url := pathpkg.Clean("/"+s) + "?h=" + query
  515. if line > 0 {
  516. url += "#L" + strconv.Itoa(line)
  517. }
  518. return url
  519. }
  520. func docLinkFunc(s string, ident string) string {
  521. return pathpkg.Clean("/pkg/"+s) + "/#" + ident
  522. }
  523. func (p *Presentation) example_textFunc(info *PageInfo, funcName, indent string) string {
  524. if !p.ShowExamples {
  525. return ""
  526. }
  527. var buf bytes.Buffer
  528. first := true
  529. for _, eg := range info.Examples {
  530. name := stripExampleSuffix(eg.Name)
  531. if name != funcName {
  532. continue
  533. }
  534. if !first {
  535. buf.WriteString("\n")
  536. }
  537. first = false
  538. // print code
  539. cnode := &printer.CommentedNode{Node: eg.Code, Comments: eg.Comments}
  540. config := &printer.Config{Mode: printer.UseSpaces, Tabwidth: p.TabWidth}
  541. var buf1 bytes.Buffer
  542. config.Fprint(&buf1, info.FSet, cnode)
  543. code := buf1.String()
  544. // Additional formatting if this is a function body. Unfortunately, we
  545. // can't print statements individually because we would lose comments
  546. // on later statements.
  547. if n := len(code); n >= 2 && code[0] == '{' && code[n-1] == '}' {
  548. // remove surrounding braces
  549. code = code[1 : n-1]
  550. // unindent
  551. code = replaceLeadingIndentation(code, strings.Repeat(" ", p.TabWidth), indent)
  552. }
  553. code = strings.Trim(code, "\n")
  554. buf.WriteString(indent)
  555. buf.WriteString("Example:\n")
  556. buf.WriteString(code)
  557. buf.WriteString("\n\n")
  558. }
  559. return buf.String()
  560. }
  561. func (p *Presentation) example_htmlFunc(info *PageInfo, funcName string) string {
  562. var buf bytes.Buffer
  563. for _, eg := range info.Examples {
  564. name := stripExampleSuffix(eg.Name)
  565. if name != funcName {
  566. continue
  567. }
  568. // print code
  569. cnode := &printer.CommentedNode{Node: eg.Code, Comments: eg.Comments}
  570. code := p.node_htmlFunc(info, cnode, true)
  571. out := eg.Output
  572. wholeFile := true
  573. // Additional formatting if this is a function body.
  574. if n := len(code); n >= 2 && code[0] == '{' && code[n-1] == '}' {
  575. wholeFile = false
  576. // remove surrounding braces
  577. code = code[1 : n-1]
  578. // unindent
  579. code = replaceLeadingIndentation(code, strings.Repeat(" ", p.TabWidth), "")
  580. // remove output comment
  581. if loc := exampleOutputRx.FindStringIndex(code); loc != nil {
  582. code = strings.TrimSpace(code[:loc[0]])
  583. }
  584. }
  585. // Write out the playground code in standard Go style
  586. // (use tabs, no comment highlight, etc).
  587. play := ""
  588. if eg.Play != nil && p.ShowPlayground {
  589. var buf bytes.Buffer
  590. if err := format.Node(&buf, info.FSet, eg.Play); err != nil {
  591. log.Print(err)
  592. } else {
  593. play = buf.String()
  594. }
  595. }
  596. // Drop output, as the output comment will appear in the code.
  597. if wholeFile && play == "" {
  598. out = ""
  599. }
  600. if p.ExampleHTML == nil {
  601. out = ""
  602. return ""
  603. }
  604. err := p.ExampleHTML.Execute(&buf, struct {
  605. Name, Doc, Code, Play, Output string
  606. GoogleCN bool
  607. }{eg.Name, eg.Doc, code, play, out, info.GoogleCN})
  608. if err != nil {
  609. log.Print(err)
  610. }
  611. }
  612. return buf.String()
  613. }
  614. // example_nameFunc takes an example function name and returns its display
  615. // name. For example, "Foo_Bar_quux" becomes "Foo.Bar (Quux)".
  616. func (p *Presentation) example_nameFunc(s string) string {
  617. name, suffix := splitExampleName(s)
  618. // replace _ with . for method names
  619. name = strings.Replace(name, "_", ".", 1)
  620. // use "Package" if no name provided
  621. if name == "" {
  622. name = "Package"
  623. }
  624. return name + suffix
  625. }
  626. // example_suffixFunc takes an example function name and returns its suffix in
  627. // parenthesized form. For example, "Foo_Bar_quux" becomes " (Quux)".
  628. func (p *Presentation) example_suffixFunc(name string) string {
  629. _, suffix := splitExampleName(name)
  630. return suffix
  631. }
  632. // implements_html returns the "> Implements" toggle for a package-level named type.
  633. // Its contents are populated from JSON data by client-side JS at load time.
  634. func (p *Presentation) implements_htmlFunc(info *PageInfo, typeName string) string {
  635. if p.ImplementsHTML == nil {
  636. return ""
  637. }
  638. index, ok := info.TypeInfoIndex[typeName]
  639. if !ok {
  640. return ""
  641. }
  642. var buf bytes.Buffer
  643. err := p.ImplementsHTML.Execute(&buf, struct{ Index int }{index})
  644. if err != nil {
  645. log.Print(err)
  646. }
  647. return buf.String()
  648. }
  649. // methodset_html returns the "> Method set" toggle for a package-level named type.
  650. // Its contents are populated from JSON data by client-side JS at load time.
  651. func (p *Presentation) methodset_htmlFunc(info *PageInfo, typeName string) string {
  652. if p.MethodSetHTML == nil {
  653. return ""
  654. }
  655. index, ok := info.TypeInfoIndex[typeName]
  656. if !ok {
  657. return ""
  658. }
  659. var buf bytes.Buffer
  660. err := p.MethodSetHTML.Execute(&buf, struct{ Index int }{index})
  661. if err != nil {
  662. log.Print(err)
  663. }
  664. return buf.String()
  665. }
  666. // callgraph_html returns the "> Call graph" toggle for a package-level func.
  667. // Its contents are populated from JSON data by client-side JS at load time.
  668. func (p *Presentation) callgraph_htmlFunc(info *PageInfo, recv, name string) string {
  669. if p.CallGraphHTML == nil {
  670. return ""
  671. }
  672. if recv != "" {
  673. // Format must match (*ssa.Function).RelString().
  674. name = fmt.Sprintf("(%s).%s", recv, name)
  675. }
  676. index, ok := info.CallGraphIndex[name]
  677. if !ok {
  678. return ""
  679. }
  680. var buf bytes.Buffer
  681. err := p.CallGraphHTML.Execute(&buf, struct{ Index int }{index})
  682. if err != nil {
  683. log.Print(err)
  684. }
  685. return buf.String()
  686. }
  687. func noteTitle(note string) string {
  688. return strings.Title(strings.ToLower(note))
  689. }
  690. func startsWithUppercase(s string) bool {
  691. r, _ := utf8.DecodeRuneInString(s)
  692. return unicode.IsUpper(r)
  693. }
  694. var exampleOutputRx = regexp.MustCompile(`(?i)//[[:space:]]*(unordered )?output:`)
  695. // stripExampleSuffix strips lowercase braz in Foo_braz or Foo_Bar_braz from name
  696. // while keeping uppercase Braz in Foo_Braz.
  697. func stripExampleSuffix(name string) string {
  698. if i := strings.LastIndex(name, "_"); i != -1 {
  699. if i < len(name)-1 && !startsWithUppercase(name[i+1:]) {
  700. name = name[:i]
  701. }
  702. }
  703. return name
  704. }
  705. func splitExampleName(s string) (name, suffix string) {
  706. i := strings.LastIndex(s, "_")
  707. if 0 <= i && i < len(s)-1 && !startsWithUppercase(s[i+1:]) {
  708. name = s[:i]
  709. suffix = " (" + strings.Title(s[i+1:]) + ")"
  710. return
  711. }
  712. name = s
  713. return
  714. }
  715. // replaceLeadingIndentation replaces oldIndent at the beginning of each line
  716. // with newIndent. This is used for formatting examples. Raw strings that
  717. // span multiple lines are handled specially: oldIndent is not removed (since
  718. // go/printer will not add any indentation there), but newIndent is added
  719. // (since we may still want leading indentation).
  720. func replaceLeadingIndentation(body, oldIndent, newIndent string) string {
  721. // Handle indent at the beginning of the first line. After this, we handle
  722. // indentation only after a newline.
  723. var buf bytes.Buffer
  724. if strings.HasPrefix(body, oldIndent) {
  725. buf.WriteString(newIndent)
  726. body = body[len(oldIndent):]
  727. }
  728. // Use a state machine to keep track of whether we're in a string or
  729. // rune literal while we process the rest of the code.
  730. const (
  731. codeState = iota
  732. runeState
  733. interpretedStringState
  734. rawStringState
  735. )
  736. searchChars := []string{
  737. "'\"`\n", // codeState
  738. `\'`, // runeState
  739. `\"`, // interpretedStringState
  740. "`\n", // rawStringState
  741. // newlineState does not need to search
  742. }
  743. state := codeState
  744. for {
  745. i := strings.IndexAny(body, searchChars[state])
  746. if i < 0 {
  747. buf.WriteString(body)
  748. break
  749. }
  750. c := body[i]
  751. buf.WriteString(body[:i+1])
  752. body = body[i+1:]
  753. switch state {
  754. case codeState:
  755. switch c {
  756. case '\'':
  757. state = runeState
  758. case '"':
  759. state = interpretedStringState
  760. case '`':
  761. state = rawStringState
  762. case '\n':
  763. if strings.HasPrefix(body, oldIndent) {
  764. buf.WriteString(newIndent)
  765. body = body[len(oldIndent):]
  766. }
  767. }
  768. case runeState:
  769. switch c {
  770. case '\\':
  771. r, size := utf8.DecodeRuneInString(body)
  772. buf.WriteRune(r)
  773. body = body[size:]
  774. case '\'':
  775. state = codeState
  776. }
  777. case interpretedStringState:
  778. switch c {
  779. case '\\':
  780. r, size := utf8.DecodeRuneInString(body)
  781. buf.WriteRune(r)
  782. body = body[size:]
  783. case '"':
  784. state = codeState
  785. }
  786. case rawStringState:
  787. switch c {
  788. case '`':
  789. state = codeState
  790. case '\n':
  791. buf.WriteString(newIndent)
  792. }
  793. }
  794. }
  795. return buf.String()
  796. }
  797. // Write an AST node to w.
  798. func (p *Presentation) writeNode(w io.Writer, fset *token.FileSet, x interface{}) {
  799. // convert trailing tabs into spaces using a tconv filter
  800. // to ensure a good outcome in most browsers (there may still
  801. // be tabs in comments and strings, but converting those into
  802. // the right number of spaces is much harder)
  803. //
  804. // TODO(gri) rethink printer flags - perhaps tconv can be eliminated
  805. // with an another printer mode (which is more efficiently
  806. // implemented in the printer than here with another layer)
  807. mode := printer.TabIndent | printer.UseSpaces
  808. err := (&printer.Config{Mode: mode, Tabwidth: p.TabWidth}).Fprint(&tconv{p: p, output: w}, fset, x)
  809. if err != nil {
  810. log.Print(err)
  811. }
  812. }
  813. // WriteNode writes x to w.
  814. // TODO(bgarcia) Is this method needed? It's just a wrapper for p.writeNode.
  815. func (p *Presentation) WriteNode(w io.Writer, fset *token.FileSet, x interface{}) {
  816. p.writeNode(w, fset, x)
  817. }