AsciidoctorJ macro to include dynamically other AsciiDoc files.

URLs

Do you want to improve this page? Please edit it on GitHub.

Description

AsciidoctorJ dynamic-include is an extension for AsciidoctorJ that allows to include files using a globbing pattern.

More concretely instead of including different files one by one:

Listing 1. traditional way to include multiple files into a document
= My book

include::content/chapter01.adoc[]

include::content/chapter02.adoc[]

include::content/chapter03.adoc[]

It is possible to include them all with a single include:

Listing 2. include of files using the dynamic-include processor
= My book

include::dynamic:content/*.adoc[]

Ordering pages

To control the order of the included files, the approach using pages.yaml described in the project path-order can be used.

Title level correction

The processor will take care of the title levels.

Each file can start with a level 2: == My title

Meaning that they are valid documents individually.

When the pages are combined using the dynamic-include, the processor will add the corresponding :leveloffset: necessary to have a consistent level. A sub-folder will correspond to a shift of one in the hierarchy, with a special processing of the index.adoc page.

This allows to have a tree structure like this:

├── chapter-one
│   ├── content-section
│   │   ├── content-1.adoc
│   │   ├── content-2.adoc
│   │   └── index.adoc
│   ├── end-section
│   │   └── index.adoc
│   ├── index.adoc
│   ├── intro.adoc
│   └── pages.yaml
├── chapter-three
│   ├── index.adoc
│   ├── page1.adoc
│   └── page2.adoc
├── chapter-two
│   ├── index.adoc
│   ├── info.adoc
│   ├── pages.yaml
│   ├── section-1.adoc
│   └── section-a.adoc
├── index.adoc
├── index.html
└── pages.yaml

When the same title is used several times, Asciidoc is handling the colliding anchors and create unique ids. For My Title it will create anchors like _my_title, _my_title_2, _my_title_3

The macro is correcting the xref links accordingly to the title anchor in the final document.

The macro is normalizing xref links.

When xref link definition contains attributes (like xref:{root}/pages/page1.adoc) if the attribute value is defined in the main document (the one using the dynamic include macro), then its value will be already replaced before including the content in the main document. If not defined, the attribute will not be changed, letting Asciidoctor handling the value replacement.

Normalization and anchor correction can only be applied if the targeted file exists (checked first relatively to the main document and secondly relatively to the included document).

Options

All options can be used as attribute of the include processor:

link:dynamic:content/*.adoc[external-xref-as-text="true"]

Or as attribute of the document:

:dynamic-include-external-xref-as-text: true

In this second case it will be applied to all usages of the dynamic-include processor.

level-offset-shifting

  • option level-offset-shifting

  • or as document attribute dynamic-include-level-offset-shifting

This option allows to shift the title level of the included document. This can be useful if you have already some title in the main document. When not set the default value is 1.

external-xref-as-text

  • option external-xref-as-text

  • or as document attribute dynamic-include-external-xref-as-text

When set, xref links to pages that are not included in the list of pages are turned to regular text.

logfile

  • option logfile

  • or as document attribute dynamic-include-logfile

When set a file listing the pages that are included by the dynamic-include processor will be generated at the location indicated by the value of this option.

Example output:

Listing 3. example log file
# File:
# Target: dynamic:pages/*.adoc
# level-offset-shifting: 1
pages/index.adoc (leveloffset: 0)
pages/page1.adoc (leveloffset: +1)
pages/page2.adoc (leveloffset: +1)

suffixes

  • option suffixes

  • or as document attribute dynamic-include-suffixes

Following the possibility qualify files introduced by the path-oder project, the suffix is defined as the section between the file name and the extension. For the file page.adoc (no suffix), following files add a suffix: page.draft.adoc (draft suffix), page.advanced.adoc (advanced suffix).

By default the files with suffixes are not included by the dynamic include processor.

When suffixes is specified, the corresponding pages are included after each corresponding file without suffix. Values are separated with :. The order in the list impacts the order of the inclusion.

Given this page tree:

Listing 4. tree of pages with suffix
├── document.adoc
└── pages
    ├── page1.adoc
    ├── page1.advanced.adoc
    ├── page1.draft.adoc
    ├── page2.adoc
    ├── page2.advanced.adoc
    └── page2.draft.adoc

In the main document document.adoc:

  • include::dynamic:pages/*.adoc[] will include only page1.adoc and page2.adoc.

  • include::dynamic:pages/*.adoc[suffixes="draft"] will include page1.adoc, page1.draft.adoc, page2.adoc and page2.draft.adoc

  • include::dynamic:pages/*.adoc[suffixes="draft:advanced"] will include page1.adoc, page1.draft.adoc, page1.advanced.adoc, page2.adoc, page2.draft.adoc and page2.advanced.adoc

Before each inclusion it is possible to have a link to the included document.

  • Indicates if the link is present or not:

    • option display-view-source

    • or as document attribute dynamic-include-display-view-source

  • Pattern of the link:

    • option view-source-link-pattern

    • or as document attribute dynamic-include-view-source-link-pattern

  • Text of the link:

    • option view-source-link-text

    • or as document attribute dynamic-include-view-source-link-text

The pattern of the link can use following special attributes (they will be computed dynamically):

  • file-relative-to-git-repository: path of the file relatively to the folder defined by local-git-repository-path attribute.

  • file-relative-to-gradle-projectdir: path of the file relatively to the folder defined by gradle-projectdir attribute.

  • file-relative-to-gradle-rootdir: path of the file relatively to the folder defined by gradle-rootdir attribute.

  • file-absolute-with-leading-slash: absolute path of the file with a leading slash.

Examples setup:

Listing 5. display a link to open the file in vscode
dynamic-include-display-view-source : true
dynamic-include-view-source-link-text : edit in vscode
dynamic-include-view-source-link-pattern : vscode://file{file-absolute-with-leading-slash}.
Listing 6. display a link to see the file in GitHub
dynamic-include-display-view-source : true
dynamic-include-view-source-link-text : view in github
dynamic-include-view-source-link-pattern : https://github.com/jmini/asciidoctorj-dynamic-include/blob/HEAD/{file-relative-to-gradle-rootdir}

AsciidoctorJ version

This extension is compatible with org.asciidoctor:asciidoctorj in range [2.0.0, 3.0.0[.

The continuous integration server runs the test suite with different AsciidoctorJ versions within this range.

Usage examples

The extension is published on maven central and can be directly consumed from maven or gradle.

Integration in a maven build

You need to declare fr.jmini.asciidoctorj:dynamic-include as a dependency for the org.asciidoctor:asciidoctor-maven-plugin plugin. Your <build> section could looks like this:

<build>
  <plugins>
    <plugin>
      <groupId>org.asciidoctor</groupId>
      <artifactId>asciidoctor-maven-plugin</artifactId>
      <version>${asciidoctor.maven.plugin.version}</version>
      <dependencies>
        <dependency> (1)
          <groupId>fr.jmini.asciidoctorj</groupId>
          <artifactId>dynamic-include</artifactId>
          <version>2.1.0</version>
        </dependency>
        <!-- ... (other dependencies) -->
      </dependencies>
      <configuration>
        <!-- ... (other configuration) -->
        <attributes>
          <dynamic-include-logfile>${project.build.directory}/dynamic-import.log</dynamic-include-logfile>
          <local-git-repository-path>${project.basedir}/../../</local-git-repository-path>
          <!-- ... (other attributes) -->
        </attributes>
      </configuration>
      <!-- ... (other blocks like executions) -->
    </plugin>
    <!-- ... (other plugins) -->
  </plugins>
</build>
1 Dependency declaration

For a complete example, see: pom.xml

Integration in a gradle build

The fr.jmini.asciidoctorj:dynamic-include jar can be declared as dependency of the asciidoctor task:

Listing 7. Configuration of the asciidoctor task in a gradle build
asciidoctor {
    configurations 'asciidoctorExtensions'

    sourceDir = file('docs')
    sources {
        include 'index.adoc'
    }
    baseDirFollowsSourceFile()
    outputDir = file('build/generated-docs')
    attributes = ['project-version'                          : "$version",
                  'dynamic-include-display-view-source'      : 'true',
                  'dynamic-include-view-source-link-text'    : 'edit in vscode',
                  'dynamic-include-view-source-link-pattern' : 'vscode://file{file-absolute-with-leading-slash}',
                  'attribute-missing'                        : 'warn',
                  'toc'                                      : 'left',
                  'icons'                                    : 'font',
                  'sectanchors'                              : 'true',
                  'idprefix'                                 : '',
                  'idseparator'                              : '-']
    repositories {
        mavenCentral()
    }
    dependencies {
        asciidoctorExtensions 'fr.jmini.asciidoctorj:dynamic-include:2.1.0' (1)
    }
}
1 Dependency declaration

For a complete example, see: build.gradle

Download

The library is hosted on maven central.

Listing 8. Maven coordinates of the library
<dependency>
  <groupId>fr.jmini.asciidoctorj</groupId>
  <artifactId>dynamic-include</artifactId>
  <version>2.1.4</version>
</dependency>

Source Code

As for any grade plugin, the source code of the plugin is available in the src/ folder.

Build

This project is using gradle.

Command to build the sources locally:

./gradlew build

The AsciidoctorJ version can be controlled with the asciidoctorjVersion project property. The property will influence the strict version in the dependency definition. This is used to test the extension against a specific AsciidoctorJ version.

Usage example:

./gradlew build -PasciidoctorjVersion=2.2.0

Command to deploy to your local maven repository:

./gradlew publishToMavenLocal

Command to build the documentation page:

./gradlew asciidoctor

The output of this command is an HTML page located at <git repo root>/build/docs/html5/index.html.

For project maintainers

signing.gnupg.keyName and signing.gnupg.passphrase are expected to be set in your local gradle.properties file to be able to sign. sonatypeUser and sonatypePassword are expected to be set in order to be able to publish to a distant repository.

Command to build and publish the result to maven central:

./gradlew publishToSonatype

Command to upload the documentation page on GitHub pages:

./gradlew gitPublishPush

Command to perform a release:

./gradlew release -Prelease.useAutomaticVersion=true

Using ssh-agent

Some tasks requires pushing into the distant git repository (release task or updating the gh-pages branch). If they are failing with errors like this:

org.eclipse.jgit.api.errors.TransportException: ... Permission denied (publickey).

Then ssh-agent can be used.

eval `ssh-agent -s`
ssh-add ~/.ssh/id_rsa

(source for this approach)

Get in touch

You can also contact me on Twitter: @j2r2b

License