github.com/linchen2chris/hugo@v0.0.0-20230307053224-cec209389705/markup/goldmark/convert.go (about) 1 // Copyright 2019 The Hugo Authors. All rights reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // http://www.apache.org/licenses/LICENSE-2.0 7 // 8 // Unless required by applicable law or agreed to in writing, software 9 // distributed under the License is distributed on an "AS IS" BASIS, 10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 // Package goldmark converts Markdown to HTML using Goldmark. 15 package goldmark 16 17 import ( 18 "bytes" 19 20 "github.com/gohugoio/hugo/identity" 21 22 "github.com/gohugoio/hugo/markup/goldmark/codeblocks" 23 "github.com/gohugoio/hugo/markup/goldmark/images" 24 "github.com/gohugoio/hugo/markup/goldmark/internal/extensions/attributes" 25 "github.com/gohugoio/hugo/markup/goldmark/internal/render" 26 27 "github.com/gohugoio/hugo/markup/converter" 28 "github.com/gohugoio/hugo/markup/tableofcontents" 29 "github.com/yuin/goldmark" 30 "github.com/yuin/goldmark/ast" 31 "github.com/yuin/goldmark/extension" 32 "github.com/yuin/goldmark/parser" 33 "github.com/yuin/goldmark/renderer" 34 "github.com/yuin/goldmark/renderer/html" 35 "github.com/yuin/goldmark/text" 36 ) 37 38 const ( 39 internalAttrPrefix = "_h__" 40 ) 41 42 // Provider is the package entry point. 43 var Provider converter.ProviderProvider = provide{} 44 45 type provide struct{} 46 47 func (p provide) New(cfg converter.ProviderConfig) (converter.Provider, error) { 48 md := newMarkdown(cfg) 49 50 return converter.NewProvider("goldmark", func(ctx converter.DocumentContext) (converter.Converter, error) { 51 return &goldmarkConverter{ 52 ctx: ctx, 53 cfg: cfg, 54 md: md, 55 sanitizeAnchorName: func(s string) string { 56 return sanitizeAnchorNameString(s, cfg.MarkupConfig.Goldmark.Parser.AutoHeadingIDType) 57 }, 58 }, nil 59 }), nil 60 } 61 62 var _ converter.AnchorNameSanitizer = (*goldmarkConverter)(nil) 63 64 type goldmarkConverter struct { 65 md goldmark.Markdown 66 ctx converter.DocumentContext 67 cfg converter.ProviderConfig 68 69 sanitizeAnchorName func(s string) string 70 } 71 72 func (c *goldmarkConverter) SanitizeAnchorName(s string) string { 73 return c.sanitizeAnchorName(s) 74 } 75 76 func newMarkdown(pcfg converter.ProviderConfig) goldmark.Markdown { 77 mcfg := pcfg.MarkupConfig 78 cfg := pcfg.MarkupConfig.Goldmark 79 var rendererOptions []renderer.Option 80 81 if cfg.Renderer.HardWraps { 82 rendererOptions = append(rendererOptions, html.WithHardWraps()) 83 } 84 85 if cfg.Renderer.XHTML { 86 rendererOptions = append(rendererOptions, html.WithXHTML()) 87 } 88 89 if cfg.Renderer.Unsafe { 90 rendererOptions = append(rendererOptions, html.WithUnsafe()) 91 } 92 93 var ( 94 extensions = []goldmark.Extender{ 95 newLinks(cfg), 96 newTocExtension(rendererOptions), 97 } 98 parserOptions []parser.Option 99 ) 100 101 extensions = append(extensions, images.New(cfg.Parser.WrapStandAloneImageWithinParagraph)) 102 103 if mcfg.Highlight.CodeFences { 104 extensions = append(extensions, codeblocks.New()) 105 } 106 107 if cfg.Extensions.Table { 108 extensions = append(extensions, extension.Table) 109 } 110 111 if cfg.Extensions.Strikethrough { 112 extensions = append(extensions, extension.Strikethrough) 113 } 114 115 if cfg.Extensions.Linkify { 116 extensions = append(extensions, extension.Linkify) 117 } 118 119 if cfg.Extensions.TaskList { 120 extensions = append(extensions, extension.TaskList) 121 } 122 123 if cfg.Extensions.Typographer { 124 extensions = append(extensions, extension.Typographer) 125 } 126 127 if cfg.Extensions.DefinitionList { 128 extensions = append(extensions, extension.DefinitionList) 129 } 130 131 if cfg.Extensions.Footnote { 132 extensions = append(extensions, extension.Footnote) 133 } 134 135 if cfg.Parser.AutoHeadingID { 136 parserOptions = append(parserOptions, parser.WithAutoHeadingID()) 137 } 138 139 if cfg.Parser.Attribute.Title { 140 parserOptions = append(parserOptions, parser.WithAttribute()) 141 } 142 if cfg.Parser.Attribute.Block { 143 extensions = append(extensions, attributes.New()) 144 } 145 146 md := goldmark.New( 147 goldmark.WithExtensions( 148 extensions..., 149 ), 150 goldmark.WithParserOptions( 151 parserOptions..., 152 ), 153 goldmark.WithRendererOptions( 154 rendererOptions..., 155 ), 156 ) 157 158 return md 159 } 160 161 var _ identity.IdentitiesProvider = (*converterResult)(nil) 162 163 type parserResult struct { 164 doc any 165 toc *tableofcontents.Fragments 166 } 167 168 func (p parserResult) Doc() any { 169 return p.doc 170 } 171 172 func (p parserResult) TableOfContents() *tableofcontents.Fragments { 173 return p.toc 174 } 175 176 type renderResult struct { 177 converter.ResultRender 178 ids identity.Identities 179 } 180 181 func (r renderResult) GetIdentities() identity.Identities { 182 return r.ids 183 } 184 185 type converterResult struct { 186 converter.ResultRender 187 tableOfContentsProvider 188 identity.IdentitiesProvider 189 } 190 191 type tableOfContentsProvider interface { 192 TableOfContents() *tableofcontents.Fragments 193 } 194 195 var converterIdentity = identity.KeyValueIdentity{Key: "goldmark", Value: "converter"} 196 197 func (c *goldmarkConverter) Parse(ctx converter.RenderContext) (converter.ResultParse, error) { 198 pctx := c.newParserContext(ctx) 199 reader := text.NewReader(ctx.Src) 200 201 doc := c.md.Parser().Parse( 202 reader, 203 parser.WithContext(pctx), 204 ) 205 206 return parserResult{ 207 doc: doc, 208 toc: pctx.TableOfContents(), 209 }, nil 210 211 } 212 func (c *goldmarkConverter) Render(ctx converter.RenderContext, doc any) (converter.ResultRender, error) { 213 n := doc.(ast.Node) 214 buf := &render.BufWriter{Buffer: &bytes.Buffer{}} 215 216 rcx := &render.RenderContextDataHolder{ 217 Rctx: ctx, 218 Dctx: c.ctx, 219 IDs: identity.NewManager(converterIdentity), 220 } 221 222 w := &render.Context{ 223 BufWriter: buf, 224 ContextData: rcx, 225 } 226 227 if err := c.md.Renderer().Render(w, ctx.Src, n); err != nil { 228 return nil, err 229 } 230 231 return renderResult{ 232 ResultRender: buf, 233 ids: rcx.IDs.GetIdentities(), 234 }, nil 235 236 } 237 238 func (c *goldmarkConverter) Convert(ctx converter.RenderContext) (converter.ResultRender, error) { 239 parseResult, err := c.Parse(ctx) 240 if err != nil { 241 return nil, err 242 } 243 renderResult, err := c.Render(ctx, parseResult.Doc()) 244 if err != nil { 245 return nil, err 246 } 247 return converterResult{ 248 ResultRender: renderResult, 249 tableOfContentsProvider: parseResult, 250 IdentitiesProvider: renderResult.(identity.IdentitiesProvider), 251 }, nil 252 253 } 254 255 var featureSet = map[identity.Identity]bool{ 256 converter.FeatureRenderHooks: true, 257 } 258 259 func (c *goldmarkConverter) Supports(feature identity.Identity) bool { 260 return featureSet[feature.GetIdentity()] 261 } 262 263 func (c *goldmarkConverter) newParserContext(rctx converter.RenderContext) *parserContext { 264 ctx := parser.NewContext(parser.WithIDs(newIDFactory(c.cfg.MarkupConfig.Goldmark.Parser.AutoHeadingIDType))) 265 ctx.Set(tocEnableKey, rctx.RenderTOC) 266 return &parserContext{ 267 Context: ctx, 268 } 269 } 270 271 type parserContext struct { 272 parser.Context 273 } 274 275 func (p *parserContext) TableOfContents() *tableofcontents.Fragments { 276 if v := p.Get(tocResultKey); v != nil { 277 return v.(*tableofcontents.Fragments) 278 } 279 return nil 280 }