github.com/kovansky/hugo@v0.92.3-0.20220224232819-63076e4ff19f/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 "fmt" 20 "path/filepath" 21 "runtime/debug" 22 23 "github.com/gohugoio/hugo/markup/goldmark/codeblocks" 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/identity" 28 29 "github.com/pkg/errors" 30 31 "github.com/spf13/afero" 32 33 "github.com/gohugoio/hugo/hugofs" 34 "github.com/gohugoio/hugo/markup/converter" 35 "github.com/gohugoio/hugo/markup/tableofcontents" 36 "github.com/yuin/goldmark" 37 "github.com/yuin/goldmark/extension" 38 "github.com/yuin/goldmark/parser" 39 "github.com/yuin/goldmark/renderer" 40 "github.com/yuin/goldmark/renderer/html" 41 "github.com/yuin/goldmark/text" 42 ) 43 44 // Provider is the package entry point. 45 var Provider converter.ProviderProvider = provide{} 46 47 type provide struct{} 48 49 func (p provide) New(cfg converter.ProviderConfig) (converter.Provider, error) { 50 md := newMarkdown(cfg) 51 52 return converter.NewProvider("goldmark", func(ctx converter.DocumentContext) (converter.Converter, error) { 53 return &goldmarkConverter{ 54 ctx: ctx, 55 cfg: cfg, 56 md: md, 57 sanitizeAnchorName: func(s string) string { 58 return sanitizeAnchorNameString(s, cfg.MarkupConfig.Goldmark.Parser.AutoHeadingIDType) 59 }, 60 }, nil 61 }), nil 62 } 63 64 var _ converter.AnchorNameSanitizer = (*goldmarkConverter)(nil) 65 66 type goldmarkConverter struct { 67 md goldmark.Markdown 68 ctx converter.DocumentContext 69 cfg converter.ProviderConfig 70 71 sanitizeAnchorName func(s string) string 72 } 73 74 func (c *goldmarkConverter) SanitizeAnchorName(s string) string { 75 return c.sanitizeAnchorName(s) 76 } 77 78 func newMarkdown(pcfg converter.ProviderConfig) goldmark.Markdown { 79 mcfg := pcfg.MarkupConfig 80 cfg := pcfg.MarkupConfig.Goldmark 81 var rendererOptions []renderer.Option 82 83 if cfg.Renderer.HardWraps { 84 rendererOptions = append(rendererOptions, html.WithHardWraps()) 85 } 86 87 if cfg.Renderer.XHTML { 88 rendererOptions = append(rendererOptions, html.WithXHTML()) 89 } 90 91 if cfg.Renderer.Unsafe { 92 rendererOptions = append(rendererOptions, html.WithUnsafe()) 93 } 94 95 var ( 96 extensions = []goldmark.Extender{ 97 newLinks(), 98 newTocExtension(rendererOptions), 99 } 100 parserOptions []parser.Option 101 ) 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 143 if cfg.Parser.Attribute.Block { 144 extensions = append(extensions, attributes.New()) 145 } 146 147 md := goldmark.New( 148 goldmark.WithExtensions( 149 extensions..., 150 ), 151 goldmark.WithParserOptions( 152 parserOptions..., 153 ), 154 goldmark.WithRendererOptions( 155 rendererOptions..., 156 ), 157 ) 158 159 return md 160 } 161 162 var _ identity.IdentitiesProvider = (*converterResult)(nil) 163 164 type converterResult struct { 165 converter.Result 166 toc tableofcontents.Root 167 ids identity.Identities 168 } 169 170 func (c converterResult) TableOfContents() tableofcontents.Root { 171 return c.toc 172 } 173 174 func (c converterResult) GetIdentities() identity.Identities { 175 return c.ids 176 } 177 178 var converterIdentity = identity.KeyValueIdentity{Key: "goldmark", Value: "converter"} 179 180 func (c *goldmarkConverter) Convert(ctx converter.RenderContext) (result converter.Result, err error) { 181 defer func() { 182 if r := recover(); r != nil { 183 dir := afero.GetTempDir(hugofs.Os, "hugo_bugs") 184 name := fmt.Sprintf("goldmark_%s.txt", c.ctx.DocumentID) 185 filename := filepath.Join(dir, name) 186 afero.WriteFile(hugofs.Os, filename, ctx.Src, 07555) 187 fmt.Print(string(debug.Stack())) 188 err = errors.Errorf("[BUG] goldmark: %s: create an issue on GitHub attaching the file in: %s", r, filename) 189 } 190 }() 191 192 buf := &render.BufWriter{Buffer: &bytes.Buffer{}} 193 result = buf 194 pctx := c.newParserContext(ctx) 195 reader := text.NewReader(ctx.Src) 196 197 doc := c.md.Parser().Parse( 198 reader, 199 parser.WithContext(pctx), 200 ) 201 202 rcx := &render.RenderContextDataHolder{ 203 Rctx: ctx, 204 Dctx: c.ctx, 205 IDs: identity.NewManager(converterIdentity), 206 } 207 208 w := &render.Context{ 209 BufWriter: buf, 210 ContextData: rcx, 211 } 212 213 if err := c.md.Renderer().Render(w, ctx.Src, doc); err != nil { 214 return nil, err 215 } 216 217 return converterResult{ 218 Result: buf, 219 ids: rcx.IDs.GetIdentities(), 220 toc: pctx.TableOfContents(), 221 }, nil 222 } 223 224 var featureSet = map[identity.Identity]bool{ 225 converter.FeatureRenderHooks: true, 226 } 227 228 func (c *goldmarkConverter) Supports(feature identity.Identity) bool { 229 return featureSet[feature.GetIdentity()] 230 } 231 232 func (c *goldmarkConverter) newParserContext(rctx converter.RenderContext) *parserContext { 233 ctx := parser.NewContext(parser.WithIDs(newIDFactory(c.cfg.MarkupConfig.Goldmark.Parser.AutoHeadingIDType))) 234 ctx.Set(tocEnableKey, rctx.RenderTOC) 235 return &parserContext{ 236 Context: ctx, 237 } 238 } 239 240 type parserContext struct { 241 parser.Context 242 } 243 244 func (p *parserContext) TableOfContents() tableofcontents.Root { 245 if v := p.Get(tocResultKey); v != nil { 246 return v.(tableofcontents.Root) 247 } 248 return tableofcontents.Root{} 249 }