spmallette / asciidoctorj

:coffee: Java bindings for Asciidoctor. Asciidoctor on the JVM!

Home Page:http://asciidoctor.org

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

AsciidoctorJ: Java bindings for Asciidoctor

AsciidoctorJ is the official library for running Asciidoctor on the JVM. Using AsciidoctorJ, you can convert AsciiDoc content or analyze the structure of a parsed AsciiDoc document from Java and other JVM languages.

Build Status (Travis CI) Build Status (AppVeyor) Build Status (Github Actions)

Distribution

AsciidoctorJ is published to Maven Central and Bintray. The artifact information can be found in the tables below.

Table 1. Artifact information for AsciidoctorJ in jCenter (Bintray)
Group Id Artifact Id Version Download

org.asciidoctor

asciidoctorj

2.2.0

pom jar javadoc (jar) sources (jar) distribution (zip tar)

org.asciidoctor

asciidoctorj-api

2.2.0

pom jar javadoc (jar) sources (jar)

org.asciidoctor

asciidoctorj-epub3

1.5.0-alpha.15

org.asciidoctor

asciidoctorj-pdf

1.5.0-beta.8

Table 2. Artifact information for AsciidoctorJ in Maven Central
Group Id Artifact Id Version Download

org.asciidoctor

asciidoctorj

2.2.0

pom jar javadoc (jar) sources (jar) distribution (zip tar)

org.asciidoctor

asciidoctorj-api

2.2.0

pom jar javadoc (jar) sources (jar)

org.asciidoctor

asciidoctorj-epub3

1.5.0-alpha.15

org.asciidoctor

asciidoctorj-pdf

1.5.0-beta.8

🔥
The artifactId changed to asciidoctorj starting in 1.5.0.

Quick win: using the command line interface

If you download from a distribution link above (zip or tar), you can get started straight away from the command line.

First, expand the downloaded file. That puts everything in directory asciidoctorj-2.2.0. Within that directory are bin and lib directories. bin contains the executables — asciidoctorj for Linux and macOS, and asciidoctorj.bat for Windows. lib contains the supporting libraries.

Verify the application runs by specifying the appropriate executable with no parameters; it should display the various run options available (i.e., help).

Linux: asciidoctorj-2.2.0/bin/asciidoctorj
Windows: asciidoctorj-2.2.0\bin\asciidoctorj.bat

Next, say you want to convert an ASCIIDOC (.adoc) file — such as this README — to a pdf.

Linux: asciidoctorj-2.2.0/bin/asciidoctorj -b pdf README.adoc
Windows: asciidoctorj-2.2.0\bin\asciidoctorj.bat -b pdf README.adoc

Boom! That should convert the README to a PDF named README.pdf. To create a PDF with a different name — say, READTHIS.pdf — just add the -o switch:

Linux: asciidoctorj-2.2.0/bin/asciidoctorj -b pdf -o READTHIS.pdf README.adoc
Windows: asciidoctorj-2.2.0\bin\asciidoctorj.bat -b pdf -o READTHIS.pdf README.adoc

The rest of the document addresses the asciidoctorj API, for doing more complex conversions from within a JVM-based application.

Installation

To start using AsciidoctorJ, you need to add the required dependency to the dependency management system of your choice, Maven, Gradle or Apache Ivy. If you don’t use a Dependency Management system please check the dependency graph and add all jars in it to your classpath.

Declaring the dependency in a Maven build file (i.e., pom.xml)
<dependencies>
  <dependency>
    <groupId>org.asciidoctor</groupId>
    <artifactId>asciidoctorj</artifactId>
    <version>2.2.0</version> <!--(1)-->
  </dependency>
</dependencies>
Declaring the dependency in a Gradle build file (e.g., build.gradle)
dependencies {
  compile 'org.asciidoctor:asciidoctorj:2.2.0'
}
Declaring the dependency in an SBT build file (e.g., build.sbt)
libraryDependencies += "org.asciidoctor" % "asciidoctorj" % "2.2.0" // (1)
  1. Specifying the version of AsciidoctorJ implicitly selects the version of Asciidoctor

Declaring the dependency in a Leiningen build file (e.g., project.clj)
:dependencies [[org.asciidoctor/asciidoctorj "2.2.0"]]
💡

In addition to using AsciidoctorJ directly, you can invoke it as part of your build using the Maven or Gradle plugin.

📎
The versions of Asciidoctor and AsciidoctorJ no longer align since version 1.6.0 of AsciidoctorJ. Please check the corresponding release notes to find out which version of Asciidoctor is packaged if you are embedding the library. If you use the distribution you can call asciidoctorj --version to get the version of Asciidoctor that is embedded in AsciidoctorJ.

Windows Installation

A Chocolatey package is available which installs the asciidoctorj-2.2.0-bin.zip Bintray artifact along with a binary shim in %ChocolateyInstall%\bin which lets you run AsciidoctorJ from the command line.

C:\> choco install asciidoctorj
C:\> where asciidoctorj
C:\ProgramData\chocolatey\bin\asciidoctorj.exe
C:\> asciidoctorj -b pdf README.adoc

Converting documents

The main entry point for AsciidoctorJ is the Asciidoctor Java interface. This interface provides four methods for converting AsciiDoc content.

  • convert

  • convertFile

  • convertFiles

  • convertDirectory

You’ll learn about these methods in the converting documents section.

Prior to Asciidoctor 1.5.0, the term render was used in these method names instead of convert (i.e., render, renderFile, renderFiles and renderDirectory). AsciidoctorJ continues to support the old method names for backwards compatibility.
Table 3. Convert methods on the Asciidoctor interface
Method Name Return Type Description

convert

String

Parses AsciiDoc content read from a string or stream and converts it to the format specified by the backend option.

convertFile

String

Parses AsciiDoc content read from a file and converts it to the format specified by the backend option.

convertFiles

String[]

Parses a collection of AsciiDoc files and converts them to the format specified by the backend option.

convertDirectory

String[]

Parses all AsciiDoc files found in the specified directory (using the provided strategy) and converts them to the format specified by the backend option.

📎
All the methods listed in Table 3 are overloaded to accommodate various input types and options.

You retrieve an instance of the Asciidoctor interface from the factory method provided.

Creating an Asciidoctor instance from Asciidoctor.Factory
import static org.asciidoctor.Asciidoctor.Factory.create;
import org.asciidoctor.Asciidoctor;

Asciidoctor asciidoctor = create();

Once you retrieve an instance of the Asciidoctor interface, you can use it to convert AsciiDoc content. Here’s an example of using AsciidoctorJ to convert an AsciiDoc string.

📎
The following convertFile or convertFiles methods will only return a converted String object or array if you disable writing to a file, which is enabled by default. To disable writing to a file, create a new Options object, disable the option to create a new file with option.setToFile(false), and then pass the object as a parameter to convertFile or convertFiles.
Converting an AsciiDoc string
//...
import java.util.HashMap;
//...

String html = asciidoctor.convert(
    "Writing AsciiDoc is _easy_!",
    new HashMap<String, Object>());
System.out.println(html);

The convertFile method will convert the contents of an AsciiDoc file.

Converting an AsciiDoc file
//...
import java.util.HashMap;
//...

String html = asciidoctor.convertFile(
    new File("sample.adoc"),
    new HashMap<String, Object>());
System.out.println(html);

The convertFiles method will convert a collection of AsciiDoc files:

Converting a collection of AsciiDoc files
//...
import java.util.Arrays;
//...

String[] result = asciidoctor.convertFiles(
    Arrays.asList(new File("sample.adoc")),
    new HashMap<String, Object>());

for (String html : result) {
    System.out.println(html);
}
⚠️
If the converted content is written to files, the convertFiles method will return a String Array (i.e., String[]) with the names of all the converted documents.

Another method provided by the Asciidoctor interface is convertDirectory. This method converts all of the files with AsciiDoc extensions (.adoc (preferred), .ad, .asciidoc, .asc) that are present within a specified folder and following given strategy.

An instance of the DirectoryWalker interface, which provides a strategy for locating files to process, must be passed as the first parameter of the convertDirectory method. Currently Asciidoctor provides two built-in implementations of the DirectoryWalker interface:

Table 4. Built-in DirectoryWalker implementations
Class Description

AsciiDocDirectoryWalker

Converts all files of given folder and all its subfolders. Ignores files starting with underscore (_).

GlobDirectoryWalker

Converts all files of given folder following a glob expression.

If the converted content is not written into files, convertDirectory will return an array listing all the documents converted.

Converting all AsciiDoc files in a directory
//...
import org.asciidoctor.AsciiDocDirectoryWalker;
//...

String[] result = asciidoctor.convertDirectory(
    new AsciiDocDirectoryWalker("src/asciidoc"),
    new HashMap<String, Object>());

for (String html : result) {
    System.out.println(html);
}

Another way to convert AsciiDoc content is by calling the convert method and providing a standard Java java.io.Reader and java.io.Writer. The Reader interface is used as the source, and the converted content is written to the Writer interface.

Converting content read from a java.io.Reader to a java.io.Writer
//...
import java.io.FileReader;
import java.io.StringWriter;
//...

FileReader reader = new FileReader(new File("sample.adoc"));
StringWriter writer = new StringWriter();

asciidoctor.convert(reader, writer, options().asMap());

StringBuffer htmlBuffer = writer.getBuffer();
System.out.println(htmlBuffer.toString());

Safe mode and file system access

Asciidoctor provides security levels that control the read and write access of attributes, the include directive, macros, and scripts while a document is processing. Each level includes the restrictions enabled in the prior security level.

When Asciidoctor (and AsciidoctorJ) is used as API, it uses SECURE safe mode by default. This mode is the most restrictive one and in summary it disallows the document from attempting to read files from the file system and including their contents into the document.

We recommend you to set SAFE safe mode when rendering AsciiDoc documents using AsciidoctorJ to have almost all Asciidoctor features such as icons, include directive or retrieving content from URIs enabled.

Safe mode is set as option when a document is rendered. For example:

import static org.asciidoctor.OptionsBuilder.options;

Map<String, Object> options = options().safe(SafeMode.SAFE)
                                       .asMap();

String outfile = asciidoctor.convertFile(new File("sample.adoc"), options);

We are going to explain in more detail options in next section.

Conversion options

Asciidoctor supports numerous options, such as:

in_place

Converts the output to a file adjacent to the input file.

template_dirs

Specifies a directory of Tilt-compatible templates to be used instead of the default built-in templates

attributes

A Hash (key-value pairs) of attributes to configure various aspects of the AsciiDoc processor

The second parameter of the convert method is java.util.Map. The options listed above can be set in java.util.Map.

Using the in_place option and the backend attribute
Map<String, Object> attributes = new HashMap<String, Object>();
attributes.put("backend", "docbook"); // (1)

Map<String, Object> options = new HashMap<String, Object>();
options.put("attributes", attributes); // (2)
options.put("in_place", true); // (3)

String outfile = asciidoctor.convertFile(new File("sample.adoc"), options);
  1. Defines the backend attribute as docbook in the attributes map

  2. Registers the attributes map as the attributes option in the options map

  3. Defines the in_place option in the options map

Another way for setting options is by using org.asciidoctor.Options class. Options is a simple Java class which contains methods for setting required options. Note that related with org.asciidoctor.Options class, there is org.asciidoctor.Attributes class, which can be used for setting attributes.

The convert method is overloaded so org.asciidoctor.Options can be passed instead of a java.util.Map.

Using the in_place option and the backend attribute
Attributes attributes = new Attributes();
attributes.setBackend("docbook"); // (1)

Options options = new Options();
options.setAttributes(attributes); // (2)
options.setInPlace(true); // (3)

String outfile = asciidoctor.convertFile(new File("sample.adoc"), options);
  1. Defines the backend attribute as docbook in the attributes class

  2. Registers the attributes class as the attributes option in the options class

  3. Defines the in_place option in the options class

AsciidoctorJ also provides two builder classes to create these maps and classes in a more readable form.

AttributesBuilder

Used to define attributes with a fluent API

OptionsBuilder

Used to define options with a fluent API

The code below results in the same output as the previous example but uses the builder classes.

Setting attributes and options with the builder classes
import static org.asciidoctor.AttributesBuilder.attributes;
import static org.asciidoctor.OptionsBuilder.options;

//...
Map<String, Object> attributes = attributes().backend("docbook") // (1)
                                             .asMap();

Map<String, Object> options = options().inPlace(true)
                                       .attributes(attributes) // (2)
                                       .asMap(); // (3)

String outfile = asciidoctor.convertFile(new File("sample.adoc"), options);
  1. Defines the backend attribute as docbook using fluent API.

  2. Registers the attributes map as attributes.

  3. Converts options to java.util.Map instance.

Setting attributes and options with the builder classes
import static org.asciidoctor.AttributesBuilder.attributes;
import static org.asciidoctor.OptionsBuilder.options;

//...
Attributes attributes = attributes().backend("docbook").get(); // (1)
Options options = options().inPlace(true).attributes(attributes).get(); // (2)

String outfile = asciidoctor.convertFile(new File("sample.adoc"), options); // (3)
  1. Defines and returns an Attributes class instead of java.util.Map by calling get() method instead of asMap().

  2. Defines and returns an Options class instead of java.util.Map by calling get() method instead of asMap().

  3. Converts the document passing Options class.

💡
All methods used to convert content are overloaded with OptionsBuilder parameter, so it is no longer required to call get nor asMap methods.
⚠️
The icons attribute requires a String to set the value used to “draw” icons. At this time, you can use two constants org.asciidoctor.Attributes.IMAGE_ICONS for using the same approach as AsciiDoc, that is using img tags, or org.asciidoctor.Attributes.FONT_ICONS for using icons from Font Awesome.

Attributes can be specified as String or Array instead of pair key/value by using org.asciidoctor.Attributes.setAttributes(String) or org.asciidoctor.Attributes.setAttributes(String...) and AttributesBuilder methods.

Passing attributes as a string
//...
Attributes attributes = attributes().attributes("toc numbered").get();
Options options = options().attributes(attributes).get();

Passing attributes as a string is equivalent to passing individual attributes.

Passing individual attributes
//...
Attributes attributes = attributes().tableOfContents(true).sectionNumbers(true).get();
Options options = options().attributes(attributes).get();

You can also use an array.

Passing attributes as an array
//...
String[] attributesArray = new String[]{"toc", "source-highlighter=coderay"};
Attributes attributes = attributes().attributes(attributesArray).sectionNumbers(true).get();
Options options = options().attributes(attributes).get();

Passing attributes as an array is equivalent to passing individual attribute.

Passing individual attributes
//...
Attributes attributes = attributes().tableOfContents(true).sectionNumbers(true).sourceHighlighter("coderay").get();
Options options = options().attributes(attributes).get();

Locating files

A utility class AsciiDocDirectoryWalker is available for searching the AsciiDoc files present in a root folder and its subfolders. AsciiDocDirectoryWalker locates all files that end with .adoc, .ad, .asciidoc or .asc. Also it ignores all files starting with underscore (_).

Locating AsciiDoc files with AsciiDocDirectoryWalker
import java.util.List;
import org.asciidoctor.AsciiDocDirectoryWalker;

DirectoryWalker directoryWalker = new AsciiDocDirectoryWalker("docs"); // (1)
List<File> asciidocFiles = directoryWalker.scan(); // (2)
  1. Defines which parent directory is used for searching.

  2. Returns a list of all AsciiDoc files found in root folder and its subfolders.

A utility class GlobDirectoryWalker is available for searching the AsciiDoc files present in a root folder and scanning using a Glob expression. GlobDirectoryWalker locates all files that end with .adoc, .ad, .asciidoc or .asc.

Locating AsciiDoc files with GlobDirectoryWalker
import java.util.List;
import org.asciidoctor.GlobDirectoryWalker;

DirectoryWalker directoryWalker = new GlobDirectoryWalker("docs", "**/*.adoc"); // (1)
List<File> asciidocFiles = directoryWalker.scan(); // (2)
  1. Defines which parent directory is used for searching and the glob expression.

  2. Returns a list of all AsciiDoc files matching given glob expression.

Reading the document tree

Instead of converting an AsciiDoc document, you may want to parse the document to read information it contains or navigate the document’s structure. AsciidoctorJ let’s you do this!

AsciidoctorJ provides a model of the abstract syntax tree of the document. These objects are directly linked to the internal representation.

To load a document, use the load or loadFile methods.

import org.asciidoctor.ast.Document;

//...

Document document = asciidoctor.load(DOCUMENT, new HashMap<String, Object>()); // (1)
assertThat(document.doctitle(), is("Document Title")); // (2)
  1. Document from a String is loaded into Document object.

  2. Title of the document is retrieved.

But also all blocks that conforms the document can be retrieved. Currently there are support for three kinds of blocks. Blocks itself, Section for sections of the document and StructuralNode which is the base type where all kind of blocks (including those not mapped as Java class) are mapped.

import org.asciidoctor.ast.Document;
import org.asciidoctor.ast.Section;

//...

Document document = asciidoctor.load(DOCUMENT, new HashMap<String, Object>()); // (1)
Section section = (Section) document.blocks().get(1); // (2)

assertThat(section.index(), is(0)); // (3)
assertThat(section.sectname(), is("sect1"));
assertThat(section.special(), is(false));
  1. Document from a String is loaded into Document object.

  2. All blocks are get and because in this example the first block is a Section block, we cast it directly.

  3. Concrete methods for sections can be called.

Blocks can also be retrieved from query using findBy method.

Document document = asciidoctor.load(DOCUMENT, new HashMap<String, Object>());
Map<Object, Object> selector = new HashMap<>(); // (1)
selector.put("context", ":image"); // (2)

List<StructuralNode> findBy = document.findBy(selector);
assertThat(findBy, hasSize(2)); //(3)
  1. To make queries you need to use a map approach. Currently this is because of the Asciidoctor API but it will change in near future.

  2. In this example all blocks with context as image is returned. Notice that the colon (:) must be added in the value part.

  3. Document used as example contains two images.

Extension API

📎
If you are migrating existing extensions to a newer version of AsciidoctorJ please read the Extension Migration Guide

One of the major improvements to Asciidoctor recently is the extensions API. AsciidoctorJ brings this extension API to the JVM environment. AsciidoctorJ allows us to write extensions in Java instead of Ruby.

Asciidoctor provides seven types of extension points. Each extension point has an abstract class in Java that maps to the extension API in Ruby.

Table 5. AsciidoctorJ extension APIs
Name Class

Preprocessor

org.asciidoctor.extension.Preprocessor

Treeprocessor

org.asciidoctor.extension.Treeprocessor

Postprocessor

org.asciidoctor.extension.Postprocessor

Block processor

org.asciidoctor.extension.BlockProcessor

Block macro processor

org.asciidoctor.extension.BlockMacroProcessor

Inline macro processor

org.asciidoctor.extension.InlineMacroProcessor

Include processor

org.asciidoctor.extension.IncludeProcessor

You can read more about the Extension API in the Integrator Guide.

Ruby extensions

You can even register extensions written in Ruby using AsciidoctorJ. To register a Ruby extension you must get a RubyExtensionRegistry class instead of JavaExtensionRegistry.

Register a Ruby extension in Java
RubyExtensionRegistry rubyExtensionRegistry = this.asciidoctor.rubyExtensionRegistry(); // (1)
rubyExtensionRegistry.loadClass(Class.class.getResourceAsStream("/YellRubyBlock.rb")).block("rubyyell", "YellRubyBlock"); // (2)

String content = asciidoctor.convertFile(new File(
                "target/test-classes/sample-with-ruby-yell-block.ad"),
                options().toFile(false).get());
  1. rubyExtensionRegistry method is called to get a rubyExtensionRegistry instance.

  2. Ruby file containing a class implementing a Block extension is loaded inside the Ruby runtime. Then the block is registered with a name (rubyyell), and we pass the name of the class to be instantiated.

YellBlock.rb
require 'asciidoctor'
require 'asciidoctor/extensions'

class YellRubyBlock < Asciidoctor::Extensions::BlockProcessor
  option :contexts, [:paragraph]
  option :content_model, :simple

  def process parent, reader, attributes
    lines = reader.lines.map {|line| line.upcase.gsub(/\.( |$)/, '!\\1') }
    Asciidoctor::Block.new parent, :paragraph, :source => lines, :attributes => attributes
  end
end

Logs handling API

📎

This API is inspired by Java Logging API (JUL). If you are familiar with java.util.logging.* you will see familiar analogies with some of its components.

AsciidoctorJ (v1.5.7+) offers the possibility to capture messages generated during document rendering. These messages correspond to logging information and are organized in 6 severity levels:

  1. DEBUG

  2. INFO

  3. WARN

  4. ERROR

  5. FATAL

  6. UNKNOWN

The easiest way to capture messages is registering a LogHandler through the Asciidoctor instance.

Registering a LogHandler
Asciidoctor asciidoctor = Asciidoctor.Factory.create();

asciidoctor.registerLogHandler(new LogHandler() { // (1)
    @Override
    public void log(LogRecord logRecord) {
        System.out.println(logRecord.getMessage());
    }
});
  1. Use registerLogHandler to register one or more handlers.

The log method in the org.asciidoctor.log.LogHandler interface provides a org.asciidoctor.log.LogRecord that exposes the following information:

Severity severity

Severity level of the current record.

Cursor cursor

Information about the location of the event, contains:

  • LineNumber: relative to the file where the message occurred.

  • Path: source file simple name, or <stdin> value when converting from a String.

  • Dir: absolute path to the source file parent directory, or the execution path when converting from a String.

  • File: absolute path to the source file, or null when converting from a String.
    These will point to the correct source file, even when this is included from another.

String message

Descriptive message about the event.

String sourceFileName

Contains the value <script>.
For the source filename see Cursor above.

String sourceMethodName

The Asciidoctor Ruby engine method used to convert the file; convertFile or convert whether you are converting a File or a String.

Logs Handling SPI

Similarly to AsciidoctorJ extensions, the Log Handling API provides an alternate method to register Handlers without accessing Asciidoctor instance.

Start creating a normal LogHandler implementation.

package my.asciidoctor.log.MemoryLogHandler;

import java.util.ArrayList;
import java.util.List;
import org.asciidoctor.log.LogHandler;
import org.asciidoctor.log.LogRecord;

/**
 * Stores LogRecords in memory for later analysis.
 */
public class MemoryLogHandler extends LogHandler {

  private List<LogRecord> logRecords = new ArrayList<>();

  @Override
  public void log(LogRecord logRecord) {
    logRecords.add(record);
  }

  public List<LogRecord> getLogRecords() {
    return logRecords;
  }
}

Next, create a file called org.asciidoctor.log.LogHandler inside META-INF/services with the implementation’s full qualified name.

META-INF/services/org.asciidoctor.log.LogHandler
my.asciidoctor.log.MemoryLogHandler

And that’s all. Now when a .jar file containing the previous structure is dropped inside classpath of AsciidoctorJ, the handler will be registered automatically.

Converting to EPUB3

The Asciidoctor EPUB3 gem (asciidoctor-epub3) is bundled inside the AsciidoctorJ EPUB3 jar (asciidoctorj-epub3). To use it, simply add the asciidoctorj-epub3 jar to your dependencies. The version of the AsciidoctorJ EPUB3 jar aligns with the version of the Asciidoctor EPUB3 gem.

Here’s how you can add the AsciidoctorJ EPUB3 jar to your Maven dependencies:

<dependencies>
  <dependency>
    <groupId>org.asciidoctor</groupId>
    <artifactId>asciidoctorj-epub3</artifactId>
    <version>1.5.0-alpha.15</version>
    <scope>runtime</scope>
  </dependency>
</dependencies>

Once you’ve added the AsciidoctorJ EPUB3 jar to your classpath, you can set the backend attribute to epub3. The document will be converted to the EPUB3 format.

🔥
The asciidoctor-epub3 gem is alpha. While it can be used successfully, there may be bugs and its functionality may change in incompatible ways before the first stable release. In other words, by using it, you are also testing it ;)

Let’s see an example of how to use AsciidoctorJ with the EPUB3 converter.

spine.adoc
= Book Title
Author Name
:imagesdir: images (1)

include::content-document.adoc[] (2)
  1. The EPUB3 converter requires the value of the imagesdir attribute to be images.

  2. The EPUB3 converter must be run on a spine document that has at least one include directive (and no other body content) in order to function properly.

content-document.adoc
= Content Title
Author Name

[abstract]
This is the actual content.

== First Section

And off we go.

And finally we can convert the document to EPUB3 using AsciidoctorJ.

asciidoctor.convertFile(new File("spine.adoc"),
                options().safe(SafeMode.SAFE).backend("epub3").get()); // (1) (2)

assertThat(new File("target/test-classes/index.epub").exists(), is(true));
  1. Currently, the EPUB3 converter must be run in SAFE or UNSAFE mode due to a bug

  2. epub3 is the name of the backend that must be set to convert to EPUB3.

Writing a custom converter

For output formats that are not natively supported by Asciidoctor it is possible to write an own converter in Java. You can find out more about how to write your own converters using AsciidoctorJ in the Integrator Guide.

Loading Ruby libraries

Simple extensions may be fully implemented in Java, but if you want to create complex extensions you can mix Ruby and Java code. This means that you may need to execute a Ruby file or a RubyGem (i.e., gem) inside your extension.

To load a Ruby file inside the Ruby runtime, you can use org.asciidoctor.jruby.internal.RubyUtils.loadRubyClass(Ruby, InputStream). You can also load a gem using an API that wraps Ruby’s require command. The gem must be available inside the classpath. Next run org.asciidoctor.jruby.internal.RubyUtils.requireLibrary(Ruby, String), passing the name of the gem as the second argument.

JRuby instance

Sometimes you may need the Ruby runtime used inside AsciidoctorJ. One reason is because you are using JRuby outside AsciidoctorJ and you want to reuse the same instance. Another reason is that you need to instantiate by yourself an Asciidoctor Ruby object.

To get this instance you can use org.asciidoctor.jruby.internal.JRubyRuntimeContext.get(Asciidoctor) to get it from a given Asciidoctor instance.

GEM_PATH

By default, AsciidoctorJ comes with all required gems bundled within the jar. But in some circumstances like OSGi environments you may require to store gems in an external folder and be loaded by AsciidoctorJ.

As the Java interface org.asciidoctor.Asciidoctor and its factory org.asciidoctor.Asciidoctor.Factory are agnostic to JRuby there are the interface org.asciidoctor.jruby.AsciidoctorJRuby and org.asciidoctor.jruby.AsciidoctorJRuby.Factory that allow to get an Asciidoctor instance using JRuby with a certain GEM_PATH. Note that org.asciidoctor.jruby.AsciidoctorJRuby directly extends org.asciidoctor.Asciidoctor.

Example of setting GEM_PATH
import static org.asciidoctor.jruby.AsciidoctorJRuby.Factory.create;
import org.asciidoctor.Asciidoctor;

Asciidoctor asciidoctor = create("/my/gem/path"); // (1)
  1. Creates an Asciidoctor instance with given GEM_PATH location.

Using AsciidoctorJ in an OSGi environment

In a non OSGi context, the following snippet will successfully create an Asciidoctor object:

import static org.asciidoctor.Asciidoctor.Factory.create;
import org.asciidoctor.Asciidoctor;

Asciidoctor asciidoctor = create();

In an OSGi context it will not work because JRuby needs some paths to find the Asciidoctor gems. In order to make it work, you will need to specify the Asciidoctor gems path using the JavaEmbedUtils class. We will update the previous snippet of code to specify this path:

import static org.asciidoctor.jruby.AsciidoctorJRuby.Factory.create;
import org.asciidoctor.Asciidoctor;

Asciidoctor asciidoctor = create(Arrays.asList("uri:classloader:/gems/asciidoctor-2.2.0/lib")); // (1)(2)
  1. uri:classloader:/gems/asciidoctor-<version>/lib specifies where the gems for Asciidoctor are located. Actually this path is located inside the asciidoctorj-<version>.jar file

  2. The version here differs from the AsciidoctorJ version as it corresponds to the version of Asciidoctor, not the AsciidoctorJ one.

📎
We consider this code to be placed inside an OSGi bundle

This solution has pros and cons:

  • Pros: you don’t need to extract the gems located in the asciidoctorj binary ;

  • Cons:

    • the version of asciidoctor is hard coded ;

Optimization

JRuby may start slower than expected versus the C-based Ruby implementation (MRI). Fortunately, JRuby offers flags that can improve the start time and tune applications. Several Java flags can also be used in conjunction with or apart from the JRuby flags in order to improve the start time even more.

For small tasks such as converting an AsciiDoc document, two JRuby flags can drastically improve the start time:

Table 6. JRuby flags
Name Value

jruby.compat.version

RUBY1_9

jruby.compile.mode

OFF

When using AsciidoctorJ via the API these flags have to be set as system properties when creating the org.asciidoctor.Asciidoctor instance:

System.setProperty("jruby.compat.version", "RUBY1_9");
System.setProperty("jruby.compile.mode", "OFF");
Asciidoctor asciidoctor = Asciidoctor.Factory.create();

When starting AsciidoctorJ via the CLI these options can be defined in the files .jrubyrc that are loaded from the current working directory and the home directory of the user.

$ cat ./.jrubyrc
compat.version=RUBY1_9
compile.mode=OFF
$ ./asciidoctorj -V
AsciidoctorJ 1.5.2 [http://asciidoctor.org]
Runtime Environment: jruby 1.7.20 (1.9.3)
📎
The properties in these .jrubyrc files do not contain the prefix jruby.. The property values also must not have trailing blanks!

Alternatively you can also set any system properties using the environment variable ASCIIDOCTORJ_OPTS:

$ export ASCIIDOCTORJ_OPTS=-Djruby.compat.version=RUBY1_9
$ asciidoctorj -V
AsciidoctorJ 1.5.2 [http://asciidoctor.org]
Runtime Environment: jruby 1.7.20 (1.9.3)

The Java flags available for improving start time depend on whether your working on a 32- or 64-bit processor and your JDK version. Let’s see a summary of these flags and in which environments they can be used.

Table 7. Java flags
Name JDK

-client

32 bit Java

-Xverify:none

32/64 bit Java

-XX:+TieredCompilation

32/64 bit Java SE 7

-XX:TieredStopAtLevel=1

32/64 bit Java SE 7

Setting flags for Java SE 6
export JAVA_OPTS="-Xverify:none -client"

You can find a full explanation on how to improve the start time of JRuby applications in Optimization.

Running AsciidoctorJ on WildFly

If you want to use AsciidoctorJ in an application deployed on WildFly, you have to follow the instruction below:

  1. Create an Asciidoctor module for WildFly.

  2. Create the following folder tree: $JBOSS_HOME/modules/org/asciidoctor/main.

  3. Create the module descriptor file module.xml.

    Asciidoctor module descriptor for WildFly
    <?xml version="1.0" encoding="UTF-8"?>
    <module xmlns="urn:jboss:module:1.0" name="org.asciidoctor">
        <resources>
            <resource-root path="asciidoctorj-api-2.2.0.jar"/>
            <resource-root path="asciidoctorj-2.2.0.jar"/>
            <resource-root path="jcommander-1.72.jar"/>
            <resource-root path="jruby-complete-9.2.9.0.jar"/>
        </resources>
        <dependencies>
            <module name="sun.jdk" export="true">
                <imports>
                    <include path="sun/misc/Unsafe" />
                </imports>
            </module>
            <module name="javax.management.j2ee.api"/>
            <module name="javax.api"/>
            <module name="org.slf4j"/>
        </dependencies>
    </module>
  4. Copy the jar files into the same folder as the module.xml file.

  5. Make sure the version numbers of the jar files agree with what’s in the current set. Restart WildFly for the new module to take effect.

  6. Add a dependency on your Java archive to this WildFly module using one of the following options:

    1. Add the dependency just into the MANIFEST.MF file.

      MANIFEST.MF file example with dependency to Asciidoctor module
      Manifest-Version: 1.0
      Dependencies: org.asciidoctor
      ...
    2. Or, configure the dependency into the pom.xml with the Maven JAR/WAR plugin.

      pom.xml file example with Maven WAR plugin configuration to add a dependency
      ...
      <dependencies>
        <dependency>
          <groupId>org.asciidoctor</groupId>
          <artifactId>asciidoctorj</artifactId>
          <version>2.2.0</version>
          <scope>provided</scope> <!--(1)-->
          ...
        </dependency>
      </dependencies>
      ...
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-war-plugin</artifactId>
          <version>2.4</version>
          <configuration>
            <archive>
              <manifestEntries>
                <Dependencies>org.asciidoctor</Dependencies> <!--(2)-->
              </manifestEntries>
            </archive>
          </configuration>
      </plugin>
      ...
      1. The AsciidoctorJ dependency and the transitives dependencies don’t need to be added to the final WAR since all JARs are available through the module.

      2. The module dependency will be added to the MANIFEST.MF file.

Using a pre-release version

Pre-release versions of AsciidoctorJ are published to Bintray. You can find them in https://bintray.com/asciidoctor/maven/asciidoctorj/view. Final releases are released to both Maven Central and Bintray.

Here’s how to use a pre-release version in Maven:

<repositories>
  <repository>
    <id>central</id>
    <name>bintray</name>
    <url>http://dl.bintray.com/asciidoctor/maven</url>
    <snapshots>
      <enabled>false</enabled>
    </snapshots>
  </repository>
</repositories>

Using a snapshot version

Snapshot versions will be published to https://oss.jfrog.org. To use a snapshot version of the the AsciidoctorJ library add this repository to your project:

<repositories>
  <repository>
    <id>oss-jfrog-artifactory</id>
    <name>oss-jfrog-artifactory-releases</name>
    <url>https://oss.jfrog.org/artifactory/oss-snapshot-local</url>
    <snapshots>
      <enabled>true</enabled>
    </snapshots>
  </repository>
</repositories>

If you build your project using Gradle add the repository like this to your build:

repositories {
    maven {
        url 'http://oss.jfrog.org/oss-snapshot-local/'
    }
}

You can also download a snapshot version of the distribution from here: https://oss.jfrog.org/oss-snapshot-local/org/asciidoctor/asciidoctorj-distribution

Development

AsciidoctorJ is built using Gradle. The project is structured as a multi-module build.

Project layout

The root folder is the root project and there are several subproject folders, each prefixed with asciidoctorj-. Each subproject produces a primary artifact (e.g., jar or zip) and its supporting artifacts (e.g., javadoc, sources, etc).

The subprojects are as follows:

asciidoctorj-api

The common API for AsciidoctorJ. Other implementations for different platforms than JRuby may reuse and implement this API. Produces the asciidoctorj-api.jar

asciidoctorj

The main Java bindings for the Asciidoctor RubyGem (asciidoctor) running on JRuby. Also bundles optional RubyGems needed at runtime, such as coderay, tilt, haml and slim. Produces the asciidoctorj jar.

asciidoctorj-distribution

Produces the distribution zip that provides the standalone asciidoctorj command.

asciidoctorj-epub3

Bundles the Asciidoctor EPUB3 RubyGem (asciidoctor-pdf) and its dependencies as the asciidoctorj-epub3 jar.

asciidoctorj-pdf

Bundles the Asciidoctor PDF RubyGem (asciidoctor-pdf) and its dependencies as the asciidoctorj-pdf jar.

asciidoctorj-diagram

Bundles the Asciidoctor Diagram RubyGem (asciidoctor-diagram) and its dependencies as the asciidoctorj-diagram jar.

asciidoctorj-arquillian-extension

Bundles an Arquillian extension that allows to inject an Asciidoctor instance or other instances commonly used by Asciidoctor tests into a test case.

asciidoctorj-test-support

Contains some common test classes that are used by multiple other subprojects and the Arquillian extension.

The Gradle build is partitioned into the following files:

build.gradle
gradle.properties
settings.gradle
gradle/
  wrapper/
    ...
  deploy.gradle
  deploySnapshot.gradle
  eclipse.gradle
  providedConfiguration.gradle
  publish.gradle
  sign.gradle
asciidoctorj-arquillian-extension/
  build.gradle
asciidoctorj-api/
  build.gradle
asciidoctorj-core/
  build.gradle
asciidoctorj-diagram/
  build.gradle
asciidoctorj-distribution/
  build.gradle
asciidoctorj-epub3/
  build.gradle
asciidoctorj-pdf/
  build.gradle
asciidoctorj-test-support/
  build.gradle

Build the project

You invoke Gradle on this project using the gradlew command (i.e., the Gradle Wrapper).

💡
We strongly recommend that you use Gradle via the Gradle daemon.

To clone the project, compile the source and build the artifacts (i.e., jars) locally, run:

$ git clone https://github.com/asciidoctor/asciidoctorj
  cd asciidoctorj
  ./gradlew assemble

You can find the built artifacts in the asciidoctorj-*/build/libs folders.

To execute tests when running the build, use:

$ ./gradlew build

To only execute the tests, run:

$ ./gradlew check

You can also run tests for a single module:

$ cd asciidoctorj-core
  ../gradlew check

To run a single test in the asciidoctorj-core subproject, use:

$ ../gradlew -Dsingle.test=NameOfTestClass test

To create the distribution, run:

$ ./gradlew distZip

You can find the distribution in the asciidoctorj-distribution/build/distributions folder.

Develop in an IDE

IntelliJ IDEA

To import the project into IntelliJ IDEA 14, simply import the project using the import wizard. For more information, see the Gradle page in the IntelliJ IDEA Web Help.

Eclipse

To open the project in Eclipse, first generate the Eclipse project files:

$ cd asciidoctorj-core
  ./gradlew eclipse

Then, import the project into Eclipse using File  Import  General  Existing Project into Workspace.

Continuous integration

Continuous integration for the AsciidoctorJ project is performed by Travis CI. You can find recent build results, including the build status of pull requests, on the asciidoctor/asciidoctorj page.

Publish the artifacts

Artifacts are published to Maven Central and jCenter by way of Bintray’s Distribution as a Service platform.

Before publishing, you need to configure your gpg signing and Bintray credentials. Create the file $HOME/.gradle/gradle.properties and populate the following properties.

signing.keyId=
signing.password=
signing.secretKeyRingFile=/home/YOUR_USERNAME/.gnupg/secring.gpg
bintrayUsername=
bintrayApiKey=

To build, assemble and sign the archives (jars and distribution zip), run:

$ ./gradlew -PpublishRelease=true signJars
💡
The publishRelease=true property is technically only required if the version is a snapshot.

To build, assemble (but not sign) and install the archives (jars and distribution zip) into the local Maven repository, run:

$ ./gradlew -PpublishRelease=true install

To build, assemble, sign and publish the archives (jars and distribution zip) to Bintray, run:

$ ./gradlew clean
  ./gradlew -i -x pMNPTML asciidoctorj-api:bintrayUpload asciidoctorj:bintrayUpload asciidoctorj-distribution:bintrayUpload
🔥
Don’t run the clean task in the same execution as the bintrayUpload because it will not upload one of the signatures.

If you want to first perform a dry run of the upload, add the dryRun=true property.

$ ./gradlew -i -PdryRun=true -x pMNPTML asciidoctorj-api:bintrayUpload asciidoctorj:bintrayUpload asciidoctorj-distribution:bintrayUpload
📎
The -x pMNPTML is necessary to work around a bug in the publishing plugin that prevents it from signing the archives.
Bintray does not allow you to publish snapshots. You have to first update the version in gradle.properties to a release (or pre-release) version number. Currently, Gradle is not configured to automatically tag a release, so you have to create the git tag manually.

Publish snapshot versions

To publish a snapshot version simply run:

$ ./gradlew asciidoctorj-api:artifactoryPublish asciidoctorj:artifactoryPublish asciidoctorj-distribution:artifactoryPublish

This will publish the all artifacts that have a snapshot version number to https://oss.jfrog.org.

Resources

The source code for AsciidoctorJ, including the latest developments and issues, can be found in the project’s repository on GitHub. If you identify an issue while using AsciidoctorJ, please don’t hesitate to file a bug report. Also, don’t forget to join the Asciidoctor discussion list, where you can ask questions and leave comments.

About

:coffee: Java bindings for Asciidoctor. Asciidoctor on the JVM!

http://asciidoctor.org

License:Apache License 2.0


Languages

Language:Java 79.0%Language:Groovy 12.7%Language:Ruby 7.0%Language:Shell 0.6%Language:CSS 0.3%Language:PowerShell 0.2%Language:CoffeeScript 0.0%Language:D 0.0%Language:HTML 0.0%