diff options
| author | Mitja Felicijan <mitja.felicijan@gmail.com> | 2024-10-25 00:47:47 +0200 |
|---|---|---|
| committer | Mitja Felicijan <mitja.felicijan@gmail.com> | 2024-10-25 00:47:47 +0200 |
| commit | c6cc0108ca7738023b45e0eeac0fa2390532dd93 (patch) | |
| tree | 36890e6cd3091bbab8efbe686cc56f467f645bfd /vendor/github.com/yuin/goldmark/parser/atx_heading.go | |
| parent | 0130404a1dc663d4aa68d780c9bcb23a4243e68d (diff) | |
| download | jbmafp-master.tar.gz | |
Diffstat (limited to 'vendor/github.com/yuin/goldmark/parser/atx_heading.go')
| -rw-r--r-- | vendor/github.com/yuin/goldmark/parser/atx_heading.go | 246 |
1 files changed, 246 insertions, 0 deletions
diff --git a/vendor/github.com/yuin/goldmark/parser/atx_heading.go b/vendor/github.com/yuin/goldmark/parser/atx_heading.go new file mode 100644 index 0000000..13a198b --- /dev/null +++ b/vendor/github.com/yuin/goldmark/parser/atx_heading.go | |||
| @@ -0,0 +1,246 @@ | |||
| 1 | package parser | ||
| 2 | |||
| 3 | import ( | ||
| 4 | "github.com/yuin/goldmark/ast" | ||
| 5 | "github.com/yuin/goldmark/text" | ||
| 6 | "github.com/yuin/goldmark/util" | ||
| 7 | ) | ||
| 8 | |||
| 9 | // A HeadingConfig struct is a data structure that holds configuration of the renderers related to headings. | ||
| 10 | type HeadingConfig struct { | ||
| 11 | AutoHeadingID bool | ||
| 12 | Attribute bool | ||
| 13 | } | ||
| 14 | |||
| 15 | // SetOption implements SetOptioner. | ||
| 16 | func (b *HeadingConfig) SetOption(name OptionName, value interface{}) { | ||
| 17 | switch name { | ||
| 18 | case optAutoHeadingID: | ||
| 19 | b.AutoHeadingID = true | ||
| 20 | case optAttribute: | ||
| 21 | b.Attribute = true | ||
| 22 | } | ||
| 23 | } | ||
| 24 | |||
| 25 | // A HeadingOption interface sets options for heading parsers. | ||
| 26 | type HeadingOption interface { | ||
| 27 | Option | ||
| 28 | SetHeadingOption(*HeadingConfig) | ||
| 29 | } | ||
| 30 | |||
| 31 | // AutoHeadingID is an option name that enables auto IDs for headings. | ||
| 32 | const optAutoHeadingID OptionName = "AutoHeadingID" | ||
| 33 | |||
| 34 | type withAutoHeadingID struct { | ||
| 35 | } | ||
| 36 | |||
| 37 | func (o *withAutoHeadingID) SetParserOption(c *Config) { | ||
| 38 | c.Options[optAutoHeadingID] = true | ||
| 39 | } | ||
| 40 | |||
| 41 | func (o *withAutoHeadingID) SetHeadingOption(p *HeadingConfig) { | ||
| 42 | p.AutoHeadingID = true | ||
| 43 | } | ||
| 44 | |||
| 45 | // WithAutoHeadingID is a functional option that enables custom heading ids and | ||
| 46 | // auto generated heading ids. | ||
| 47 | func WithAutoHeadingID() HeadingOption { | ||
| 48 | return &withAutoHeadingID{} | ||
| 49 | } | ||
| 50 | |||
| 51 | type withHeadingAttribute struct { | ||
| 52 | Option | ||
| 53 | } | ||
| 54 | |||
| 55 | func (o *withHeadingAttribute) SetHeadingOption(p *HeadingConfig) { | ||
| 56 | p.Attribute = true | ||
| 57 | } | ||
| 58 | |||
| 59 | // WithHeadingAttribute is a functional option that enables custom heading attributes. | ||
| 60 | func WithHeadingAttribute() HeadingOption { | ||
| 61 | return &withHeadingAttribute{WithAttribute()} | ||
| 62 | } | ||
| 63 | |||
| 64 | type atxHeadingParser struct { | ||
| 65 | HeadingConfig | ||
| 66 | } | ||
| 67 | |||
| 68 | // NewATXHeadingParser return a new BlockParser that can parse ATX headings. | ||
| 69 | func NewATXHeadingParser(opts ...HeadingOption) BlockParser { | ||
| 70 | p := &atxHeadingParser{} | ||
| 71 | for _, o := range opts { | ||
| 72 | o.SetHeadingOption(&p.HeadingConfig) | ||
| 73 | } | ||
| 74 | return p | ||
| 75 | } | ||
| 76 | |||
| 77 | func (b *atxHeadingParser) Trigger() []byte { | ||
| 78 | return []byte{'#'} | ||
| 79 | } | ||
| 80 | |||
| 81 | func (b *atxHeadingParser) Open(parent ast.Node, reader text.Reader, pc Context) (ast.Node, State) { | ||
| 82 | line, segment := reader.PeekLine() | ||
| 83 | pos := pc.BlockOffset() | ||
| 84 | if pos < 0 { | ||
| 85 | return nil, NoChildren | ||
| 86 | } | ||
| 87 | i := pos | ||
| 88 | for ; i < len(line) && line[i] == '#'; i++ { | ||
| 89 | } | ||
| 90 | level := i - pos | ||
| 91 | if i == pos || level > 6 { | ||
| 92 | return nil, NoChildren | ||
| 93 | } | ||
| 94 | if i == len(line) { // alone '#' (without a new line character) | ||
| 95 | return ast.NewHeading(level), NoChildren | ||
| 96 | } | ||
| 97 | l := util.TrimLeftSpaceLength(line[i:]) | ||
| 98 | if l == 0 { | ||
| 99 | return nil, NoChildren | ||
| 100 | } | ||
| 101 | start := i + l | ||
| 102 | if start >= len(line) { | ||
| 103 | start = len(line) - 1 | ||
| 104 | } | ||
| 105 | origstart := start | ||
| 106 | stop := len(line) - util.TrimRightSpaceLength(line) | ||
| 107 | |||
| 108 | node := ast.NewHeading(level) | ||
| 109 | parsed := false | ||
| 110 | if b.Attribute { // handles special case like ### heading ### {#id} | ||
| 111 | start-- | ||
| 112 | closureClose := -1 | ||
| 113 | closureOpen := -1 | ||
| 114 | for j := start; j < stop; { | ||
| 115 | c := line[j] | ||
| 116 | if util.IsEscapedPunctuation(line, j) { | ||
| 117 | j += 2 | ||
| 118 | } else if util.IsSpace(c) && j < stop-1 && line[j+1] == '#' { | ||
| 119 | closureOpen = j + 1 | ||
| 120 | k := j + 1 | ||
| 121 | for ; k < stop && line[k] == '#'; k++ { | ||
| 122 | } | ||
| 123 | closureClose = k | ||
| 124 | break | ||
| 125 | } else { | ||
| 126 | j++ | ||
| 127 | } | ||
| 128 | } | ||
| 129 | if closureClose > 0 { | ||
| 130 | reader.Advance(closureClose) | ||
| 131 | attrs, ok := ParseAttributes(reader) | ||
| 132 | rest, _ := reader.PeekLine() | ||
| 133 | parsed = ok && util.IsBlank(rest) | ||
| 134 | if parsed { | ||
| 135 | for _, attr := range attrs { | ||
| 136 | node.SetAttribute(attr.Name, attr.Value) | ||
| 137 | } | ||
| 138 | node.Lines().Append(text.NewSegment(segment.Start+start+1-segment.Padding, segment.Start+closureOpen-segment.Padding)) | ||
| 139 | } | ||
| 140 | } | ||
| 141 | } | ||
| 142 | if !parsed { | ||
| 143 | start = origstart | ||
| 144 | stop := len(line) - util.TrimRightSpaceLength(line) | ||
| 145 | if stop <= start { // empty headings like '##[space]' | ||
| 146 | stop = start | ||
| 147 | } else { | ||
| 148 | i = stop - 1 | ||
| 149 | for ; line[i] == '#' && i >= start; i-- { | ||
| 150 | } | ||
| 151 | if i != stop-1 && !util.IsSpace(line[i]) { | ||
| 152 | i = stop - 1 | ||
| 153 | } | ||
| 154 | i++ | ||
| 155 | stop = i | ||
| 156 | } | ||
| 157 | |||
| 158 | if len(util.TrimRight(line[start:stop], []byte{'#'})) != 0 { // empty heading like '### ###' | ||
| 159 | node.Lines().Append(text.NewSegment(segment.Start+start-segment.Padding, segment.Start+stop-segment.Padding)) | ||
| 160 | } | ||
| 161 | } | ||
| 162 | return node, NoChildren | ||
| 163 | } | ||
| 164 | |||
| 165 | func (b *atxHeadingParser) Continue(node ast.Node, reader text.Reader, pc Context) State { | ||
| 166 | return Close | ||
| 167 | } | ||
| 168 | |||
| 169 | func (b *atxHeadingParser) Close(node ast.Node, reader text.Reader, pc Context) { | ||
| 170 | if b.Attribute { | ||
| 171 | _, ok := node.AttributeString("id") | ||
| 172 | if !ok { | ||
| 173 | parseLastLineAttributes(node, reader, pc) | ||
| 174 | } | ||
| 175 | } | ||
| 176 | |||
| 177 | if b.AutoHeadingID { | ||
| 178 | id, ok := node.AttributeString("id") | ||
| 179 | if !ok { | ||
| 180 | generateAutoHeadingID(node.(*ast.Heading), reader, pc) | ||
| 181 | } else { | ||
| 182 | pc.IDs().Put(id.([]byte)) | ||
| 183 | } | ||
| 184 | } | ||
| 185 | } | ||
| 186 | |||
| 187 | func (b *atxHeadingParser) CanInterruptParagraph() bool { | ||
| 188 | return true | ||
| 189 | } | ||
| 190 | |||
| 191 | func (b *atxHeadingParser) CanAcceptIndentedLine() bool { | ||
| 192 | return false | ||
| 193 | } | ||
| 194 | |||
| 195 | func generateAutoHeadingID(node *ast.Heading, reader text.Reader, pc Context) { | ||
| 196 | var line []byte | ||
| 197 | lastIndex := node.Lines().Len() - 1 | ||
| 198 | if lastIndex > -1 { | ||
| 199 | lastLine := node.Lines().At(lastIndex) | ||
| 200 | line = lastLine.Value(reader.Source()) | ||
| 201 | } | ||
| 202 | headingID := pc.IDs().Generate(line, ast.KindHeading) | ||
| 203 | node.SetAttribute(attrNameID, headingID) | ||
| 204 | } | ||
| 205 | |||
| 206 | func parseLastLineAttributes(node ast.Node, reader text.Reader, pc Context) { | ||
| 207 | lastIndex := node.Lines().Len() - 1 | ||
| 208 | if lastIndex < 0 { // empty headings | ||
| 209 | return | ||
| 210 | } | ||
| 211 | lastLine := node.Lines().At(lastIndex) | ||
| 212 | line := lastLine.Value(reader.Source()) | ||
| 213 | lr := text.NewReader(line) | ||
| 214 | var attrs Attributes | ||
| 215 | var ok bool | ||
| 216 | var start text.Segment | ||
| 217 | var sl int | ||
| 218 | var end text.Segment | ||
| 219 | for { | ||
| 220 | c := lr.Peek() | ||
| 221 | if c == text.EOF { | ||
| 222 | break | ||
| 223 | } | ||
| 224 | if c == '\\' { | ||
| 225 | lr.Advance(1) | ||
| 226 | if lr.Peek() == '{' { | ||
| 227 | lr.Advance(1) | ||
| 228 | } | ||
| 229 | continue | ||
| 230 | } | ||
| 231 | if c == '{' { | ||
| 232 | sl, start = lr.Position() | ||
| 233 | attrs, ok = ParseAttributes(lr) | ||
| 234 | _, end = lr.Position() | ||
| 235 | lr.SetPosition(sl, start) | ||
| 236 | } | ||
| 237 | lr.Advance(1) | ||
| 238 | } | ||
| 239 | if ok && util.IsBlank(line[end.Start:]) { | ||
| 240 | for _, attr := range attrs { | ||
| 241 | node.SetAttribute(attr.Name, attr.Value) | ||
| 242 | } | ||
| 243 | lastLine.Stop = lastLine.Start + start.Start | ||
| 244 | node.Lines().Set(lastIndex, lastLine) | ||
| 245 | } | ||
| 246 | } | ||
