diff --git a/docs/content/en/content-management/formats.md b/docs/content/en/content-management/formats.md index f7b2bc1b733..04421e3fb9b 100644 --- a/docs/content/en/content-management/formats.md +++ b/docs/content/en/content-management/formats.md @@ -48,7 +48,7 @@ Hugo passes reasonable default arguments to these external helpers by default: - `asciidoctor`: `--no-header-footer -` - `rst2html`: `--leave-comments --initial-header-level=2` -- `pandoc`: `--mathjax --citeproc` +- `pandoc`: `--mathjax` and, for pandoc >= 2.11, `--citeproc` {{% warning "Performance of External Helpers" %}} Because additional formats are external commands, generation performance will rely heavily on the performance of the external tool you are using. As this feature is still in its infancy, feedback is welcome. @@ -137,7 +137,8 @@ This will render in your HTML as: ``` You will have to [add MathJax](https://www.mathjax.org/#gettingstarted) to your template to properly render the math. -Additionally, Pandoc enables [citations](https://pandoc.org/MANUAL.html#extension-citations) using, e.g., [BibTeX files](https://en.wikibooks.org/wiki/LaTeX/Bibliography_Management#BibTeX): +For **Pandoc >= 2.11**, you can use [citations](https://pandoc.org/MANUAL.html#extension-citations). +One way is to employ [BibTeX files](https://en.wikibooks.org/wiki/LaTeX/Bibliography_Management#BibTeX) to cite: ``` --- @@ -149,9 +150,10 @@ bibliography: assets/bibliography.bib This is a citation: @Doe2022 ``` -Note that Hugo will **not** pass its metadata YAML block to Pandoc; however, it will pass the **second** meta data block, denoted with `---` and `...` to Pandoc. Thus, all pandoc settings should go there. +Note that Hugo will **not** pass its metadata YAML block to Pandoc; however, it will pass the **second** meta data block, denoted with `---` and `...` to Pandoc. +Thus, all Pandoc settings should go there. -You can also add all elements from a bibliography file (without citing them first) using: +You can also add all elements from a bibliography file (without citing them explicitly) using: ``` --- diff --git a/markup/pandoc/convert.go b/markup/pandoc/convert.go index fed8cff2715..9da48792538 100644 --- a/markup/pandoc/convert.go +++ b/markup/pandoc/convert.go @@ -15,12 +15,17 @@ package pandoc import ( + "bytes" + "fmt" + "strings" + "sync" + + "github.com/gohugoio/hugo/common/collections" "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. @@ -64,7 +69,10 @@ func (c *pandocConverter) getPandocContent(src []byte, ctx converter.DocumentCon " Leaving pandoc content unrendered.") return src, nil } - args := []string{"--mathjax", "--citeproc"} + args := []string{"--mathjax"} + if supportsCitations(c.cfg) { + args = append(args[:], "--citeproc") + } return internal.ExternallyRenderContent(c.cfg, ctx, src, binaryName, args) } @@ -77,6 +85,51 @@ func getPandocBinaryName() string { return "" } +var versionOnce sync.Once + +// getPandocVersion parses the pandoc version output +func getPandocVersion(cfg converter.ProviderConfig) (string, error) { + var version string + var err error + + versionOnce.Do(func() { + argsv := collections.StringSliceToInterfaceSlice([]string{"--version"}) + + var out bytes.Buffer + argsv = append(argsv, hexec.WithStdout(&out)) + + cmd, err := cfg.Exec.New(pandocBinary, argsv...) + if err != nil { + version = "" + return + } + + err = cmd.Run() + if err != nil { + cfg.Logger.Errorf("%s --version: %v", pandocBinary, err) + } + + outbytes := bytes.Replace(out.Bytes(), []byte("\r"), []byte(""), -1) + output := strings.Split(string(outbytes), "\n")[0] + version = strings.Split(output, " ")[1] + }) + + return version, err +} + +// SupportsCitations returns true for pandoc versions >= 2.11, which include citeproc +func supportsCitations(cfg converter.ProviderConfig) bool { + pandocVersion, err := getPandocVersion(cfg) + supportsCitations := pandocVersion >= "2.11" && err != nil + if htesting.SupportsAll() { + if !supportsCitations { + panic(fmt.Sprintf("pandoc %s does not support citations", pandocVersion)) + } + return true + } + return supportsCitations +} + // Supports returns whether Pandoc is installed on this computer. func Supports() bool { hasBin := getPandocBinaryName() != "" diff --git a/markup/pandoc/convert_test.go b/markup/pandoc/convert_test.go index f549d5f4ff8..c750c01636c 100644 --- a/markup/pandoc/convert_test.go +++ b/markup/pandoc/convert_test.go @@ -25,18 +25,114 @@ import ( qt "github.com/frankban/quicktest" ) -func TestConvert(t *testing.T) { +func setupTestConverter(t *testing.T) (*qt.C, converter.Converter, converter.ProviderConfig) { if !Supports() { t.Skip("pandoc not installed") } c := qt.New(t) sc := security.DefaultConfig sc.Exec.Allow = security.NewWhitelist("pandoc") - p, err := Provider.New(converter.ProviderConfig{Exec: hexec.New(sc), Logger: loggers.NewErrorLogger()}) + cfg := converter.ProviderConfig{Exec: hexec.New(sc), Logger: loggers.NewErrorLogger()} + p, err := Provider.New(cfg) c.Assert(err, qt.IsNil) conv, err := p.New(converter.DocumentContext{}) c.Assert(err, qt.IsNil) - b, err := conv.Convert(converter.RenderContext{Src: []byte("testContent")}) + return c, conv, cfg +} + +func TestConvert(t *testing.T) { + c, conv, _ := setupTestConverter(t) + output, err := conv.Convert(converter.RenderContext{Src: []byte("testContent")}) c.Assert(err, qt.IsNil) - c.Assert(string(b.Bytes()), qt.Equals, "
testContent
\n") + c.Assert(string(output.Bytes()), qt.Equals, "testContent
\n") +} + +func runCiteprocTest(t *testing.T, content string, expected string) { + c, conv, cfg := setupTestConverter(t) + if !supportsCitations(cfg) { + t.Skip("pandoc does not support citations") + } + output, err := conv.Convert(converter.RenderContext{Src: []byte(content)}) + c.Assert(err, qt.IsNil) + c.Assert(string(output.Bytes()), qt.Equals, expected) +} + +func TestCiteprocWithHugoMeta(t *testing.T) { + content := ` +--- +title: Test +published: 2022-05-30 +--- +testContent +` + expected := "testContent
\n" + runCiteprocTest(t, content, expected) +} + +func TestCiteprocWithPandocMeta(t *testing.T) { + content := ` +--- +--- +--- +... +testContent +` + expected := "testContent
\n" + runCiteprocTest(t, content, expected) +} + +func TestCiteprocWithBibliography(t *testing.T) { + content := ` +--- +--- +--- +bibliography: testdata/bibliography.bib +... +testContent +` + expected := "testContent
\n" + runCiteprocTest(t, content, expected) +} + +func TestCiteprocWithExplicitCitation(t *testing.T) { + content := ` +--- +--- +--- +bibliography: testdata/bibliography.bib +... +@Doe2022 +` + expected := `Doe and Mustermann +(2022)
+