convert.go (5783B)
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/markup/goldmark/codeblocks" 21 "github.com/gohugoio/hugo/markup/goldmark/internal/extensions/attributes" 22 "github.com/gohugoio/hugo/markup/goldmark/internal/render" 23 24 "github.com/gohugoio/hugo/identity" 25 26 "github.com/gohugoio/hugo/markup/converter" 27 "github.com/gohugoio/hugo/markup/tableofcontents" 28 "github.com/yuin/goldmark" 29 "github.com/yuin/goldmark/extension" 30 "github.com/yuin/goldmark/parser" 31 "github.com/yuin/goldmark/renderer" 32 "github.com/yuin/goldmark/renderer/html" 33 "github.com/yuin/goldmark/text" 34 ) 35 36 // Provider is the package entry point. 37 var Provider converter.ProviderProvider = provide{} 38 39 type provide struct{} 40 41 func (p provide) New(cfg converter.ProviderConfig) (converter.Provider, error) { 42 md := newMarkdown(cfg) 43 44 return converter.NewProvider("goldmark", func(ctx converter.DocumentContext) (converter.Converter, error) { 45 return &goldmarkConverter{ 46 ctx: ctx, 47 cfg: cfg, 48 md: md, 49 sanitizeAnchorName: func(s string) string { 50 return sanitizeAnchorNameString(s, cfg.MarkupConfig.Goldmark.Parser.AutoHeadingIDType) 51 }, 52 }, nil 53 }), nil 54 } 55 56 var _ converter.AnchorNameSanitizer = (*goldmarkConverter)(nil) 57 58 type goldmarkConverter struct { 59 md goldmark.Markdown 60 ctx converter.DocumentContext 61 cfg converter.ProviderConfig 62 63 sanitizeAnchorName func(s string) string 64 } 65 66 func (c *goldmarkConverter) SanitizeAnchorName(s string) string { 67 return c.sanitizeAnchorName(s) 68 } 69 70 func newMarkdown(pcfg converter.ProviderConfig) goldmark.Markdown { 71 mcfg := pcfg.MarkupConfig 72 cfg := pcfg.MarkupConfig.Goldmark 73 var rendererOptions []renderer.Option 74 75 if cfg.Renderer.HardWraps { 76 rendererOptions = append(rendererOptions, html.WithHardWraps()) 77 } 78 79 if cfg.Renderer.XHTML { 80 rendererOptions = append(rendererOptions, html.WithXHTML()) 81 } 82 83 if cfg.Renderer.Unsafe { 84 rendererOptions = append(rendererOptions, html.WithUnsafe()) 85 } 86 87 var ( 88 extensions = []goldmark.Extender{ 89 newLinks(cfg), 90 newTocExtension(rendererOptions), 91 } 92 parserOptions []parser.Option 93 ) 94 95 if mcfg.Highlight.CodeFences { 96 extensions = append(extensions, codeblocks.New()) 97 } 98 99 if cfg.Extensions.Table { 100 extensions = append(extensions, extension.Table) 101 } 102 103 if cfg.Extensions.Strikethrough { 104 extensions = append(extensions, extension.Strikethrough) 105 } 106 107 if cfg.Extensions.Linkify { 108 extensions = append(extensions, extension.Linkify) 109 } 110 111 if cfg.Extensions.TaskList { 112 extensions = append(extensions, extension.TaskList) 113 } 114 115 if cfg.Extensions.Typographer { 116 extensions = append(extensions, extension.Typographer) 117 } 118 119 if cfg.Extensions.DefinitionList { 120 extensions = append(extensions, extension.DefinitionList) 121 } 122 123 if cfg.Extensions.Footnote { 124 extensions = append(extensions, extension.Footnote) 125 } 126 127 if cfg.Parser.AutoHeadingID { 128 parserOptions = append(parserOptions, parser.WithAutoHeadingID()) 129 } 130 131 if cfg.Parser.Attribute.Title { 132 parserOptions = append(parserOptions, parser.WithAttribute()) 133 } 134 135 if cfg.Parser.Attribute.Block { 136 extensions = append(extensions, attributes.New()) 137 } 138 139 md := goldmark.New( 140 goldmark.WithExtensions( 141 extensions..., 142 ), 143 goldmark.WithParserOptions( 144 parserOptions..., 145 ), 146 goldmark.WithRendererOptions( 147 rendererOptions..., 148 ), 149 ) 150 151 return md 152 } 153 154 var _ identity.IdentitiesProvider = (*converterResult)(nil) 155 156 type converterResult struct { 157 converter.Result 158 toc tableofcontents.Root 159 ids identity.Identities 160 } 161 162 func (c converterResult) TableOfContents() tableofcontents.Root { 163 return c.toc 164 } 165 166 func (c converterResult) GetIdentities() identity.Identities { 167 return c.ids 168 } 169 170 var converterIdentity = identity.KeyValueIdentity{Key: "goldmark", Value: "converter"} 171 172 func (c *goldmarkConverter) Convert(ctx converter.RenderContext) (result converter.Result, err error) { 173 174 buf := &render.BufWriter{Buffer: &bytes.Buffer{}} 175 result = buf 176 pctx := c.newParserContext(ctx) 177 reader := text.NewReader(ctx.Src) 178 179 doc := c.md.Parser().Parse( 180 reader, 181 parser.WithContext(pctx), 182 ) 183 184 rcx := &render.RenderContextDataHolder{ 185 Rctx: ctx, 186 Dctx: c.ctx, 187 IDs: identity.NewManager(converterIdentity), 188 } 189 190 w := &render.Context{ 191 BufWriter: buf, 192 ContextData: rcx, 193 } 194 195 if err := c.md.Renderer().Render(w, ctx.Src, doc); err != nil { 196 return nil, err 197 } 198 199 return converterResult{ 200 Result: buf, 201 ids: rcx.IDs.GetIdentities(), 202 toc: pctx.TableOfContents(), 203 }, nil 204 } 205 206 var featureSet = map[identity.Identity]bool{ 207 converter.FeatureRenderHooks: true, 208 } 209 210 func (c *goldmarkConverter) Supports(feature identity.Identity) bool { 211 return featureSet[feature.GetIdentity()] 212 } 213 214 func (c *goldmarkConverter) newParserContext(rctx converter.RenderContext) *parserContext { 215 ctx := parser.NewContext(parser.WithIDs(newIDFactory(c.cfg.MarkupConfig.Goldmark.Parser.AutoHeadingIDType))) 216 ctx.Set(tocEnableKey, rctx.RenderTOC) 217 return &parserContext{ 218 Context: ctx, 219 } 220 } 221 222 type parserContext struct { 223 parser.Context 224 } 225 226 func (p *parserContext) TableOfContents() tableofcontents.Root { 227 if v := p.Get(tocResultKey); v != nil { 228 return v.(tableofcontents.Root) 229 } 230 return tableofcontents.Root{} 231 }