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
This commit is contained in:
Bjørn Erik Pedersen
2023-01-04 18:24:36 +01:00
parent 6aededf6b4
commit 241b21b0fd
337 changed files with 13377 additions and 14898 deletions

View File

@@ -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")

View File

@@ -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)

View File

@@ -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()
}

View File

@@ -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)

View File

@@ -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

View File

@@ -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,

View File

@@ -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)
}

View File

@@ -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{})

View File

@@ -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 + "-"

View File

@@ -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 {

View File

@@ -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) {

View File

@@ -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)

View File

@@ -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)

View File

@@ -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{})

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.