mirror of
https://github.com/gohugoio/hugo.git
synced 2025-08-22 21:42:50 +02:00
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:
@@ -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")
|
||||
|
@@ -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)
|
||||
|
274
markup/asciidocext/internal/converter.go
Normal file
274
markup/asciidocext/internal/converter.go
Normal 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()
|
||||
}
|
@@ -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)
|
||||
|
@@ -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
|
||||
|
||||
|
@@ -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,
|
||||
|
@@ -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\">"Hugo Rocks!"</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 "Hugo Rocks!"\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 = "«"
|
||||
mconf.Goldmark.Extensions.Typographer.RightDoubleQuote = "»"
|
||||
b := convert(c, mconf, content)
|
||||
|
||||
confStr := `
|
||||
[markup]
|
||||
[markup.goldmark]
|
||||
[markup.goldmark.extensions]
|
||||
[markup.goldmark.extensions.typographer]
|
||||
leftDoubleQuote = "«"
|
||||
rightDoubleQuote = "»"
|
||||
`
|
||||
|
||||
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 «quote» and ‘another quote’ and a «quote with a ’nested’ quote» and a ‘quote with a «nested» quote’ and an ellipsis…</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)
|
||||
|
||||
}
|
||||
|
@@ -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{})
|
||||
|
@@ -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 + "-"
|
||||
|
@@ -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 {
|
||||
|
@@ -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) {
|
||||
|
@@ -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)
|
||||
|
@@ -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)
|
||||
|
@@ -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{})
|
||||
|
@@ -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.
|
||||
|
@@ -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.
|
||||
|
@@ -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.
|
||||
|
Reference in New Issue
Block a user