aboutsummaryrefslogtreecommitdiffhomepage
path: root/markup
diff options
context:
space:
mode:
authorBjørn Erik Pedersen <[email protected]>2023-01-04 18:24:36 +0100
committerBjørn Erik Pedersen <[email protected]>2023-05-16 18:01:29 +0200
commit241b21b0fd34d91fccb2ce69874110dceae6f926 (patch)
treed4e0118eac7e9c42f065815447a70805f8d6ad3e /markup
parent6aededf6b42011c3039f5f66487a89a8dd65e0e7 (diff)
downloadhugo-241b21b0fd34d91fccb2ce69874110dceae6f926.tar.gz
hugo-241b21b0fd34d91fccb2ce69874110dceae6f926.zip
Create a struct with all of Hugo's config options
Primary motivation is documentation, but it will also hopefully simplify the code. Also, * Lower case the default output format names; this is in line with the custom ones (map keys) and how it's treated all the places. This avoids doing `stringds.EqualFold` everywhere. Closes #10896 Closes #10620
Diffstat (limited to 'markup')
-rw-r--r--markup/asciidocext/convert.go283
-rw-r--r--markup/asciidocext/convert_test.go202
-rw-r--r--markup/asciidocext/internal/converter.go274
-rw-r--r--markup/converter/converter.go8
-rw-r--r--markup/converter/hooks/hooks.go2
-rw-r--r--markup/goldmark/convert.go8
-rw-r--r--markup/goldmark/convert_test.go213
-rw-r--r--markup/goldmark/toc_test.go34
-rw-r--r--markup/highlight/config.go2
-rw-r--r--markup/highlight/highlight.go4
-rw-r--r--markup/markup.go12
-rw-r--r--markup/markup_config/config.go14
-rw-r--r--markup/markup_test.go11
-rw-r--r--markup/org/convert_test.go13
-rw-r--r--markup/pandoc/convert.go2
-rw-r--r--markup/rst/convert.go2
-rw-r--r--markup/tableofcontents/tableofcontents.go1
17 files changed, 602 insertions, 483 deletions
diff --git a/markup/asciidocext/convert.go b/markup/asciidocext/convert.go
index c3bd90edd..ecf3eb9ac 100644
--- a/markup/asciidocext/convert.go
+++ b/markup/asciidocext/convert.go
@@ -17,26 +17,11 @@
package asciidocext
import (
- "bytes"
- "path/filepath"
- "strings"
-
- "github.com/gohugoio/hugo/common/hexec"
"github.com/gohugoio/hugo/htesting"
-
- "github.com/gohugoio/hugo/identity"
- "github.com/gohugoio/hugo/markup/asciidocext/asciidocext_config"
+ "github.com/gohugoio/hugo/markup/asciidocext/internal"
"github.com/gohugoio/hugo/markup/converter"
- "github.com/gohugoio/hugo/markup/internal"
- "github.com/gohugoio/hugo/markup/tableofcontents"
- "golang.org/x/net/html"
)
-/* ToDo: RelPermalink patch for svg posts not working*/
-type pageSubset interface {
- RelPermalink() string
-}
-
// Provider is the package entry point.
var Provider converter.ProviderProvider = provider{}
@@ -44,274 +29,16 @@ type provider struct{}
func (p provider) New(cfg converter.ProviderConfig) (converter.Provider, error) {
return converter.NewProvider("asciidocext", func(ctx converter.DocumentContext) (converter.Converter, error) {
- return &asciidocConverter{
- ctx: ctx,
- cfg: cfg,
+ return &internal.AsciidocConverter{
+ Ctx: ctx,
+ Cfg: cfg,
}, nil
}), nil
}
-type asciidocResult struct {
- converter.ResultRender
- toc *tableofcontents.Fragments
-}
-
-func (r asciidocResult) TableOfContents() *tableofcontents.Fragments {
- return r.toc
-}
-
-type asciidocConverter struct {
- ctx converter.DocumentContext
- cfg converter.ProviderConfig
-}
-
-func (a *asciidocConverter) Convert(ctx converter.RenderContext) (converter.ResultRender, error) {
- b, err := a.getAsciidocContent(ctx.Src, a.ctx)
- if err != nil {
- return nil, err
- }
- content, toc, err := a.extractTOC(b)
- if err != nil {
- return nil, err
- }
- return asciidocResult{
- ResultRender: converter.Bytes(content),
- toc: toc,
- }, nil
-}
-
-func (a *asciidocConverter) Supports(_ identity.Identity) bool {
- return false
-}
-
-// getAsciidocContent calls asciidoctor as an external helper
-// to convert AsciiDoc content to HTML.
-func (a *asciidocConverter) getAsciidocContent(src []byte, ctx converter.DocumentContext) ([]byte, error) {
- if !hasAsciiDoc() {
- a.cfg.Logger.Errorln("asciidoctor not found in $PATH: Please install.\n",
- " Leaving AsciiDoc content unrendered.")
- return src, nil
- }
-
- args := a.parseArgs(ctx)
- args = append(args, "-")
-
- a.cfg.Logger.Infoln("Rendering", ctx.DocumentName, " using asciidoctor args", args, "...")
-
- return internal.ExternallyRenderContent(a.cfg, ctx, src, asciiDocBinaryName, args)
-}
-
-func (a *asciidocConverter) parseArgs(ctx converter.DocumentContext) []string {
- cfg := a.cfg.MarkupConfig.AsciidocExt
- args := []string{}
-
- args = a.appendArg(args, "-b", cfg.Backend, asciidocext_config.CliDefault.Backend, asciidocext_config.AllowedBackend)
-
- for _, extension := range cfg.Extensions {
- if strings.LastIndexAny(extension, `\/.`) > -1 {
- a.cfg.Logger.Errorln("Unsupported asciidoctor extension was passed in. Extension `" + extension + "` ignored. Only installed asciidoctor extensions are allowed.")
- continue
- }
- args = append(args, "-r", extension)
- }
-
- for attributeKey, attributeValue := range cfg.Attributes {
- if asciidocext_config.DisallowedAttributes[attributeKey] {
- a.cfg.Logger.Errorln("Unsupported asciidoctor attribute was passed in. Attribute `" + attributeKey + "` ignored.")
- continue
- }
-
- args = append(args, "-a", attributeKey+"="+attributeValue)
- }
-
- if cfg.WorkingFolderCurrent {
- contentDir := filepath.Dir(ctx.Filename)
- sourceDir := a.cfg.Cfg.GetString("source")
- destinationDir := a.cfg.Cfg.GetString("destination")
-
- if destinationDir == "" {
- a.cfg.Logger.Errorln("markup.asciidocext.workingFolderCurrent requires hugo command option --destination to be set")
- }
- if !filepath.IsAbs(destinationDir) && sourceDir != "" {
- destinationDir = filepath.Join(sourceDir, destinationDir)
- }
-
- var outDir string
- var err error
-
- file := filepath.Base(ctx.Filename)
- if a.cfg.Cfg.GetBool("uglyUrls") || file == "_index.adoc" || file == "index.adoc" {
- outDir, err = filepath.Abs(filepath.Dir(filepath.Join(destinationDir, ctx.DocumentName)))
- } else {
- postDir := ""
- page, ok := ctx.Document.(pageSubset)
- if ok {
- postDir = filepath.Base(page.RelPermalink())
- } else {
- a.cfg.Logger.Errorln("unable to cast interface to pageSubset")
- }
-
- outDir, err = filepath.Abs(filepath.Join(destinationDir, filepath.Dir(ctx.DocumentName), postDir))
- }
-
- if err != nil {
- a.cfg.Logger.Errorln("asciidoctor outDir: ", err)
- }
-
- args = append(args, "--base-dir", contentDir, "-a", "outdir="+outDir)
- }
-
- if cfg.NoHeaderOrFooter {
- args = append(args, "--no-header-footer")
- } else {
- a.cfg.Logger.Warnln("asciidoctor parameter NoHeaderOrFooter is expected for correct html rendering")
- }
-
- if cfg.SectionNumbers {
- args = append(args, "--section-numbers")
- }
-
- if cfg.Verbose {
- args = append(args, "--verbose")
- }
-
- if cfg.Trace {
- args = append(args, "--trace")
- }
-
- args = a.appendArg(args, "--failure-level", cfg.FailureLevel, asciidocext_config.CliDefault.FailureLevel, asciidocext_config.AllowedFailureLevel)
-
- args = a.appendArg(args, "--safe-mode", cfg.SafeMode, asciidocext_config.CliDefault.SafeMode, asciidocext_config.AllowedSafeMode)
-
- return args
-}
-
-func (a *asciidocConverter) appendArg(args []string, option, value, defaultValue string, allowedValues map[string]bool) []string {
- if value != defaultValue {
- if allowedValues[value] {
- args = append(args, option, value)
- } else {
- a.cfg.Logger.Errorln("Unsupported asciidoctor value `" + value + "` for option " + option + " was passed in and will be ignored.")
- }
- }
- return args
-}
-
-const asciiDocBinaryName = "asciidoctor"
-
-func hasAsciiDoc() bool {
- return hexec.InPath(asciiDocBinaryName)
-}
-
-// extractTOC extracts the toc from the given src html.
-// It returns the html without the TOC, and the TOC data
-func (a *asciidocConverter) extractTOC(src []byte) ([]byte, *tableofcontents.Fragments, error) {
- var buf bytes.Buffer
- buf.Write(src)
- node, err := html.Parse(&buf)
- if err != nil {
- return nil, nil, err
- }
- var (
- f func(*html.Node) bool
- toc *tableofcontents.Fragments
- toVisit []*html.Node
- )
- f = func(n *html.Node) bool {
- if n.Type == html.ElementNode && n.Data == "div" && attr(n, "id") == "toc" {
- toc = parseTOC(n)
- if !a.cfg.MarkupConfig.AsciidocExt.PreserveTOC {
- n.Parent.RemoveChild(n)
- }
- return true
- }
- if n.FirstChild != nil {
- toVisit = append(toVisit, n.FirstChild)
- }
- if n.NextSibling != nil && f(n.NextSibling) {
- return true
- }
- for len(toVisit) > 0 {
- nv := toVisit[0]
- toVisit = toVisit[1:]
- if f(nv) {
- return true
- }
- }
- return false
- }
- f(node)
- if err != nil {
- return nil, nil, err
- }
- buf.Reset()
- err = html.Render(&buf, node)
- if err != nil {
- return nil, nil, err
- }
- // ltrim <html><head></head><body> and rtrim </body></html> which are added by html.Render
- res := buf.Bytes()[25:]
- res = res[:len(res)-14]
- return res, toc, nil
-}
-
-// parseTOC returns a TOC root from the given toc Node
-func parseTOC(doc *html.Node) *tableofcontents.Fragments {
- var (
- toc tableofcontents.Builder
- f func(*html.Node, int, int)
- )
- f = func(n *html.Node, row, level int) {
- if n.Type == html.ElementNode {
- switch n.Data {
- case "ul":
- if level == 0 {
- row++
- }
- level++
- f(n.FirstChild, row, level)
- case "li":
- for c := n.FirstChild; c != nil; c = c.NextSibling {
- if c.Type != html.ElementNode || c.Data != "a" {
- continue
- }
- href := attr(c, "href")[1:]
- toc.AddAt(&tableofcontents.Heading{
- Title: nodeContent(c),
- ID: href,
- }, row, level)
- }
- f(n.FirstChild, row, level)
- }
- }
- if n.NextSibling != nil {
- f(n.NextSibling, row, level)
- }
- }
- f(doc.FirstChild, -1, 0)
- return toc.Build()
-}
-
-func attr(node *html.Node, key string) string {
- for _, a := range node.Attr {
- if a.Key == key {
- return a.Val
- }
- }
- return ""
-}
-
-func nodeContent(node *html.Node) string {
- var buf bytes.Buffer
- for c := node.FirstChild; c != nil; c = c.NextSibling {
- html.Render(&buf, c)
- }
- return buf.String()
-}
-
// Supports returns whether Asciidoctor is installed on this computer.
func Supports() bool {
- hasBin := hasAsciiDoc()
+ hasBin := internal.HasAsciiDoc()
if htesting.SupportsAll() {
if !hasBin {
panic("asciidoctor not installed")
diff --git a/markup/asciidocext/convert_test.go b/markup/asciidocext/convert_test.go
index 47208c066..cdc981263 100644
--- a/markup/asciidocext/convert_test.go
+++ b/markup/asciidocext/convert_test.go
@@ -1,4 +1,4 @@
-// Copyright 2020 The Hugo Authors. All rights reserved.
+// Copyright 2023 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -15,7 +15,7 @@
// external binary. The `asciidoc` module is reserved for a future golang
// implementation.
-package asciidocext
+package asciidocext_test
import (
"path/filepath"
@@ -26,8 +26,12 @@ import (
"github.com/gohugoio/hugo/common/loggers"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/config/security"
+ "github.com/gohugoio/hugo/config/testconfig"
+ "github.com/gohugoio/hugo/markup/asciidocext"
+ "github.com/gohugoio/hugo/markup/asciidocext/internal"
"github.com/gohugoio/hugo/markup/converter"
"github.com/gohugoio/hugo/markup/markup_config"
+ "github.com/spf13/afero"
qt "github.com/frankban/quicktest"
)
@@ -35,13 +39,12 @@ import (
func TestAsciidoctorDefaultArgs(t *testing.T) {
c := qt.New(t)
cfg := config.New()
- mconf := markup_config.Default
+ conf := testconfig.GetTestConfig(afero.NewMemMapFs(), cfg)
- p, err := Provider.New(
+ p, err := asciidocext.Provider.New(
converter.ProviderConfig{
- Cfg: cfg,
- MarkupConfig: mconf,
- Logger: loggers.NewErrorLogger(),
+ Conf: conf,
+ Logger: loggers.NewErrorLogger(),
},
)
c.Assert(err, qt.IsNil)
@@ -49,17 +52,16 @@ func TestAsciidoctorDefaultArgs(t *testing.T) {
conv, err := p.New(converter.DocumentContext{})
c.Assert(err, qt.IsNil)
- ac := conv.(*asciidocConverter)
+ ac := conv.(*internal.AsciidocConverter)
c.Assert(ac, qt.Not(qt.IsNil))
- args := ac.parseArgs(converter.DocumentContext{})
+ args := ac.ParseArgs(converter.DocumentContext{})
expected := []string{"--no-header-footer"}
c.Assert(args, qt.DeepEquals, expected)
}
func TestAsciidoctorNonDefaultArgs(t *testing.T) {
c := qt.New(t)
- cfg := config.New()
mconf := markup_config.Default
mconf.AsciidocExt.Backend = "manpage"
mconf.AsciidocExt.NoHeaderOrFooter = false
@@ -68,11 +70,13 @@ func TestAsciidoctorNonDefaultArgs(t *testing.T) {
mconf.AsciidocExt.Verbose = true
mconf.AsciidocExt.Trace = false
mconf.AsciidocExt.FailureLevel = "warn"
- p, err := Provider.New(
+
+ conf := testconfig.GetTestConfigSectionFromStruct("markup", mconf)
+
+ p, err := asciidocext.Provider.New(
converter.ProviderConfig{
- Cfg: cfg,
- MarkupConfig: mconf,
- Logger: loggers.NewErrorLogger(),
+ Conf: conf,
+ Logger: loggers.NewErrorLogger(),
},
)
c.Assert(err, qt.IsNil)
@@ -80,28 +84,29 @@ func TestAsciidoctorNonDefaultArgs(t *testing.T) {
conv, err := p.New(converter.DocumentContext{})
c.Assert(err, qt.IsNil)
- ac := conv.(*asciidocConverter)
+ ac := conv.(*internal.AsciidocConverter)
c.Assert(ac, qt.Not(qt.IsNil))
- args := ac.parseArgs(converter.DocumentContext{})
+ args := ac.ParseArgs(converter.DocumentContext{})
expected := []string{"-b", "manpage", "--section-numbers", "--verbose", "--failure-level", "warn", "--safe-mode", "safe"}
c.Assert(args, qt.DeepEquals, expected)
}
func TestAsciidoctorDisallowedArgs(t *testing.T) {
c := qt.New(t)
- cfg := config.New()
mconf := markup_config.Default
mconf.AsciidocExt.Backend = "disallowed-backend"
mconf.AsciidocExt.Extensions = []string{"./disallowed-extension"}
mconf.AsciidocExt.Attributes = map[string]string{"outdir": "disallowed-attribute"}
mconf.AsciidocExt.SafeMode = "disallowed-safemode"
mconf.AsciidocExt.FailureLevel = "disallowed-failurelevel"
- p, err := Provider.New(
+
+ conf := testconfig.GetTestConfigSectionFromStruct("markup", mconf)
+
+ p, err := asciidocext.Provider.New(
converter.ProviderConfig{
- Cfg: cfg,
- MarkupConfig: mconf,
- Logger: loggers.NewErrorLogger(),
+ Conf: conf,
+ Logger: loggers.NewErrorLogger(),
},
)
c.Assert(err, qt.IsNil)
@@ -109,24 +114,23 @@ func TestAsciidoctorDisallowedArgs(t *testing.T) {
conv, err := p.New(converter.DocumentContext{})
c.Assert(err, qt.IsNil)
- ac := conv.(*asciidocConverter)
+ ac := conv.(*internal.AsciidocConverter)
c.Assert(ac, qt.Not(qt.IsNil))
- args := ac.parseArgs(converter.DocumentContext{})
+ args := ac.ParseArgs(converter.DocumentContext{})
expected := []string{"--no-header-footer"}
c.Assert(args, qt.DeepEquals, expected)
}
func TestAsciidoctorArbitraryExtension(t *testing.T) {
c := qt.New(t)
- cfg := config.New()
mconf := markup_config.Default
mconf.AsciidocExt.Extensions = []string{"arbitrary-extension"}
- p, err := Provider.New(
+ conf := testconfig.GetTestConfigSectionFromStruct("markup", mconf)
+ p, err := asciidocext.Provider.New(
converter.ProviderConfig{
- Cfg: cfg,
- MarkupConfig: mconf,
- Logger: loggers.NewErrorLogger(),
+ Conf: conf,
+ Logger: loggers.NewErrorLogger(),
},
)
c.Assert(err, qt.IsNil)
@@ -134,17 +138,17 @@ func TestAsciidoctorArbitraryExtension(t *testing.T) {
conv, err := p.New(converter.DocumentContext{})
c.Assert(err, qt.IsNil)
- ac := conv.(*asciidocConverter)
+ ac := conv.(*internal.AsciidocConverter)
c.Assert(ac, qt.Not(qt.IsNil))
- args := ac.parseArgs(converter.DocumentContext{})
+ args := ac.ParseArgs(converter.DocumentContext{})
expected := []string{"-r", "arbitrary-extension", "--no-header-footer"}
c.Assert(args, qt.DeepEquals, expected)
}
func TestAsciidoctorDisallowedExtension(t *testing.T) {
c := qt.New(t)
- cfg := config.New()
+
for _, disallowedExtension := range []string{
`foo-bar//`,
`foo-bar\\ `,
@@ -156,11 +160,11 @@ func TestAsciidoctorDisallowedExtension(t *testing.T) {
} {
mconf := markup_config.Default
mconf.AsciidocExt.Extensions = []string{disallowedExtension}
- p, err := Provider.New(
+ conf := testconfig.GetTestConfigSectionFromStruct("markup", mconf)
+ p, err := asciidocext.Provider.New(
converter.ProviderConfig{
- Cfg: cfg,
- MarkupConfig: mconf,
- Logger: loggers.NewErrorLogger(),
+ Conf: conf,
+ Logger: loggers.NewErrorLogger(),
},
)
c.Assert(err, qt.IsNil)
@@ -168,10 +172,10 @@ func TestAsciidoctorDisallowedExtension(t *testing.T) {
conv, err := p.New(converter.DocumentContext{})
c.Assert(err, qt.IsNil)
- ac := conv.(*asciidocConverter)
+ ac := conv.(*internal.AsciidocConverter)
c.Assert(ac, qt.Not(qt.IsNil))
- args := ac.parseArgs(converter.DocumentContext{})
+ args := ac.ParseArgs(converter.DocumentContext{})
expected := []string{"--no-header-footer"}
c.Assert(args, qt.DeepEquals, expected)
}
@@ -179,15 +183,19 @@ func TestAsciidoctorDisallowedExtension(t *testing.T) {
func TestAsciidoctorWorkingFolderCurrent(t *testing.T) {
c := qt.New(t)
- cfg := config.New()
- mconf := markup_config.Default
- mconf.AsciidocExt.WorkingFolderCurrent = true
- mconf.AsciidocExt.Trace = false
- p, err := Provider.New(
+ cfg := config.FromTOMLConfigString(`
+[markup]
+[markup.asciidocext]
+workingFolderCurrent = true
+trace = false
+`)
+
+ conf := testconfig.GetTestConfig(afero.NewMemMapFs(), cfg)
+
+ p, err := asciidocext.Provider.New(
converter.ProviderConfig{
- Cfg: cfg,
- MarkupConfig: mconf,
- Logger: loggers.NewErrorLogger(),
+ Conf: conf,
+ Logger: loggers.NewErrorLogger(),
},
)
c.Assert(err, qt.IsNil)
@@ -196,32 +204,35 @@ func TestAsciidoctorWorkingFolderCurrent(t *testing.T) {
conv, err := p.New(ctx)
c.Assert(err, qt.IsNil)
- ac := conv.(*asciidocConverter)
+ ac := conv.(*internal.AsciidocConverter)
c.Assert(ac, qt.Not(qt.IsNil))
- args := ac.parseArgs(ctx)
+ args := ac.ParseArgs(ctx)
c.Assert(len(args), qt.Equals, 5)
c.Assert(args[0], qt.Equals, "--base-dir")
c.Assert(filepath.ToSlash(args[1]), qt.Matches, "/tmp/hugo_asciidoc_ddd/docs/chapter2")
c.Assert(args[2], qt.Equals, "-a")
- c.Assert(args[3], qt.Matches, `outdir=.*[/\\]{1,2}asciidocext[/\\]{1,2}chapter2`)
+ c.Assert(args[3], qt.Matches, `outdir=.*chapter2`)
c.Assert(args[4], qt.Equals, "--no-header-footer")
}
func TestAsciidoctorWorkingFolderCurrentAndExtensions(t *testing.T) {
c := qt.New(t)
- cfg := config.New()
- mconf := markup_config.Default
- mconf.AsciidocExt.NoHeaderOrFooter = true
- mconf.AsciidocExt.Extensions = []string{"asciidoctor-html5s", "asciidoctor-diagram"}
- mconf.AsciidocExt.Backend = "html5s"
- mconf.AsciidocExt.WorkingFolderCurrent = true
- mconf.AsciidocExt.Trace = false
- p, err := Provider.New(
+ cfg := config.FromTOMLConfigString(`
+[markup]
+[markup.asciidocext]
+backend = "html5s"
+workingFolderCurrent = true
+trace = false
+noHeaderOrFooter = true
+extensions = ["asciidoctor-html5s", "asciidoctor-diagram"]
+`)
+ conf := testconfig.GetTestConfig(afero.NewMemMapFs(), cfg)
+
+ p, err := asciidocext.Provider.New(
converter.ProviderConfig{
- Cfg: cfg,
- MarkupConfig: mconf,
- Logger: loggers.NewErrorLogger(),
+ Conf: conf,
+ Logger: loggers.NewErrorLogger(),
},
)
c.Assert(err, qt.IsNil)
@@ -229,10 +240,10 @@ func TestAsciidoctorWorkingFolderCurrentAndExtensions(t *testing.T) {
conv, err := p.New(converter.DocumentContext{})
c.Assert(err, qt.IsNil)
- ac := conv.(*asciidocConverter)
+ ac := conv.(*internal.AsciidocConverter)
c.Assert(ac, qt.Not(qt.IsNil))
- args := ac.parseArgs(converter.DocumentContext{})
+ args := ac.ParseArgs(converter.DocumentContext{})
c.Assert(len(args), qt.Equals, 11)
c.Assert(args[0], qt.Equals, "-b")
c.Assert(args[1], qt.Equals, "html5s")
@@ -249,15 +260,19 @@ func TestAsciidoctorWorkingFolderCurrentAndExtensions(t *testing.T) {
func TestAsciidoctorAttributes(t *testing.T) {
c := qt.New(t)
- cfg := config.New()
- mconf := markup_config.Default
- mconf.AsciidocExt.Attributes = map[string]string{"my-base-url": "https://gohugo.io/", "my-attribute-name": "my value"}
- mconf.AsciidocExt.Trace = false
- p, err := Provider.New(
+ cfg := config.FromTOMLConfigString(`
+[markup]
+[markup.asciidocext]
+trace = false
+[markup.asciidocext.attributes]
+my-base-url = "https://gohugo.io/"
+my-attribute-name = "my value"
+`)
+ conf := testconfig.GetTestConfig(nil, cfg)
+ p, err := asciidocext.Provider.New(
converter.ProviderConfig{
- Cfg: cfg,
- MarkupConfig: mconf,
- Logger: loggers.NewErrorLogger(),
+ Conf: conf,
+ Logger: loggers.NewErrorLogger(),
},
)
c.Assert(err, qt.IsNil)
@@ -265,7 +280,7 @@ func TestAsciidoctorAttributes(t *testing.T) {
conv, err := p.New(converter.DocumentContext{})
c.Assert(err, qt.IsNil)
- ac := conv.(*asciidocConverter)
+ ac := conv.(*internal.AsciidocConverter)
c.Assert(ac, qt.Not(qt.IsNil))
expectedValues := map[string]bool{
@@ -273,7 +288,7 @@ func TestAsciidoctorAttributes(t *testing.T) {
"my-attribute-name=my value": true,
}
- args := ac.parseArgs(converter.DocumentContext{})
+ args := ac.ParseArgs(converter.DocumentContext{})
c.Assert(len(args), qt.Equals, 5)
c.Assert(args[0], qt.Equals, "-a")
c.Assert(expectedValues[args[1]], qt.Equals, true)
@@ -282,15 +297,23 @@ func TestAsciidoctorAttributes(t *testing.T) {
c.Assert(args[4], qt.Equals, "--no-header-footer")
}
-func getProvider(c *qt.C, mconf markup_config.Config) converter.Provider {
- sc := security.DefaultConfig
- sc.Exec.Allow = security.NewWhitelist("asciidoctor")
+func getProvider(c *qt.C, mConfStr string) converter.Provider {
+ confStr := `
+[security]
+[security.exec]
+allow = ['asciidoctor']
+`
+ confStr += mConfStr
- p, err := Provider.New(
+ cfg := config.FromTOMLConfigString(confStr)
+ conf := testconfig.GetTestConfig(nil, cfg)
+ securityConfig := conf.GetConfigSection("security").(security.Config)
+
+ p, err := asciidocext.Provider.New(
converter.ProviderConfig{
- MarkupConfig: mconf,
- Logger: loggers.NewErrorLogger(),
- Exec: hexec.New(sc),
+ Logger: loggers.NewErrorLogger(),
+ Conf: conf,
+ Exec: hexec.New(securityConfig),
},
)
c.Assert(err, qt.IsNil)
@@ -298,12 +321,12 @@ func getProvider(c *qt.C, mconf markup_config.Config) converter.Provider {
}
func TestConvert(t *testing.T) {
- if !Supports() {
+ if !asciidocext.Supports() {
t.Skip("asciidoctor not installed")
}
c := qt.New(t)
- p := getProvider(c, markup_config.Default)
+ p := getProvider(c, "")
conv, err := p.New(converter.DocumentContext{})
c.Assert(err, qt.IsNil)
@@ -314,11 +337,11 @@ func TestConvert(t *testing.T) {
}
func TestTableOfContents(t *testing.T) {
- if !Supports() {
+ if !asciidocext.Supports() {
t.Skip("asciidoctor not installed")
}
c := qt.New(t)
- p := getProvider(c, markup_config.Default)
+ p := getProvider(c, "")
conv, err := p.New(converter.DocumentContext{})
c.Assert(err, qt.IsNil)
@@ -349,11 +372,11 @@ testContent
}
func TestTableOfContentsWithCode(t *testing.T) {
- if !Supports() {
+ if !asciidocext.Supports() {
t.Skip("asciidoctor not installed")
}
c := qt.New(t)
- p := getProvider(c, markup_config.Default)
+ p := getProvider(c, "")
conv, err := p.New(converter.DocumentContext{})
c.Assert(err, qt.IsNil)
r, err := conv.Convert(converter.RenderContext{Src: []byte(`:toc: auto
@@ -368,13 +391,16 @@ func TestTableOfContentsWithCode(t *testing.T) {
}
func TestTableOfContentsPreserveTOC(t *testing.T) {
- if !Supports() {
+ if !asciidocext.Supports() {
t.Skip("asciidoctor not installed")
}
c := qt.New(t)
- mconf := markup_config.Default
- mconf.AsciidocExt.PreserveTOC = true
- p := getProvider(c, mconf)
+ confStr := `
+[markup]
+[markup.asciidocExt]
+preserveTOC = true
+ `
+ p := getProvider(c, confStr)
conv, err := p.New(converter.DocumentContext{})
c.Assert(err, qt.IsNil)
diff --git a/markup/asciidocext/internal/converter.go b/markup/asciidocext/internal/converter.go
new file mode 100644
index 000000000..5108bdd0a
--- /dev/null
+++ b/markup/asciidocext/internal/converter.go
@@ -0,0 +1,274 @@
+package internal
+
+import (
+ "bytes"
+ "path/filepath"
+ "strings"
+
+ "github.com/gohugoio/hugo/common/hexec"
+ "github.com/gohugoio/hugo/identity"
+ "github.com/gohugoio/hugo/markup/asciidocext/asciidocext_config"
+ "github.com/gohugoio/hugo/markup/converter"
+ "github.com/gohugoio/hugo/markup/internal"
+ "github.com/gohugoio/hugo/markup/tableofcontents"
+ "golang.org/x/net/html"
+)
+
+type AsciidocConverter struct {
+ Ctx converter.DocumentContext
+ Cfg converter.ProviderConfig
+}
+
+type AsciidocResult struct {
+ converter.ResultRender
+ toc *tableofcontents.Fragments
+}
+
+/* ToDo: RelPermalink patch for svg posts not working*/
+type pageSubset interface {
+ RelPermalink() string
+}
+
+func (r AsciidocResult) TableOfContents() *tableofcontents.Fragments {
+ return r.toc
+}
+
+func (a *AsciidocConverter) Convert(ctx converter.RenderContext) (converter.ResultRender, error) {
+ b, err := a.GetAsciidocContent(ctx.Src, a.Ctx)
+ if err != nil {
+ return nil, err
+ }
+ content, toc, err := a.extractTOC(b)
+ if err != nil {
+ return nil, err
+ }
+ return AsciidocResult{
+ ResultRender: converter.Bytes(content),
+ toc: toc,
+ }, nil
+}
+
+func (a *AsciidocConverter) Supports(_ identity.Identity) bool {
+ return false
+}
+
+// GetAsciidocContent calls asciidoctor as an external helper
+// to convert AsciiDoc content to HTML.
+func (a *AsciidocConverter) GetAsciidocContent(src []byte, ctx converter.DocumentContext) ([]byte, error) {
+ if !HasAsciiDoc() {
+ a.Cfg.Logger.Errorln("asciidoctor not found in $PATH: Please install.\n",
+ " Leaving AsciiDoc content unrendered.")
+ return src, nil
+ }
+
+ args := a.ParseArgs(ctx)
+ args = append(args, "-")
+
+ a.Cfg.Logger.Infoln("Rendering", ctx.DocumentName, " using asciidoctor args", args, "...")
+
+ return internal.ExternallyRenderContent(a.Cfg, ctx, src, asciiDocBinaryName, args)
+}
+
+func (a *AsciidocConverter) ParseArgs(ctx converter.DocumentContext) []string {
+ cfg := a.Cfg.MarkupConfig().AsciidocExt
+ args := []string{}
+
+ args = a.AppendArg(args, "-b", cfg.Backend, asciidocext_config.CliDefault.Backend, asciidocext_config.AllowedBackend)
+
+ for _, extension := range cfg.Extensions {
+ if strings.LastIndexAny(extension, `\/.`) > -1 {
+ a.Cfg.Logger.Errorln("Unsupported asciidoctor extension was passed in. Extension `" + extension + "` ignored. Only installed asciidoctor extensions are allowed.")
+ continue
+ }
+ args = append(args, "-r", extension)
+ }
+
+ for attributeKey, attributeValue := range cfg.Attributes {
+ if asciidocext_config.DisallowedAttributes[attributeKey] {
+ a.Cfg.Logger.Errorln("Unsupported asciidoctor attribute was passed in. Attribute `" + attributeKey + "` ignored.")
+ continue
+ }
+
+ args = append(args, "-a", attributeKey+"="+attributeValue)
+ }
+
+ if cfg.WorkingFolderCurrent {
+ contentDir := filepath.Dir(ctx.Filename)
+ destinationDir := a.Cfg.Conf.BaseConfig().PublishDir
+
+ if destinationDir == "" {
+ a.Cfg.Logger.Errorln("markup.asciidocext.workingFolderCurrent requires hugo command option --destination to be set")
+ }
+
+ var outDir string
+ var err error
+
+ file := filepath.Base(ctx.Filename)
+ if a.Cfg.Conf.IsUglyURLs("") || file == "_index.adoc" || file == "index.adoc" {
+ outDir, err = filepath.Abs(filepath.Dir(filepath.Join(destinationDir, ctx.DocumentName)))
+ } else {
+ postDir := ""
+ page, ok := ctx.Document.(pageSubset)
+ if ok {
+ postDir = filepath.Base(page.RelPermalink())
+ } else {
+ a.Cfg.Logger.Errorln("unable to cast interface to pageSubset")
+ }
+
+ outDir, err = filepath.Abs(filepath.Join(destinationDir, filepath.Dir(ctx.DocumentName), postDir))
+ }
+
+ if err != nil {
+ a.Cfg.Logger.Errorln("asciidoctor outDir: ", err)
+ }
+
+ args = append(args, "--base-dir", contentDir, "-a", "outdir="+outDir)
+ }
+
+ if cfg.NoHeaderOrFooter {
+ args = append(args, "--no-header-footer")
+ } else {
+ a.Cfg.Logger.Warnln("asciidoctor parameter NoHeaderOrFooter is expected for correct html rendering")
+ }
+
+ if cfg.SectionNumbers {
+ args = append(args, "--section-numbers")
+ }
+
+ if cfg.Verbose {
+ args = append(args, "--verbose")
+ }
+
+ if cfg.Trace {
+ args = append(args, "--trace")
+ }
+
+ args = a.AppendArg(args, "--failure-level", cfg.FailureLevel, asciidocext_config.CliDefault.FailureLevel, asciidocext_config.AllowedFailureLevel)
+
+ args = a.AppendArg(args, "--safe-mode", cfg.SafeMode, asciidocext_config.CliDefault.SafeMode, asciidocext_config.AllowedSafeMode)
+
+ return args
+}
+
+func (a *AsciidocConverter) AppendArg(args []string, option, value, defaultValue string, allowedValues map[string]bool) []string {
+ if value != defaultValue {
+ if allowedValues[value] {
+ args = append(args, option, value)
+ } else {
+ a.Cfg.Logger.Errorln("Unsupported asciidoctor value `" + value + "` for option " + option + " was passed in and will be ignored.")
+ }
+ }
+ return args
+}
+
+const asciiDocBinaryName = "asciidoctor"
+
+func HasAsciiDoc() bool {
+ return hexec.InPath(asciiDocBinaryName)
+}
+
+// extractTOC extracts the toc from the given src html.
+// It returns the html without the TOC, and the TOC data
+func (a *AsciidocConverter) extractTOC(src []byte) ([]byte, *tableofcontents.Fragments, error) {
+ var buf bytes.Buffer
+ buf.Write(src)
+ node, err := html.Parse(&buf)
+ if err != nil {
+ return nil, nil, err
+ }
+ var (
+ f func(*html.Node) bool
+ toc *tableofcontents.Fragments
+ toVisit []*html.Node
+ )
+ f = func(n *html.Node) bool {
+ if n.Type == html.ElementNode && n.Data == "div" && attr(n, "id") == "toc" {
+ toc = parseTOC(n)
+ if !a.Cfg.MarkupConfig().AsciidocExt.PreserveTOC {
+ n.Parent.RemoveChild(n)
+ }
+ return true
+ }
+ if n.FirstChild != nil {
+ toVisit = append(toVisit, n.FirstChild)
+ }
+ if n.NextSibling != nil && f(n.NextSibling) {
+ return true
+ }
+ for len(toVisit) > 0 {
+ nv := toVisit[0]
+ toVisit = toVisit[1:]
+ if f(nv) {
+ return true
+ }
+ }
+ return false
+ }
+ f(node)
+ if err != nil {
+ return nil, nil, err
+ }
+ buf.Reset()
+ err = html.Render(&buf, node)
+ if err != nil {
+ return nil, nil, err
+ }
+ // ltrim <html><head></head><body> and rtrim </body></html> which are added by html.Render
+ res := buf.Bytes()[25:]
+ res = res[:len(res)-14]
+ return res, toc, nil
+}
+
+// parseTOC returns a TOC root from the given toc Node
+func parseTOC(doc *html.Node) *tableofcontents.Fragments {
+ var (
+ toc tableofcontents.Builder
+ f func(*html.Node, int, int)
+ )
+ f = func(n *html.Node, row, level int) {
+ if n.Type == html.ElementNode {
+ switch n.Data {
+ case "ul":
+ if level == 0 {
+ row++
+ }
+ level++
+ f(n.FirstChild, row, level)
+ case "li":
+ for c := n.FirstChild; c != nil; c = c.NextSibling {
+ if c.Type != html.ElementNode || c.Data != "a" {
+ continue
+ }
+ href := attr(c, "href")[1:]
+ toc.AddAt(&tableofcontents.Heading{
+ Title: nodeContent(c),
+ ID: href,
+ }, row, level)
+ }
+ f(n.FirstChild, row, level)
+ }
+ }
+ if n.NextSibling != nil {
+ f(n.NextSibling, row, level)
+ }
+ }
+ f(doc.FirstChild, -1, 0)
+ return toc.Build()
+}
+
+func attr(node *html.Node, key string) string {
+ for _, a := range node.Attr {
+ if a.Key == key {
+ return a.Val
+ }
+ }
+ return ""
+}
+
+func nodeContent(node *html.Node) string {
+ var buf bytes.Buffer
+ for c := node.FirstChild; c != nil; c = c.NextSibling {
+ html.Render(&buf, c)
+ }
+ return buf.String()
+}
diff --git a/markup/converter/converter.go b/markup/converter/converter.go
index 544d4841a..7c4898592 100644
--- a/markup/converter/converter.go
+++ b/markup/converter/converter.go
@@ -30,15 +30,17 @@ import (
// ProviderConfig configures a new Provider.
type ProviderConfig struct {
- MarkupConfig markup_config.Config
-
- Cfg config.Provider // Site config
+ Conf config.AllProvider // Site config
ContentFs afero.Fs
Logger loggers.Logger
Exec *hexec.Exec
highlight.Highlighter
}
+func (p ProviderConfig) MarkupConfig() markup_config.Config {
+ return p.Conf.GetConfigSection("markup").(markup_config.Config)
+}
+
// ProviderProvider creates converter providers.
type ProviderProvider interface {
New(cfg ProviderConfig) (Provider, error)
diff --git a/markup/converter/hooks/hooks.go b/markup/converter/hooks/hooks.go
index 55d7c1127..5c7b9692d 100644
--- a/markup/converter/hooks/hooks.go
+++ b/markup/converter/hooks/hooks.go
@@ -31,6 +31,7 @@ type AttributesProvider interface {
Attributes() map[string]any
}
+// LinkContext is the context passed to a link render hook.
type LinkContext interface {
// The Page being rendered.
Page() any
@@ -48,6 +49,7 @@ type LinkContext interface {
PlainText() string
}
+// ImageLinkContext is the context passed to a image link render hook.
type ImageLinkContext interface {
LinkContext
diff --git a/markup/goldmark/convert.go b/markup/goldmark/convert.go
index efcfb7142..20bbfc210 100644
--- a/markup/goldmark/convert.go
+++ b/markup/goldmark/convert.go
@@ -54,7 +54,7 @@ func (p provide) New(cfg converter.ProviderConfig) (converter.Provider, error) {
cfg: cfg,
md: md,
sanitizeAnchorName: func(s string) string {
- return sanitizeAnchorNameString(s, cfg.MarkupConfig.Goldmark.Parser.AutoHeadingIDType)
+ return sanitizeAnchorNameString(s, cfg.MarkupConfig().Goldmark.Parser.AutoHeadingIDType)
},
}, nil
}), nil
@@ -75,8 +75,8 @@ func (c *goldmarkConverter) SanitizeAnchorName(s string) string {
}
func newMarkdown(pcfg converter.ProviderConfig) goldmark.Markdown {
- mcfg := pcfg.MarkupConfig
- cfg := pcfg.MarkupConfig.Goldmark
+ mcfg := pcfg.MarkupConfig()
+ cfg := mcfg.Goldmark
var rendererOptions []renderer.Option
if cfg.Renderer.HardWraps {
@@ -265,7 +265,7 @@ func (c *goldmarkConverter) Supports(feature identity.Identity) bool {
}
func (c *goldmarkConverter) newParserContext(rctx converter.RenderContext) *parserContext {
- ctx := parser.NewContext(parser.WithIDs(newIDFactory(c.cfg.MarkupConfig.Goldmark.Parser.AutoHeadingIDType)))
+ ctx := parser.NewContext(parser.WithIDs(newIDFactory(c.cfg.MarkupConfig().Goldmark.Parser.AutoHeadingIDType)))
ctx.Set(tocEnableKey, rctx.RenderTOC)
return &parserContext{
Context: ctx,
diff --git a/markup/goldmark/convert_test.go b/markup/goldmark/convert_test.go
index e92c651fc..05279cd2c 100644
--- a/markup/goldmark/convert_test.go
+++ b/markup/goldmark/convert_test.go
@@ -1,4 +1,4 @@
-// Copyright 2019 The Hugo Authors. All rights reserved.
+// Copyright 2023 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -11,37 +11,52 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package goldmark
+package goldmark_test
import (
"fmt"
"strings"
"testing"
+ "github.com/pelletier/go-toml/v2"
"github.com/spf13/cast"
+ "github.com/gohugoio/hugo/config"
+ "github.com/gohugoio/hugo/config/testconfig"
"github.com/gohugoio/hugo/markup/converter/hooks"
- "github.com/gohugoio/hugo/markup/goldmark/goldmark_config"
+ "github.com/gohugoio/hugo/markup/goldmark"
"github.com/gohugoio/hugo/markup/highlight"
"github.com/gohugoio/hugo/markup/markup_config"
"github.com/gohugoio/hugo/common/loggers"
+ "github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/markup/converter"
qt "github.com/frankban/quicktest"
)
-func convert(c *qt.C, mconf markup_config.Config, content string) converter.ResultRender {
- p, err := Provider.New(
- converter.ProviderConfig{
- MarkupConfig: mconf,
- Logger: loggers.NewErrorLogger(),
- },
+var cfgStrHighlichgtNoClasses = `
+[markup]
+[markup.highlight]
+noclasses=false
+`
+
+func convert(c *qt.C, conf config.AllProvider, content string) converter.ResultRender {
+ pconf := converter.ProviderConfig{
+ Logger: loggers.NewErrorLogger(),
+ Conf: conf,
+ }
+
+ p, err := goldmark.Provider.New(
+ pconf,
)
c.Assert(err, qt.IsNil)
+
+ mconf := pconf.MarkupConfig()
+
h := highlight.New(mconf.Highlight)
getRenderer := func(t hooks.RendererType, id any) any {
@@ -140,11 +155,17 @@ description
// Code fences
content = strings.Replace(content, "§§§", "```", -1)
- mconf := markup_config.Default
- mconf.Highlight.NoClasses = false
- mconf.Goldmark.Renderer.Unsafe = true
- b := convert(c, mconf, content)
+ cfg := config.FromTOMLConfigString(`
+[markup]
+[markup.highlight]
+noClasses = false
+[markup.goldmark.renderer]
+unsafe = true
+
+`)
+
+ b := convert(c, testconfig.GetTestConfig(nil, cfg), content)
got := string(b.Bytes())
fmt.Println(got)
@@ -193,9 +214,17 @@ func TestConvertAutoIDAsciiOnly(t *testing.T) {
content := `
## God is Good: 神真美好
`
- mconf := markup_config.Default
- mconf.Goldmark.Parser.AutoHeadingIDType = goldmark_config.AutoHeadingIDTypeGitHubAscii
- b := convert(c, mconf, content)
+
+ cfg := config.FromTOMLConfigString(`
+[markup]
+[markup.goldmark]
+[markup.goldmark.parser]
+autoHeadingIDType = 'github-ascii'
+
+`)
+
+ b := convert(c, testconfig.GetTestConfig(nil, cfg), content)
+
got := string(b.Bytes())
c.Assert(got, qt.Contains, "<h2 id=\"god-is-good-\">")
@@ -208,9 +237,16 @@ func TestConvertAutoIDBlackfriday(t *testing.T) {
## Let's try this, shall we?
`
- mconf := markup_config.Default
- mconf.Goldmark.Parser.AutoHeadingIDType = goldmark_config.AutoHeadingIDTypeBlackfriday
- b := convert(c, mconf, content)
+
+ cfg := config.FromTOMLConfigString(`
+[markup]
+[markup.goldmark]
+[markup.goldmark.parser]
+autoHeadingIDType = 'blackfriday'
+`)
+
+ b := convert(c, testconfig.GetTestConfig(nil, cfg), content)
+
got := string(b.Bytes())
c.Assert(got, qt.Contains, "<h2 id=\"let-s-try-this-shall-we\">")
@@ -356,7 +392,13 @@ func TestConvertAttributes(t *testing.T) {
if test.withConfig != nil {
test.withConfig(&mconf)
}
- b := convert(c, mconf, test.input)
+ data, err := toml.Marshal(mconf)
+ c.Assert(err, qt.IsNil)
+ m := maps.Params{
+ "markup": config.FromTOMLConfigString(string(data)).Get(""),
+ }
+ conf := testconfig.GetTestConfig(nil, config.NewFrom(m))
+ b := convert(c, conf, test.input)
got := string(b.Bytes())
for _, s := range cast.ToStringSlice(test.expect) {
@@ -378,7 +420,7 @@ func TestConvertIssues(t *testing.T) {
</custom-element>
`
- b := convert(c, mconf, input)
+ b := convert(c, unsafeConf(), input)
got := string(b.Bytes())
c.Assert(got, qt.Contains, "<custom-element>\n <div>This will be \"slotted\" into the custom element.</div>\n</custom-element>\n")
@@ -395,18 +437,18 @@ LINE4
LINE5
`
- convertForConfig := func(c *qt.C, conf highlight.Config, code, language string) string {
- mconf := markup_config.Default
- mconf.Highlight = conf
-
- p, err := Provider.New(
- converter.ProviderConfig{
- MarkupConfig: mconf,
- Logger: loggers.NewErrorLogger(),
- },
+ convertForConfig := func(c *qt.C, confStr, code, language string) string {
+ cfg := config.FromTOMLConfigString(confStr)
+ conf := testconfig.GetTestConfig(nil, cfg)
+ pcfg := converter.ProviderConfig{
+ Conf: conf,
+ Logger: loggers.NewErrorLogger(),
+ }
+ p, err := goldmark.Provider.New(
+ pcfg,
)
- h := highlight.New(conf)
+ h := highlight.New(pcfg.MarkupConfig().Highlight)
getRenderer := func(t hooks.RendererType, id any) any {
if t == hooks.CodeBlockRendererType {
@@ -427,75 +469,92 @@ LINE5
}
c.Run("Basic", func(c *qt.C) {
- cfg := highlight.DefaultConfig
- cfg.NoClasses = false
+ confStr := `
+[markup]
+[markup.highlight]
+noclasses=false
+`
- result := convertForConfig(c, cfg, `echo "Hugo Rocks!"`, "bash")
+ result := convertForConfig(c, confStr, `echo "Hugo Rocks!"`, "bash")
// TODO(bep) there is a whitespace mismatch (\n) between this and the highlight template func.
c.Assert(result, qt.Equals, "<div class=\"highlight\"><pre tabindex=\"0\" class=\"chroma\"><code class=\"language-bash\" data-lang=\"bash\"><span class=\"line\"><span class=\"cl\"><span class=\"nb\">echo</span> <span class=\"s2\">&#34;Hugo Rocks!&#34;</span>\n</span></span></code></pre></div>")
- result = convertForConfig(c, cfg, `echo "Hugo Rocks!"`, "unknown")
+ result = convertForConfig(c, confStr, `echo "Hugo Rocks!"`, "unknown")
c.Assert(result, qt.Equals, "<pre tabindex=\"0\"><code class=\"language-unknown\" data-lang=\"unknown\">echo &#34;Hugo Rocks!&#34;\n</code></pre>")
})
c.Run("Highlight lines, default config", func(c *qt.C) {
- cfg := highlight.DefaultConfig
- cfg.NoClasses = false
- result := convertForConfig(c, cfg, lines, `bash {linenos=table,hl_lines=[2 "4-5"],linenostart=3}`)
+ result := convertForConfig(c, cfgStrHighlichgtNoClasses, lines, `bash {linenos=table,hl_lines=[2 "4-5"],linenostart=3}`)
c.Assert(result, qt.Contains, "<div class=\"highlight\"><div class=\"chroma\">\n<table class=\"lntable\"><tr><td class=\"lntd\">\n<pre tabindex=\"0\" class=\"chroma\"><code><span class")
c.Assert(result, qt.Contains, "<span class=\"hl\"><span class=\"lnt\">4")
- result = convertForConfig(c, cfg, lines, "bash {linenos=inline,hl_lines=[2]}")
+ result = convertForConfig(c, cfgStrHighlichgtNoClasses, lines, "bash {linenos=inline,hl_lines=[2]}")
c.Assert(result, qt.Contains, "<span class=\"ln\">2</span><span class=\"cl\">LINE2\n</span></span>")
c.Assert(result, qt.Not(qt.Contains), "<table")
- result = convertForConfig(c, cfg, lines, "bash {linenos=true,hl_lines=[2]}")
+ result = convertForConfig(c, cfgStrHighlichgtNoClasses, lines, "bash {linenos=true,hl_lines=[2]}")
c.Assert(result, qt.Contains, "<table")
c.Assert(result, qt.Contains, "<span class=\"hl\"><span class=\"lnt\">2\n</span>")
})
c.Run("Highlight lines, linenumbers default on", func(c *qt.C) {
- cfg := highlight.DefaultConfig
- cfg.NoClasses = false
- cfg.LineNos = true
+ confStr := `
+[markup]
+[markup.highlight]
+noclasses=false
+linenos=true
+`
- result := convertForConfig(c, cfg, lines, "bash")
+ result := convertForConfig(c, confStr, lines, "bash")
c.Assert(result, qt.Contains, "<span class=\"lnt\">2\n</span>")
- result = convertForConfig(c, cfg, lines, "bash {linenos=false,hl_lines=[2]}")
+ result = convertForConfig(c, confStr, lines, "bash {linenos=false,hl_lines=[2]}")
c.Assert(result, qt.Not(qt.Contains), "class=\"lnt\"")
})
c.Run("Highlight lines, linenumbers default on, linenumbers in table default off", func(c *qt.C) {
- cfg := highlight.DefaultConfig
- cfg.NoClasses = false
- cfg.LineNos = true
- cfg.LineNumbersInTable = false
+ confStr := `
+[markup]
+[markup.highlight]
+noClasses = false
+lineNos = true
+lineNumbersInTable = false
+`
- result := convertForConfig(c, cfg, lines, "bash")
+ result := convertForConfig(c, confStr, lines, "bash")
c.Assert(result, qt.Contains, "<span class=\"ln\">2</span><span class=\"cl\">LINE2\n</span>")
- result = convertForConfig(c, cfg, lines, "bash {linenos=table}")
+ result = convertForConfig(c, confStr, lines, "bash {linenos=table}")
c.Assert(result, qt.Contains, "<span class=\"lnt\">1\n</span>")
})
c.Run("No language", func(c *qt.C) {
+ confStr := `
+[markup]
+[markup.highlight]
+noClasses = false
+lineNos = true
+lineNumbersInTable = false
+`
cfg := highlight.DefaultConfig
cfg.NoClasses = false
cfg.LineNos = true
cfg.LineNumbersInTable = false
- result := convertForConfig(c, cfg, lines, "")
+ result := convertForConfig(c, confStr, lines, "")
c.Assert(result, qt.Contains, "<pre tabindex=\"0\"><code>LINE1\n")
})
c.Run("No language, guess syntax", func(c *qt.C) {
- cfg := highlight.DefaultConfig
- cfg.NoClasses = false
- cfg.GuessSyntax = true
- cfg.LineNos = true
- cfg.LineNumbersInTable = false
+ confStr := `
+[markup]
+[markup.highlight]
+noClasses = false
+lineNos = true
+lineNumbersInTable = false
+guessSyntax = true
+`
- result := convertForConfig(c, cfg, lines, "")
+ result := convertForConfig(c, confStr, lines, "")
c.Assert(result, qt.Contains, "<span class=\"ln\">2</span><span class=\"cl\">LINE2\n</span></span>")
})
}
@@ -506,11 +565,41 @@ func TestTypographerConfig(t *testing.T) {
content := `
A "quote" and 'another quote' and a "quote with a 'nested' quote" and a 'quote with a "nested" quote' and an ellipsis...
`
- mconf := markup_config.Default
- mconf.Goldmark.Extensions.Typographer.LeftDoubleQuote = "&laquo;"
- mconf.Goldmark.Extensions.Typographer.RightDoubleQuote = "&raquo;"
- b := convert(c, mconf, content)
+
+ confStr := `
+[markup]
+[markup.goldmark]
+[markup.goldmark.extensions]
+[markup.goldmark.extensions.typographer]
+leftDoubleQuote = "&laquo;"
+rightDoubleQuote = "&raquo;"
+`
+
+ cfg := config.FromTOMLConfigString(confStr)
+ conf := testconfig.GetTestConfig(nil, cfg)
+
+ b := convert(c, conf, content)
got := string(b.Bytes())
c.Assert(got, qt.Contains, "<p>A &laquo;quote&raquo; and &lsquo;another quote&rsquo; and a &laquo;quote with a &rsquo;nested&rsquo; quote&raquo; and a &lsquo;quote with a &laquo;nested&raquo; quote&rsquo; and an ellipsis&hellip;</p>\n")
}
+
+func unsafeConf() config.AllProvider {
+ cfg := config.FromTOMLConfigString(`
+[markup]
+[markup.goldmark.renderer]
+unsafe = true
+`)
+ return testconfig.GetTestConfig(nil, cfg)
+
+}
+
+func safeConf() config.AllProvider {
+ cfg := config.FromTOMLConfigString(`
+[markup]
+[markup.goldmark.renderer]
+unsafe = false
+`)
+ return testconfig.GetTestConfig(nil, cfg)
+
+}
diff --git a/markup/goldmark/toc_test.go b/markup/goldmark/toc_test.go
index 947f58a36..78811cfb4 100644
--- a/markup/goldmark/toc_test.go
+++ b/markup/goldmark/toc_test.go
@@ -1,4 +1,4 @@
-// Copyright 2019 The Hugo Authors. All rights reserved.
+// Copyright 2023 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -11,15 +11,15 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-// Package goldmark converts Markdown to HTML using Goldmark.
-package goldmark
+package goldmark_test
import (
"strings"
"testing"
+ "github.com/gohugoio/hugo/config/testconfig"
"github.com/gohugoio/hugo/markup/converter/hooks"
- "github.com/gohugoio/hugo/markup/markup_config"
+ "github.com/gohugoio/hugo/markup/goldmark"
"github.com/gohugoio/hugo/common/loggers"
@@ -53,10 +53,10 @@ And then some.
#### First H4
`
- p, err := Provider.New(
+ p, err := goldmark.Provider.New(
converter.ProviderConfig{
- MarkupConfig: markup_config.Default,
- Logger: loggers.NewErrorLogger(),
+ Conf: testconfig.GetTestConfig(nil, nil),
+ Logger: loggers.NewErrorLogger(),
})
c.Assert(err, qt.IsNil)
conv, err := p.New(converter.DocumentContext{})
@@ -83,23 +83,15 @@ And then some.
func TestEscapeToc(t *testing.T) {
c := qt.New(t)
- defaultConfig := markup_config.Default
-
- safeConfig := defaultConfig
- unsafeConfig := defaultConfig
-
- safeConfig.Goldmark.Renderer.Unsafe = false
- unsafeConfig.Goldmark.Renderer.Unsafe = true
-
- safeP, _ := Provider.New(
+ safeP, _ := goldmark.Provider.New(
converter.ProviderConfig{
- MarkupConfig: safeConfig,
- Logger: loggers.NewErrorLogger(),
+ Conf: safeConf(),
+ Logger: loggers.NewErrorLogger(),
})
- unsafeP, _ := Provider.New(
+ unsafeP, _ := goldmark.Provider.New(
converter.ProviderConfig{
- MarkupConfig: unsafeConfig,
- Logger: loggers.NewErrorLogger(),
+ Conf: unsafeConf(),
+ Logger: loggers.NewErrorLogger(),
})
safeConv, _ := safeP.New(converter.DocumentContext{})
unsafeConv, _ := unsafeP.New(converter.DocumentContext{})
diff --git a/markup/highlight/config.go b/markup/highlight/config.go
index b1f6d4603..ca065fd2d 100644
--- a/markup/highlight/config.go
+++ b/markup/highlight/config.go
@@ -84,7 +84,7 @@ type Config struct {
GuessSyntax bool
}
-func (cfg Config) ToHTMLOptions() []html.Option {
+func (cfg Config) toHTMLOptions() []html.Option {
var lineAnchors string
if cfg.LineAnchors != "" {
lineAnchors = cfg.LineAnchors + "-"
diff --git a/markup/highlight/highlight.go b/markup/highlight/highlight.go
index 410beb740..cb0d578de 100644
--- a/markup/highlight/highlight.go
+++ b/markup/highlight/highlight.go
@@ -148,10 +148,12 @@ func (h chromaHighlighter) IsDefaultCodeBlockRenderer() bool {
var id = identity.NewPathIdentity("chroma", "highlight")
+// GetIdentify is for internal use.
func (h chromaHighlighter) GetIdentity() identity.Identity {
return id
}
+// HightlightResult holds the result of an highlighting operation.
type HightlightResult struct {
innerLow int
innerHigh int
@@ -211,7 +213,7 @@ func highlight(fw hugio.FlexiWriter, code, lang string, attributes []attributes.
writeDivStart(w, attributes)
}
- options := cfg.ToHTMLOptions()
+ options := cfg.toHTMLOptions()
var wrapper html.PreWrapper
if cfg.Hl_inline {
diff --git a/markup/markup.go b/markup/markup.go
index aefa50867..ebd86f38f 100644
--- a/markup/markup.go
+++ b/markup/markup.go
@@ -35,17 +35,13 @@ import (
func NewConverterProvider(cfg converter.ProviderConfig) (ConverterProvider, error) {
converters := make(map[string]converter.Provider)
- markupConfig, err := markup_config.Decode(cfg.Cfg)
- if err != nil {
- return nil, err
- }
+ mcfg := cfg.MarkupConfig()
if cfg.Highlighter == nil {
- cfg.Highlighter = highlight.New(markupConfig.Highlight)
+ cfg.Highlighter = highlight.New(mcfg.Highlight)
}
- cfg.MarkupConfig = markupConfig
- defaultHandler := cfg.MarkupConfig.DefaultMarkdownHandler
+ defaultHandler := mcfg.DefaultMarkdownHandler
var defaultFound bool
add := func(p converter.ProviderProvider, aliases ...string) error {
@@ -123,7 +119,7 @@ func (r *converterRegistry) GetHighlighter() highlight.Highlighter {
}
func (r *converterRegistry) GetMarkupConfig() markup_config.Config {
- return r.config.MarkupConfig
+ return r.config.MarkupConfig()
}
func addConverter(m map[string]converter.Provider, c converter.Provider, aliases ...string) {
diff --git a/markup/markup_config/config.go b/markup/markup_config/config.go
index 60446b9bc..0793669a7 100644
--- a/markup/markup_config/config.go
+++ b/markup/markup_config/config.go
@@ -28,14 +28,18 @@ import (
type Config struct {
// Default markdown handler for md/markdown extensions.
// Default is "goldmark".
- // Before Hugo 0.60 this was "blackfriday".
DefaultMarkdownHandler string
- Highlight highlight.Config
+ // The configuration used by code highlighters.
+ Highlight highlight.Config
+
+ // Table of contents configuration
TableOfContents tableofcontents.Config
- // Content renderers
- Goldmark goldmark_config.Config
+ // Configuration for the Goldmark markdown engine.
+ Goldmark goldmark_config.Config
+
+ // Configuration for the Asciidoc external markdown engine.
AsciidocExt asciidocext_config.Config
}
@@ -46,6 +50,8 @@ func Decode(cfg config.Provider) (conf Config, err error) {
if m == nil {
return
}
+ m = maps.CleanConfigStringMap(m)
+
normalizeConfig(m)
err = mapstructure.WeakDecode(m, &conf)
diff --git a/markup/markup_test.go b/markup/markup_test.go
index 5ec27c45c..5cf08758d 100644
--- a/markup/markup_test.go
+++ b/markup/markup_test.go
@@ -1,4 +1,4 @@
-// Copyright 2019 The Hugo Authors. All rights reserved.
+// Copyright 2023 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -11,20 +11,21 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package markup
+package markup_test
import (
"testing"
qt "github.com/frankban/quicktest"
- "github.com/gohugoio/hugo/config"
+ "github.com/gohugoio/hugo/config/testconfig"
+ "github.com/gohugoio/hugo/markup"
"github.com/gohugoio/hugo/markup/converter"
)
func TestConverterRegistry(t *testing.T) {
c := qt.New(t)
-
- r, err := NewConverterProvider(converter.ProviderConfig{Cfg: config.New()})
+ conf := testconfig.GetTestConfig(nil, nil)
+ r, err := markup.NewConverterProvider(converter.ProviderConfig{Conf: conf})
c.Assert(err, qt.IsNil)
c.Assert("goldmark", qt.Equals, r.GetMarkupConfig().DefaultMarkdownHandler)
diff --git a/markup/org/convert_test.go b/markup/org/convert_test.go
index e3676fc34..08841b2d7 100644
--- a/markup/org/convert_test.go
+++ b/markup/org/convert_test.go
@@ -1,4 +1,4 @@
-// Copyright 2019 The Hugo Authors. All rights reserved.
+// Copyright 2023 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -11,25 +11,26 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package org
+package org_test
import (
"testing"
- "github.com/gohugoio/hugo/config"
-
"github.com/gohugoio/hugo/common/loggers"
+ "github.com/gohugoio/hugo/config/testconfig"
+ "github.com/spf13/afero"
"github.com/gohugoio/hugo/markup/converter"
+ "github.com/gohugoio/hugo/markup/org"
qt "github.com/frankban/quicktest"
)
func TestConvert(t *testing.T) {
c := qt.New(t)
- p, err := Provider.New(converter.ProviderConfig{
+ p, err := org.Provider.New(converter.ProviderConfig{
Logger: loggers.NewErrorLogger(),
- Cfg: config.New(),
+ Conf: testconfig.GetTestConfig(afero.NewMemMapFs(), nil),
})
c.Assert(err, qt.IsNil)
conv, err := p.New(converter.DocumentContext{})
diff --git a/markup/pandoc/convert.go b/markup/pandoc/convert.go
index 386a9ff26..eaa9bfb6a 100644
--- a/markup/pandoc/convert.go
+++ b/markup/pandoc/convert.go
@@ -18,9 +18,9 @@ import (
"github.com/gohugoio/hugo/common/hexec"
"github.com/gohugoio/hugo/htesting"
"github.com/gohugoio/hugo/identity"
- "github.com/gohugoio/hugo/markup/internal"
"github.com/gohugoio/hugo/markup/converter"
+ "github.com/gohugoio/hugo/markup/internal"
)
// Provider is the package entry point.
diff --git a/markup/rst/convert.go b/markup/rst/convert.go
index 59ce38408..b7aa5a2ce 100644
--- a/markup/rst/convert.go
+++ b/markup/rst/convert.go
@@ -22,9 +22,9 @@ import (
"github.com/gohugoio/hugo/htesting"
"github.com/gohugoio/hugo/identity"
- "github.com/gohugoio/hugo/markup/internal"
"github.com/gohugoio/hugo/markup/converter"
+ "github.com/gohugoio/hugo/markup/internal"
)
// Provider is the package entry point.
diff --git a/markup/tableofcontents/tableofcontents.go b/markup/tableofcontents/tableofcontents.go
index bd0aaa801..774b5c6cd 100644
--- a/markup/tableofcontents/tableofcontents.go
+++ b/markup/tableofcontents/tableofcontents.go
@@ -237,6 +237,7 @@ var DefaultConfig = Config{
type Config struct {
// Heading start level to include in the table of contents, starting
// at h1 (inclusive).
+ // <docsmeta>{ "identifiers": ["h1"] }</docsmeta>
StartLevel int
// Heading end level, inclusive, to include in the table of contents.