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  }