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  }