1
2
3
4
5 package comment
6
7 import (
8 "bytes"
9 "fmt"
10 "strings"
11 )
12
13
14 type mdPrinter struct {
15 *Printer
16 headingPrefix string
17 raw bytes.Buffer
18 }
19
20
21
22 func (p *Printer) Markdown(d *Doc) []byte {
23 mp := &mdPrinter{
24 Printer: p,
25 headingPrefix: strings.Repeat("#", p.headingLevel()) + " ",
26 }
27
28 var out bytes.Buffer
29 for i, x := range d.Content {
30 if i > 0 {
31 out.WriteByte('\n')
32 }
33 mp.block(&out, x)
34 }
35 return out.Bytes()
36 }
37
38
39 func (p *mdPrinter) block(out *bytes.Buffer, x Block) {
40 switch x := x.(type) {
41 default:
42 fmt.Fprintf(out, "?%T", x)
43
44 case *Paragraph:
45 p.text(out, x.Text)
46 out.WriteString("\n")
47
48 case *Heading:
49 out.WriteString(p.headingPrefix)
50 p.text(out, x.Text)
51 if id := p.headingID(x); id != "" {
52 out.WriteString(" {#")
53 out.WriteString(id)
54 out.WriteString("}")
55 }
56 out.WriteString("\n")
57
58 case *Code:
59 md := x.Text
60 for md != "" {
61 var line string
62 line, md, _ = strings.Cut(md, "\n")
63 if line != "" {
64 out.WriteString("\t")
65 out.WriteString(line)
66 }
67 out.WriteString("\n")
68 }
69
70 case *List:
71 loose := x.BlankBetween()
72 for i, item := range x.Items {
73 if i > 0 && loose {
74 out.WriteString("\n")
75 }
76 if n := item.Number; n != "" {
77 out.WriteString(" ")
78 out.WriteString(n)
79 out.WriteString(". ")
80 } else {
81 out.WriteString(" - ")
82 }
83 for i, blk := range item.Content {
84 const fourSpace = " "
85 if i > 0 {
86 out.WriteString("\n" + fourSpace)
87 }
88 p.text(out, blk.(*Paragraph).Text)
89 out.WriteString("\n")
90 }
91 }
92 }
93 }
94
95
96 func (p *mdPrinter) text(out *bytes.Buffer, x []Text) {
97 p.raw.Reset()
98 p.rawText(&p.raw, x)
99 line := bytes.TrimSpace(p.raw.Bytes())
100 if len(line) == 0 {
101 return
102 }
103 switch line[0] {
104 case '+', '-', '*', '#':
105
106 out.WriteByte('\\')
107 case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
108 i := 1
109 for i < len(line) && '0' <= line[i] && line[i] <= '9' {
110 i++
111 }
112 if i < len(line) && (line[i] == '.' || line[i] == ')') {
113
114 out.Write(line[:i])
115 out.WriteByte('\\')
116 line = line[i:]
117 }
118 }
119 out.Write(line)
120 }
121
122
123
124
125 func (p *mdPrinter) rawText(out *bytes.Buffer, x []Text) {
126 for _, t := range x {
127 switch t := t.(type) {
128 case Plain:
129 p.escape(out, string(t))
130 case Italic:
131 out.WriteString("*")
132 p.escape(out, string(t))
133 out.WriteString("*")
134 case *Link:
135 out.WriteString("[")
136 p.rawText(out, t.Text)
137 out.WriteString("](")
138 out.WriteString(t.URL)
139 out.WriteString(")")
140 case *DocLink:
141 url := p.docLinkURL(t)
142 if url != "" {
143 out.WriteString("[")
144 }
145 p.rawText(out, t.Text)
146 if url != "" {
147 out.WriteString("](")
148 url = strings.ReplaceAll(url, "(", "%28")
149 url = strings.ReplaceAll(url, ")", "%29")
150 out.WriteString(url)
151 out.WriteString(")")
152 }
153 }
154 }
155 }
156
157
158
159
160 func (p *mdPrinter) escape(out *bytes.Buffer, s string) {
161 start := 0
162 for i := 0; i < len(s); i++ {
163 switch s[i] {
164 case '\n':
165
166
167
168
169
170 out.WriteString(s[start:i])
171 out.WriteByte(' ')
172 start = i + 1
173 continue
174 case '`', '_', '*', '[', '<', '\\':
175
176
177
178
179
180
181 out.WriteString(s[start:i])
182 out.WriteByte('\\')
183 out.WriteByte(s[i])
184 start = i + 1
185 }
186 }
187 out.WriteString(s[start:])
188 }
189
View as plain text