Skip to content

hermit-ink/goldmark-template

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

46 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

goldmark-template

Build Status Go Reference Go Report Card Latest Release License

A goldmark extension that preserves Go template actions ({{...}}) in rendered Markdown, preventing HTML escaping and maintaining template syntax wherever it appears so that the HTML output can be used directly by the Go stdlib html/template

Motivation

You want to use go html/template actions in your markdown. Markdown is not HTML so you can't execute the markdown as a template and you might not want to do that anyway. Instead you want to goldmark to simply "ignore" all the go template actions putting them verbatim into the correct places in the output HTML so that you can execute the output from goldmark using go's html/template package.

Warning

This makes your goldmark instance no longer commonmark compliant since go template actions are not valid URLs and you want to be able to use a template action in place of a URL potentially.

Features

  • Preserves template actions in inline code and code blocks
  • Template-aware parsing for links, images, and autolinks
  • Reference link support with template URLs and titles
  • Standalone template actions as inline elements
  • Full compatibility with other goldmark extensions (GFM, etc.)
  • Smart parsing that handles quotes and nested braces correctly
  • Comprehensive testing for 100% compatibility with the existing goldmark parsers and renderers

Installation

go get github.com/hermit-ink/goldmark-template

Usage

Basic Usage

package main

import (
    "bytes"
    "fmt"

    "github.com/yuin/goldmark"
    "github.com/yuin/goldmark/renderer/html"
    goldmarktemplate "github.com/hermit-ink/goldmark-template"
)

func main() {
    md := goldmark.New(
        goldmark.WithExtensions(
            goldmarktemplate.New(),
        ),
        goldmark.WithRendererOptions(
            html.WithUnsafe(), // Required for action preservation in raw HTML
        ),
    )

    input := []byte("# {{ .Title }}\n\n[Link]({{ .URL }})")
    var buf bytes.Buffer
    if err := md.Convert(input, &buf); err != nil {
        panic(err)
    }
    fmt.Println(buf.String())
    // Output: <h1>{{ .Title }}</h1>\n<p><a href="{{ .URL }}">Link</a></p>
}

With Parser Options

Since goldmark-template replaces built-in parsers you need to use the goldmark-template specific way of adding parser options. The API is identical to goldmark's WithParserOptions except it's an alternative constructor for the extensions. Use it just like you would goldmark's WithParserOptions.

package main

import (
    "bytes"
    "fmt"

    "github.com/yuin/goldmark"
    "github.com/yuin/goldmark/parser"
    "github.com/yuin/goldmark/renderer/html"
    goldmarktemplate "github.com/hermit-ink/goldmark-template"
)

func main() {
    md := goldmark.New(
        goldmark.WithExtensions(
            goldmarktemplate.WithParserOptions(
                parser.WithAutoHeadingID(),
                parser.WithAttribute(),
            ),
        ),
        goldmark.WithRendererOptions(
            html.WithUnsafe(),
        ),
    )

    input := []byte("# {{ .Title }}\n\n[Link]({{ .URL }})")
    var buf bytes.Buffer
    if err := md.Convert(input, &buf); err != nil {
        panic(err)
    }
    fmt.Println(buf.String())
    // Output: <h1 id="title">{{ .Title }}</h1>\n<p><a href="{{ .URL }}">Link</a></p>
}

With GFM Extension

md := goldmark.New(
    goldmark.WithExtensions(
        goldmarktemplate.New(), // Must come FIRST
        extension.GFM,
    ),
    goldmark.WithRendererOptions(
        html.WithUnsafe(),
    ),
)

Examples

Template Actions in Code

Inline: `{{ .Variable }}`
Block:
```go
func main() {
    fmt.Println("{{ .Message }}")
}
```

Output:

<p>Inline: <code>{{ .Variable }}</code></p>
<pre><code class="language-go">func main() {
    fmt.Println("{{ .Message }}")
}
</code></pre>

Template Actions in Links and Images

Input:

[{{ .LinkText }}]({{ .URL }})
![{{ .Alt }}]({{ .ImagePath }})
<{{ .BaseURL }}/page>

Output:

<p><a href="{{ .URL }}">{{ .LinkText }}</a></p>
<p><img src="{{ .ImagePath }}" alt="{{ .Alt }}"></p>
<p><a href="{{ .BaseURL }}/page">{{ .BaseURL }}/page</a></p>

Reference Links with Templates

[Example][ref]

[ref]: {{ .URL }} "{{ .Title }}"

Output:

<p><a href="{{ .URL }}" title="{{ .Title }}">Example</a></p>

Standalone Template Actions

Welcome {{ .User.Name }}!

Today is {{ .Date }}.

Output:

<p>Welcome {{ .User.Name }}!</p>
<p>Today is {{ .Date }}.</p>

Limitations and Caveats

Actions can only be used as values in attributes

# Heading {id="{{ .HeadingID }}"}
# Heading {class="{{ .CSSClass }}"}
# Heading {data-value="{{ .Data }}"}

Extension Order Matters

Always register goldmark-template BEFORE other extensions that might interfere with template syntax:

goldmark.WithExtensions(
    goldmarktemplate.New(), // First
    extension.GFM,
)

No Template Validation

This extension does not validate Go template syntax. Invalid templates pass through unchanged.

// 1. Process Markdown with goldmark-template
md := goldmark.New(goldmark.WithExtensions(goldmarktemplate.New()))

input := []byte("# {{ .Title }}\n\n[Link]({{ .URL }})")
var htmlTmpl bytes.Buffer
if err := md.Convert(input, &htmlTmpl); err != nil {
    panic(err)
}

// 2. Process the HTML with Go templates
data := struct {
    URL   string
    Title string
}{
    URL:   "https://example.com",
    Title: "Example Page",
}
tmpl := template.Must(template.New("").Parse(string(htmlTmpl)))
tmpl.Execute(w, data)

Development

Running Tests

make test

Linting

make lint

Code Coverage

make test-coverage

Notes

The extension follows goldmark's established patterns:

  • Custom Parsers: Template-aware parsers for links, autolinks, and reference definitions. These are taken directly from the goldmark source with the minimal possible changes to allow template actions to be preserved untouched.
  • Custom Renderers:
    • Renderer - Overrides standard elements to preserve template actions properly within attributes
    • TemplateActionHTMLRenderer - Renders standalone template actions
  • Custom AST Node: TemplateAction for actions that do not appear in positions controlled by other parsers such as images and links

Contributing

Contributions are welcome! Please ensure:

  1. All tests pass
  2. Code is properly formatted (make fmt)
  3. Linting passes (make lint)
  4. New features include tests

License

Apache License 2.0 - see LICENSE file for details.

Acknowledgments

Built on top of the excellent goldmark Markdown parser by Yusuke Inuzuka.

About

Passes go template actions through goldmark untouched

Resources

License

Code of conduct

Security policy

Stars

Watchers

Forks

Packages

No packages published