AsciiDoc `include::` of resources located in Hugo modules seems not to work

It seems that Hugo’s AsciiDoc extension is not working on Hugo’s VFS but on the actual file system. Therefore AsciiDoc’s include:: directive is not working as expected when it comes to inclusions of AsciiDoc partials of Hugo modules (and of course across Hugo modules).

To be more precise: It should be possible to include AsciiDoc partials located in a Hugo module into adoc pages of the Hugo site.

Is there any chance to get this done or is is just impossible?

Why is this required?
When it comes to technical documentation one often wants to maintain parts of the documentation as close as possible at the application code itself. In a distributed environment this often means distributed repositories. Following the DRY principle i want to re-use elements of my documentation in a consolidated site if needed (e.g. architecture building blocks). HUGO modules looked to me as an option to achieve this.

You cannot do this with the AsciiDoc include directive, but you can accomplish the same thing by creating an include shortcode.

So there is no way to mount the files contained within a Hugo module to a mount point inside the site repository? Includes are a core concept inside the AsciiDoc ecosystem.

Is there an way to achieve this with Markdown? I think this would only be possible when there is a function which allows reading file contents of the union file system, right?

You can mount files contained within a Hugo module to any of the component directories (e.g., content, assets, etc.) within the virtual file system, but the asciidoctor executable has neither knowledge of nor access to the virtual file system. That means you cannot use the AsciiDoc include directive to include files provided by a Hugo module; a module mount is not a symbolic link.

The idiomatic way to include content, regardless of content format[1], is to create a shortcode that can include content from:

I’ll post an example in a day or two.


  1. Hugo natively supports Markdown, HTML, and Emacs Org Mode. Hugo calls external executables to render AsciiDoc, Pandoc, and reStructuredText. ↩︎

1 Like

I’ve revised my responses above.

When initially answering your questions, I was focused on AsciiDoc’s include directive and why it cannot work with mounted files. I should have focused on a generic approach to file inclusion… an include shortcode as noted above.

Here’s a working example of an include shortcode used with both Markdown and AsciiDoc content:

git clone --single-branch -b hugo-forum-topic-49219 https://github.com/jmooring/hugo-testing hugo-forum-topic-49219
cd hugo-forum-topic-49219
hugo server

As expected, you need to use site-relative URLs for image and link references in the included page. If they are page-relative, they will be relative to the including page, not to the included page.

1 Like

Good morning :slight_smile:

I’ll give it a try how it feels as an AsciiDoc author. I also considered an alternative using Shortcodes, but was not able to have a working solution here. So this really helps a lot!

BTW: I like your approach of having an examples repository with branches for forum topics. That’s a cool idea :+1:

To give some context: Currently i’m working on PoCs for technical documentation using Antora (which of course has built-in AsciiDoc capabilities) and Hugo. Antora has a lot of complex concepts, which are not always a good thing. But it supports AsciiDoc with its full capabilities. I had a look into AsciiDoc because there is a well-defined specifiction behind and a huge amount of structural/visual elements. With Markdown this was not the case for a long time (no spec, reduced set of elements, and if just by having dialects with again no real spec). With CommonMark it seems this has changed. Furthermore many of the “*-flavored” Markdown dialects seems to be pretty stable or even have a specification. So for now i’m thinking about what is not possible with Markdown what is possible with AsciiDoc. And the only thing is that AsciiDoctor provides render processors for nearly all document formats (PDF, Word, EPUB, …). But for a SSG this is not really relevant if you don’t want to create other document formats out of the same sources. Besides that Asciidoctor Diagram was one thing i liked a lot. Especially rendering all kinds of diagram types with the same high level tooling. But using Docsy the mermaid integration already provides most of the elements i need and the draw.io integration of Docsy is quite nice.

So maybe it will become Hugo w/ Docsy theme and Markdown if it provides everything we need. Markdown feels to be a better fit with Hugo than AsciiDoc. But let’s see.

I agree with this, and building sites is much faster since we don’t have to “shell out” to another executable.

Thank you! Your include shortcode works quite nice with some limitations:

{{ .Content }} needs to be replaced with {{ .Content | safeHTML }} as otherwise characters are replaced which leads to non-working rendering of e.g. AsciiDoc diagrams.

Additionally this part seems to create an undesired behavior:

  {{ with site.GetPage $path }}
{{ .RenderShortcodes }} {{/* Do not indent this line. */}}

Here i tried to load an .adoc file mounted to the assets folder (global). But also having a file with the same name in a sub-directory of one of the content folders it led here to an arbitrary behavior. In my case the wrong file was included due to internal sorting i guess.

I tried to fix it with checking a regex if the path param has a directory prefx ../** or ./** or /**. Then global resources are loaded correctly, but site.GetPage is not working as expected. Always a nopPage is the result. Furthermore exeuting page.Path within the shortcode snippet always outputs a different path. Now i’m totally confused :sweat_smile:

{{ with $path := .Get 0 }} 
  {{ if (gt (strings.FindRE `^(.{0,2}\/)+` $path) 0) }}
    {{ with site.GetPage $path }}
{{ .RenderShortcodes }} {{/* Do not indent this line. */}}
    {{ else }}
       <!-- debug code -->
       <div class="debug">
        debug code <code>{{.}}</code>
        <br>page.Path = <code>{{page.Path}}</code>
        <br>site.GetPage $path = <code>{{site.GetPage $path}}</code>
      </div>
      <!-- ### -->   
    {{ end }}
  {{ else }}
...

The shortcode uses a waterfall approach

  1. Look for a page using site.GetPage. If it can’t find the given page…
  2. Look for a page resource using .Resources.Get. If it can’t find the given resource…
  3. Look for a global resource using resources.Get. If it can’t find the given resource…
  4. Look for a remote resource if the path is something like https://…

If the order of precedence doesn’t suit your needs, move stuff around.

Don’t use the page function unless you know what you are doing. Specifically, don’t use it in a shortcode. See https://gohugo.io/functions/global/page/#warnings.

Waterfall approach was clear to me. I like that approach too :slight_smile: It’s a bit convention over configuration.

Got your point with the

Don’t use the page function unless you know what you are doing

Now it’s clear to me why this was not leading to a consistent behavior.

Hugo caches rendered shortcodes.

But i’m wondering why site.GetPage still provides a nopPage result, although the path is correct… Have to check what i’m doing wrong here.

Furthermore is it right that it’s not possible to include relative documents to a given path then? This would only be possible with a path object, which does not has the intended behavior in shortcodes.

I’d need to see an example including (a) the path to the including page, and (b) the path to the included page.

It occurs to me, that for maximum flexibility and precision, the shortcode could take either:

  • One positional parameter as it does now: path
  • Two named parameters: path and type

I dislike using “type” but I can’t think of anything better at the moment. Conceptually, calls using the second form might look like:

{{% include path="foo" type="page" %}}
{{% include path="bar" type="page resource" %}}
{{% include path="baz" type="global resource" %}}

The remote resource call would be inferred if the path begins with a scheme (e.g., https://). By explicitly looking for a particular “type” of inclusion, we could then throw a specific error if not found. For example:

ERROR: The “include” shortcode was unable to find the page “foo” …

ERROR: The “include” shortcode was unable to find the page resource “bar” …

ERROR: The “include” shortcode was unable to find the global resource “baz” …

1 Like