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 }