summaryrefslogtreecommitdiffstats
path: root/markup/asciidocext/internal
diff options
context:
space:
mode:
authorBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>2023-01-04 18:24:36 +0100
committerBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>2023-05-16 18:01:29 +0200
commit241b21b0fd34d91fccb2ce69874110dceae6f926 (patch)
treed4e0118eac7e9c42f065815447a70805f8d6ad3e /markup/asciidocext/internal
parent6aededf6b42011c3039f5f66487a89a8dd65e0e7 (diff)
Create a struct with all of Hugo's config options
Primary motivation is documentation, but it will also hopefully simplify the code. Also, * Lower case the default output format names; this is in line with the custom ones (map keys) and how it's treated all the places. This avoids doing `stringds.EqualFold` everywhere. Closes #10896 Closes #10620
Diffstat (limited to 'markup/asciidocext/internal')
-rw-r--r--markup/asciidocext/internal/converter.go274
1 files changed, 274 insertions, 0 deletions
diff --git a/markup/asciidocext/internal/converter.go b/markup/asciidocext/internal/converter.go
new file mode 100644
index 000000000..5108bdd0a
--- /dev/null
+++ b/markup/asciidocext/internal/converter.go
@@ -0,0 +1,274 @@
+package internal
+
+import (
+ "bytes"
+ "path/filepath"
+ "strings"
+
+ "github.com/gohugoio/hugo/common/hexec"
+ "github.com/gohugoio/hugo/identity"
+ "github.com/gohugoio/hugo/markup/asciidocext/asciidocext_config"
+ "github.com/gohugoio/hugo/markup/converter"
+ "github.com/gohugoio/hugo/markup/internal"
+ "github.com/gohugoio/hugo/markup/tableofcontents"
+ "golang.org/x/net/html"
+)
+
+type AsciidocConverter struct {
+ Ctx converter.DocumentContext
+ Cfg converter.ProviderConfig
+}
+
+type AsciidocResult struct {
+ converter.ResultRender
+ toc *tableofcontents.Fragments
+}
+
+/* ToDo: RelPermalink patch for svg posts not working*/
+type pageSubset interface {
+ RelPermalink() string
+}
+
+func (r AsciidocResult) TableOfContents() *tableofcontents.Fragments {
+ return r.toc
+}
+
+func (a *AsciidocConverter) Convert(ctx converter.RenderContext) (converter.ResultRender, error) {
+ b, err := a.GetAsciidocContent(ctx.Src, a.Ctx)
+ if err != nil {
+ return nil, err
+ }
+ content, toc, err := a.extractTOC(b)
+ if err != nil {
+ return nil, err
+ }
+ return AsciidocResult{
+ ResultRender: converter.Bytes(content),
+ toc: toc,
+ }, nil
+}
+
+func (a *AsciidocConverter) Supports(_ identity.Identity) bool {
+ return false
+}
+
+// GetAsciidocContent calls asciidoctor as an external helper
+// to convert AsciiDoc content to HTML.
+func (a *AsciidocConverter) GetAsciidocContent(src []byte, ctx converter.DocumentContext) ([]byte, error) {
+ if !HasAsciiDoc() {
+ a.Cfg.Logger.Errorln("asciidoctor not found in $PATH: Please install.\n",
+ " Leaving AsciiDoc content unrendered.")
+ return src, nil
+ }
+
+ args := a.ParseArgs(ctx)
+ args = append(args, "-")
+
+ a.Cfg.Logger.Infoln("Rendering", ctx.DocumentName, " using asciidoctor args", args, "...")
+
+ return internal.ExternallyRenderContent(a.Cfg, ctx, src, asciiDocBinaryName, args)
+}
+
+func (a *AsciidocConverter) ParseArgs(ctx converter.DocumentContext) []string {
+ cfg := a.Cfg.MarkupConfig().AsciidocExt
+ args := []string{}
+
+ args = a.AppendArg(args, "-b", cfg.Backend, asciidocext_config.CliDefault.Backend, asciidocext_config.AllowedBackend)
+
+ for _, extension := range cfg.Extensions {
+ if strings.LastIndexAny(extension, `\/.`) > -1 {
+ a.Cfg.Logger.Errorln("Unsupported asciidoctor extension was passed in. Extension `" + extension + "` ignored. Only installed asciidoctor extensions are allowed.")
+ continue
+ }
+ args = append(args, "-r", extension)
+ }
+
+ for attributeKey, attributeValue := range cfg.Attributes {
+ if asciidocext_config.DisallowedAttributes[attributeKey] {
+ a.Cfg.Logger.Errorln("Unsupported asciidoctor attribute was passed in. Attribute `" + attributeKey + "` ignored.")
+ continue
+ }
+
+ args = append(args, "-a", attributeKey+"="+attributeValue)
+ }
+
+ if cfg.WorkingFolderCurrent {
+ contentDir := filepath.Dir(ctx.Filename)
+ destinationDir := a.Cfg.Conf.BaseConfig().PublishDir
+
+ if destinationDir == "" {
+ a.Cfg.Logger.Errorln("markup.asciidocext.workingFolderCurrent requires hugo command option --destination to be set")
+ }
+
+ var outDir string
+ var err error
+
+ file := filepath.Base(ctx.Filename)
+ if a.Cfg.Conf.IsUglyURLs("") || file == "_index.adoc" || file == "index.adoc" {
+ outDir, err = filepath.Abs(filepath.Dir(filepath.Join(destinationDir, ctx.DocumentName)))
+ } else {
+ postDir := ""
+ page, ok := ctx.Document.(pageSubset)
+ if ok {
+ postDir = filepath.Base(page.RelPermalink())
+ } else {
+ a.Cfg.Logger.Errorln("unable to cast interface to pageSubset")
+ }
+
+ outDir, err = filepath.Abs(filepath.Join(destinationDir, filepath.Dir(ctx.DocumentName), postDir))
+ }
+
+ if err != nil {
+ a.Cfg.Logger.Errorln("asciidoctor outDir: ", err)
+ }
+
+ args = append(args, "--base-dir", contentDir, "-a", "outdir="+outDir)
+ }
+
+ if cfg.NoHeaderOrFooter {
+ args = append(args, "--no-header-footer")
+ } else {
+ a.Cfg.Logger.Warnln("asciidoctor parameter NoHeaderOrFooter is expected for correct html rendering")
+ }
+
+ if cfg.SectionNumbers {
+ args = append(args, "--section-numbers")
+ }
+
+ if cfg.Verbose {
+ args = append(args, "--verbose")
+ }
+
+ if cfg.Trace {
+ args = append(args, "--trace")
+ }
+
+ args = a.AppendArg(args, "--failure-level", cfg.FailureLevel, asciidocext_config.CliDefault.FailureLevel, asciidocext_config.AllowedFailureLevel)
+
+ args = a.AppendArg(args, "--safe-mode", cfg.SafeMode, asciidocext_config.CliDefault.SafeMode, asciidocext_config.AllowedSafeMode)
+
+ return args
+}
+
+func (a *AsciidocConverter) AppendArg(args []string, option, value, defaultValue string, allowedValues map[string]bool) []string {
+ if value != defaultValue {
+ if allowedValues[value] {
+ args = append(args, option, value)
+ } else {
+ a.Cfg.Logger.Errorln("Unsupported asciidoctor value `" + value + "` for option " + option + " was passed in and will be ignored.")
+ }
+ }
+ return args
+}
+
+const asciiDocBinaryName = "asciidoctor"
+
+func HasAsciiDoc() bool {
+ return hexec.InPath(asciiDocBinaryName)
+}
+
+// extractTOC extracts the toc from the given src html.
+// It returns the html without the TOC, and the TOC data
+func (a *AsciidocConverter) extractTOC(src []byte) ([]byte, *tableofcontents.Fragments, error) {
+ var buf bytes.Buffer
+ buf.Write(src)
+ node, err := html.Parse(&buf)
+ if err != nil {
+ return nil, nil, err
+ }
+ var (
+ f func(*html.Node) bool
+ toc *tableofcontents.Fragments
+ toVisit []*html.Node
+ )
+ f = func(n *html.Node) bool {
+ if n.Type == html.ElementNode && n.Data == "div" && attr(n, "id") == "toc" {
+ toc = parseTOC(n)
+ if !a.Cfg.MarkupConfig().AsciidocExt.PreserveTOC {
+ n.Parent.RemoveChild(n)
+ }
+ return true
+ }
+ if n.FirstChild != nil {
+ toVisit = append(toVisit, n.FirstChild)
+ }
+ if n.NextSibling != nil && f(n.NextSibling) {
+ return true
+ }
+ for len(toVisit) > 0 {
+ nv := toVisit[0]
+ toVisit = toVisit[1:]
+ if f(nv) {
+ return true
+ }
+ }
+ return false
+ }
+ f(node)
+ if err != nil {
+ return nil, nil, err
+ }
+ buf.Reset()
+ err = html.Render(&buf, node)
+ if err != nil {
+ return nil, nil, err
+ }
+ // ltrim <html><head></head><body> and rtrim </body></html> which are added by html.Render
+ res := buf.Bytes()[25:]
+ res = res[:len(res)-14]
+ return res, toc, nil
+}
+
+// parseTOC returns a TOC root from the given toc Node
+func parseTOC(doc *html.Node) *tableofcontents.Fragments {
+ var (
+ toc tableofcontents.Builder
+ f func(*html.Node, int, int)
+ )
+ f = func(n *html.Node, row, level int) {
+ if n.Type == html.ElementNode {
+ switch n.Data {
+ case "ul":
+ if level == 0 {
+ row++
+ }
+ level++
+ f(n.FirstChild, row, level)
+ case "li":
+ for c := n.FirstChild; c != nil; c = c.NextSibling {
+ if c.Type != html.ElementNode || c.Data != "a" {
+ continue
+ }
+ href := attr(c, "href")[1:]
+ toc.AddAt(&tableofcontents.Heading{
+ Title: nodeContent(c),
+ ID: href,
+ }, row, level)
+ }
+ f(n.FirstChild, row, level)
+ }
+ }
+ if n.NextSibling != nil {
+ f(n.NextSibling, row, level)
+ }
+ }
+ f(doc.FirstChild, -1, 0)
+ return toc.Build()
+}
+
+func attr(node *html.Node, key string) string {
+ for _, a := range node.Attr {
+ if a.Key == key {
+ return a.Val
+ }
+ }
+ return ""
+}
+
+func nodeContent(node *html.Node) string {
+ var buf bytes.Buffer
+ for c := node.FirstChild; c != nil; c = c.NextSibling {
+ html.Render(&buf, c)
+ }
+ return buf.String()
+}