diff --git a/docs/content/content-management/formats.md b/docs/content/content-management/formats.md index ad5036a2f..6ad665400 100644 --- a/docs/content/content-management/formats.md +++ b/docs/content/content-management/formats.md @@ -6,7 +6,7 @@ date: 2017-01-10 publishdate: 2017-01-10 lastmod: 2017-04-06 categories: [content management] -keywords: [markdown,asciidoc,mmark,content format] +keywords: [markdown,asciidoc,mmark,pandoc,content format] menu: docs: parent: "content-management" @@ -195,12 +195,19 @@ With this setup, everything is in place for a natural usage of MathJax on pages ## Additional Formats Through External Helpers -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].) +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].) 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/)). To use these formats, just use the standard extension and the front matter exactly as you would do with natively supported `.md` files. +Hugo passes reasonable default arguments to these external helpers by default: + +- `asciidoc`: `--no-header-footer --safe -` +- `asciidoctor`: `--no-header-footer --safe --trace -` +- `rst2html`: `--leave-comments --initial-header-level=2` +- `pandoc`: `--mathjax` + {{% 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. {{% /warning %}} @@ -235,6 +242,7 @@ Markdown syntax is simple enough to learn in a single sitting. The following are [mmark]: https://github.com/miekg/mmark [mmarkgh]: https://github.com/miekg/mmark/wiki/Syntax [org]: http://orgmode.org/ +[pandoc]: http://www.pandoc.org/ [Pygments]: http://pygments.org/ [rest]: http://docutils.sourceforge.net/rst.html [sc]: /content-management/shortcodes/ diff --git a/helpers/content.go b/helpers/content.go index a79da090b..b776d0813 100644 --- a/helpers/content.go +++ b/helpers/content.go @@ -454,6 +454,8 @@ func (c ContentSpec) RenderBytes(ctx *RenderingContext) []byte { return getRstContent(ctx) case "org": return orgRender(ctx, c) + case "pandoc": + return getPandocContent(ctx) } } @@ -578,11 +580,6 @@ func getAsciidocExecPath() string { return path } -// HasAsciidoc returns whether Asciidoc is installed on this computer. -func HasAsciidoc() bool { - return getAsciidocExecPath() != "" -} - func getAsciidoctorExecPath() string { path, err := exec.LookPath("asciidoctor") if err != nil { @@ -591,17 +588,15 @@ func getAsciidoctorExecPath() string { return path } -// HasAsciidoctor returns whether Asciidoctor is installed on this computer. -func HasAsciidoctor() bool { - return getAsciidoctorExecPath() != "" +// HasAsciidoc returns whether Asciidoc or Asciidoctor is installed on this computer. +func HasAsciidoc() bool { + return (getAsciidoctorExecPath() != "" || + getAsciidocExecPath() != "") } // getAsciidocContent calls asciidoctor or asciidoc as an external helper // to convert AsciiDoc content to HTML. func getAsciidocContent(ctx *RenderingContext) []byte { - content := ctx.Content - cleanContent := bytes.Replace(content, SummaryDivider, []byte(""), 1) - var isAsciidoctor bool path := getAsciidoctorExecPath() if path == "" { @@ -609,7 +604,7 @@ func getAsciidocContent(ctx *RenderingContext) []byte { if path == "" { jww.ERROR.Println("asciidoctor / asciidoc not found in $PATH: Please install.\n", " Leaving AsciiDoc content unrendered.") - return content + return ctx.Content } } else { isAsciidoctor = true @@ -622,25 +617,7 @@ func getAsciidocContent(ctx *RenderingContext) []byte { args = append(args, "--trace") } args = append(args, "-") - cmd := exec.Command(path, args...) - cmd.Stdin = bytes.NewReader(cleanContent) - var out, cmderr bytes.Buffer - cmd.Stdout = &out - cmd.Stderr = &cmderr - err := cmd.Run() - // asciidoctor has exit code 0 even if there are errors in stderr - // -> log stderr output regardless of state of err - for _, item := range strings.Split(string(cmderr.Bytes()), "\n") { - item := strings.TrimSpace(item) - if item != "" { - jww.ERROR.Println(strings.Replace(item, "", ctx.DocumentName, 1)) - } - } - if err != nil { - jww.ERROR.Printf("%s rendering %s: %v", path, ctx.DocumentName, err) - } - - return normalizeExternalHelperLineFeeds(out.Bytes()) + return externallyRenderContent(ctx, path, args) } // HasRst returns whether rst2html is installed on this computer. @@ -673,40 +650,18 @@ func getPythonExecPath() string { // getRstContent calls the Python script rst2html as an external helper // to convert reStructuredText content to HTML. func getRstContent(ctx *RenderingContext) []byte { - content := ctx.Content - cleanContent := bytes.Replace(content, SummaryDivider, []byte(""), 1) - python := getPythonExecPath() path := getRstExecPath() if path == "" { jww.ERROR.Println("rst2html / rst2html.py not found in $PATH: Please install.\n", " Leaving reStructuredText content unrendered.") - return content + return ctx.Content } - jww.INFO.Println("Rendering", ctx.DocumentName, "with", path, "...") - cmd := exec.Command(python, path, "--leave-comments", "--initial-header-level=2") - cmd.Stdin = bytes.NewReader(cleanContent) - var out, cmderr bytes.Buffer - cmd.Stdout = &out - cmd.Stderr = &cmderr - err := cmd.Run() - // By default rst2html exits 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(string(cmderr.Bytes()), "\n") { - item := strings.TrimSpace(item) - if item != "" { - jww.ERROR.Println(strings.Replace(item, "", ctx.DocumentName, 1)) - } - } - if err != nil { - jww.ERROR.Printf("%s rendering %s: %v", path, ctx.DocumentName, err) - } - - result := normalizeExternalHelperLineFeeds(out.Bytes()) - + args := []string{path, "--leave-comments", "--initial-header-level=2"} + result := externallyRenderContent(ctx, python, args) // TODO(bep) check if rst2html has a body only option. bodyStart := bytes.Index(result, []byte("\n")) if bodyStart < 0 { @@ -724,9 +679,46 @@ func getRstContent(ctx *RenderingContext) []byte { return result[bodyStart+7 : bodyEnd] } +// getPandocContent calls pandoc as an external helper to convert pandoc markdown to HTML. +func getPandocContent(ctx *RenderingContext) []byte { + path, err := exec.LookPath("pandoc") + if err != nil { + jww.ERROR.Println("pandoc not found in $PATH: Please install.\n", + " Leaving pandoc content unrendered.") + return ctx.Content + } + args := []string{"--mathjax"} + return externallyRenderContent(ctx, path, args) +} + func orgRender(ctx *RenderingContext, c ContentSpec) []byte { content := ctx.Content cleanContent := bytes.Replace(content, []byte("# more"), []byte(""), 1) return goorgeous.Org(cleanContent, c.getHTMLRenderer(blackfriday.HTML_TOC, ctx)) } + +func externallyRenderContent(ctx *RenderingContext, path string, args []string) []byte { + content := ctx.Content + cleanContent := bytes.Replace(content, SummaryDivider, []byte(""), 1) + + cmd := exec.Command(path, args...) + cmd.Stdin = bytes.NewReader(cleanContent) + var out, cmderr bytes.Buffer + cmd.Stdout = &out + cmd.Stderr = &cmderr + 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(string(cmderr.Bytes()), "\n") { + item := strings.TrimSpace(item) + if item != "" { + jww.ERROR.Printf("%s: %s", ctx.DocumentName, item) + } + } + if err != nil { + jww.ERROR.Printf("%s rendering %s: %v", path, ctx.DocumentName, err) + } + + return normalizeExternalHelperLineFeeds(out.Bytes()) +} diff --git a/helpers/general.go b/helpers/general.go index a064309d3..4517a3232 100644 --- a/helpers/general.go +++ b/helpers/general.go @@ -78,6 +78,8 @@ func GuessType(in string) string { return "mmark" case "rst": return "rst" + case "pandoc", "pdc": + return "pandoc" case "html", "htm": return "html" case "org": diff --git a/helpers/general_test.go b/helpers/general_test.go index 561f59522..2bca632e0 100644 --- a/helpers/general_test.go +++ b/helpers/general_test.go @@ -34,6 +34,8 @@ func TestGuessType(t *testing.T) { {"adoc", "asciidoc"}, {"ad", "asciidoc"}, {"rst", "rst"}, + {"pandoc", "pandoc"}, + {"pdc", "pandoc"}, {"mmark", "mmark"}, {"html", "html"}, {"htm", "html"}, diff --git a/hugolib/handler_page.go b/hugolib/handler_page.go index 6e230dad0..c9e22fe76 100644 --- a/hugolib/handler_page.go +++ b/hugolib/handler_page.go @@ -25,6 +25,7 @@ func init() { RegisterHandler(new(htmlHandler)) RegisterHandler(new(asciidocHandler)) RegisterHandler(new(rstHandler)) + RegisterHandler(new(pandocHandler)) RegisterHandler(new(mmarkHandler)) RegisterHandler(new(orgHandler)) } @@ -104,6 +105,15 @@ func (h rstHandler) PageConvert(p *Page) HandledResult { return commonConvert(p) } +type pandocHandler struct { + basicPageHandler +} + +func (h pandocHandler) Extensions() []string { return []string{"pandoc", "pdc"} } +func (h pandocHandler) PageConvert(p *Page) HandledResult { + return commonConvert(p) +} + type mmarkHandler struct { basicPageHandler } diff --git a/hugolib/page_test.go b/hugolib/page_test.go index f3a917acc..973e8509b 100644 --- a/hugolib/page_test.go +++ b/hugolib/page_test.go @@ -561,7 +561,7 @@ func testAllMarkdownEnginesForPages(t *testing.T, }{ {"md", func() bool { return true }}, {"mmark", func() bool { return true }}, - {"ad", func() bool { return helpers.HasAsciidoctor() || helpers.HasAsciidoc() }}, + {"ad", func() bool { return helpers.HasAsciidoc() }}, // TODO(bep) figure a way to include this without too much work.{"html", func() bool { return true }}, {"rst", func() bool { return helpers.HasRst() }}, } diff --git a/hugolib/shortcode_test.go b/hugolib/shortcode_test.go index ba0d43c65..7672befc7 100644 --- a/hugolib/shortcode_test.go +++ b/hugolib/shortcode_test.go @@ -565,7 +565,7 @@ tags: th := testHelper{s.Cfg, s.Fs, t} for _, test := range tests { - if strings.HasSuffix(test.contentPath, ".ad") && !helpers.HasAsciidoctor() && !helpers.HasAsciidoc() { + if strings.HasSuffix(test.contentPath, ".ad") && !helpers.HasAsciidoc() { fmt.Println("Skip Asciidoc test case as no Asciidoc present.") continue } else if strings.HasSuffix(test.contentPath, ".rst") && !helpers.HasRst() {