Skip to content

Commit

Permalink
Support custom external formats/renderers
Browse files Browse the repository at this point in the history
Add the configuration variable externalFormats, which maps formats to a
custom external executable and arguments, as either a string parsed as
whitespace-separated tokens or a list. Shell out to these executables to
render content of those formats.

Fixes gohugoio#234.
  • Loading branch information
betaveros committed Nov 5, 2017
1 parent b88a105 commit 3e9549e
Show file tree
Hide file tree
Showing 2 changed files with 67 additions and 0 deletions.
14 changes: 14 additions & 0 deletions docs/content/content-management/formats.md
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,20 @@ For example, for Asciidoc files, Hugo will try to call the `asciidoctor` or `asc

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

You can also specify entirely custom external helpers for a particular format, possibly overriding the built-in renderers, by adding entries under the `externalFormats` variable in your [site configuration][config]. For example, if you wanted to use `pandoc` to render your markup with the flag `--strip-comments`, you might put the following in `config.toml`:

```
[externalFormats]
markdown: pandoc --strip-comments
```

Or in `config.yaml`:

```
externalFormats:
markdown: pandoc --strip-comments
```

{{% 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 %}}
Expand Down
53 changes: 53 additions & 0 deletions helpers/content.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ type ContentSpec struct {
footnoteReturnLinkContents string
// SummaryLength is the length of the summary that Hugo extracts from a content.
summaryLength int
// map of format to executable name and following args, as either a string
// parsed as a list of whitespace-separated tokens or as list of strings
externalFormats map[string]interface{}

Highlight func(code, lang, optsStr string) (string, error)
defatultPygmentsOpts map[string]string
Expand All @@ -61,6 +64,7 @@ func NewContentSpec(cfg config.Provider) (*ContentSpec, error) {
footnoteAnchorPrefix: cfg.GetString("footnoteAnchorPrefix"),
footnoteReturnLinkContents: cfg.GetString("footnoteReturnLinkContents"),
summaryLength: cfg.GetInt("summaryLength"),
externalFormats: cfg.GetStringMap("externalFormats"),

cfg: cfg,
}
Expand Down Expand Up @@ -441,6 +445,18 @@ type RenderingContext struct {

// RenderBytes renders a []byte.
func (c ContentSpec) RenderBytes(ctx *RenderingContext) []byte {
external := c.externalFormats[ctx.PageFmt]
if external != nil {
switch external.(type) {
case []string:
return externallyRenderContent(ctx, external.([]string))
case string:
return externallyRenderContent(ctx, strings.Fields(external.(string)))
default:
jww.ERROR.Printf("Could not understand external format for %s", ctx.PageFmt)
jww.ERROR.Println("Falling back to normal rendering methods")
}
}
switch ctx.PageFmt {
default:
return c.markdownRender(ctx)
Expand Down Expand Up @@ -730,3 +746,40 @@ func orgRender(ctx *RenderingContext, c ContentSpec) []byte {
return goorgeous.Org(cleanContent,
c.getHTMLRenderer(blackfriday.HTML_TOC, ctx))
}

func externallyRenderContent(ctx *RenderingContext, externalFields []string) []byte {
content := ctx.Content
cleanContent := bytes.Replace(content, SummaryDivider, []byte(""), 1)

externalProgram := externalFields[0]
externalArgs := externalFields[1:]

path, err := exec.LookPath(externalProgram)

if err != nil {
jww.ERROR.Println(externalProgram, "not found in $PATH. Leaving content unrendered.")
return content
}

jww.INFO.Println("Rendering", ctx.DocumentName, "with", path, "...")
cmd := exec.Command(path, externalArgs...)
cmd.Stdin = bytes.NewReader(cleanContent)
var out, cmderr bytes.Buffer
cmd.Stdout = &out
cmd.Stderr = &cmderr
err = cmd.Run()
// like in rst2html and AsciiDoc, 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)
}

result := normalizeExternalHelperLineFeeds(out.Bytes())

return result
}

0 comments on commit 3e9549e

Please sign in to comment.