diff options
Diffstat (limited to 'src/cmd/vendor/rsc.io/markdown/heading.go')
| -rw-r--r-- | src/cmd/vendor/rsc.io/markdown/heading.go | 161 |
1 files changed, 161 insertions, 0 deletions
diff --git a/src/cmd/vendor/rsc.io/markdown/heading.go b/src/cmd/vendor/rsc.io/markdown/heading.go new file mode 100644 index 0000000000..7be6a473aa --- /dev/null +++ b/src/cmd/vendor/rsc.io/markdown/heading.go @@ -0,0 +1,161 @@ +// Copyright 2021 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 markdown + +import ( + "bytes" + "fmt" + "strings" +) + +type Heading struct { + Position + Level int + Text *Text + // The HTML id attribute. The parser populates this field if + // [Parser.HeadingIDs] is true and the heading ends with text like "{#id}". + ID string +} + +func (b *Heading) PrintHTML(buf *bytes.Buffer) { + fmt.Fprintf(buf, "<h%d", b.Level) + if b.ID != "" { + fmt.Fprintf(buf, ` id="%s"`, htmlQuoteEscaper.Replace(b.ID)) + } + buf.WriteByte('>') + b.Text.PrintHTML(buf) + fmt.Fprintf(buf, "</h%d>\n", b.Level) +} + +func (b *Heading) printMarkdown(buf *bytes.Buffer, s mdState) { + // TODO: handle setext headings properly. + buf.WriteString(s.prefix) + for i := 0; i < b.Level; i++ { + buf.WriteByte('#') + } + buf.WriteByte(' ') + // The prefix has already been printed for this line of text. + s.prefix = "" + b.Text.printMarkdown(buf, s) + if b.ID != "" { + // A heading text is a block, so it ends in a newline. Move the newline + // after the ID. + buf.Truncate(buf.Len() - 1) + fmt.Fprintf(buf, " {#%s}\n", b.ID) + } +} + +func newATXHeading(p *parseState, s line) (line, bool) { + peek := s + var n int + if peek.trimHeading(&n) { + s := peek.string() + s = trimRightSpaceTab(s) + // Remove trailing '#'s. + if t := strings.TrimRight(s, "#"); t != trimRightSpaceTab(t) || t == "" { + s = t + } + var id string + if p.HeadingIDs { + // Parse and remove ID attribute. + // It must come before trailing '#'s to more closely follow the spec: + // The optional closing sequence of #s must be preceded by spaces or tabs + // and may be followed by spaces or tabs only. + // But Goldmark allows it to come after. + id, s = extractID(p, s) + + // Goldmark is strict about the id syntax. + for _, c := range id { + if c >= 0x80 || !isLetterDigit(byte(c)) { + p.corner = true + } + } + } + pos := Position{p.lineno, p.lineno} + p.doneBlock(&Heading{pos, n, p.newText(pos, s), id}) + return line{}, true + } + return s, false +} + +// extractID removes an ID attribute from s if one is present. +// It returns the attribute value and the resulting string. +// The attribute has the form "{#...}", where the "..." can contain +// any character other than '}'. +// The attribute must be followed only by whitespace. +func extractID(p *parseState, s string) (id, s2 string) { + i := strings.LastIndexByte(s, '{') + if i < 0 { + return "", s + } + if i+1 >= len(s) || s[i+1] != '#' { + p.corner = true // goldmark accepts {} + return "", s + } + j := i + strings.IndexByte(s[i:], '}') + if j < 0 || trimRightSpaceTab(s[j+1:]) != "" { + return "", s + } + id = strings.TrimSpace(s[i+2 : j]) + if id == "" { + p.corner = true // goldmark accepts {#} + return "", s + } + return s[i+2 : j], s[:i] +} + +func newSetextHeading(p *parseState, s line) (line, bool) { + var n int + peek := s + if p.nextB() == p.para() && peek.trimSetext(&n) { + p.closeBlock() + para, ok := p.last().(*Paragraph) + if !ok { + return s, false + } + p.deleteLast() + p.doneBlock(&Heading{Position{para.StartLine, p.lineno}, n, para.Text, ""}) + return line{}, true + } + return s, false +} + +func (s *line) trimHeading(width *int) bool { + t := *s + t.trimSpace(0, 3, false) + if !t.trim('#') { + return false + } + n := 1 + for n < 6 && t.trim('#') { + n++ + } + if !t.trimSpace(1, 1, true) { + return false + } + *width = n + *s = t + return true +} + +func (s *line) trimSetext(n *int) bool { + t := *s + t.trimSpace(0, 3, false) + c := t.peek() + if c == '-' || c == '=' { + for t.trim(c) { + } + t.skipSpace() + if t.eof() { + if c == '=' { + *n = 1 + } else { + *n = 2 + } + return true + } + } + return false +} |
