Custom Markdown Containers

I recently started using hugo as a static site generator. So far it has been a very good experience but there is one thing that I am missing.

I use pandoc quite frequently for md → tex → pdf conversion. Pandoc markdown gives me the ability to use custom container syntax like this:

::: environment
Hello World
:::

This syntax will be turned into (using my lua filter):

\begin{environment}
Hello World
\end{environment}

I was wondering if something similiar was achievable in hugo when converting from markdown to html, with the environment being the class of a div:

<div class="environment">
Hello World
</div>

Since the ::: container syntax is not native to goldmark, markdown render hooks won’t be able to solve this issue (and if there are any).

The only other option I have found is replaceRE, which I wasn’t able to set up properly.

Since I am quite new to hugo I am interested in possible solutions to this problem.

Ideally, I would like to be able to configure short forms for the environment names. For example, thm → theorem. I was thinking that one could add a yaml file to the data directory containing the short form as a key and long form as a value, like so:

thm: theorem
def: definition
prb: problem
...

Nesting would also be great.

I think that this extension to goldmark’s built in syntax would allow for a lot of flexibility. Adding this container syntax to goldmark itself would allow for even more flexibility, considering the possibility of adding a markdown render hook for containers.

You have two options:

  1. Shortcodes
  2. Code fences with render hooks

The last option is probably closest to standart Markdown.

Both should be fairly well documented.

Thanks for the fast reply.

Shortcodes are not really an option to me because of their syntax.

But let’s consider code fences with render hooks. The documentation introduces a way to specifically target certain types by creating render-codeblock-type.html files. In my case, since all of the custom environments should produce divs (with a class), that would lead to a lot of code duplication.
Is there a better way to do it, for example by grouping all custom environments? Consider this pseudo code:

// a go map containing key values of custom environments
{{
$envs := map[string]string {
    "thm": "theorem"
    ...
}
}}

// if .Type of codeblock is indeed a custom environment not a language
{{ if ne envs[.Type] nil }}
<div class="envs[.Type]">
{{ .Inner | safeHTML }}
</div>
{{ else }}
// default codeblock parse behaviour, I don't want to change anything in here
{{ end }}

Or maybe an approach that uses a yaml file inside the data directory? Like described in my original question.

Still, the syntax would require the normal code fences ``` as opposed to ::: …

After reading a little bit about golang regex I noticed that . does not match newlines, which is why I could not get things to work using replaceRE in the first place. With that being said, the following approach could answer my question:

<!-- If containers is set to true in the front matter, enable container syntax -->
{{ if eq .Params.containers true }}
<!-- Replace every instance of the container syntax -->
{{ .Content | replaceRE "(?s)::: ?([a-zA-Z]*)(.*?):::" "<div class=\"$1\">$2</div>" | safeHTML }}
{{ else }}
{{ .Content }}
{{ end }}

For further information I recommend checking out the documentation on replaceRE and/or testing the regex on this website.

However, I won’t accept this solution as of right now, because I find it to be a bit hacky and I am not sure if it could cause any performance issues. Maybe someone can provide a better solution.

If you are already using Org mode or willing to switch to it, you can do this:

#+begin_environment
Hello World
#+end_environment

Org mode can be converted to Markdown for Hugo to this:

<div class="environment">
Hello World
</div>

Pandoc has really good support for converting from Org mode to any other format too.

If you are not aware of Org mode to begin with, this might be a stretch. But I thought of putting this option out there.

I know only a little bit about org mode. Thank you for mentioning it!

Sure, lots of options, note that you can pass attributes in your fenced blocks.

But if all you want to do is to apply some CSS classes you may fare well with block level attributes.