Skip to content

Asciidoctor extensions (currently Asciidoctor.js only) developed for the Spring docs.

License

Notifications You must be signed in to change notification settings

spring-io/asciidoctor-extensions

Spring.io Asciidoctor Extensions

This library provides Asciidoctor extensions that support the Spring documentation. For now, these extensions are only designed for use with Asciidoctor.js. The extensions evolved out of the Spring Asciidoctor backends project.

Prerequisites

In order to use this extension, you must have Node.js 16 or higher installed on your machine. These extensions are intended to be used with Asciidoctor.js. Since this project does not declare Asciidoctor.js as a dependency to afford you flexibility, you must have Asciidoctor.js 2.2 or higher installed in your project.

Installation

Use the following command to install the @springio/asciidoctor-extensions package into your project:

$ npm i @springio/asciidoctor-extensions

To use the development version instead, refer to the Development Quickstart.

You also need to register the extension in the Antora playbook. The require name(s) for each extension is listed in the documentation for that extension. Here’s an example of how to register the common extensions in the asciidoc.extensions key in the Antora playbook:

asciidoc:
  extensions:
  - '@springio/asciidoctor-extensions'

Several extensions will log a warning if a scenario is invalid. In order to see the line number of the correct file where the problem occurs in the message, you must enable Asciidoctor’s sourcemap. To do so, set the asciidoc.sourcemap key in the Antora playbook as follows:

asciidoc:
  sourcemap: true
  extensions:
  # ...

Extensions

This section documents the Asciidoctor extensions that are provided by this library.

Code Chomping

require name: @springio/asciidoctor-extensions or @springio/asciidoctor-extensions/code-chomping-extension

The code chomping extension allows specific parts of a Java, Kotlin, or Groovy source block to be removed. The extension will not run on any other source, listing, or literal blocks. This extension is mainly useful if you have externalized code that includes comments and annotations intended for the compiler’s eyes only.

When this extension is registered, it will remove parts of the code that match chomp tags, @Suppress / @SuppressWarnings annotations, and @formatter:on / @formatter:off line comments. You can also turn on chomping of the header (typically a Javadoc comment or license statement) and package declaration.

The following chomp tags (i.e., comments) are supported:

Comment Description

/**/

Chomps the rest of the line and replaces it with ...

/* @chomp:line <replacement> */

Chomps the rest of the line and replaces it with <replacement>, which defaults to ...

// @chomp:line

Chomps (drops) this line only

// @chomp:file

Chomps (drops) this line to the end of the file

Here’s an example source block that uses code chomping:

[,java]
----
public class Example {
    private final Something something;

    private final Other other;

    public Example() {
        this.something = /**/ new MockSomething();
        this.other = /* @chomp:line your thing... */new MyThing();
    }
}
----

The output of this block will appear as follows:

public class Example {
    private final Something something;

    private final Other other;

    public Example() {
      this.something = ...
      this.other = your thing...
    }
}

You can set the chomp AsciiDoc attribute to change the default settings. The attribute can be set on the document or the block. The document attribute is the default value. The attribute value may be one of the following keywords:

Flag Description

default

Enable the tags, formatters, and suppresswarnings operations

all

Enable all chomping operations

none

Disable all chomping operations

tags

Chomp the comment tags

formatters

Chomp any @formatter:on / @formatter:off line comments

suppresswarnings

Chomp any @Suppress or @SuppressWarnings annotations

headers

Chomp any file headers up to package declaration

packages

Chomp the package declaration, or replace the name if the chomp-package-replacement attribute is set

Package names should follow Java naming conventions (all lowercase characters). Instead of dropping the package declaration, you can replace the name. To do so, ensure the packages operation is enabled. Then, set the chomp-package-replacement to the replacement name, such as org.example. When the extension finds the package declaration, it will replace the name in the source with the replacement name you specified.

The following document is configured to update the name in the package declaration in each Java-like source file with org.example.

= My Document
:chomp-package-replacement: org.example

If this attribute is set and its value is empty, the original package declaration is preserved.

Code Folding

require name: @springio/asciidoctor-extensions or @springio/asciidoctor-extensions/code-folding-extension

The code folding extension allows non-pertinent code in a source block to be hidden on initial view. The user can click the “unfold” button to reveal the hidden code. The extension will not run on any other source, listing, or literal blocks. This extension is mainly useful if you have externalized code that includes boilerplate lines that detract from the focus of the snippet.

When this extension is registered, all Java imports will be automatically folded. Additional fold blocks can also be defined using fold tags. The fold tags are @fold:on and @fold:off comment lines.

Here’s an example source block that uses code folding to hide the fields on initial view:

[,java]
----
public class Example {
    // @fold:on
    private final String first;

    private final String second;
    // @fold:off

    public Example(String first, String second) {
        this.first = first;
        this.second = second;
    }
}
----

The @fold:on tag supports replacement text to show when the block is folded. Here’s an example source block that replaces the getters and setters with a comment when folded:

[,java]
----
public class Example {
    private String first;

    private String second;

    // @fold:on // getters / setters...
    public String getFirst() {
        return this.first;
    }

    public void setFirst(String first) {
        this.first = first;
    }

    public String getSecond() {
        return this.second;
    }

    public void setSecond(String second) {
        this.second = second;
    }
    // @fold:off
}
----

You can set the fold AsciiDoc attribute to change the default settings. The attribute can be used on the document or the block. The document attribute is the default value. The attribute value may be one of the following keywords:

Flag Description

default

Enable the imports and tags operations

all

Enable all folding operations

none

Disable all folding operations

imports

Fold import statements

tags

Fold @fold:on / @fold:off tags

Include Code

require name: @springio/asciidoctor-extensions or @springio/asciidoctor-extensions/include-code-extension

The include code extension provides the include-code block macro. This macro generates code (i.e., source) blocks by importing auto-discovered code files in one or more languages (currently only XML, Java, Kotlin, or Groovy) from preconfigured locations. The location is defined first by an AsciiDoc attribute, then by the ID of the nearest ancestor section or document. The ID is converted to a path by removing all - characters and replacing all . characters with /. When searching, the extension appends the appropriate file extension for each configured language.

Consider the following AsciiDoc source:

[[my.example-project]]
== My Example Project

include-code::SomeCode[]

If the include-java attribute is defined, the include-code block macro will look for the Java file at the location {include-java}/my/exampleproject/SomeCode.java. It will then create a source block with the language java that contains the contents of this file.

The following languages are supported by convention based imports:

Language Root Directory Attribute File Extension

XML

include-xml

.xml

Java

include-java

.java

Kotlin

include-kotlin

.kt

Groovy

include-groovy

.groovy

A code block will be created for a matching file in each configured language. If more than one file is found, and the Asciidoctor Tabs extension is registered, the code blocks will automatically be enclosed in a tabs block. If Asciidoctor Tabs is not registered in this case, the source blocks will be created as siblings.

If none of the supported include-* attributes are defined, or if no code files are found for a target at the locations defined by these attributes, the extension will log a warning.

If the target path starts with ./, then the path (with - removed) of the source file with be used to compute the location of the import. The location is defined first by an AsciiDoc attribute, next by the path of the source file, and then by the ID of the nearest ancestor section or document.

Consider the following AsciiDoc source:

org/spring/index.adoc
[[my.example-project]]
== My Example Project

include-code::./SomeCode[]

If the include-java attribute is defined, the include-code block macro will look for the Java file at the location {include-java}/org/spring/my/exampleproject/SomeCode.java. It will then create a source block with the language java that contains the contents of this file.

Configuration Properties

require name: @springio/asciidoctor-extensions/configuration-properties-extension

The configuration properties extensions augments the AsciiDoc syntax to validate and display Spring configuration properties in the documentation. Those enhancements are as follows:

configprop inline macro

The configprop inline macro verifies that a configuration property is valid and its deprecation status, then outputs the property name as a monospaced (i.e, code) phrase.

configprops block

The configprops block is an enhanced source blocks that validates all the configuration properties listed in either the Java properties format or YAML format, then displays the properties as a source (i.e., code) block. The block can either output the input format, or, if the input format is YAML, can display tabs containing the properties assignments in both the Java properties format and the YAML format.

This extension loads the Spring configuration properties from spring-configuration-metadata.json files in the partial family for the current component version (e.g., modules/ROOT/partial/spring-boot/spring-configuration-metadata.json). It will aggregate the properties from all matching files into a single lookup table. The table is used for validating property names and querying their deprecation status. The extension assumes that these files are put into that location by another extension, such as Antora Collector. Here’s an example Gradle task that can be used with Antora Collector:

task collectConfigurationMetadataFiles(type: Copy) {
  from project.configurations.configurationProperties
  eachFile {
    it.path = it.file
      .toString()
      .replaceFirst('/build/(?:classes|resources)/java/main/', '/')
      .replaceFirst('^.*/([^/]+)/META-INF/(spring-configuration-metadata\\.json)$', '$1/$2')
  }
  into layout.buildDirectory.dir('generated-docs/modules/ROOT/partials/configuration-metadata')
}

configprop inline macro

The configprop macro is an inline macro (single colon) that can be used to reference a Spring configuration property. The macro outputs the name of the property as monospaced text. Here’s an example that references the server.port configuration property.

Use the configprop:server.port[] property to configure the server's port.

This property is defined in a Spring configuration metadata file as follows:

{
  "name": "server.port",
  "type": "java.lang.Integer",
  "description": "Server HTTP port.",
  "sourceType": "org.springframework.boot.autoconfigure.web.ServerProperties",
  "defaultValue": 8080
}

If the property does not exist, the extension will log a warning message. If the property is deprecated, the extension will log a warning message by default. These two validations also apply to the configprops block.

If the deprecation is expected, the deprecation message can be suppressed by setting the deprecated option on the macro:

In the old days, you would use configprop:server.servlet.path[opts=deprecated] to set the path of the main dispatcher servlet.

If the deprecated option is specified, but the property is not deprecated, the extension will log a warning.

Instead of outputting the property name itself, you can configure the macro to output the environment variable that maps to the property by setting the format=envvar attribute. In the following example, the macro outputs the environment variable name SERVER_PORT instead of the property name servlet.port.

Use the configprop:server.port[format=envvar] environment variable to configure the server's port.

configprops block

The configprops block is an enhanced source block that can be used to validate configuration property names, demonstrate how properties are used, and display properties in multiple formats. To the reader, the configprops block will appear as a source block, optionally in a tabbed interface. The block has the indent=0 attribute implicitly set.

The configprops block accepts input as either Java properties (properties) or YAML (yaml). The input language is specified using the second positional parameter, just like a source block. Currently, only the yaml input format allows the properties to be displayed in both formats (which is the preferred means of display).

Here’s an example of that shows how to use the configprops block with properties in the Java properties format.

[configprops,properties]
----
spring.datasource.url=jdbc:mysql://localhost/test
spring.datasource.username=dbuser
spring.datasource.password=dbpass
----

If a property in the block is not valid or is deprecated, the extension will log a warning.

In the precending example, three property names will be validated:

  • spring.datasource.url

  • spring.datasource.username

  • spring.datasource.password

While the Java properties format may suffice for simple use cases, the YAML format is preferred. Let’s look at that same block again, this time using YAML as the source language.

[configprops,yaml]
----
spring:
  datasource:
    url: jdbc:mysql://localhost/test
    username: dbuser
    password: dbpass
----

In addition to displaying the configuration properties in YAML format, this block will also display the properties in the Java properties format. The reader will be able to switch between the two formats using a tabbed interface.

You can disable the tabbed interface, and the automatic conversion to the Java properties format, by setting the noblocks option:

[configprops%noblocks,yaml]
----
spring:
  datasource:
    url: jdbc:mysql://localhost/test
    username: dbuser
    password: dbpass
----

When the configuration properties are specified in the YAML format, the validator will not validate keys of a java.util.Map property or indices of an java.util.List property, but will still convert the data to the Java properties format. Here’s an example that contains these scenarios:

[configprops,yaml]
----
spring:
  mail:
    properties:
      "[mail.smtp.connectiontimeout]": 5000
      "[mail.smtp.timeout]": 3000
      "[mail.smtp.writetimeout]": 5000
  ldap:
    embedded:
      base-dn:
      - dc=spring,dc=io
      - dc=pivotal,dc=io
  mvc:
    contentnegotiation:
      media-types:
        markdown: text/markdown
----

In the preceding example, three property names will be validated:

  • spring.mail.properties

  • spring.ldap.embedded.base-dn

  • spring.mvc.contentnegotiation.media-type

The other keys are user-defined and thus cannot be validated.

Here’s how this configuration will be displayed in the Java properties format:

spring.mail.properties[mail.smtp.connectiontimeout]=5000
spring.mail.properties[mail.smtp.timeout]=3000
spring.mail.properties[mail.smtp.writetimeout]=5000
spring.ldap.embedded.base-dn[0]=dc=spring,dc=io
spring.ldap.embedded.base-dn[1]=dc=pivotal,dc=io
spring.mvc.contentnegotiation.media-types.markdown=text/markdown

If you want to forgo validation of property names, such as for hypothetical examples, set the novalidate option.

[configprops%novalidate,yaml]
----
hypothetical:
  property:
    name: value
----

Section IDs

require name: @springio/asciidoctor-extensions/section-ids-extension

This section validates the document ID and the IDs of its sections. It ensures that each ID uses valid characters (kebab-case by default). It also checks that the IDs of sections extend the ID of their parent section.

The following IDs are valid (using the default configuration):

  • top-level

    • top-level.nested-child

      • top-level.nested-child.grandchild

A warning is logged for each invalid ID.

The word and level separators separators can be controlled using the following two AsciiDoc attributes:

sectid-word-separator (default: -)

The character that must be used to separate words in a local ID (the final segment).

sectid-level-separator (default: .)

The character that must be used to separate levels in a section ID.

All IDs must be lowercase, must start with an alphabetic character, and may only contain alphanumeric characters aside from the aforementioned separators.

Javadoc

require name: @springio/asciidoctor-extensions/javadoc-extension

The javadoc extension allows you to quickly create links to javadoc sites. For example, javadoc:com.example.MyClass[] will create a link to xref:attachment$api/java/com/example/MyClass.html with the text MyClass.

Syntax

The following format can be used when declaring a javadoc link:

[<location>]<class-reference>[#<anchor>]

Only <class-reference> is mandatory, and it must be the fully qualified name of the class to link to. For example, com.example.MyClass. References to inner-classes should use $ notation instead of .. For example, com.example.MyClass$Builder.

If a <location> is specified, it must end with /.

Any <anchor> must exactly match the anchor in the corresponding javadoc page.

Locations

Unless otherwise overridden, the default javadoc location will be xref:attachment$api/java. If you want a different default, you can set the javadoc-location document attribute. For example, you can set javadoc-location to xref:api:java if you publish javadoc under a api Antora module.

Note
document attributes can be set in your Antora playback under the attributes key.
Package Specific Locations

You can use package specific javadoc-location document attributes if you want to support multiple locations. To set a package specific location, add a javadoc-location-<package> document attribute. The <package> should be the java package name with all . characters replaced with -.

For example, the following will use different locations for com.example.core links:

= Example
:javadoc-location-com-example-core: {url-example-javadoc}

Please see javadoc:com.example.core.util.MyUtils[]

You can override locations on a per-link basis by including the location directly in the link. The embedded link must end with a /.

For example:

= Example
:url-jdk-javadoc: https://docs.oracle.com/en/java/javase/17/docs/api

Please read javadoc:{url-jdk-javadoc}/java.base/java.io.InputStream[]

By default, a short form of the class name is used as the link text. For example, if you link to com.example.MyClass, only MyClass is used for the link text.

If you want to change the format of the link text, you can use the format attribute. For example, javadoc:com.example.MyClass[format=full] will use com.example.MyClass as the link text.

The following formats are supported:

Name Description Example

short (default)

The short class name

com.example.MyClass$BuilderMyClass.Builder

full

The fully-qualified class name

com.example.MyClass$Buildercom.example.MyClass.Builder

annotation

The short class name prefixed with @

com.example.MyAnnotation@MyAnnotation

Tip
You can change the default format by setting a javadoc-format document attribute. This can be done site-wide, or on a page-by-page basis.

You can also specify directly specify link text if the existing formats are not suitable:

javadoc:com.example.MyClass$Builder[See `Builder` for details].

Anchors

Anchors may be used to link to a specific part of a javadoc page. The anchor link must exactly match the link in the target page. For example, javadoc:com.example.MyClass#myMethod(java.lang.String)

When an anchor is specified, the link text will include a readable version. The example above would render the link text MyClass.myMethod(String).

UI Support

All anchors created using the javadoc macro will have a role of apiref to allow the UI to style them appropriately.

Development Quickstart

This section provides information on how to develop on this project.

Prerequisites

To build this project and run the tests, you need the following software installed on your computer:

  • git (command: git)

  • Node.js (commands: node, npm, and npx)

git

First, make sure you have git installed.

$ git --version

If not, download and install the git package for your system.

Node.js

Next, make sure that you have Node.js installed (which also provides npm and npx).

$ node --version

If this command fails with an error, you don’t have Node.js installed. If the command doesn’t report an active LTS version of Node.js, it means you don’t have a suitable version of Node.js installed.

We strongly recommend that you use nvm (Node Version Manager) to manage your Node.js installation(s). Follow the nvm installation instructions to set up nvm on your machine.

Once you’ve installed nvm, open a new terminal and install Node.js 16 using the following command:

$ nvm install 16

You can switch to this version of Node.js at any time using the following command:

$ nvm use 16

To make Node.js 16 the default in new terminals, type:

$ nvm alias default 16

Now that you have git and Node.js installed, you’re ready to start developing on this project.

Clone Project

Clone the project using git:

$ git clone https://github.com/spring-io/asciidoctor-extensions &&
  cd "`basename $_`"

The previous chained command clones the project then switches to the project folder on your filesystem. Stay in this project folder when running all subsequent commands.

Install Dependencies

Use npm to install the project’s dependencies inside the project. In your terminal, run the following command:

$ npm ci

This command installs the dependencies listed in package-lock.json into the node_modules/ folder inside the project. This folder should not be committed to the source control repository.

Run Tests

This project uses mocha to run the tests and the assertion library chai to assert outcomes. To run the test suite, use:

$ npm test

By default, npm test will run all tests. You can run the tests in a single test suite by passing the path of that test suite as the final argument:

$ npm test test/code-chomping-extension-test.js

You can also run a single test by adding .only to the it function (e.g., it.only). If it.only is present, npm test will only run that test.

To generate a coverage report when running the tests (enabled by default in CI), run the coverage script instead:

$ npm run coverage

A coverage report shows the lines, statements, and branches that the tests exercise. You can view the coverage report by opening the HTML file reports/lcov-report/index.html in your browser.

Verify Code Style

This project adheres to the JavaScript Standard style with some exceptions defined in .eslintrc. The code style is verified using ESLint.

To verify that the style of the code is correct, run the following command:

$ npm run lint

To format the code to adhere to the code style, run the following command:

$ npm run format

The CI workflow will fail if there are pending code style changes, so be sure to run it before you push a change.

Use Project From Source

If you want to use the project locally before it is published, you can specify the path to the project as the version in package.json.

"dependencies": {
  "@springio/asciidoctor-extensions": "/path/to/project"
}

When you run npm i in that project, npm will set up a symlink to the location of this project. Any changes to this project will take effect immediately.

License

Use of this software is granted under the terms of the Apache License, Version 2.0 (Apache-2.0). See LICENSE to find the full license text.

About

Asciidoctor extensions (currently Asciidoctor.js only) developed for the Spring docs.

Resources

License

Code of conduct

Security policy

Stars

Watchers

Forks