Skip to content

Commit e69da7a

Browse files
betaverosbep
authored andcommitted
Add Pandoc support, refactor external helpers
Recognize the Pandoc format under the file extension .pandoc or .pdc, and shell out to pandoc as an external helper to format Pandoc content. Refactor out repeated code with external helpers. Change the error output formatting. I did not see any of the external helpers print the string "<input>" to represent stdin as a file; just prepending the file name to error output is more general and doesn't sacrifice that much in terms of readability. Closes #234
1 parent e765218 commit e69da7a

File tree

7 files changed

+74
-60
lines changed

7 files changed

+74
-60
lines changed

docs/content/content-management/formats.md

+10-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ date: 2017-01-10
66
publishdate: 2017-01-10
77
lastmod: 2017-04-06
88
categories: [content management]
9-
keywords: [markdown,asciidoc,mmark,content format]
9+
keywords: [markdown,asciidoc,mmark,pandoc,content format]
1010
menu:
1111
docs:
1212
parent: "content-management"
@@ -195,12 +195,19 @@ With this setup, everything is in place for a natural usage of MathJax on pages
195195

196196
## Additional Formats Through External Helpers
197197

198-
Hugo has new concept called _external helpers_. It means that you can write your content using [Asciidoc][ascii], [reStructuredText][rest]. If you have files with associated extensions, Hugo will call external commands to generate the content. ([See the Hugo source code for external helpers][helperssource].)
198+
Hugo has a new concept called _external helpers_. It means that you can write your content using [Asciidoc][ascii], [reStructuredText][rest], or [pandoc]. If you have files with associated extensions, Hugo will call external commands to generate the content. ([See the Hugo source code for external helpers][helperssource].)
199199

200200
For example, for Asciidoc files, Hugo will try to call the `asciidoctor` or `asciidoc` command. This means that you will have to install the associated tool on your machine to be able to use these formats. ([See the Asciidoctor docs for installation instructions](http://asciidoctor.org/docs/install-toolchain/)).
201201

202202
To use these formats, just use the standard extension and the front matter exactly as you would do with natively supported `.md` files.
203203

204+
Hugo passes reasonable default arguments to these external helpers by default:
205+
206+
- `asciidoc`: `--no-header-footer --safe -`
207+
- `asciidoctor`: `--no-header-footer --safe --trace -`
208+
- `rst2html`: `--leave-comments --initial-header-level=2`
209+
- `pandoc`: `--mathjax`
210+
204211
{{% warning "Performance of External Helpers" %}}
205212
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.
206213
{{% /warning %}}
@@ -235,6 +242,7 @@ Markdown syntax is simple enough to learn in a single sitting. The following are
235242
[mmark]: https://github.com/miekg/mmark
236243
[mmarkgh]: https://github.com/miekg/mmark/wiki/Syntax
237244
[org]: http://orgmode.org/
245+
[pandoc]: http://www.pandoc.org/
238246
[Pygments]: http://pygments.org/
239247
[rest]: http://docutils.sourceforge.net/rst.html
240248
[sc]: /content-management/shortcodes/

helpers/content.go

+48-56
Original file line numberDiff line numberDiff line change
@@ -454,6 +454,8 @@ func (c ContentSpec) RenderBytes(ctx *RenderingContext) []byte {
454454
return getRstContent(ctx)
455455
case "org":
456456
return orgRender(ctx, c)
457+
case "pandoc":
458+
return getPandocContent(ctx)
457459
}
458460
}
459461

@@ -578,11 +580,6 @@ func getAsciidocExecPath() string {
578580
return path
579581
}
580582

581-
// HasAsciidoc returns whether Asciidoc is installed on this computer.
582-
func HasAsciidoc() bool {
583-
return getAsciidocExecPath() != ""
584-
}
585-
586583
func getAsciidoctorExecPath() string {
587584
path, err := exec.LookPath("asciidoctor")
588585
if err != nil {
@@ -591,25 +588,23 @@ func getAsciidoctorExecPath() string {
591588
return path
592589
}
593590

594-
// HasAsciidoctor returns whether Asciidoctor is installed on this computer.
595-
func HasAsciidoctor() bool {
596-
return getAsciidoctorExecPath() != ""
591+
// HasAsciidoc returns whether Asciidoc or Asciidoctor is installed on this computer.
592+
func HasAsciidoc() bool {
593+
return (getAsciidoctorExecPath() != "" ||
594+
getAsciidocExecPath() != "")
597595
}
598596

599597
// getAsciidocContent calls asciidoctor or asciidoc as an external helper
600598
// to convert AsciiDoc content to HTML.
601599
func getAsciidocContent(ctx *RenderingContext) []byte {
602-
content := ctx.Content
603-
cleanContent := bytes.Replace(content, SummaryDivider, []byte(""), 1)
604-
605600
var isAsciidoctor bool
606601
path := getAsciidoctorExecPath()
607602
if path == "" {
608603
path = getAsciidocExecPath()
609604
if path == "" {
610605
jww.ERROR.Println("asciidoctor / asciidoc not found in $PATH: Please install.\n",
611606
" Leaving AsciiDoc content unrendered.")
612-
return content
607+
return ctx.Content
613608
}
614609
} else {
615610
isAsciidoctor = true
@@ -622,25 +617,7 @@ func getAsciidocContent(ctx *RenderingContext) []byte {
622617
args = append(args, "--trace")
623618
}
624619
args = append(args, "-")
625-
cmd := exec.Command(path, args...)
626-
cmd.Stdin = bytes.NewReader(cleanContent)
627-
var out, cmderr bytes.Buffer
628-
cmd.Stdout = &out
629-
cmd.Stderr = &cmderr
630-
err := cmd.Run()
631-
// asciidoctor has exit code 0 even if there are errors in stderr
632-
// -> log stderr output regardless of state of err
633-
for _, item := range strings.Split(string(cmderr.Bytes()), "\n") {
634-
item := strings.TrimSpace(item)
635-
if item != "" {
636-
jww.ERROR.Println(strings.Replace(item, "<stdin>", ctx.DocumentName, 1))
637-
}
638-
}
639-
if err != nil {
640-
jww.ERROR.Printf("%s rendering %s: %v", path, ctx.DocumentName, err)
641-
}
642-
643-
return normalizeExternalHelperLineFeeds(out.Bytes())
620+
return externallyRenderContent(ctx, path, args)
644621
}
645622

646623
// HasRst returns whether rst2html is installed on this computer.
@@ -673,40 +650,18 @@ func getPythonExecPath() string {
673650
// getRstContent calls the Python script rst2html as an external helper
674651
// to convert reStructuredText content to HTML.
675652
func getRstContent(ctx *RenderingContext) []byte {
676-
content := ctx.Content
677-
cleanContent := bytes.Replace(content, SummaryDivider, []byte(""), 1)
678-
679653
python := getPythonExecPath()
680654
path := getRstExecPath()
681655

682656
if path == "" {
683657
jww.ERROR.Println("rst2html / rst2html.py not found in $PATH: Please install.\n",
684658
" Leaving reStructuredText content unrendered.")
685-
return content
659+
return ctx.Content
686660

687661
}
688-
689662
jww.INFO.Println("Rendering", ctx.DocumentName, "with", path, "...")
690-
cmd := exec.Command(python, path, "--leave-comments", "--initial-header-level=2")
691-
cmd.Stdin = bytes.NewReader(cleanContent)
692-
var out, cmderr bytes.Buffer
693-
cmd.Stdout = &out
694-
cmd.Stderr = &cmderr
695-
err := cmd.Run()
696-
// By default rst2html exits w/ non-zero exit code only if severe, i.e.
697-
// halting errors occurred. -> log stderr output regardless of state of err
698-
for _, item := range strings.Split(string(cmderr.Bytes()), "\n") {
699-
item := strings.TrimSpace(item)
700-
if item != "" {
701-
jww.ERROR.Println(strings.Replace(item, "<stdin>", ctx.DocumentName, 1))
702-
}
703-
}
704-
if err != nil {
705-
jww.ERROR.Printf("%s rendering %s: %v", path, ctx.DocumentName, err)
706-
}
707-
708-
result := normalizeExternalHelperLineFeeds(out.Bytes())
709-
663+
args := []string{path, "--leave-comments", "--initial-header-level=2"}
664+
result := externallyRenderContent(ctx, python, args)
710665
// TODO(bep) check if rst2html has a body only option.
711666
bodyStart := bytes.Index(result, []byte("<body>\n"))
712667
if bodyStart < 0 {
@@ -724,9 +679,46 @@ func getRstContent(ctx *RenderingContext) []byte {
724679
return result[bodyStart+7 : bodyEnd]
725680
}
726681

682+
// getPandocContent calls pandoc as an external helper to convert pandoc markdown to HTML.
683+
func getPandocContent(ctx *RenderingContext) []byte {
684+
path, err := exec.LookPath("pandoc")
685+
if err != nil {
686+
jww.ERROR.Println("pandoc not found in $PATH: Please install.\n",
687+
" Leaving pandoc content unrendered.")
688+
return ctx.Content
689+
}
690+
args := []string{"--mathjax"}
691+
return externallyRenderContent(ctx, path, args)
692+
}
693+
727694
func orgRender(ctx *RenderingContext, c ContentSpec) []byte {
728695
content := ctx.Content
729696
cleanContent := bytes.Replace(content, []byte("# more"), []byte(""), 1)
730697
return goorgeous.Org(cleanContent,
731698
c.getHTMLRenderer(blackfriday.HTML_TOC, ctx))
732699
}
700+
701+
func externallyRenderContent(ctx *RenderingContext, path string, args []string) []byte {
702+
content := ctx.Content
703+
cleanContent := bytes.Replace(content, SummaryDivider, []byte(""), 1)
704+
705+
cmd := exec.Command(path, args...)
706+
cmd.Stdin = bytes.NewReader(cleanContent)
707+
var out, cmderr bytes.Buffer
708+
cmd.Stdout = &out
709+
cmd.Stderr = &cmderr
710+
err := cmd.Run()
711+
// Most external helpers exit w/ non-zero exit code only if severe, i.e.
712+
// halting errors occurred. -> log stderr output regardless of state of err
713+
for _, item := range strings.Split(string(cmderr.Bytes()), "\n") {
714+
item := strings.TrimSpace(item)
715+
if item != "" {
716+
jww.ERROR.Printf("%s: %s", ctx.DocumentName, item)
717+
}
718+
}
719+
if err != nil {
720+
jww.ERROR.Printf("%s rendering %s: %v", path, ctx.DocumentName, err)
721+
}
722+
723+
return normalizeExternalHelperLineFeeds(out.Bytes())
724+
}

helpers/general.go

+2
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,8 @@ func GuessType(in string) string {
7878
return "mmark"
7979
case "rst":
8080
return "rst"
81+
case "pandoc", "pdc":
82+
return "pandoc"
8183
case "html", "htm":
8284
return "html"
8385
case "org":

helpers/general_test.go

+2
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ func TestGuessType(t *testing.T) {
3434
{"adoc", "asciidoc"},
3535
{"ad", "asciidoc"},
3636
{"rst", "rst"},
37+
{"pandoc", "pandoc"},
38+
{"pdc", "pandoc"},
3739
{"mmark", "mmark"},
3840
{"html", "html"},
3941
{"htm", "html"},

hugolib/handler_page.go

+10
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ func init() {
2525
RegisterHandler(new(htmlHandler))
2626
RegisterHandler(new(asciidocHandler))
2727
RegisterHandler(new(rstHandler))
28+
RegisterHandler(new(pandocHandler))
2829
RegisterHandler(new(mmarkHandler))
2930
RegisterHandler(new(orgHandler))
3031
}
@@ -104,6 +105,15 @@ func (h rstHandler) PageConvert(p *Page) HandledResult {
104105
return commonConvert(p)
105106
}
106107

108+
type pandocHandler struct {
109+
basicPageHandler
110+
}
111+
112+
func (h pandocHandler) Extensions() []string { return []string{"pandoc", "pdc"} }
113+
func (h pandocHandler) PageConvert(p *Page) HandledResult {
114+
return commonConvert(p)
115+
}
116+
107117
type mmarkHandler struct {
108118
basicPageHandler
109119
}

hugolib/page_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -561,7 +561,7 @@ func testAllMarkdownEnginesForPages(t *testing.T,
561561
}{
562562
{"md", func() bool { return true }},
563563
{"mmark", func() bool { return true }},
564-
{"ad", func() bool { return helpers.HasAsciidoctor() || helpers.HasAsciidoc() }},
564+
{"ad", func() bool { return helpers.HasAsciidoc() }},
565565
// TODO(bep) figure a way to include this without too much work.{"html", func() bool { return true }},
566566
{"rst", func() bool { return helpers.HasRst() }},
567567
}

hugolib/shortcode_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -565,7 +565,7 @@ tags:
565565
th := testHelper{s.Cfg, s.Fs, t}
566566

567567
for _, test := range tests {
568-
if strings.HasSuffix(test.contentPath, ".ad") && !helpers.HasAsciidoctor() && !helpers.HasAsciidoc() {
568+
if strings.HasSuffix(test.contentPath, ".ad") && !helpers.HasAsciidoc() {
569569
fmt.Println("Skip Asciidoc test case as no Asciidoc present.")
570570
continue
571571
} else if strings.HasSuffix(test.contentPath, ".rst") && !helpers.HasRst() {

0 commit comments

Comments
 (0)