mirror of
https://github.com/gohugoio/hugo.git
synced 2026-08-24 15:28:54 +00:00
markup/asciidocext: Improve Asciidoctor integration
Fixes an issue where improper attribute derivation from the page's relative permalink caused failures with `outdir`, `imagesoutdir`, and `imagesdir` when `markup.asciidocext.workingFolderCurrent` is enabled. The updated logic now correctly handles: - Multi-byte characters - Multilingual multi-host sites - Site builds from a subdirectory - Pages using ugly URLs Supports diagram caching as implemented in v3.1.0 of the asciidoctor-diagram extension: - Enables caching by default - Sets default cache location to the compiled value of caches.misc.dir Reduces duration of integration tests by: - Generating GoAT diagrams instead of Ditaa diagrams - Taking advantage of asciidoctor-diagram caching Closes #9202 Closes #10183 Closes #10473 Closes #14160
This commit is contained in:
committed by
Bjørn Erik Pedersen
parent
34b0c15a54
commit
3d21b0687b
File diff suppressed because it is too large
Load Diff
@@ -29,21 +29,38 @@ 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 &internal.AsciidocConverter{
|
||||
return &internal.AsciiDocConverter{
|
||||
Ctx: ctx,
|
||||
Cfg: cfg,
|
||||
}, nil
|
||||
}), nil
|
||||
}
|
||||
|
||||
// Supports returns whether Asciidoctor is installed on this computer.
|
||||
func Supports() bool {
|
||||
hasBin := internal.HasAsciiDoc()
|
||||
// Supports reports whether the AsciiDoc converter is installed. Only used in
|
||||
// tests.
|
||||
func Supports() (bool, error) {
|
||||
hasAsciiDoc, err := internal.HasAsciiDoc()
|
||||
if htesting.SupportsAll() {
|
||||
if !hasBin {
|
||||
panic("asciidoctor not installed")
|
||||
if !hasAsciiDoc {
|
||||
panic(err)
|
||||
}
|
||||
return true
|
||||
return true, nil
|
||||
|
||||
}
|
||||
return hasBin
|
||||
|
||||
return hasAsciiDoc, err
|
||||
}
|
||||
|
||||
// SupportsGoATDiagrams reports whether the AsciiDoc converter can render GoAT
|
||||
// diagrams. Only used in tests.
|
||||
func SupportsGoATDiagrams() (bool, error) {
|
||||
supportsGoATDiagrams, err := internal.CanRenderGoATDiagrams()
|
||||
if htesting.SupportsAll() {
|
||||
if !supportsGoATDiagrams {
|
||||
panic(err)
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return supportsGoATDiagrams, err
|
||||
}
|
||||
|
||||
@@ -11,14 +11,14 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package asciidocext converts AsciiDoc to HTML using Asciidoctor
|
||||
// Package asciidocext converts AsciiDoc to HTML using the Asciidoctor
|
||||
// external binary. The `asciidoc` module is reserved for a future golang
|
||||
// implementation.
|
||||
|
||||
package asciidocext_test
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/gohugoio/hugo/common/collections"
|
||||
@@ -28,6 +28,7 @@ import (
|
||||
"github.com/gohugoio/hugo/config/security"
|
||||
"github.com/gohugoio/hugo/config/testconfig"
|
||||
"github.com/gohugoio/hugo/markup/asciidocext"
|
||||
"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/markup_config"
|
||||
@@ -36,6 +37,24 @@ import (
|
||||
qt "github.com/frankban/quicktest"
|
||||
)
|
||||
|
||||
var defaultAsciiDocExtConfigAsJSON []byte
|
||||
|
||||
func init() {
|
||||
var err error
|
||||
defaultAsciiDocExtConfigAsJSON, err = json.Marshal(markup_config.Default.AsciiDocExt)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func resetDefaultAsciiDocExtConfig() {
|
||||
markup_config.Default.AsciiDocExt = asciidocext_config.Config{}
|
||||
err := json.Unmarshal(defaultAsciiDocExtConfigAsJSON, &markup_config.Default.AsciiDocExt)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAsciidoctorDefaultArgs(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
cfg := config.New()
|
||||
@@ -52,24 +71,30 @@ func TestAsciidoctorDefaultArgs(t *testing.T) {
|
||||
conv, err := p.New(converter.DocumentContext{})
|
||||
c.Assert(err, qt.IsNil)
|
||||
|
||||
ac := conv.(*internal.AsciidocConverter)
|
||||
ac := conv.(*internal.AsciiDocConverter)
|
||||
c.Assert(ac, qt.Not(qt.IsNil))
|
||||
|
||||
args := ac.ParseArgs(converter.DocumentContext{})
|
||||
args, err := ac.ParseArgs(converter.DocumentContext{})
|
||||
c.Assert(err, qt.IsNil)
|
||||
expected := []string{"--no-header-footer"}
|
||||
c.Assert(args, qt.DeepEquals, expected)
|
||||
}
|
||||
|
||||
func TestAsciidoctorNonDefaultArgs(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
|
||||
t.Cleanup(func() {
|
||||
resetDefaultAsciiDocExtConfig()
|
||||
})
|
||||
|
||||
mconf := markup_config.Default
|
||||
mconf.AsciidocExt.Backend = "manpage"
|
||||
mconf.AsciidocExt.NoHeaderOrFooter = false
|
||||
mconf.AsciidocExt.SafeMode = "safe"
|
||||
mconf.AsciidocExt.SectionNumbers = true
|
||||
mconf.AsciidocExt.Verbose = true
|
||||
mconf.AsciidocExt.Trace = false
|
||||
mconf.AsciidocExt.FailureLevel = "warn"
|
||||
mconf.AsciiDocExt.Backend = "manpage"
|
||||
mconf.AsciiDocExt.NoHeaderOrFooter = false
|
||||
mconf.AsciiDocExt.SafeMode = "safe"
|
||||
mconf.AsciiDocExt.SectionNumbers = true
|
||||
mconf.AsciiDocExt.Verbose = true
|
||||
mconf.AsciiDocExt.Trace = false
|
||||
mconf.AsciiDocExt.FailureLevel = "warn"
|
||||
|
||||
conf := testconfig.GetTestConfigSectionFromStruct("markup", mconf)
|
||||
|
||||
@@ -84,22 +109,28 @@ func TestAsciidoctorNonDefaultArgs(t *testing.T) {
|
||||
conv, err := p.New(converter.DocumentContext{})
|
||||
c.Assert(err, qt.IsNil)
|
||||
|
||||
ac := conv.(*internal.AsciidocConverter)
|
||||
ac := conv.(*internal.AsciiDocConverter)
|
||||
c.Assert(ac, qt.Not(qt.IsNil))
|
||||
|
||||
args := ac.ParseArgs(converter.DocumentContext{})
|
||||
args, err := ac.ParseArgs(converter.DocumentContext{})
|
||||
c.Assert(err, qt.IsNil)
|
||||
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)
|
||||
|
||||
t.Cleanup(func() {
|
||||
resetDefaultAsciiDocExtConfig()
|
||||
})
|
||||
|
||||
mconf := markup_config.Default
|
||||
mconf.AsciidocExt.Backend = "disallowed-backend"
|
||||
mconf.AsciidocExt.Extensions = []string{"./disallowed-extension"}
|
||||
mconf.AsciidocExt.Attributes = map[string]any{"outdir": "disallowed-attribute"}
|
||||
mconf.AsciidocExt.SafeMode = "disallowed-safemode"
|
||||
mconf.AsciidocExt.FailureLevel = "disallowed-failurelevel"
|
||||
mconf.AsciiDocExt.Backend = "disallowed-backend"
|
||||
mconf.AsciiDocExt.Extensions = []string{"./disallowed-extension"}
|
||||
mconf.AsciiDocExt.Attributes = map[string]any{"outdir": "disallowed-attribute"}
|
||||
mconf.AsciiDocExt.SafeMode = "disallowed-safemode"
|
||||
mconf.AsciiDocExt.FailureLevel = "disallowed-failurelevel"
|
||||
|
||||
conf := testconfig.GetTestConfigSectionFromStruct("markup", mconf)
|
||||
|
||||
@@ -114,18 +145,24 @@ func TestAsciidoctorDisallowedArgs(t *testing.T) {
|
||||
conv, err := p.New(converter.DocumentContext{})
|
||||
c.Assert(err, qt.IsNil)
|
||||
|
||||
ac := conv.(*internal.AsciidocConverter)
|
||||
ac := conv.(*internal.AsciiDocConverter)
|
||||
c.Assert(ac, qt.Not(qt.IsNil))
|
||||
|
||||
args := ac.ParseArgs(converter.DocumentContext{})
|
||||
args, err := ac.ParseArgs(converter.DocumentContext{})
|
||||
c.Assert(err, qt.IsNil)
|
||||
expected := []string{"--no-header-footer"}
|
||||
c.Assert(args, qt.DeepEquals, expected)
|
||||
}
|
||||
|
||||
func TestAsciidoctorArbitraryExtension(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
|
||||
t.Cleanup(func() {
|
||||
resetDefaultAsciiDocExtConfig()
|
||||
})
|
||||
|
||||
mconf := markup_config.Default
|
||||
mconf.AsciidocExt.Extensions = []string{"arbitrary-extension"}
|
||||
mconf.AsciiDocExt.Extensions = []string{"arbitrary-extension"}
|
||||
conf := testconfig.GetTestConfigSectionFromStruct("markup", mconf)
|
||||
p, err := asciidocext.Provider.New(
|
||||
converter.ProviderConfig{
|
||||
@@ -138,10 +175,11 @@ func TestAsciidoctorArbitraryExtension(t *testing.T) {
|
||||
conv, err := p.New(converter.DocumentContext{})
|
||||
c.Assert(err, qt.IsNil)
|
||||
|
||||
ac := conv.(*internal.AsciidocConverter)
|
||||
ac := conv.(*internal.AsciiDocConverter)
|
||||
c.Assert(ac, qt.Not(qt.IsNil))
|
||||
|
||||
args := ac.ParseArgs(converter.DocumentContext{})
|
||||
args, err := ac.ParseArgs(converter.DocumentContext{})
|
||||
c.Assert(err, qt.IsNil)
|
||||
expected := []string{"-r", "arbitrary-extension", "--no-header-footer"}
|
||||
c.Assert(args, qt.DeepEquals, expected)
|
||||
}
|
||||
@@ -149,6 +187,10 @@ func TestAsciidoctorArbitraryExtension(t *testing.T) {
|
||||
func TestAsciidoctorDisallowedExtension(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
|
||||
t.Cleanup(func() {
|
||||
resetDefaultAsciiDocExtConfig()
|
||||
})
|
||||
|
||||
for _, disallowedExtension := range []string{
|
||||
`foo-bar//`,
|
||||
`foo-bar\\ `,
|
||||
@@ -159,7 +201,7 @@ func TestAsciidoctorDisallowedExtension(t *testing.T) {
|
||||
`foo.bar`,
|
||||
} {
|
||||
mconf := markup_config.Default
|
||||
mconf.AsciidocExt.Extensions = []string{disallowedExtension}
|
||||
mconf.AsciiDocExt.Extensions = []string{disallowedExtension}
|
||||
conf := testconfig.GetTestConfigSectionFromStruct("markup", mconf)
|
||||
p, err := asciidocext.Provider.New(
|
||||
converter.ProviderConfig{
|
||||
@@ -172,94 +214,23 @@ func TestAsciidoctorDisallowedExtension(t *testing.T) {
|
||||
conv, err := p.New(converter.DocumentContext{})
|
||||
c.Assert(err, qt.IsNil)
|
||||
|
||||
ac := conv.(*internal.AsciidocConverter)
|
||||
ac := conv.(*internal.AsciiDocConverter)
|
||||
c.Assert(ac, qt.Not(qt.IsNil))
|
||||
|
||||
args := ac.ParseArgs(converter.DocumentContext{})
|
||||
args, err := ac.ParseArgs(converter.DocumentContext{})
|
||||
c.Assert(err, qt.IsNil)
|
||||
expected := []string{"--no-header-footer"}
|
||||
c.Assert(args, qt.DeepEquals, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAsciidoctorWorkingFolderCurrent(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
cfg := config.FromTOMLConfigString(`
|
||||
[markup]
|
||||
[markup.asciidocext]
|
||||
workingFolderCurrent = true
|
||||
trace = false
|
||||
`)
|
||||
|
||||
conf := testconfig.GetTestConfig(afero.NewMemMapFs(), cfg)
|
||||
|
||||
p, err := asciidocext.Provider.New(
|
||||
converter.ProviderConfig{
|
||||
Conf: conf,
|
||||
Logger: loggers.NewDefault(),
|
||||
},
|
||||
)
|
||||
c.Assert(err, qt.IsNil)
|
||||
|
||||
ctx := converter.DocumentContext{Filename: "/tmp/hugo_asciidoc_ddd/docs/chapter2/index.adoc", DocumentName: "chapter2/index.adoc"}
|
||||
conv, err := p.New(ctx)
|
||||
c.Assert(err, qt.IsNil)
|
||||
|
||||
ac := conv.(*internal.AsciidocConverter)
|
||||
c.Assert(ac, qt.Not(qt.IsNil))
|
||||
|
||||
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=.*chapter2`)
|
||||
c.Assert(args[4], qt.Equals, "--no-header-footer")
|
||||
}
|
||||
|
||||
func TestAsciidoctorWorkingFolderCurrentAndExtensions(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
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{
|
||||
Conf: conf,
|
||||
Logger: loggers.NewDefault(),
|
||||
},
|
||||
)
|
||||
c.Assert(err, qt.IsNil)
|
||||
|
||||
conv, err := p.New(converter.DocumentContext{})
|
||||
c.Assert(err, qt.IsNil)
|
||||
|
||||
ac := conv.(*internal.AsciidocConverter)
|
||||
c.Assert(ac, qt.Not(qt.IsNil))
|
||||
|
||||
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")
|
||||
c.Assert(args[2], qt.Equals, "-r")
|
||||
c.Assert(args[3], qt.Equals, "asciidoctor-html5s")
|
||||
c.Assert(args[4], qt.Equals, "-r")
|
||||
c.Assert(args[5], qt.Equals, "asciidoctor-diagram")
|
||||
c.Assert(args[6], qt.Equals, "--base-dir")
|
||||
c.Assert(args[7], qt.Equals, ".")
|
||||
c.Assert(args[8], qt.Equals, "-a")
|
||||
c.Assert(args[9], qt.Contains, "outdir=")
|
||||
c.Assert(args[10], qt.Equals, "--no-header-footer")
|
||||
}
|
||||
|
||||
func TestAsciidoctorAttributes(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
|
||||
t.Cleanup(func() {
|
||||
resetDefaultAsciiDocExtConfig()
|
||||
})
|
||||
|
||||
cfg := config.FromTOMLConfigString(`
|
||||
[markup]
|
||||
[markup.asciidocext]
|
||||
@@ -282,7 +253,7 @@ my-attribute-false = false
|
||||
conv, err := p.New(converter.DocumentContext{})
|
||||
c.Assert(err, qt.IsNil)
|
||||
|
||||
ac := conv.(*internal.AsciidocConverter)
|
||||
ac := conv.(*internal.AsciiDocConverter)
|
||||
c.Assert(ac, qt.Not(qt.IsNil))
|
||||
|
||||
expectedValues := map[string]bool{
|
||||
@@ -292,7 +263,8 @@ my-attribute-false = false
|
||||
"'!my-attribute-false'": true,
|
||||
}
|
||||
|
||||
args := ac.ParseArgs(converter.DocumentContext{})
|
||||
args, err := ac.ParseArgs(converter.DocumentContext{})
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(len(args), qt.Equals, 9)
|
||||
c.Assert(args[0], qt.Equals, "-a")
|
||||
c.Assert(expectedValues[args[1]], qt.Equals, true)
|
||||
@@ -329,8 +301,8 @@ allow = ['asciidoctor']
|
||||
}
|
||||
|
||||
func TestConvert(t *testing.T) {
|
||||
if !asciidocext.Supports() {
|
||||
t.Skip("asciidoctor not installed")
|
||||
if ok, err := asciidocext.Supports(); !ok {
|
||||
t.Skip(err)
|
||||
}
|
||||
c := qt.New(t)
|
||||
|
||||
@@ -345,8 +317,8 @@ func TestConvert(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestTableOfContents(t *testing.T) {
|
||||
if !asciidocext.Supports() {
|
||||
t.Skip("asciidoctor not installed")
|
||||
if ok, err := asciidocext.Supports(); !ok {
|
||||
t.Skip(err)
|
||||
}
|
||||
c := qt.New(t)
|
||||
p := getProvider(c, "")
|
||||
@@ -387,8 +359,8 @@ testContent
|
||||
}
|
||||
|
||||
func TestTableOfContentsWithCode(t *testing.T) {
|
||||
if !asciidocext.Supports() {
|
||||
t.Skip("asciidoctor not installed")
|
||||
if ok, err := asciidocext.Supports(); !ok {
|
||||
t.Skip(err)
|
||||
}
|
||||
c := qt.New(t)
|
||||
p := getProvider(c, "")
|
||||
@@ -406,8 +378,8 @@ func TestTableOfContentsWithCode(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestTableOfContentsPreserveTOC(t *testing.T) {
|
||||
if !asciidocext.Supports() {
|
||||
t.Skip("asciidoctor not installed")
|
||||
if ok, err := asciidocext.Supports(); !ok {
|
||||
t.Skip(err)
|
||||
}
|
||||
c := qt.New(t)
|
||||
confStr := `
|
||||
|
||||
@@ -2,11 +2,17 @@ package internal
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/gohugoio/hugo/common/hexec"
|
||||
"github.com/gohugoio/hugo/common/loggers"
|
||||
"github.com/gohugoio/hugo/config/security"
|
||||
"github.com/gohugoio/hugo/identity"
|
||||
"github.com/gohugoio/hugo/langs"
|
||||
"github.com/gohugoio/hugo/markup/asciidocext/asciidocext_config"
|
||||
"github.com/gohugoio/hugo/markup/converter"
|
||||
"github.com/gohugoio/hugo/markup/internal"
|
||||
@@ -15,27 +21,52 @@ import (
|
||||
"golang.org/x/net/html"
|
||||
)
|
||||
|
||||
type AsciidocConverter struct {
|
||||
type AsciiDocConverter struct {
|
||||
Ctx converter.DocumentContext
|
||||
Cfg converter.ProviderConfig
|
||||
}
|
||||
|
||||
type AsciidocResult struct {
|
||||
type asciiDocResult struct {
|
||||
converter.ResultRender
|
||||
toc *tableofcontents.Fragments
|
||||
}
|
||||
|
||||
/* ToDo: RelPermalink patch for svg posts not working*/
|
||||
type pageSubset interface {
|
||||
IsPage() bool
|
||||
RelPermalink() string
|
||||
Section() string
|
||||
}
|
||||
|
||||
func (r AsciidocResult) TableOfContents() *tableofcontents.Fragments {
|
||||
const (
|
||||
// asciiDocBinaryName is name of the AsciiDoc converter CLI.
|
||||
asciiDocBinaryName = "asciidoctor"
|
||||
|
||||
// asciiDocDiagramExtension is the name of the AsciiDoc converter diagram
|
||||
// extension.
|
||||
asciiDocDiagramExtension = "asciidoctor-diagram"
|
||||
|
||||
// asciiDocDiagramCacheDirKey is the AsciiDoc converter attribute key for
|
||||
// setting the path to the diagram cache directory.
|
||||
asciiDocDiagramCacheDirKey = "diagram-cachedir"
|
||||
|
||||
// asciiDocDiagramCacheImagesOptionKey is the AsciiDoc converter attribute
|
||||
// key for determining whether to cache image files in addition to
|
||||
// metadata files.
|
||||
asciiDocDiagramCacheImagesOptionKey = "diagram-cache-images-option"
|
||||
|
||||
// gemBinaryName is the name of the RubyGems CLI.
|
||||
gemBinaryName = "gem"
|
||||
|
||||
// goatBinaryName is the name of the GoAT CLI.
|
||||
goatBinaryName = "goat"
|
||||
)
|
||||
|
||||
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)
|
||||
func (a *AsciiDocConverter) Convert(ctx converter.RenderContext) (converter.ResultRender, error) {
|
||||
b, err := a.GetAsciiDocContent(ctx.Src, a.Ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -43,50 +74,81 @@ func (a *AsciidocConverter) Convert(ctx converter.RenderContext) (converter.Resu
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return AsciidocResult{
|
||||
return asciiDocResult{
|
||||
ResultRender: converter.Bytes(content),
|
||||
toc: toc,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (a *AsciidocConverter) Supports(_ identity.Identity) bool {
|
||||
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.")
|
||||
// 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 ok, err := HasAsciiDoc(); !ok {
|
||||
a.Cfg.Logger.Errorf("leaving AsciiDoc content unrendered: %s", err.Error())
|
||||
return src, nil
|
||||
}
|
||||
|
||||
args := a.ParseArgs(ctx)
|
||||
args = append(args, "-")
|
||||
args, err := a.ParseArgs(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
args = append(args, "-") // read from stdin
|
||||
|
||||
a.Cfg.Logger.Infoln("Rendering", ctx.DocumentName, " using asciidoctor args", args, "...")
|
||||
a.Cfg.Logger.Infof("Rendering %s using Asciidoctor args %s ...", ctx.DocumentName, args)
|
||||
|
||||
return internal.ExternallyRenderContent(a.Cfg, ctx, src, asciiDocBinaryName, args)
|
||||
}
|
||||
|
||||
func (a *AsciidocConverter) ParseArgs(ctx converter.DocumentContext) []string {
|
||||
cfg := a.Cfg.MarkupConfig().AsciidocExt
|
||||
func (a *AsciiDocConverter) ParseArgs(ctx converter.DocumentContext) ([]string, error) {
|
||||
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.")
|
||||
a.Cfg.Logger.Errorf(
|
||||
"The %q Asciidoctor extension is unsupported and ignored. Only installed Asciidoctor extensions are allowed.",
|
||||
extension,
|
||||
)
|
||||
continue
|
||||
}
|
||||
|
||||
args = append(args, "-r", extension)
|
||||
|
||||
if extension == asciiDocDiagramExtension {
|
||||
cacheDir := filepath.Clean(filepath.Join(a.Cfg.Conf.CacheDirMisc(), asciiDocDiagramExtension))
|
||||
args = append(args, "-a", asciiDocDiagramCacheDirKey+"="+cacheDir)
|
||||
args = append(args, "-a", asciiDocDiagramCacheImagesOptionKey)
|
||||
}
|
||||
}
|
||||
|
||||
for attributeKey, attributeValue := range cfg.Attributes {
|
||||
if asciidocext_config.DisallowedAttributes[attributeKey] {
|
||||
a.Cfg.Logger.Errorln("Unsupported asciidoctor attribute was passed in. Attribute `" + attributeKey + "` ignored.")
|
||||
a.Cfg.Logger.Errorf(
|
||||
"The %q Asciidoctor attribute is unsupported and ignored.",
|
||||
attributeKey,
|
||||
)
|
||||
continue
|
||||
}
|
||||
|
||||
if attributeKey == asciiDocDiagramCacheImagesOptionKey {
|
||||
a.Cfg.Logger.Warnf(
|
||||
"The %q Asciidoctor attribute is fixed and cannot be modified. To disable caching of both image and metadata files, set markup.asciidocext.attributes.diagram-nocache-option to true in your site configuration.",
|
||||
attributeKey,
|
||||
)
|
||||
continue
|
||||
}
|
||||
|
||||
if attributeKey == asciiDocDiagramCacheDirKey {
|
||||
a.Cfg.Logger.Warnf(
|
||||
"The %q Asciidoctor attribute is fixed and cannot be modified. To change the cache location, modify caches.misc.dir in your site configuration.",
|
||||
attributeKey,
|
||||
)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -105,42 +167,63 @@ func (a *AsciidocConverter) ParseArgs(ctx converter.DocumentContext) []string {
|
||||
}
|
||||
|
||||
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")
|
||||
page, ok := ctx.Document.(pageSubset)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("expected pageSubset, got %T", ctx.Document)
|
||||
}
|
||||
|
||||
var outDir string
|
||||
var err error
|
||||
// Derive the outdir document attribute from the relative permalink.
|
||||
relPath := strings.TrimPrefix(page.RelPermalink(), a.Cfg.Conf.BaseURL().BasePathNoTrailingSlash)
|
||||
relPath, err := url.PathUnescape(relPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
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())
|
||||
if a.Cfg.Conf.IsMultihost() {
|
||||
// In a multi-host configuration, neither absolute nor relative
|
||||
// permalinks include the language key; prepend it.
|
||||
language, ok := a.Cfg.Conf.Language().(*langs.Language)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("expected *langs.Language, got %T", a.Cfg.Conf.Language())
|
||||
}
|
||||
relPath = path.Join(language.Lang, relPath)
|
||||
}
|
||||
|
||||
if a.Cfg.Conf.IsUglyURLs(page.Section()) {
|
||||
if page.IsPage() {
|
||||
// Remove the extension.
|
||||
relPath = strings.TrimSuffix(relPath, path.Ext(relPath))
|
||||
} else {
|
||||
a.Cfg.Logger.Errorln("unable to cast interface to pageSubset")
|
||||
// Remove the file name.
|
||||
relPath = path.Dir(relPath)
|
||||
}
|
||||
|
||||
outDir, err = filepath.Abs(filepath.Join(destinationDir, filepath.Dir(ctx.DocumentName), postDir))
|
||||
}
|
||||
// Set imagesoutdir and imagesdir attributes.
|
||||
imagesoutdir, err := filepath.Abs(filepath.Join(a.Cfg.Conf.BaseConfig().PublishDir, relPath))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
imagesdir := filepath.Base(imagesoutdir)
|
||||
|
||||
if page.IsPage() {
|
||||
args = append(args, "-a", "imagesoutdir="+imagesoutdir, "-a", "imagesdir="+imagesdir)
|
||||
} else {
|
||||
args = append(args, "-a", "imagesoutdir="+imagesoutdir)
|
||||
}
|
||||
}
|
||||
// Prepend the publishDir.
|
||||
outDir, err := filepath.Abs(filepath.Join(a.Cfg.Conf.BaseConfig().PublishDir, relPath))
|
||||
if err != nil {
|
||||
a.Cfg.Logger.Errorln("asciidoctor outDir: ", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
args = append(args, "--base-dir", contentDir, "-a", "outdir="+outDir)
|
||||
args = append(args, "--base-dir", filepath.Dir(ctx.Filename), "-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")
|
||||
a.Cfg.Logger.Warnln("Asciidoctor parameter NoHeaderOrFooter is required for correct HTML rendering")
|
||||
}
|
||||
|
||||
if cfg.SectionNumbers {
|
||||
@@ -159,29 +242,71 @@ func (a *AsciidocConverter) ParseArgs(ctx converter.DocumentContext) []string {
|
||||
|
||||
args = a.AppendArg(args, "--safe-mode", cfg.SafeMode, asciidocext_config.CliDefault.SafeMode, asciidocext_config.AllowedSafeMode)
|
||||
|
||||
return args
|
||||
return args, nil
|
||||
}
|
||||
|
||||
func (a *AsciidocConverter) AppendArg(args []string, option, value, defaultValue string, allowedValues map[string]bool) []string {
|
||||
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.")
|
||||
a.Cfg.Logger.Errorf(
|
||||
"Unsupported Asciidoctor value %q for option %q was passed in and will be ignored.",
|
||||
value,
|
||||
option,
|
||||
)
|
||||
}
|
||||
}
|
||||
return args
|
||||
}
|
||||
|
||||
const asciiDocBinaryName = "asciidoctor"
|
||||
// HasAsciiDoc reports whether the AsciiDoc converter is installed.
|
||||
func HasAsciiDoc() (bool, error) {
|
||||
if !hexec.InPath(asciiDocBinaryName) {
|
||||
return false, fmt.Errorf("the AsciiDoc converter (%s) is not installed", asciiDocBinaryName)
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func HasAsciiDoc() bool {
|
||||
return hexec.InPath(asciiDocBinaryName)
|
||||
// CanRenderGoATDiagrams reports whether the AsciiDoc converter can render
|
||||
// GoAT diagrams. Only used in tests.
|
||||
func CanRenderGoATDiagrams() (bool, error) {
|
||||
// Verify that the AsciiDoc converter is installed.
|
||||
if ok, err := HasAsciiDoc(); !ok {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// Verify that the RubyGems CLI is installed.
|
||||
if !hexec.InPath(gemBinaryName) {
|
||||
return false, fmt.Errorf("the RubyGems CLI (%s) is not installed", gemBinaryName)
|
||||
}
|
||||
|
||||
// Verify that the required AsciiDoc converter extension is installed.
|
||||
sc := security.DefaultConfig
|
||||
sc.Exec.Allow = security.MustNewWhitelist(gemBinaryName)
|
||||
ex := hexec.New(sc, "", loggers.NewDefault())
|
||||
|
||||
args := []any{"list", asciiDocDiagramExtension, "--installed"}
|
||||
cmd, err := ex.New(gemBinaryName, args...)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("the %s gem is not installed", asciiDocDiagramExtension)
|
||||
}
|
||||
|
||||
// Verify that the GoAT CLI is installed.
|
||||
if !hexec.InPath(goatBinaryName) {
|
||||
return false, fmt.Errorf("the GoAT CLI (%s) is not installed", goatBinaryName)
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// 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) {
|
||||
func (a *AsciiDocConverter) extractTOC(src []byte) ([]byte, *tableofcontents.Fragments, error) {
|
||||
var buf bytes.Buffer
|
||||
buf.Write(src)
|
||||
node, err := html.Parse(&buf)
|
||||
@@ -196,7 +321,7 @@ func (a *AsciidocConverter) extractTOC(src []byte) ([]byte, *tableofcontents.Fra
|
||||
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 {
|
||||
if !a.Cfg.MarkupConfig().AsciiDocExt.PreserveTOC {
|
||||
n.Parent.RemoveChild(n)
|
||||
}
|
||||
return true
|
||||
|
||||
@@ -37,8 +37,8 @@ type Config struct {
|
||||
// Configuration for the Goldmark markdown engine.
|
||||
Goldmark goldmark_config.Config
|
||||
|
||||
// Configuration for the Asciidoc external markdown engine.
|
||||
AsciidocExt asciidocext_config.Config
|
||||
// Configuration for the AsciiDoc external markdown engine.
|
||||
AsciiDocExt asciidocext_config.Config
|
||||
}
|
||||
|
||||
func (c *Config) Init() error {
|
||||
@@ -118,5 +118,5 @@ var Default = Config{
|
||||
Highlight: highlight.DefaultConfig,
|
||||
|
||||
Goldmark: goldmark_config.Default,
|
||||
AsciidocExt: asciidocext_config.Default,
|
||||
AsciiDocExt: asciidocext_config.Default,
|
||||
}
|
||||
|
||||
@@ -48,8 +48,8 @@ func TestConfig(t *testing.T) {
|
||||
c.Assert(conf.Goldmark.Parser.Attribute.Title, qt.Equals, true)
|
||||
c.Assert(conf.Goldmark.Parser.Attribute.Block, qt.Equals, false)
|
||||
|
||||
c.Assert(conf.AsciidocExt.WorkingFolderCurrent, qt.Equals, true)
|
||||
c.Assert(conf.AsciidocExt.Extensions[0], qt.Equals, "asciidoctor-html5s")
|
||||
c.Assert(conf.AsciiDocExt.WorkingFolderCurrent, qt.Equals, true)
|
||||
c.Assert(conf.AsciiDocExt.Extensions[0], qt.Equals, "asciidoctor-html5s")
|
||||
})
|
||||
|
||||
// We changed the typographer extension config from a bool to a struct in 0.112.0.
|
||||
|
||||
Reference in New Issue
Block a user