We can write extension for Asciidoctor using the extension API in Groovy (or any other JVM language) if we run Asciidoctor on the Java platform using AsciidoctorJ. Extensions could also be written in Ruby of course, but in this post we see how to write a simple inline macro with Groovy.
The extension API has several extension points (Source):
- Preprocessor: Processes the raw source lines before they are passed to the parser
- Treeprocessor: Processes the Document (AST) once parsing is complete
- Postprocessor: Processes the output after the Document has been rendered, before it's gets written to file
- Block processor: Processes a block of content marked with a custom style (i.e., name) (equivalent to filters in AsciiDoc)
- Block macro processor: Registers a custom block macro and process it (e.g., gist::12345[])
- Inline macro processor: Registers a custom inline macro and process it (e.g., btn:[Save])
- Include processor: Processes the include::
[] macro
To write an extension in Groovy (or Java) we must write our implementation class for a specific extension point and we must register the class so AsciidoctorJ knows the class can be used. Registering the implementation is very simple, because it is using the Java Service Provider. This means we have to place a file in the META-INF/services
directory on the classpath. The contents of the file is the class name of the implementation class.
Let's start with the Asciidoc markup and then write an implementation to process the inline macro twitter that is used:
= Groovy Inline Macro Sample document to show extension for Asciidoctor written in Groovy. // Here we use the twitter: macro. // The implementation is done in Groovy. With the twitter macro we can create links to the user's Twitter page like twitter:mrhaki[].
To implement an inline macro we create a new class and extend InlineMacroProcessor
. We override the process
method to return the value that needs to replace the inline macro in our Asciidoc markup.
// File: src/main/groovy/com/mrhaki/asciidoctor/extension/TwitterMacro.groovy package com.mrhaki.asciidoctor.extension import org.asciidoctor.extension.* import org.asciidoctor.ast.* import groovy.transform.CompileStatic @CompileStatic class TwitterMacro extends InlineMacroProcessor { TwitterMacro(final String name, final Map<String, Object> config) { super(name, config) } @Override protected Object process(final AbstractBlock parent, final String twitterHandle, final Map<String, Object> attributes) { // Define options for an 'anchor' element. final Map options = [ type: ':link', target: "http://www.twitter.com/${twitterHandle}".toString() ] as Map<String, Object> // Prepend twitterHandle with @ as text link. final Inline inlineTwitterLink = createInline(parent, 'anchor', "@${twitterHandle}", attributes, options) // Convert to String value. inlineTwitterLink.convert() } }
We have the implementation class so now we can register the class with Asciidoctor. To register our custom extensions we need to implement the ExtensionRegistry
interface. We implement the register
method where we can couple our extension class to Asciidoctor.
// File: src/main/groovy/com/mrhaki/asciidoctor/extension/TwitterMacroExtension.groovy package com.mrhaki.asciidoctor.extension import org.asciidoctor.extension.spi.ExtensionRegistry import org.asciidoctor.extension.JavaExtensionRegistry import org.asciidoctor.Asciidoctor import groovy.transform.CompileStatic @CompileStatic class TwitterMacroExtension implements ExtensionRegistry { @Override void register(final Asciidoctor asciidoctor) { final JavaExtensionRegistry javaExtensionRegistry = asciidoctor.javaExtensionRegistry() javaExtensionRegistry.inlineMacro 'twitter', TwitterMacro } }
The class that registers our extension must be available via the Java Service Provider so it is automatically registered within the JVM used to run Asciidoctor. Therefore we need to create the file META-INF/services/org.asciidoctor.extension.spi.ExtensionRegistry
with the following contents:
# File: src/main/resources/META-INF/services/org.asciidoctor.extension.spi.ExtensionRegistry com.mrhaki.asciidoctor.extension.TwitterMacroExtension
We have taken all steps necessary to have our inline macro implementation. We must compile the Groovy classes and add those with the Java Service Provider file to the classpath. We can package the files in a JAR file and define a dependency on the JAR file in our project. If we use Gradle and the Gradle Asciidoctor plugin we can also add the source files to the buildSrc
directory of our project. The files will be compiled and added to the classpath of the Gradle project.
With the following Gradle build file we can process Asciidoc markup and execute the twitter inline macro. We store the source files in the buildSrc
directory.
buildscript { repositories { jcenter() } dependencies { classpath 'org.asciidoctor:asciidoctor-gradle-plugin:1.5.0' } } apply plugin: 'org.asciidoctor.gradle.asciidoctor'
The build file in the buildSrc
directory has a dependency on AsciidoctorJ. This module makes it possible to run Asciidoctor on the JVM.
// File: buildSrc/build.gradle. apply plugin: 'groovy' repositories { jcenter() } dependencies { compile 'org.asciidoctor:asciidoctorj:1.5.0' }
Let's see part of the HTML that is generated if we transform the Asciidoc markup that is shown at the beginning of this blog post. The twitter inline macro is transformed into a link to the Twitter page of the user:
... <div class="paragraph"> <p>With the twitter macro we can create links to the user’s Twitter page like <a href="http://www.twitter.com/mrhaki">@mrhaki</a>.</p> </div> ...
Andres Almiray also wrote about writing extensions with Gradle.
Written with Asciidoctor 1.5.0 and Gradle 2.0.