This commit is contained in:
Sebastian Höffner 2025-04-17 19:15:17 +02:00 committed by GitHub
commit 45bbac2744
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 210 additions and 6 deletions

View file

@ -0,0 +1,50 @@
---
title: Bibliographies in Markdown
linkTitle: Bibliography
description: Include citations and a bibliography in Markdown using LaTeX markup.
categories: [content management]
keywords: [latex,pandoc,citation,reference,bibliography]
menu:
docs:
parent: content-management
weight: 320
weight: 320
toc: true
---
{{< new-in 0.144.0 />}}
## Citations and Bibliographies
[Pandoc](https://pandoc.org) is a universal document converter and can be used to convert markdown files.
With **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:
```
---
title: Citation document
---
---
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-specific settings should go there.
You can also add all elements from a bibliography file (without citing them explicitly) using:
```
---
title: My Publications
---
---
bibliography: assets/bibliography.bib
nocite: |
@*
...
```
It is also possible to provide a custom [CSL style](https://citationstyles.org/authors/) by passing `csl: path-to-style.csl` as a Pandoc option.

View file

@ -105,6 +105,12 @@ Hugo passes these CLI flags when calling the Pandoc executable:
--mathjax
```
If your Pandoc has version 2.11 or later, it also passes this CLI flag:
```text
--citeproc
```
[Pandoc]: https://pandoc.org/
### reStructuredText

View file

@ -15,10 +15,12 @@
package pandoc
import (
"bytes"
"sync"
"github.com/gohugoio/hugo/common/hexec"
"github.com/gohugoio/hugo/htesting"
"github.com/gohugoio/hugo/identity"
"github.com/gohugoio/hugo/markup/converter"
"github.com/gohugoio/hugo/markup/internal"
)
@ -64,6 +66,9 @@ func (c *pandocConverter) getPandocContent(src []byte, ctx converter.DocumentCon
return src, nil
}
args := []string{"--mathjax"}
if supportsCitations(c.cfg) {
args = append(args[:], "--citeproc")
}
return internal.ExternallyRenderContent(c.cfg, ctx, src, binaryName, args)
}
@ -76,6 +81,45 @@ func getPandocBinaryName() string {
return ""
}
var pandocSupportsCiteprocOnce sync.Once
var pandocSupportsCiteproc bool
// getPandocSupportsCiteproc runs a dump-args to determine if pandoc knows the --citeproc argument
func getPandocSupportsCiteproc(cfg converter.ProviderConfig) (bool, error) {
var err error
pandocSupportsCiteprocOnce.Do(func() {
argsv := []any{"--dump-args", "--citeproc"}
var out bytes.Buffer
argsv = append(argsv, hexec.WithStdout(&out))
cmd, err := cfg.Exec.New(pandocBinary, argsv...)
if err != nil {
pandocSupportsCiteproc = false
return
}
err = cmd.Run()
if err != nil {
pandocSupportsCiteproc = false
return
}
pandocSupportsCiteproc = true
})
return pandocSupportsCiteproc, err
}
// supportsCitations returns true if citeproc is available
func supportsCitations(cfg converter.ProviderConfig) bool {
if Supports() {
supportsCiteproc, err := getPandocSupportsCiteproc(cfg)
return supportsCiteproc && err == nil
}
return false
}
// Supports returns whether Pandoc is installed on this computer.
func Supports() bool {
hasBin := getPandocBinaryName() != ""

View file

@ -25,7 +25,7 @@ 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")
}
@ -34,11 +34,109 @@ func TestConvert(t *testing.T) {
var err error
sc.Exec.Allow, err = security.NewWhitelist("pandoc")
c.Assert(err, qt.IsNil)
p, err := Provider.New(converter.ProviderConfig{Exec: hexec.New(sc, "", loggers.NewDefault()), Logger: loggers.NewDefault()})
cfg := converter.ProviderConfig{Exec: hexec.New(sc, "", loggers.NewDefault()), Logger: loggers.NewDefault()}
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")})
c.Assert(err, qt.IsNil)
c.Assert(string(b.Bytes()), qt.Equals, "<p>testContent</p>\n")
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(output.Bytes()), qt.Equals, "<p>testContent</p>\n")
}
func runCiteprocTest(t *testing.T, content string, expectContained []string, expectNotContained []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)
for _, expected := range expectContained {
c.Assert(string(output.Bytes()), qt.Contains, expected)
}
for _, notExpected := range expectNotContained {
c.Assert(string(output.Bytes()), qt.Not(qt.Contains), notExpected)
}
}
func TestGetPandocSupportsCiteprocCallTwice(t *testing.T) {
c, _, cfg := setupTestConverter(t)
supports1, err1 := getPandocSupportsCiteproc(cfg)
supports2, err2 := getPandocSupportsCiteproc(cfg)
c.Assert(supports1, qt.Equals, supports2)
c.Assert(err1, qt.IsNil)
c.Assert(err2, qt.IsNil)
}
func TestCiteprocWithHugoMeta(t *testing.T) {
content := `
---
title: Test
published: 2022-05-30
---
testContent
`
expected := []string{"testContent"}
unexpected := []string{"Doe", "Mustermann", "2022", "Treatise"}
runCiteprocTest(t, content, expected, unexpected)
}
func TestCiteprocWithPandocMeta(t *testing.T) {
content := `
---
---
---
...
testContent
`
expected := []string{"testContent"}
unexpected := []string{"Doe", "Mustermann", "2022", "Treatise"}
runCiteprocTest(t, content, expected, unexpected)
}
func TestCiteprocWithBibliography(t *testing.T) {
content := `
---
---
---
bibliography: testdata/bibliography.bib
...
testContent
`
expected := []string{"testContent"}
unexpected := []string{"Doe", "Mustermann", "2022", "Treatise"}
runCiteprocTest(t, content, expected, unexpected)
}
func TestCiteprocWithExplicitCitation(t *testing.T) {
content := `
---
---
---
bibliography: testdata/bibliography.bib
...
@Doe2022
`
expected := []string{"Doe", "Mustermann", "2022", "Treatise"}
runCiteprocTest(t, content, expected, []string{})
}
func TestCiteprocWithNocite(t *testing.T) {
content := `
---
---
---
bibliography: testdata/bibliography.bib
nocite: |
@*
...
`
expected := []string{"Doe", "Mustermann", "2022", "Treatise"}
runCiteprocTest(t, content, expected, []string{})
}

View file

@ -0,0 +1,6 @@
@article{Doe2022,
author = "Jane Doe and Max Mustermann",
title = "A Treatise on Hugo Tests",
journal = "Hugo Websites",
year = "2022",
}