github.com/bir3/gocompiler@v0.9.2202/src/go/doc/comment/markdown.go (about) 1 // Copyright 2022 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 5 package comment 6 7 import ( 8 "bytes" 9 "fmt" 10 "strings" 11 ) 12 13 // An mdPrinter holds the state needed for printing a Doc as Markdown. 14 type mdPrinter struct { 15 *Printer 16 headingPrefix string 17 raw bytes.Buffer 18 } 19 20 // Markdown returns a Markdown formatting of the Doc. 21 // See the [Printer] documentation for ways to customize the Markdown output. 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 // block prints the block x to out. 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(" - ") // SP SP - SP 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 // text prints the text sequence x to out. 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 // Escape what would be the start of an unordered list or heading. 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 // Escape what would be the start of an ordered list. 114 out.Write(line[:i]) 115 out.WriteByte('\\') 116 line = line[i:] 117 } 118 } 119 out.Write(line) 120 } 121 122 // rawText prints the text sequence x to out, 123 // without worrying about escaping characters 124 // that have special meaning at the start of a Markdown line. 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 // escape prints s to out as plain text, 158 // escaping special characters to avoid being misinterpreted 159 // as Markdown markup sequences. 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 // Turn all \n into spaces, for a few reasons: 166 // - Avoid introducing paragraph breaks accidentally. 167 // - Avoid the need to reindent after the newline. 168 // - Avoid problems with Markdown renderers treating 169 // every mid-paragraph newline as a <br>. 170 out.WriteString(s[start:i]) 171 out.WriteByte(' ') 172 start = i + 1 173 continue 174 case '`', '_', '*', '[', '<', '\\': 175 // Not all of these need to be escaped all the time, 176 // but is valid and easy to do so. 177 // We assume the Markdown is being passed to a 178 // Markdown renderer, not edited by a person, 179 // so it's fine to have escapes that are not strictly 180 // necessary in some cases. 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 }