diff options
Diffstat (limited to 'markup')
-rw-r--r-- | markup/asciidocext/convert.go | 36 | ||||
-rw-r--r-- | markup/asciidocext/convert_test.go | 50 | ||||
-rw-r--r-- | markup/converter/converter.go | 2 | ||||
-rw-r--r-- | markup/internal/external.go | 54 | ||||
-rw-r--r-- | markup/pandoc/convert.go | 36 | ||||
-rw-r--r-- | markup/pandoc/convert_test.go | 6 | ||||
-rw-r--r-- | markup/rst/convert.go | 58 | ||||
-rw-r--r-- | markup/rst/convert_test.go | 11 |
8 files changed, 151 insertions, 102 deletions
diff --git a/markup/asciidocext/convert.go b/markup/asciidocext/convert.go index ff843cb6e..4c83e0e95 100644 --- a/markup/asciidocext/convert.go +++ b/markup/asciidocext/convert.go @@ -21,10 +21,9 @@ import ( "path/filepath" "strings" + "github.com/gohugoio/hugo/common/hexec" "github.com/gohugoio/hugo/htesting" - "github.com/cli/safeexec" - "github.com/gohugoio/hugo/identity" "github.com/gohugoio/hugo/markup/asciidocext/asciidocext_config" "github.com/gohugoio/hugo/markup/converter" @@ -67,7 +66,11 @@ type asciidocConverter struct { } func (a *asciidocConverter) Convert(ctx converter.RenderContext) (converter.Result, error) { - content, toc, err := a.extractTOC(a.getAsciidocContent(ctx.Src, a.ctx)) + 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 } @@ -83,20 +86,19 @@ func (a *asciidocConverter) Supports(_ identity.Identity) bool { // getAsciidocContent calls asciidoctor as an external helper // to convert AsciiDoc content to HTML. -func (a *asciidocConverter) getAsciidocContent(src []byte, ctx converter.DocumentContext) []byte { - path := getAsciidoctorExecPath() - if path == "" { +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 + return src, nil } args := a.parseArgs(ctx) args = append(args, "-") - a.cfg.Logger.Infoln("Rendering", ctx.DocumentName, "with", path, "using asciidoctor args", args, "...") + a.cfg.Logger.Infoln("Rendering", ctx.DocumentName, " using asciidoctor args", args, "...") - return internal.ExternallyRenderContent(a.cfg, ctx, src, path, args) + return internal.ExternallyRenderContent(a.cfg, ctx, src, asciiDocBinaryName, args) } func (a *asciidocConverter) parseArgs(ctx converter.DocumentContext) []string { @@ -195,12 +197,10 @@ func (a *asciidocConverter) appendArg(args []string, option, value, defaultValue return args } -func getAsciidoctorExecPath() string { - path, err := safeexec.LookPath("asciidoctor") - if err != nil { - return "" - } - return path +const asciiDocBinaryName = "asciidoctor" + +func hasAsciiDoc() bool { + return hexec.InPath(asciiDocBinaryName) } // extractTOC extracts the toc from the given src html. @@ -311,8 +311,12 @@ func nodeContent(node *html.Node) string { // Supports returns whether Asciidoctor is installed on this computer. func Supports() bool { + hasBin := hasAsciiDoc() if htesting.SupportsAll() { + if !hasBin { + panic("asciidoctor not installed") + } return true } - return getAsciidoctorExecPath() != "" + return hasBin } diff --git a/markup/asciidocext/convert_test.go b/markup/asciidocext/convert_test.go index acc525c3b..3a350c5ce 100644 --- a/markup/asciidocext/convert_test.go +++ b/markup/asciidocext/convert_test.go @@ -21,8 +21,10 @@ import ( "path/filepath" "testing" + "github.com/gohugoio/hugo/common/hexec" "github.com/gohugoio/hugo/common/loggers" "github.com/gohugoio/hugo/config" + "github.com/gohugoio/hugo/config/security" "github.com/gohugoio/hugo/markup/converter" "github.com/gohugoio/hugo/markup/markup_config" "github.com/gohugoio/hugo/markup/tableofcontents" @@ -280,20 +282,28 @@ func TestAsciidoctorAttributes(t *testing.T) { c.Assert(args[4], qt.Equals, "--no-header-footer") } -func TestConvert(t *testing.T) { - if !Supports() { - t.Skip("asciidoctor not installed") - } - c := qt.New(t) +func getProvider(c *qt.C, mconf markup_config.Config) converter.Provider { + sc := security.DefaultConfig + sc.Exec.Allow = security.NewWhitelist("asciidoctor") - mconf := markup_config.Default p, err := Provider.New( converter.ProviderConfig{ MarkupConfig: mconf, Logger: loggers.NewErrorLogger(), + Exec: hexec.New(sc), }, ) c.Assert(err, qt.IsNil) + return p +} + +func TestConvert(t *testing.T) { + if !Supports() { + t.Skip("asciidoctor not installed") + } + c := qt.New(t) + + p := getProvider(c, markup_config.Default) conv, err := p.New(converter.DocumentContext{}) c.Assert(err, qt.IsNil) @@ -308,14 +318,8 @@ func TestTableOfContents(t *testing.T) { t.Skip("asciidoctor not installed") } c := qt.New(t) - mconf := markup_config.Default - p, err := Provider.New( - converter.ProviderConfig{ - MarkupConfig: mconf, - Logger: loggers.NewErrorLogger(), - }, - ) - c.Assert(err, qt.IsNil) + p := getProvider(c, markup_config.Default) + conv, err := p.New(converter.DocumentContext{}) c.Assert(err, qt.IsNil) r, err := conv.Convert(converter.RenderContext{Src: []byte(`:toc: macro @@ -390,14 +394,7 @@ func TestTableOfContentsWithCode(t *testing.T) { t.Skip("asciidoctor not installed") } c := qt.New(t) - mconf := markup_config.Default - p, err := Provider.New( - converter.ProviderConfig{ - MarkupConfig: mconf, - Logger: loggers.NewErrorLogger(), - }, - ) - c.Assert(err, qt.IsNil) + p := getProvider(c, markup_config.Default) conv, err := p.New(converter.DocumentContext{}) c.Assert(err, qt.IsNil) r, err := conv.Convert(converter.RenderContext{Src: []byte(`:toc: auto @@ -433,13 +430,8 @@ func TestTableOfContentsPreserveTOC(t *testing.T) { c := qt.New(t) mconf := markup_config.Default mconf.AsciidocExt.PreserveTOC = true - p, err := Provider.New( - converter.ProviderConfig{ - MarkupConfig: mconf, - Logger: loggers.NewErrorLogger(), - }, - ) - c.Assert(err, qt.IsNil) + p := getProvider(c, mconf) + conv, err := p.New(converter.DocumentContext{}) c.Assert(err, qt.IsNil) r, err := conv.Convert(converter.RenderContext{Src: []byte(`:toc: diff --git a/markup/converter/converter.go b/markup/converter/converter.go index 3fa3bea39..180208a7b 100644 --- a/markup/converter/converter.go +++ b/markup/converter/converter.go @@ -16,6 +16,7 @@ package converter import ( "bytes" + "github.com/gohugoio/hugo/common/hexec" "github.com/gohugoio/hugo/common/loggers" "github.com/gohugoio/hugo/config" "github.com/gohugoio/hugo/identity" @@ -32,6 +33,7 @@ type ProviderConfig struct { Cfg config.Provider // Site config ContentFs afero.Fs Logger loggers.Logger + Exec *hexec.Exec Highlight func(code, lang, optsStr string) (string, error) } diff --git a/markup/internal/external.go b/markup/internal/external.go index 0937afa34..97cf5cc7d 100644 --- a/markup/internal/external.go +++ b/markup/internal/external.go @@ -2,42 +2,56 @@ package internal import ( "bytes" + "fmt" "strings" - "github.com/cli/safeexec" + "github.com/gohugoio/hugo/common/collections" "github.com/gohugoio/hugo/common/hexec" - "github.com/gohugoio/hugo/markup/converter" ) func ExternallyRenderContent( cfg converter.ProviderConfig, ctx converter.DocumentContext, - content []byte, path string, args []string) []byte { + content []byte, binaryName string, args []string) ([]byte, error) { logger := cfg.Logger - cmd, err := hexec.SafeCommand(path, args...) - if err != nil { - logger.Errorf("%s rendering %s: %v", path, ctx.DocumentName, err) - return nil + + if strings.Contains(binaryName, "/") { + panic(fmt.Sprintf("should be no slash in %q", binaryName)) } - cmd.Stdin = bytes.NewReader(content) + + argsv := collections.StringSliceToInterfaceSlice(args) + var out, cmderr bytes.Buffer - cmd.Stdout = &out - cmd.Stderr = &cmderr + argsv = append(argsv, hexec.WithStdout(&out)) + argsv = append(argsv, hexec.WithStderr(&cmderr)) + argsv = append(argsv, hexec.WithStdin(bytes.NewReader(content))) + + cmd, err := cfg.Exec.New(binaryName, argsv...) + if err != nil { + return nil, err + } + err = cmd.Run() + // Most external helpers exit w/ non-zero exit code only if severe, i.e. // halting errors occurred. -> log stderr output regardless of state of err for _, item := range strings.Split(cmderr.String(), "\n") { item := strings.TrimSpace(item) if item != "" { - logger.Errorf("%s: %s", ctx.DocumentName, item) + if err == nil { + logger.Warnf("%s: %s", ctx.DocumentName, item) + } else { + logger.Errorf("%s: %s", ctx.DocumentName, item) + } } } + if err != nil { - logger.Errorf("%s rendering %s: %v", path, ctx.DocumentName, err) + logger.Errorf("%s rendering %s: %v", binaryName, ctx.DocumentName, err) } - return normalizeExternalHelperLineFeeds(out.Bytes()) + return normalizeExternalHelperLineFeeds(out.Bytes()), nil } // Strips carriage returns from third-party / external processes (useful for Windows) @@ -45,13 +59,13 @@ func normalizeExternalHelperLineFeeds(content []byte) []byte { return bytes.Replace(content, []byte("\r"), []byte(""), -1) } -func GetPythonExecPath() string { - path, err := safeexec.LookPath("python") - if err != nil { - path, err = safeexec.LookPath("python.exe") - if err != nil { - return "" +var pythonBinaryCandidates = []string{"python", "python.exe"} + +func GetPythonBinaryAndExecPath() (string, string) { + for _, p := range pythonBinaryCandidates { + if pth := hexec.LookPath(p); pth != "" { + return p, pth } } - return path + return "", "" } diff --git a/markup/pandoc/convert.go b/markup/pandoc/convert.go index 1c25e41d2..ae90cf417 100644 --- a/markup/pandoc/convert.go +++ b/markup/pandoc/convert.go @@ -15,7 +15,7 @@ package pandoc import ( - "github.com/cli/safeexec" + "github.com/gohugoio/hugo/common/hexec" "github.com/gohugoio/hugo/htesting" "github.com/gohugoio/hugo/identity" "github.com/gohugoio/hugo/markup/internal" @@ -44,7 +44,11 @@ type pandocConverter struct { } func (c *pandocConverter) Convert(ctx converter.RenderContext) (converter.Result, error) { - return converter.Bytes(c.getPandocContent(ctx.Src, c.ctx)), nil + b, err := c.getPandocContent(ctx.Src, c.ctx) + if err != nil { + return nil, err + } + return converter.Bytes(b), nil } func (c *pandocConverter) Supports(feature identity.Identity) bool { @@ -52,31 +56,35 @@ func (c *pandocConverter) Supports(feature identity.Identity) bool { } // getPandocContent calls pandoc as an external helper to convert pandoc markdown to HTML. -func (c *pandocConverter) getPandocContent(src []byte, ctx converter.DocumentContext) []byte { +func (c *pandocConverter) getPandocContent(src []byte, ctx converter.DocumentContext) ([]byte, error) { logger := c.cfg.Logger - path := getPandocExecPath() - if path == "" { + binaryName := getPandocBinaryName() + if binaryName == "" { logger.Println("pandoc not found in $PATH: Please install.\n", " Leaving pandoc content unrendered.") - return src + return src, nil } args := []string{"--mathjax"} - return internal.ExternallyRenderContent(c.cfg, ctx, src, path, args) + return internal.ExternallyRenderContent(c.cfg, ctx, src, binaryName, args) } -func getPandocExecPath() string { - path, err := safeexec.LookPath("pandoc") - if err != nil { - return "" - } +const pandocBinary = "pandoc" - return path +func getPandocBinaryName() string { + if hexec.InPath(pandocBinary) { + return pandocBinary + } + return "" } // Supports returns whether Pandoc is installed on this computer. func Supports() bool { + hasBin := getPandocBinaryName() != "" if htesting.SupportsAll() { + if !hasBin { + panic("pandoc not installed") + } return true } - return getPandocExecPath() != "" + return hasBin } diff --git a/markup/pandoc/convert_test.go b/markup/pandoc/convert_test.go index bd6ca19e6..f549d5f4f 100644 --- a/markup/pandoc/convert_test.go +++ b/markup/pandoc/convert_test.go @@ -16,7 +16,9 @@ package pandoc import ( "testing" + "github.com/gohugoio/hugo/common/hexec" "github.com/gohugoio/hugo/common/loggers" + "github.com/gohugoio/hugo/config/security" "github.com/gohugoio/hugo/markup/converter" @@ -28,7 +30,9 @@ func TestConvert(t *testing.T) { t.Skip("pandoc not installed") } c := qt.New(t) - p, err := Provider.New(converter.ProviderConfig{Logger: loggers.NewErrorLogger()}) + sc := security.DefaultConfig + sc.Exec.Allow = security.NewWhitelist("pandoc") + p, err := Provider.New(converter.ProviderConfig{Exec: hexec.New(sc), Logger: loggers.NewErrorLogger()}) c.Assert(err, qt.IsNil) conv, err := p.New(converter.DocumentContext{}) c.Assert(err, qt.IsNil) diff --git a/markup/rst/convert.go b/markup/rst/convert.go index 4c11c4be8..b86b35f1b 100644 --- a/markup/rst/convert.go +++ b/markup/rst/convert.go @@ -18,7 +18,7 @@ import ( "bytes" "runtime" - "github.com/cli/safeexec" + "github.com/gohugoio/hugo/common/hexec" "github.com/gohugoio/hugo/htesting" "github.com/gohugoio/hugo/identity" @@ -48,7 +48,11 @@ type rstConverter struct { } func (c *rstConverter) Convert(ctx converter.RenderContext) (converter.Result, error) { - return converter.Bytes(c.getRstContent(ctx.Src, c.ctx)), nil + b, err := c.getRstContent(ctx.Src, c.ctx) + if err != nil { + return nil, err + } + return converter.Bytes(b), nil } func (c *rstConverter) Supports(feature identity.Identity) bool { @@ -57,31 +61,38 @@ func (c *rstConverter) Supports(feature identity.Identity) bool { // getRstContent calls the Python script rst2html as an external helper // to convert reStructuredText content to HTML. -func (c *rstConverter) getRstContent(src []byte, ctx converter.DocumentContext) []byte { +func (c *rstConverter) getRstContent(src []byte, ctx converter.DocumentContext) ([]byte, error) { logger := c.cfg.Logger - path := getRstExecPath() + binaryName, binaryPath := getRstBinaryNameAndPath() - if path == "" { + if binaryName == "" { logger.Println("rst2html / rst2html.py not found in $PATH: Please install.\n", " Leaving reStructuredText content unrendered.") - return src + return src, nil } - logger.Infoln("Rendering", ctx.DocumentName, "with", path, "...") + logger.Infoln("Rendering", ctx.DocumentName, "with", binaryName, "...") var result []byte + var err error + // certain *nix based OSs wrap executables in scripted launchers // invoking binaries on these OSs via python interpreter causes SyntaxError // invoke directly so that shebangs work as expected // handle Windows manually because it doesn't do shebangs if runtime.GOOS == "windows" { - python := internal.GetPythonExecPath() - args := []string{path, "--leave-comments", "--initial-header-level=2"} - result = internal.ExternallyRenderContent(c.cfg, ctx, src, python, args) + pythonBinary, _ := internal.GetPythonBinaryAndExecPath() + args := []string{binaryPath, "--leave-comments", "--initial-header-level=2"} + result, err = internal.ExternallyRenderContent(c.cfg, ctx, src, pythonBinary, args) } else { args := []string{"--leave-comments", "--initial-header-level=2"} - result = internal.ExternallyRenderContent(c.cfg, ctx, src, path, args) + result, err = internal.ExternallyRenderContent(c.cfg, ctx, src, binaryName, args) + } + + if err != nil { + return nil, err } + // TODO(bep) check if rst2html has a body only option. bodyStart := bytes.Index(result, []byte("<body>\n")) if bodyStart < 0 { @@ -96,24 +107,29 @@ func (c *rstConverter) getRstContent(src []byte, ctx converter.DocumentContext) } } - return result[bodyStart+7 : bodyEnd] + return result[bodyStart+7 : bodyEnd], err } -func getRstExecPath() string { - path, err := safeexec.LookPath("rst2html") - if err != nil { - path, err = safeexec.LookPath("rst2html.py") - if err != nil { - return "" +var rst2Binaries = []string{"rst2html", "rst2html.py"} + +func getRstBinaryNameAndPath() (string, string) { + for _, candidate := range rst2Binaries { + if pth := hexec.LookPath(candidate); pth != "" { + return candidate, pth } } - return path + return "", "" } -// Supports returns whether rst is installed on this computer. +// Supports returns whether rst is (or should be) installed on this computer. func Supports() bool { + name, _ := getRstBinaryNameAndPath() + hasBin := name != "" if htesting.SupportsAll() { + if !hasBin { + panic("rst not installed") + } return true } - return getRstExecPath() != "" + return hasBin } diff --git a/markup/rst/convert_test.go b/markup/rst/convert_test.go index 269d92caa..5d2882de1 100644 --- a/markup/rst/convert_test.go +++ b/markup/rst/convert_test.go @@ -16,7 +16,9 @@ package rst import ( "testing" + "github.com/gohugoio/hugo/common/hexec" "github.com/gohugoio/hugo/common/loggers" + "github.com/gohugoio/hugo/config/security" "github.com/gohugoio/hugo/markup/converter" @@ -28,7 +30,14 @@ func TestConvert(t *testing.T) { t.Skip("rst not installed") } c := qt.New(t) - p, err := Provider.New(converter.ProviderConfig{Logger: loggers.NewErrorLogger()}) + sc := security.DefaultConfig + sc.Exec.Allow = security.NewWhitelist("rst", "python") + + p, err := Provider.New( + converter.ProviderConfig{ + Logger: loggers.NewErrorLogger(), + Exec: hexec.New(sc), + }) c.Assert(err, qt.IsNil) conv, err := p.New(converter.DocumentContext{}) c.Assert(err, qt.IsNil) |