move code out of linkify_comment.go
Move all code into linkify.go.
Code motion, except for shortened package prefix.
Change-Id: Id2683386bd1c959888ed9e7089919ad61f0b8768
Reviewed-on: https://go-review.googlesource.com/c/pkgsite/+/413315
Run-TryBot: Jonathan Amsterdam <jba@google.com>
Reviewed-by: Jamal Carvalho <jamal@golang.org>
diff --git a/internal/godoc/dochtml/internal/render/linkify.go b/internal/godoc/dochtml/internal/render/linkify.go
index 542fd50..93fb3d2 100644
--- a/internal/godoc/dochtml/internal/render/linkify.go
+++ b/internal/godoc/dochtml/internal/render/linkify.go
@@ -9,6 +9,7 @@
"errors"
"fmt"
"go/ast"
+ "go/doc/comment"
"go/format"
"go/printer"
"go/scanner"
@@ -18,7 +19,7 @@
"strings"
"unicode"
- "github.com/google/safehtml"
+ safe "github.com/google/safehtml"
"github.com/google/safehtml/legacyconversions"
"github.com/google/safehtml/template"
"golang.org/x/pkgsite/internal/godoc/internal/doc"
@@ -56,15 +57,135 @@
badAnchorRx = regexp.MustCompile(`[^a-zA-Z0-9]`)
)
-func (r *Renderer) declHTML(doc string, decl ast.Decl, extractLinks bool) (out struct{ Doc, Decl safehtml.HTML }) {
- if doc != "" {
- out.Doc = r.formatDocHTML(doc, extractLinks)
+type link struct {
+ Class string
+ Href string
+ Text any // string or safe.HTML
+}
+
+type heading struct {
+ ID safe.Identifier
+ Title safe.HTML
+}
+
+var (
+ // tocTemplate expects a []heading.
+ tocTemplate = template.Must(template.New("toc").Parse(`<div role="navigation" aria-label="Table of Contents">
+ <ul class="Documentation-toc{{if gt (len .) 5}} Documentation-toc-columns{{end}}">
+ {{range . -}}
+ <li class="Documentation-tocItem">
+ <a href="#{{.ID}}">{{.Title}}</a>
+ </li>
+ {{end -}}
+ </ul>
+</div>
+`))
+
+ italicTemplate = template.Must(template.New("italics").Parse(`<i>{{.}}</i>`))
+
+ codeTemplate = template.Must(template.New("code").Parse(`<pre>{{.}}</pre>`))
+
+ paraTemplate = template.Must(template.New("para").Parse("<p>{{.}}\n</p>"))
+
+ headingTemplate = template.Must(template.New("heading").Parse(
+ `<h4 id="{{.ID}}">{{.Title}} <a class="Documentation-idLink" href="#{{.ID}}">¶</a></h4>`))
+
+ linkTemplate = template.Must(template.New("link").Parse(
+ `<a{{with .Class}}class="{{.}}" {{end}} href="{{.Href}}">{{.Text}}</a>`))
+
+ uListTemplate = template.Must(template.New("ulist").Parse(
+ `<ul>
+{{- range .}}
+ {{.}}
+{{- end}}
+</ul>`))
+
+ oListTemplate = template.Must(template.New("olist").Parse(
+ `<ol>
+ {{range .}}
+ {{.}}
+ {{end}}
+ </ol>`))
+
+ listItemTemplate = template.Must(template.New("li").Parse(
+ `<li{{with .Number}}value="{{.}}" {{end}}>{{.Content}}</li>`))
+)
+
+func (r *Renderer) formatDocHTML(text string, extractLinks bool) safe.HTML {
+ p := comment.Parser{}
+ doc := p.Parse(text)
+ if extractLinks {
+ r.removeLinks(doc)
}
- if decl != nil {
- idr := &identifierResolver{r.pids, newDeclIDs(decl), r.packageURL}
- out.Decl = r.formatDeclHTML(decl, idr)
+ var headings []heading
+ for _, b := range doc.Content {
+ if h, ok := b.(*comment.Heading); ok {
+ headings = append(headings, r.newHeading(h))
+ }
}
- return out
+ h := r.blocksToHTML(doc.Content, true, extractLinks)
+ if r.enableCommandTOC && len(headings) > 0 {
+ h = safe.HTMLConcat(ExecuteToHTML(tocTemplate, headings), h)
+ }
+ return h
+}
+
+func (r *Renderer) removeLinks(doc *comment.Doc) {
+ var bs []comment.Block
+ inLinks := false
+ for _, b := range doc.Content {
+ switch b := b.(type) {
+ case *comment.Heading:
+ if textsToString(b.Text) == "Links" {
+ inLinks = true
+ } else {
+ inLinks = false
+ bs = append(bs, b)
+ }
+ case *comment.List:
+ if inLinks {
+ for _, item := range b.Items {
+ fmt.Println(" ", item)
+ if link, ok := itemLink(item); ok {
+ r.links = append(r.links, link)
+ }
+ }
+ } else {
+ bs = append(bs, b)
+ }
+ case *comment.Paragraph:
+ if inLinks {
+ // Links section doesn't require leading whitespace, so
+ // the link may be in a paragraph.
+ s := textsToString(b.Text)
+ r.links = append(r.links, parseLinks(strings.Split(s, "\n"))...)
+ } else {
+ bs = append(bs, b)
+ }
+
+ default:
+ if !inLinks {
+ bs = append(bs, b)
+ }
+ }
+ }
+ doc.Content = bs
+}
+
+func itemLink(item *comment.ListItem) (l Link, ok bool) {
+ // Should be a single Paragraph.
+ if len(item.Content) != 1 {
+ return l, false
+ }
+ p, ok := item.Content[0].(*comment.Paragraph)
+ if !ok {
+ return l, false
+ }
+ // TODO: clean up.
+ if lp := parseLink("- " + textsToString(p.Text)); lp != nil {
+ return *lp, true
+ }
+ return l, false
}
// parseLinks extracts links from lines.
@@ -95,6 +216,176 @@
}
}
+func (r *Renderer) blocksToHTML(bs []comment.Block, useParagraph, extractLinks bool) safe.HTML {
+ return concatHTML(bs, func(b comment.Block) safe.HTML {
+ return r.blockToHTML(b, useParagraph, extractLinks)
+ })
+}
+
+func (r *Renderer) blockToHTML(b comment.Block, useParagraph, extractLinks bool) safe.HTML {
+ switch b := b.(type) {
+ case *comment.Paragraph:
+ th := r.textsToHTML(b.Text)
+ if useParagraph {
+ return ExecuteToHTML(paraTemplate, th)
+ }
+ return th
+
+ case *comment.Code:
+ return ExecuteToHTML(codeTemplate, b.Text)
+
+ case *comment.Heading:
+ return ExecuteToHTML(headingTemplate, r.newHeading(b))
+
+ case *comment.List:
+ var items []safe.HTML
+ useParagraph = b.BlankBetween()
+ for _, item := range b.Items {
+ items = append(items, ExecuteToHTML(listItemTemplate, struct {
+ Number string
+ Content safe.HTML
+ }{item.Number, r.blocksToHTML(item.Content, useParagraph, false)}))
+ }
+ t := oListTemplate
+ if b.Items[0].Number == "" {
+ t = uListTemplate
+ }
+ return ExecuteToHTML(t, items)
+ default:
+ return badType(b)
+ }
+}
+
+func (r *Renderer) newHeading(h *comment.Heading) heading {
+ return heading{headingID(h), r.textsToHTML(h.Text)}
+}
+
+func (r *Renderer) textsToHTML(ts []comment.Text) safe.HTML {
+ return concatHTML(ts, r.textToHTML)
+}
+
+func (r *Renderer) textToHTML(t comment.Text) safe.HTML {
+ switch t := t.(type) {
+ case comment.Plain:
+ // Don't auto-link URLs. The doc/comment package already does that.
+ return linkRFCs(string(t))
+ case comment.Italic:
+ return ExecuteToHTML(italicTemplate, t)
+ case *comment.Link:
+ return ExecuteToHTML(linkTemplate, link{"", t.URL, r.textsToHTML(t.Text)})
+ case *comment.DocLink:
+ url := r.docLinkURL(t)
+ return ExecuteToHTML(linkTemplate, link{"", url, r.textsToHTML(t.Text)})
+ default:
+ return badType(t)
+ }
+}
+
+func (r *Renderer) docLinkURL(dl *comment.DocLink) string {
+ var url string
+ if dl.ImportPath != "" {
+ url = "/" + dl.ImportPath
+ if r.packageURL != nil {
+ url = r.packageURL(dl.ImportPath)
+ }
+ }
+ id := dl.Name
+ if dl.Recv != "" {
+ id = dl.Recv + "." + id
+ }
+ if id != "" {
+ url += "#" + id
+ }
+ return url
+}
+
+// TODO: any -> *comment.Text | *comment.Block
+func concatHTML[T any](xs []T, toHTML func(T) safe.HTML) safe.HTML {
+ var hs []safe.HTML
+ for _, x := range xs {
+ hs = append(hs, toHTML(x))
+ }
+ return safe.HTMLConcat(hs...)
+}
+
+func badType(x interface{}) safe.HTML {
+ return safe.HTMLEscaped(fmt.Sprintf("bad type %T", x))
+}
+
+func headingID(h *comment.Heading) safe.Identifier {
+ s := textsToString(h.Text)
+ id := badAnchorRx.ReplaceAllString(s, "_")
+ return safe.IdentifierFromConstantPrefix("hdr", id)
+}
+
+func textsToString(ts []comment.Text) string {
+ var b strings.Builder
+ for _, t := range ts {
+ switch t := t.(type) {
+ case comment.Plain:
+ b.WriteString(string(t))
+ case comment.Italic:
+ b.WriteString(string(t))
+ case *comment.Link:
+ b.WriteString(textsToString(t.Text))
+ case *comment.DocLink:
+ b.WriteString(textsToString(t.Text))
+ default:
+ fmt.Fprintf(&b, "bad text type %T", t)
+ }
+ }
+ return b.String()
+}
+
+var rfcRegexp = regexp.MustCompile(rfcRx)
+
+// TODO: merge/replace Renderer.formatLineHTML.
+// TODO: make more efficient.
+func linkRFCs(s string) safe.HTML {
+ var hs []safe.HTML
+ for len(s) > 0 {
+ m0, m1 := len(s), len(s)
+ if m := rfcRegexp.FindStringIndex(s); m != nil {
+ m0, m1 = m[0], m[1]
+ }
+ if m0 > 0 {
+ hs = append(hs, safe.HTMLEscaped(s[:m0]))
+ }
+ if m1 > m0 {
+ word := s[m0:m1]
+ // Strip all characters except for letters, numbers, and '.' to
+ // obtain RFC fields.
+ rfcFields := strings.FieldsFunc(word, func(c rune) bool {
+ return !unicode.IsLetter(c) && !unicode.IsNumber(c) && c != '.'
+ })
+ var url string
+ if len(rfcFields) >= 4 {
+ // RFC x Section y
+ url = fmt.Sprintf("https://rfc-editor.org/rfc/rfc%s.html#section-%s",
+ rfcFields[1], rfcFields[3])
+ } else if len(rfcFields) >= 2 {
+ url = fmt.Sprintf("https://rfc-editor.org/rfc/rfc%s.html", rfcFields[1])
+ }
+ if url != "" {
+ hs = append(hs, ExecuteToHTML(linkTemplate, link{"", url, word}))
+ }
+ }
+ s = s[m1:]
+ }
+ return safe.HTMLConcat(hs...)
+}
+
+func (r *Renderer) declHTML(doc string, decl ast.Decl, extractLinks bool) (out struct{ Doc, Decl safe.HTML }) {
+ if doc != "" {
+ out.Doc = r.formatDocHTML(doc, extractLinks)
+ }
+ if decl != nil {
+ idr := &identifierResolver{r.pids, newDeclIDs(decl), r.packageURL}
+ out.Decl = r.formatDeclHTML(decl, idr)
+ }
+ return out
+}
+
func (r *Renderer) codeString(ex *doc.Example) (string, error) {
if ex == nil || ex.Code == nil {
return "", errors.New("please include an example with code")
@@ -118,7 +409,7 @@
return buf.String(), nil
}
-func (r *Renderer) codeHTML(ex *doc.Example) safehtml.HTML {
+func (r *Renderer) codeHTML(ex *doc.Example) safe.HTML {
codeStr, err := r.codeString(ex)
if err != nil {
log.Errorf(r.ctx, "Error converting *doc.Example into string: %v", err)
@@ -132,7 +423,7 @@
Comment bool
}
-func codeHTML(src string, codeTmpl *template.Template) safehtml.HTML {
+func codeHTML(src string, codeTmpl *template.Template) safe.HTML {
var els []codeElement
// If code is an *ast.BlockStmt, then trim the braces.
var indent string
@@ -191,8 +482,8 @@
// formatLineHTML formats the line as HTML-annotated text.
// URLs and Go identifiers are linked to corresponding declarations.
// If pre is true no conversion of doubled ` and ' to “ and ” is performed.
-func (r *Renderer) formatLineHTML(line string, pre bool) safehtml.HTML {
- var htmls []safehtml.HTML
+func (r *Renderer) formatLineHTML(line string, pre bool) safe.HTML {
+ var htmls []safe.HTML
addLink := func(href, text string) {
htmls = append(htmls, ExecuteToHTML(LinkTemplate, Link{Href: href, Text: text}))
}
@@ -207,7 +498,7 @@
}
if m0 > 0 {
nonWord := line[:m0]
- htmls = append(htmls, safehtml.HTMLEscaped(nonWord))
+ htmls = append(htmls, safe.HTMLEscaped(nonWord))
}
if m1 > m0 {
word := line[m0:m1]
@@ -251,25 +542,25 @@
addLink(fmt.Sprintf("https://rfc-editor.org/rfc/rfc%s.html", rfcFields[1]), word)
}
default:
- htmls = append(htmls, safehtml.HTMLEscaped(word))
+ htmls = append(htmls, safe.HTMLEscaped(word))
}
}
line = line[m1:]
}
- return safehtml.HTMLConcat(htmls...)
+ return safe.HTMLConcat(htmls...)
}
-func ExecuteToHTML(tmpl *template.Template, data interface{}) safehtml.HTML {
+func ExecuteToHTML(tmpl *template.Template, data interface{}) safe.HTML {
h, err := tmpl.ExecuteToHTML(data)
if err != nil {
- return safehtml.HTMLEscaped("[" + err.Error() + "]")
+ return safe.HTMLEscaped("[" + err.Error() + "]")
}
return h
}
// formatDeclHTML formats the decl as HTML-annotated source code for the
// provided decl. Type identifiers are linked to corresponding declarations.
-func (r *Renderer) formatDeclHTML(decl ast.Decl, idr *identifierResolver) safehtml.HTML {
+func (r *Renderer) formatDeclHTML(decl ast.Decl, idr *identifierResolver) safe.HTML {
// Generate all anchor points and links for the given decl.
anchorPointsMap := generateAnchorPoints(decl)
anchorLinksMap := generateAnchorLinks(idr, decl)
@@ -309,7 +600,7 @@
numLines := bytes.Count(src, []byte("\n")) + 1
anchorLines := make([][]idKind, numLines)
lineTypes := make([]lineType, numLines)
- htmlLines := make([][]safehtml.HTML, numLines)
+ htmlLines := make([][]safe.HTML, numLines)
// Scan through the source code, appropriately annotating it with HTML spans
// for comments, and HTML links and anchors for relevant identifiers.
@@ -331,7 +622,7 @@
if n < 0 { // possible at EOF
n = 0
}
- htmlLines[n] = append(htmlLines[n], safehtml.HTMLEscaped(ln))
+ htmlLines[n] = append(htmlLines[n], safe.HTMLEscaped(ln))
}
lastOffset = offset
@@ -373,7 +664,7 @@
}
// Emit anchor IDs and data-kind attributes for each relevant line.
- var htmls []safehtml.HTML
+ var htmls []safe.HTML
for line, iks := range anchorLines {
inAnchor := false
for _, ik := range iks {
@@ -395,7 +686,7 @@
htmls = append(htmls, template.MustParseAndExecuteToHTML("</span>"))
}
}
- return safehtml.HTMLConcat(htmls...)
+ return safe.HTMLConcat(htmls...)
}
var anchorTemplate = template.Must(template.New("anchor").Parse(`<span id="{{.ID}}" data-kind="{{.Kind}}">`))
@@ -464,13 +755,13 @@
// An idKind holds an anchor ID and the kind of the identifier being anchored.
// The valid kinds are: "constant", "variable", "type", "function", "method" and "field".
type idKind struct {
- ID safehtml.Identifier
+ ID safe.Identifier
Kind string
}
// SafeGoID constructs a safe identifier from a Go symbol or dotted concatenation of symbols
// (e.g. "Time.Equal").
-func SafeGoID(s string) safehtml.Identifier {
+func SafeGoID(s string) safe.Identifier {
ValidateGoDottedExpr(s)
return legacyconversions.RiskilyAssumeIdentifier(s)
}
diff --git a/internal/godoc/dochtml/internal/render/linkify_comment.go b/internal/godoc/dochtml/internal/render/linkify_comment.go
deleted file mode 100644
index 057ccec..0000000
--- a/internal/godoc/dochtml/internal/render/linkify_comment.go
+++ /dev/null
@@ -1,306 +0,0 @@
-// Copyright 2022 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.
-
-package render
-
-import (
- "fmt"
- "go/doc/comment"
- "regexp"
- "strings"
- "unicode"
-
- safe "github.com/google/safehtml"
- "github.com/google/safehtml/template"
-)
-
-type link struct {
- Class string
- Href string
- Text any // string or safe.HTML
-}
-
-type heading struct {
- ID safe.Identifier
- Title safe.HTML
-}
-
-var (
- // tocTemplate expects a []heading.
- tocTemplate = template.Must(template.New("toc").Parse(`<div role="navigation" aria-label="Table of Contents">
- <ul class="Documentation-toc{{if gt (len .) 5}} Documentation-toc-columns{{end}}">
- {{range . -}}
- <li class="Documentation-tocItem">
- <a href="#{{.ID}}">{{.Title}}</a>
- </li>
- {{end -}}
- </ul>
-</div>
-`))
-
- italicTemplate = template.Must(template.New("italics").Parse(`<i>{{.}}</i>`))
-
- codeTemplate = template.Must(template.New("code").Parse(`<pre>{{.}}</pre>`))
-
- paraTemplate = template.Must(template.New("para").Parse("<p>{{.}}\n</p>"))
-
- headingTemplate = template.Must(template.New("heading").Parse(
- `<h4 id="{{.ID}}">{{.Title}} <a class="Documentation-idLink" href="#{{.ID}}">¶</a></h4>`))
-
- linkTemplate = template.Must(template.New("link").Parse(
- `<a{{with .Class}}class="{{.}}" {{end}} href="{{.Href}}">{{.Text}}</a>`))
-
- uListTemplate = template.Must(template.New("ulist").Parse(
- `<ul>
-{{- range .}}
- {{.}}
-{{- end}}
-</ul>`))
-
- oListTemplate = template.Must(template.New("olist").Parse(
- `<ol>
- {{range .}}
- {{.}}
- {{end}}
- </ol>`))
-
- listItemTemplate = template.Must(template.New("li").Parse(
- `<li{{with .Number}}value="{{.}}" {{end}}>{{.Content}}</li>`))
-)
-
-func (r *Renderer) formatDocHTML(text string, extractLinks bool) safe.HTML {
- p := comment.Parser{}
- doc := p.Parse(text)
- if extractLinks {
- r.removeLinks(doc)
- }
- var headings []heading
- for _, b := range doc.Content {
- if h, ok := b.(*comment.Heading); ok {
- headings = append(headings, r.newHeading(h))
- }
- }
- h := r.blocksToHTML(doc.Content, true, extractLinks)
- if r.enableCommandTOC && len(headings) > 0 {
- h = safe.HTMLConcat(ExecuteToHTML(tocTemplate, headings), h)
- }
- return h
-}
-
-func (r *Renderer) removeLinks(doc *comment.Doc) {
- var bs []comment.Block
- inLinks := false
- for _, b := range doc.Content {
- switch b := b.(type) {
- case *comment.Heading:
- if textsToString(b.Text) == "Links" {
- inLinks = true
- } else {
- inLinks = false
- bs = append(bs, b)
- }
- case *comment.List:
- if inLinks {
- for _, item := range b.Items {
- fmt.Println(" ", item)
- if link, ok := itemLink(item); ok {
- r.links = append(r.links, link)
- }
- }
- } else {
- bs = append(bs, b)
- }
- case *comment.Paragraph:
- if inLinks {
- // Links section doesn't require leading whitespace, so
- // the link may be in a paragraph.
- s := textsToString(b.Text)
- r.links = append(r.links, parseLinks(strings.Split(s, "\n"))...)
- } else {
- bs = append(bs, b)
- }
-
- default:
- if !inLinks {
- bs = append(bs, b)
- }
- }
- }
- doc.Content = bs
-}
-
-func itemLink(item *comment.ListItem) (l Link, ok bool) {
- // Should be a single Paragraph.
- if len(item.Content) != 1 {
- return l, false
- }
- p, ok := item.Content[0].(*comment.Paragraph)
- if !ok {
- return l, false
- }
- // TODO: clean up.
- if lp := parseLink("- " + textsToString(p.Text)); lp != nil {
- return *lp, true
- }
- return l, false
-}
-
-func (r *Renderer) blocksToHTML(bs []comment.Block, useParagraph, extractLinks bool) safe.HTML {
- return concatHTML(bs, func(b comment.Block) safe.HTML {
- return r.blockToHTML(b, useParagraph, extractLinks)
- })
-}
-
-func (r *Renderer) blockToHTML(b comment.Block, useParagraph, extractLinks bool) safe.HTML {
- switch b := b.(type) {
- case *comment.Paragraph:
- th := r.textsToHTML(b.Text)
- if useParagraph {
- return ExecuteToHTML(paraTemplate, th)
- }
- return th
-
- case *comment.Code:
- return ExecuteToHTML(codeTemplate, b.Text)
-
- case *comment.Heading:
- return ExecuteToHTML(headingTemplate, r.newHeading(b))
-
- case *comment.List:
- var items []safe.HTML
- useParagraph = b.BlankBetween()
- for _, item := range b.Items {
- items = append(items, ExecuteToHTML(listItemTemplate, struct {
- Number string
- Content safe.HTML
- }{item.Number, r.blocksToHTML(item.Content, useParagraph, false)}))
- }
- t := oListTemplate
- if b.Items[0].Number == "" {
- t = uListTemplate
- }
- return ExecuteToHTML(t, items)
- default:
- return badType(b)
- }
-}
-
-func (r *Renderer) newHeading(h *comment.Heading) heading {
- return heading{headingID(h), r.textsToHTML(h.Text)}
-}
-
-func (r *Renderer) textsToHTML(ts []comment.Text) safe.HTML {
- return concatHTML(ts, r.textToHTML)
-}
-
-func (r *Renderer) textToHTML(t comment.Text) safe.HTML {
- switch t := t.(type) {
- case comment.Plain:
- // Don't auto-link URLs. The doc/comment package already does that.
- return linkRFCs(string(t))
- case comment.Italic:
- return ExecuteToHTML(italicTemplate, t)
- case *comment.Link:
- return ExecuteToHTML(linkTemplate, link{"", t.URL, r.textsToHTML(t.Text)})
- case *comment.DocLink:
- url := r.docLinkURL(t)
- return ExecuteToHTML(linkTemplate, link{"", url, r.textsToHTML(t.Text)})
- default:
- return badType(t)
- }
-}
-
-func (r *Renderer) docLinkURL(dl *comment.DocLink) string {
- var url string
- if dl.ImportPath != "" {
- url = "/" + dl.ImportPath
- if r.packageURL != nil {
- url = r.packageURL(dl.ImportPath)
- }
- }
- id := dl.Name
- if dl.Recv != "" {
- id = dl.Recv + "." + id
- }
- if id != "" {
- url += "#" + id
- }
- return url
-}
-
-// TODO: any -> *comment.Text | *comment.Block
-func concatHTML[T any](xs []T, toHTML func(T) safe.HTML) safe.HTML {
- var hs []safe.HTML
- for _, x := range xs {
- hs = append(hs, toHTML(x))
- }
- return safe.HTMLConcat(hs...)
-}
-
-func badType(x interface{}) safe.HTML {
- return safe.HTMLEscaped(fmt.Sprintf("bad type %T", x))
-}
-
-func headingID(h *comment.Heading) safe.Identifier {
- s := textsToString(h.Text)
- id := badAnchorRx.ReplaceAllString(s, "_")
- return safe.IdentifierFromConstantPrefix("hdr", id)
-}
-
-func textsToString(ts []comment.Text) string {
- var b strings.Builder
- for _, t := range ts {
- switch t := t.(type) {
- case comment.Plain:
- b.WriteString(string(t))
- case comment.Italic:
- b.WriteString(string(t))
- case *comment.Link:
- b.WriteString(textsToString(t.Text))
- case *comment.DocLink:
- b.WriteString(textsToString(t.Text))
- default:
- fmt.Fprintf(&b, "bad text type %T", t)
- }
- }
- return b.String()
-}
-
-var rfcRegexp = regexp.MustCompile(rfcRx)
-
-// TODO: merge/replace Renderer.formatLineHTML.
-// TODO: make more efficient.
-func linkRFCs(s string) safe.HTML {
- var hs []safe.HTML
- for len(s) > 0 {
- m0, m1 := len(s), len(s)
- if m := rfcRegexp.FindStringIndex(s); m != nil {
- m0, m1 = m[0], m[1]
- }
- if m0 > 0 {
- hs = append(hs, safe.HTMLEscaped(s[:m0]))
- }
- if m1 > m0 {
- word := s[m0:m1]
- // Strip all characters except for letters, numbers, and '.' to
- // obtain RFC fields.
- rfcFields := strings.FieldsFunc(word, func(c rune) bool {
- return !unicode.IsLetter(c) && !unicode.IsNumber(c) && c != '.'
- })
- var url string
- if len(rfcFields) >= 4 {
- // RFC x Section y
- url = fmt.Sprintf("https://rfc-editor.org/rfc/rfc%s.html#section-%s",
- rfcFields[1], rfcFields[3])
- } else if len(rfcFields) >= 2 {
- url = fmt.Sprintf("https://rfc-editor.org/rfc/rfc%s.html", rfcFields[1])
- }
- if url != "" {
- hs = append(hs, ExecuteToHTML(linkTemplate, link{"", url, word}))
- }
- }
- s = s[m1:]
- }
- return safe.HTMLConcat(hs...)
-}