Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow configuring Pandoc #7529

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions markup/bibliography/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Copyright 2019 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package bibliography

type Config struct {
// File containing bibliography. E.g. 'my-doc.bibtex'. By default assumed
// to be in BibTex format.
Source string

// Path to .csl file describing citation file.
CitationStyle string
}

var Default Config
8 changes: 8 additions & 0 deletions markup/markup_config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@ import (
"github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/markup/asciidocext/asciidocext_config"
"github.com/gohugoio/hugo/markup/bibliography"
"github.com/gohugoio/hugo/markup/goldmark/goldmark_config"
"github.com/gohugoio/hugo/markup/highlight"
"github.com/gohugoio/hugo/markup/pandoc/pandoc_config"
"github.com/gohugoio/hugo/markup/tableofcontents"
"github.com/mitchellh/mapstructure"
)
Expand All @@ -33,12 +35,16 @@ type Config struct {

// Table of contents configuration
TableOfContents tableofcontents.Config
Bibliography bibliography.Config

// Configuration for the Goldmark markdown engine.
Goldmark goldmark_config.Config

// Configuration for the Asciidoc external markdown engine.
AsciidocExt asciidocext_config.Config

// Configuration for Pandoc external markdown engine.
Pandoc pandoc_config.Config
}

func Decode(cfg config.Provider) (conf Config, err error) {
Expand Down Expand Up @@ -102,7 +108,9 @@ var Default = Config{

TableOfContents: tableofcontents.DefaultConfig,
Highlight: highlight.DefaultConfig,
Bibliography: bibliography.Default,

Goldmark: goldmark_config.Default,
Pandoc: pandoc_config.Default,
AsciidocExt: asciidocext_config.Default,
}
82 changes: 61 additions & 21 deletions markup/pandoc/convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,34 @@
package pandoc

import (
"errors"
"strings"

"github.com/gohugoio/hugo/common/hexec"
"github.com/gohugoio/hugo/htesting"
"github.com/gohugoio/hugo/identity"
"github.com/mitchellh/mapstructure"

"github.com/gohugoio/hugo/identity"
"github.com/gohugoio/hugo/markup/bibliography"
"github.com/gohugoio/hugo/markup/converter"
"github.com/gohugoio/hugo/markup/internal"
"github.com/gohugoio/hugo/markup/pandoc/pandoc_config"

"path"
)

type paramer interface {
Param(interface{}) (interface{}, error)
}

type searchPaths struct {
Paths []string
}

func (s *searchPaths) AsResourcePath() string {
return strings.Join(s.Paths, ":")
}

// Provider is the package entry point.
var Provider converter.ProviderProvider = provider{}

Expand All @@ -32,19 +52,19 @@ type provider struct {
func (p provider) New(cfg converter.ProviderConfig) (converter.Provider, error) {
return converter.NewProvider("pandoc", func(ctx converter.DocumentContext) (converter.Converter, error) {
return &pandocConverter{
ctx: ctx,
cfg: cfg,
docCtx: ctx,
cfg: cfg,
}, nil
}), nil
}

type pandocConverter struct {
ctx converter.DocumentContext
cfg converter.ProviderConfig
docCtx converter.DocumentContext
cfg converter.ProviderConfig
}

func (c *pandocConverter) Convert(ctx converter.RenderContext) (converter.ResultRender, error) {
b, err := c.getPandocContent(ctx.Src, c.ctx)
b, err := c.getPandocContent(ctx.Src)
if err != nil {
return nil, err
}
Expand All @@ -56,30 +76,50 @@ func (c *pandocConverter) Supports(feature identity.Identity) bool {
}

// getPandocContent calls pandoc as an external helper to convert pandoc markdown to HTML.
func (c *pandocConverter) getPandocContent(src []byte, ctx converter.DocumentContext) ([]byte, error) {
logger := c.cfg.Logger
binaryName := getPandocBinaryName()
if binaryName == "" {
logger.Println("pandoc not found in $PATH: Please install.\n",
" Leaving pandoc content unrendered.")
return src, nil
func (c *pandocConverter) getPandocContent(src []byte) ([]byte, error) {
pandocPath, pandocFound := getPandocBinaryName()
if !pandocFound {
return nil, errors.New("pandoc not found in $PATH: Please install.")
}
args := []string{"--mathjax"}
return internal.ExternallyRenderContent(c.cfg, ctx, src, binaryName, args)

var pandocConfig pandoc_config.Config = c.cfg.MarkupConfig().Pandoc
var bibConfig bibliography.Config = c.cfg.MarkupConfig().Bibliography

if pageParameters, ok := c.docCtx.Document.(paramer); ok {
if bibParam, err := pageParameters.Param("bibliography"); err == nil {
mapstructure.WeakDecode(bibParam, &bibConfig)
}

if pandocParam, err := pageParameters.Param("pandoc"); err == nil {
mapstructure.WeakDecode(pandocParam, &pandocConfig)
}
}

arguments := pandocConfig.AsPandocArguments()

if bibConfig.Source != "" {
arguments = append(arguments, "--citeproc", "--bibliography", bibConfig.Source)
if bibConfig.CitationStyle != "" {
arguments = append(arguments, "--csl", bibConfig.CitationStyle)
}
}

resourcePath := strings.Join([]string{path.Dir(c.docCtx.Filename), "static", "."}, ":")
arguments = append(arguments, "--resource-path", resourcePath)

renderedContent, _ := internal.ExternallyRenderContent(c.cfg, c.docCtx, src, pandocPath, arguments)
return renderedContent, nil
}

const pandocBinary = "pandoc"

func getPandocBinaryName() string {
if hexec.InPath(pandocBinary) {
return pandocBinary
}
return ""
func getPandocBinaryName() (string, bool) {
return pandocBinary, hexec.InPath(pandocBinary)
}

// Supports returns whether Pandoc is installed on this computer.
func Supports() bool {
hasBin := getPandocBinaryName() != ""
_, hasBin := getPandocBinaryName()
if htesting.SupportsAll() {
if !hasBin {
panic("pandoc not installed")
Expand Down
165 changes: 165 additions & 0 deletions markup/pandoc/pandoc_config/pandoc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
package pandoc_config

import (
"fmt"
"strings"
)

// Config contains configuration settings for Pandoc.
type Config struct {
// Input format. Use the 'Extensions' field to specify extensions thereof.
// Only specify the bare format here. Defaults to 'markdown' if empty. Invoke
// "pandoc --list-input-formats" to see the list of supported input formats
// including various Markdown dialects.
InputFormat string

// If true, the output format is HTML (i.e. "--to=html"). Otherwise the output
// format is HTML5 (i.e. "--to=html5").
UseLegacyHtml bool

// Equivalent to specifying "--mathjax". For compatibility, this option is
// always true if none of the other math options are used.
// See https://pandoc.org/MANUAL.html#math-rendering-in-html
UseMathjax bool

// Equivalent to specifying "--mathml".
// See https://pandoc.org/MANUAL.html#math-rendering-in-html
UseMathml bool

// Equivalent to specifying "--webtex".
// See https://pandoc.org/MANUAL.html#math-rendering-in-html. Uses the default
// Webtex rendering URL.
UseWebtex bool

// Equivalent to specifying "--katex".
// See https://pandoc.org/MANUAL.html#math-rendering-in-html
UseKatex bool

// List of filters to use. These translate to '--filter=' or '--lua-filter'
// arguments to the pandoc invocation. The order of elements in `Filters`
// is preserved when constructing the `pandoc` commandline.
//
// Use the prefix 'lua:' or the suffix '.lua' to indicate Lua filters.
Filters []string

// List of Pandoc Markdown extensions to use. No need to include default
// extensions. Specifying ["foo", "bar"] is equivalent to specifying
// --from=markdown+foo+bar on the pandoc commandline.
Extensions []string

// List of input format extensions to use. Specifying ["foo", "bar"] is
// equivalent to specifying --from=markdown+foo+bar on the pandoc commandline
// assuming InputFormat is "markdown".
InputExtensions []string

// List of output format extensions to use. Specifying ["foo", "bar"] is
// equivalent to specifying --to=html5+foo+bar on the pandoc commandline,
// assuming UseLegacyHTML is false. Invoke "pandoc --list-extensions=html5" to
// or "pandoc --list-extensions=html5" to see the list of extensions that can
// be specified here.
OutputExtensions []string

// Metadata. The dictionary keys and values are handled in the obvious way.
Metadata map[string]interface{}

// Extra commandline options passed to the pandoc invocation. These options
// are appended to the commandline after the format and filter options.
// Arguments are passed in literally. Hence must have the "--" or "-" prefix
// where applicable.
ExtraArgs []string
}

var Default = Config{
InputFormat: "markdown",
UseLegacyHtml: false,
UseMathjax: true,
}

func (c *Config) getInputArg() string {
var b strings.Builder
b.WriteString("--from=")
if len(c.InputFormat) > 0 {
b.WriteString(c.InputFormat)
} else {
b.WriteString("markdown")
}

for _, extension := range c.InputExtensions {
b.WriteString("+")
b.WriteString(extension)
}
return b.String()
}

func (c *Config) getOutputArg() string {
var b strings.Builder
b.WriteString("--to=")
if c.UseLegacyHtml {
b.WriteString("html")
} else {
b.WriteString("html5")
}

for _, extension := range c.OutputExtensions {
b.WriteString("+")
b.WriteString(extension)
}
return b.String()
}

func (c *Config) getMathRenderingArg() string {
switch {
case c.UseMathml:
return "--mathml"
case c.UseWebtex:
return "--webtex"
case c.UseKatex:
return "--katex"
default:
return "--mathjax"
}
}

func (c *Config) getMetadataArgs() []string {
var args []string
for k, iv := range c.Metadata {
var v string
if sv, ok := iv.(string); ok {
v = sv
} else if sv, ok := iv.(fmt.Stringer); ok {
v = sv.String()
} else {
v = fmt.Sprintf("%v", iv)
}
args = append(args, fmt.Sprintf("-M%s=%s", k, v))
}
return args
}

func (c *Config) getFilterArgs() []string {
var args []string
for _, filterPath := range c.Filters {
if strings.HasPrefix(filterPath, "lua:") || strings.HasSuffix(filterPath, ".lua") {
args = append(args, fmt.Sprintf("--lua-filter=%s", strings.TrimPrefix(filterPath, "lua:")))
} else {
args = append(args, fmt.Sprintf("--filter=%s", filterPath))
}
}
return args
}

// AsPandocArguments returns a list of strings that can be used as arguments to
// a "pandoc" invocation. All the settings contained in Config are represented
// in the returned list of arguments.
func (c *Config) AsPandocArguments() []string {
args := []string{
c.getInputArg(),
c.getOutputArg(),
c.getMathRenderingArg()}

args = append(args, c.getMetadataArgs()...)
args = append(args, c.getFilterArgs()...)
args = append(args, c.ExtraArgs...)

return args
}