github.com/anthonyme00/gomarkdoc@v1.0.0/lang/span.go (about) 1 package lang 2 3 import ( 4 "fmt" 5 "go/doc/comment" 6 "regexp" 7 "strings" 8 ) 9 10 type ( 11 // Span defines a single text span in a block for documentation of a symbol 12 // or package. 13 Span struct { 14 cfg *Config 15 kind SpanKind 16 text string 17 url string 18 } 19 20 // SpanKind identifies the type of span element represented by the 21 // corresponding Span. 22 SpanKind string 23 ) 24 25 const ( 26 // TextSpan defines a span that represents plain text. 27 TextSpan SpanKind = "text" 28 29 // RawTextSpan defines a span that represents plain text that should be 30 // displayed as-is. 31 RawTextSpan SpanKind = "rawText" 32 33 // LinkSpan defines a span that represents a link. 34 LinkSpan SpanKind = "link" 35 36 // AutolinkSpan defines a span that represents text which is itself a link. 37 AutolinkSpan SpanKind = "autolink" 38 ) 39 40 // NewSpan creates a new span. 41 func NewSpan(cfg *Config, kind SpanKind, text string, url string) *Span { 42 return &Span{cfg, kind, text, url} 43 } 44 45 // Kind provides the kind of data that this span represents. 46 func (s *Span) Kind() SpanKind { 47 return s.kind 48 } 49 50 // Text provides the raw text for the span. 51 func (s *Span) Text() string { 52 return s.text 53 } 54 55 // URL provides the url associated with the span, if any. 56 func (s *Span) URL() string { 57 return s.url 58 } 59 60 // ParseSpans turns a set of *comment.Text entries into a slice of spans. 61 func ParseSpans(cfg *Config, texts []comment.Text) []*Span { 62 var s []*Span 63 for _, t := range texts { 64 switch v := t.(type) { 65 case comment.Plain: 66 s = append(s, NewSpan(cfg.Inc(0), TextSpan, collapseWhitespace(string(v)), "")) 67 case comment.Italic: 68 s = append(s, NewSpan(cfg.Inc(0), TextSpan, collapseWhitespace(string(v)), "")) 69 case *comment.DocLink: 70 var b strings.Builder 71 printText(&b, v.Text...) 72 str := collapseWhitespace(b.String()) 73 74 // Replace local links as needed 75 if v.ImportPath == "" { 76 name := symbolName(v.Recv, v.Name) 77 if sym, ok := cfg.Symbols[name]; ok { 78 s = append(s, NewSpan(cfg.Inc(0), LinkSpan, str, fmt.Sprintf("#%s", sym.Anchor()))) 79 } else { 80 cfg.Log.Warnf("Unable to find symbol %s", name) 81 s = append(s, NewSpan(cfg.Inc(0), TextSpan, collapseWhitespace(str), "")) 82 } 83 break 84 } 85 86 s = append(s, NewSpan(cfg.Inc(0), LinkSpan, str, v.DefaultURL("https://pkg.go.dev/"))) 87 case *comment.Link: 88 var b strings.Builder 89 printText(&b, v.Text...) 90 str := collapseWhitespace(b.String()) 91 92 if v.Auto { 93 s = append(s, NewSpan(cfg.Inc(0), AutolinkSpan, str, str)) 94 break 95 } 96 97 s = append(s, NewSpan(cfg.Inc(0), LinkSpan, str, v.URL)) 98 } 99 } 100 101 return s 102 } 103 104 func printText(b *strings.Builder, text ...comment.Text) { 105 for i, t := range text { 106 if i > 0 { 107 b.WriteRune(' ') 108 } 109 110 switch v := t.(type) { 111 case comment.Plain: 112 b.WriteString(string(v)) 113 case comment.Italic: 114 b.WriteString(string(v)) 115 case *comment.DocLink: 116 printText(b, v.Text...) 117 case *comment.Link: 118 // No need to linkify implicit links 119 if v.Auto { 120 printText(b, v.Text...) 121 continue 122 } 123 124 b.WriteRune('[') 125 printText(b, v.Text...) 126 b.WriteRune(']') 127 b.WriteRune('(') 128 b.WriteString(v.URL) 129 b.WriteRune(')') 130 } 131 } 132 } 133 134 var whitespaceRegex = regexp.MustCompile(`\s+`) 135 136 func collapseWhitespace(s string) string { 137 return string(whitespaceRegex.ReplaceAll([]byte(s), []byte(" "))) 138 }