How to Include Remote Sources in AsciiDoc
AsciiDoc can include code from remote sources, not just local files. In this post, I'm going to show you both how to do it, as well as how doing so can make your content extremely flexible and much less intensive to maintain.
Recently, I've been investing time updating my book Zend Expressive Essentials, to reflect the latest version of the framework (rebranded as Mezzio). I self-published the book using a toolchain based around the AsciiDoc file format, the Asciidoctor toolchain, and git.
Overall, I've been delighted with how the combination of AsciiDoc and Asciidoctor let me create a book with no expensive tooling on any operating system that I choose. All that I need are VIM and a terminal.
However, it's not been flawless. While updating the book for the latest version, I've become increasingly frustrated with how I've included code samples, because it feels like too much work.
To put it into context, in one chapter of the book, I create an initial version of an application using the framework, and then improve it across several iterations. The source code for the examples is stored in a single git repository. Within that repository is a directory for each iteration of the application.
For each source code example in the book, I use an include directive inside a source code block, similar to the example below.
[source,php] ---- include::code/version/3.0.0/code/refactor-three/config/routes.php ----
This approach lets me keep the PHP source code separate from the book's AsciiDoc source. By doing so, I can use PHP to ensure that the code is syntactically correct and that the application runs as expected.
Including code from a git repository organised this way, from the perspective of AsciiDoc, isn't a concern and isn't wrong. When asciidoctor processes the book's source files, so long as the included PHP file can be found and read, then the content can be included in the generated book.
However, from the perspective of code organisation, this was becoming a right royal pain the proverbial. I was keeping multiple versions of the code in a way that was reminiscent of the dark days of the past, where people might update a website by manually FTP'ing update files from a development machine to a production server.
There are still places that do that, would you believe?!
The more I looked at this approach, the more I believed that there must be a better way to organise the book's PHP source code, yet still be able to reference it fairly trivially in the book's AsciiDoc source files.
After a bit of reading, I remembered that AsciiDoc's include directive can reference external content from URIs — such as a GitHub repository! Consequently, after a little bit of thinking and planning, I recreated the book's source code in a new repository.
This time, as I should have done it the first time, I created a branch for each iteration of the application, where each branch contained only the differences for that particular iteration of the code.
After pushing it to GitHub, I updated each of the relevant source code blocks' include directive to reference the relevant version of the file in the remote GitHub repository. You can see an example of the change in the sample below.
[source,php,linenos] ---- +include::https://raw.githubusercontent.com/settermjd/mezzio-expressive-book-code-examples-manual-build/iteration-one/config/routes.php ----
GitHub (and I assume GitLab, Gitea, etc.) makes referencing multiple versions quite convenient, because of the URIs that it makes available from a repository.
The example above shows how to refer to a file (
config/routes.php) in a particular branch (
Below is how I could refer to two other versions of the file.
[source,php,linenos] ---- +include::https://raw.githubusercontent.com/settermjd/mezzio-expressive-book-code-examples-manual-build/iteration-two/config/routes.php ----
[source,php,linenos] ---- +include::https://raw.githubusercontent.com/settermjd/mezzio-expressive-book-code-examples-manual-build/iteration-three/config/routes.php ----
By taking this approach, the code is much easier to create and maintain, yet just as simple to refer to — if not more so — in the AsciiDoc source. To be clear, this isn't a problem with AsciiDoc. However, it does show an essential aspect of AsciiDoc's functionality that makes it extremely flexible and accommodating.
Good luck trying to do that in Markdown!
Enable Support for Remote Content in Asciidoctor
That's not the end of the story, however. As my PHP source files were now on GitHub, and not available on the local filesystem, when I tried to build the book with asciidoctor, the examples would not be retrieved.
This is because - for security reasons — Asciidoctor disables including remote content.
It does this to avoid potentially dangerous macros in source files, such as those brought in using the
include:: directive, being used.
To get around this, as I trust the remote content, when calling asciidoctor, I had to set the
allow-uri-read attribute, which enables including remote content.
Here is an example of how to do so, when using asciidoctor's Docker container:
docker run -it -v "$(pwd)/source/":/documents/ \ asciidoctor/docker-asciidoctor \ asciidoctor-pdf \ -a allow-uri-read \ -d book \ --base-dir "." \ --out-file "build/book.pdf" \ "book.asciidoc"
With that done, the revised version of the book builds correctly, and I have less work to do and fewer things to think about.
I hope that this article's shown yet another way in which AsciiDoc is an exceptionally well thought out file format, and is an excellent choice for creating sophisticated content, such as technical documentation — one far preferable to Markdown. I'd love to get your thoughts in the comments below about this feature.
Are you using it? Are you keen to use it?
Please share your thoughts and let's have a lively discussion.
P.S. If you need assistance on any of your PHP projects, I am available for hire for freelance work.