An open API service indexing awesome lists of open source software.

https://github.com/dheid/adocfmt

An opinionated AsciiDoc formatter for Java and the command line.
https://github.com/dheid/adocfmt

asciidoc cli formatter formatting java library maven nfpm tool

Last synced: 13 days ago
JSON representation

An opinionated AsciiDoc formatter for Java and the command line.

Awesome Lists containing this project

README

          

# adocfmt

[![Java CI with Maven](https://github.com/dheid/adocfmt/actions/workflows/ci.yml/badge.svg)](https://github.com/dheid/adocfmt/actions/workflows/ci.yml)
[![Maven Central](https://img.shields.io/maven-central/v/org.drjekyll/adocfmt)](https://central.sonatype.com/artifact/org.drjekyll/adocfmt)
[![GitHub Release](https://img.shields.io/github/v/release/dheid/adocfmt)](https://github.com/dheid/adocfmt/releases/latest)
[![Java](https://img.shields.io/badge/Java-17%2B-blue)](https://adoptium.net/)

An opinionated AsciiDoc formatter for Java and the command line.

![adocfmt Banner](banner.png)

## Features

adocfmt applies a configurable set of transformations to AsciiDoc source files.
The following transformations are available (default state shown):

| Transformation | Default | Description |
|----------------------------------|---------|--------------------------------------------------------------------------|
| Normalize setext headings | **on** | Convert underline-style headings to ATX-style (`=` prefix) |
| Collapse consecutive blank lines | **on** | Reduce runs of multiple blank lines to a single blank line |
| One sentence per line | **on** | Reflow paragraph text so each sentence starts on its own line |
| Normalize block delimiters | **on** | Shorten delimiter lines to exactly four characters (e.g. `----`) |
| Remove trailing header `=` signs | **on** | Strip trailing `=` from headings (e.g. `== Title ==` → `== Title`) |
| Remove trailing whitespace | **on** | Strip trailing whitespace from every line |
| Ensure heading blank lines | **on** | Surround each section heading with exactly one blank line |
| Title case | off | Apply title-case formatting to section headings and block titles |
| Normalize list bullets | off | Normalise unordered list bullets from `- ` to `* ` |
| Normalize ordered list markers | off | Replace explicit numbers (`1. `, `2. `) with the auto-number marker `. ` |
| Ensure source delimiters | off | Wrap bare `[source]`/`[listing]` blocks in `----` delimiters |

**Invariants guaranteed by every run:**

- **Idempotent:** `format(format(x)) == format(x)`.
- **Safe:** Delimited blocks (code, literal, passthrough), comments, and directives are never modified.
- **Semantic:** Input and output render to identical normalised HTML (verified via AsciidoctorJ in tests).
- **Trailing Newline:** Formatted output always ends with exactly one newline.

## Code Style

This section describes the AsciiDoc style that adocfmt enforces.
The rationale behind each rule is consistent diffs, readable source, and
unambiguous structure -- not aesthetic preference.

### ATX Headings

Use `=`-prefixed headings (ATX style) instead of underline-style (setext) headings.
The level maps directly to the number of `=` signs: one for the document title, two
for the first section level, and so on.

```asciidoc
// Before
My Document Title
=================

Introduction
------------

// After
= My Document Title

== Introduction
```

Trailing `=` signs are also removed:

```asciidoc
// Before
== Installation ==

// After
== Installation
```

### Blank Lines Around Headings

Every section heading is surrounded by exactly one blank line on each side.
This makes section boundaries visible at a glance and avoids parser ambiguity.

```asciidoc
// Before
Some text.
== Next Section
More text.

// After
Some text.

== Next Section

More text.
```

### One Sentence Per Line

Each sentence in a paragraph is placed on its own line.
This produces minimal, meaningful diffs: editing one sentence touches exactly one line,
and adding a sentence does not reflow the paragraph.

```asciidoc
// Before
AsciiDoc is a lightweight markup language. It is used for writing documentation. It can be converted to HTML, PDF, and other formats.

// After
AsciiDoc is a lightweight markup language.
It is used for writing documentation.
It can be converted to HTML, PDF, and other formats.
```

Common abbreviations (e.g. `Dr.`, `etc.`, `ca.`) are recognised and do not trigger a split.

### Consecutive Blank Lines

At most one consecutive blank line is permitted.
Multiple blank lines carry no structural meaning in AsciiDoc and are collapsed to one.

```asciidoc
// Before
First paragraph.

Second paragraph.

// After
First paragraph.

Second paragraph.
```

### Block Delimiters

Block delimiter lines use exactly four characters, regardless of how many were written originally.
This removes visual noise and prevents mismatched delimiter lengths from silently breaking block structure.

```asciidoc
// Before
--------
$ echo hello
--------

// After
----
$ echo hello
----
```

### Source Blocks (optional, off by default)

`[source]` and `[listing]` attribute lines that are not followed by `----` delimiters
have them added automatically, ensuring the code is rendered as a verbatim block.

```asciidoc
// Before
[source,java]
int x = 1;

// After
[source,java]
----
int x = 1;
----
```

### List Bullets (optional, off by default)

Unordered list items use `*` as the bullet character.
The `-` form is converted for consistency.

```asciidoc
// Before
- Item A
- Item B

// After
* Item A
* Item B
```

### Ordered Lists (optional, off by default)

Ordered list items use the AsciiDoc auto-numbering marker `.` instead of explicit numbers.
This prevents numbering from going out of sync when items are reordered.

```asciidoc
// Before
1. First
2. Second
3. Third

// After
. First
. Second
. Third
```

### Title Case (optional, off by default)

Section headings and block titles are formatted in title case.
Short words (articles, conjunctions, short prepositions) are lowercased unless they
appear as the first or last word.

```asciidoc
// Before
== getting started with the cli

// After
== Getting Started with the CLI
```

## Installation

Requires **Java 17+**.

### Debian / Ubuntu

Download the `.deb` from the latest release and run:

```bash
sudo apt install ./adocfmt__all.deb
```

### Fedora / RHEL / CentOS

Download the `.rpm` from the latest release and run:

```bash
sudo dnf install ./adocfmt-.noarch.rpm
```

### macOS (Homebrew)

```bash
brew install drjekyll-org/tap/adocfmt
```

### Windows

Download `adocfmt.exe` from the latest release. Ensure Java 17+ is on your PATH.

### Universal (Any Platform)

Download `adocfmt.jar` from the latest release and run:

```bash
java -jar adocfmt.jar [args]
```

## Usage

### Java Library

Requires Java 17+. Add the core module to your project:

```xml

org.drjekyll
adocfmt
0.2.0

```

Build an `AsciidocFormatterConfig` with the desired transformations enabled, then create an `AsciidocFormatter` and call
one of its `format` overloads:

```java
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import org.drjekyll.adocfmt.AsciidocFormatter;
import org.drjekyll.adocfmt.AsciidocFormatterConfig;
import org.drjekyll.adocfmt.UnsupportedLineEndingException;

AsciidocFormatterConfig config = AsciidocFormatterConfig.builder()
.normalizeSetextHeadings(true)
.collapseConsecutiveBlankLines(true)
.oneSentencePerLine(true)
.normalizeBlockDelimiters(true)
.removeTrailingHeaderEqualsSign(true)
.removeTrailingWhitespace(true)
.ensureHeadingBlankLines(true)
.titleCase(false)
.normalizeListBullets(false)
.normalizeOrderedListMarkers(false)
.ensureSourceDelimiters(false)
.build();

AsciidocFormatter formatter = new AsciidocFormatter(config);

// Format a string
String formatted = formatter.format("== my title\n\ncontent");

// Format a file (charset is auto-detected and preserved)
Path path = Path.of("my.adoc");
byte[] formattedBytes = formatter.format(Files.readAllBytes(path));

// Format a stream
try (InputStream in = Files.newInputStream(path);
OutputStream out = Files.newOutputStream(path)) {
formatter.format(in, out);
}
```

### CLI

```
Usage: adocfmt [-chVw] [-cbl[=]] [-ehlb[=]]
[-esd[=]] [-nbd[=]] [-nlb[=]]
[-nolm[=]] [-nsh[=]] [-ols[=]]
[-rthe[=]] [-rtrw[=]] [-tc[=]]
[...]
```

| Option | Default | Description |
|---------------------------------------------|---------|---------------------------------------------------------------------------|
| `-w`, `--write` | -- | Write formatted content back to files |
| `-c`, `--check` | -- | Check formatting without modifying files; exit 1 if any file would change |
| `-nsh`, `--normalize-setext-headings` | `true` | Convert setext headings to ATX |
| `-cbl`, `--collapse-blank-lines` | `true` | Collapse consecutive blank lines |
| `-ols`, `--one-sentence-per-line` | `true` | One sentence per line |
| `-nbd`, `--normalize-block-delimiters` | `true` | Normalise block delimiters to four characters |
| `-rthe`, `--remove-trailing-header-equals` | `true` | Remove trailing `=` from headings |
| `-rtrw`, `--remove-trailing-whitespace` | `true` | Remove trailing whitespace |
| `-ehlb`, `--ensure-heading-blank-lines` | `true` | Ensure blank lines around headings |
| `-tc`, `--title-case` | `false` | Apply title case to headings |
| `-nlb`, `--normalize-list-bullets` | `false` | Normalise list bullets to `*` |
| `-nolm`, `--normalize-ordered-list-markers` | `false` | Replace explicit numbers with `.` |
| `-esd`, `--ensure-source-delimiters` | `false` | Add missing `----` delimiters around source blocks |

#### Exit Codes

| Code | Meaning |
|------|------------------------------------------------------------------|
| `0` | Success -- all files processed without error |
| `1` | Unformatted files detected (only when `--check` is active) |
| `2` | Processing error (file not found, not readable, I/O error, etc.) |

#### Examples

```bash
# Format a single file in-place
adocfmt -w my.adoc

# Format multiple files in-place
adocfmt -w docs/getting-started.adoc docs/reference.adoc

# Format all .adoc files in a directory tree
find . -name "*.adoc" | xargs adocfmt -w

# Preview formatted output without modifying the file (single file → stdout)
adocfmt my.adoc

# Format stdin to stdout
cat my.adoc | adocfmt

# Check formatting in CI -- exits 1 if any file would change
adocfmt --check docs/*.adoc

# Check all .adoc files in a directory tree in CI
find . -name "*.adoc" | xargs adocfmt --check

# Check only the staged .adoc files (useful in a pre-commit hook)
git diff --cached --name-only --diff-filter=ACM | grep '\.adoc$' | xargs adocfmt --check

# Disable a transformation that is on by default
adocfmt -w --one-sentence-per-line=false my.adoc

# Disable multiple default transformations
adocfmt -w -ols=false -cbl=false my.adoc

# Enable optional transformations
adocfmt -w --title-case --normalize-list-bullets my.adoc

# Enable optional transformations using short aliases
adocfmt -w -tc -nlb -nolm -esd my.adoc

# Enable all optional transformations and disable one default
adocfmt -w -tc -nlb -nolm -esd -ols=false my.adoc
```

## pre-commit

adocfmt can be used as a [pre-commit](https://pre-commit.com/) hook.
Add the following to your `.pre-commit-config.yaml`:

```yaml
repos:
- repo: https://github.com/dheid/adocfmt
rev: v0.2.0
hooks:
- id: adocfmt # formats .adoc/.asciidoc files in place
- id: adocfmt-check # CI mode: fails if any file would change
```

Use `adocfmt` in a local developer workflow (rewrites files before the commit is
recorded) and `adocfmt-check` in CI (read-only, exits 1 if any file is not
formatted).

**Requirement:** Java 17 or later must be on your `PATH`.
The hook downloads `adocfmt.jar` from the GitHub release on first use and caches
it in `~/.cache/adocfmt/` (or `$XDG_CACHE_HOME/adocfmt/`).
Set the `ADOCFMT_JAR` environment variable to point at a local jar to skip the
download entirely.

## GitHub Actions

adocfmt is available as a reusable composite GitHub Action.
Java is set up automatically — no pre-installed runtime is required in the
calling workflow.

### Check mode (gate a PR)

Fails the job when any `.adoc` or `.asciidoc` file is not formatted.
Directories are walked recursively.

```yaml
steps:
- uses: actions/checkout@v6
- uses: dheid/adocfmt@v0.2.0
with:
mode: check # default — can be omitted
paths: . # default — whole repo
```

### Write mode (auto-fix)

Formats files in place and leaves the working tree changed.
The action does not commit or open a PR; use a separate step for that,
for example [stefanzweifel/git-auto-commit-action](https://github.com/stefanzweifel/git-auto-commit-action)
or [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request).

```yaml
steps:
- uses: actions/checkout@v6
- uses: dheid/adocfmt@v0.2.0
with:
mode: write
paths: docs # limit to a subdirectory
# working tree now contains any formatting changes — commit or PR here
```

### Inputs

| Input | Default | Description |
|-----------|---------|--------------------------------------------------------------------------|
| `mode` | `check` | `check` or `write` |
| `paths` | `.` | Space-separated files or directories; directories are walked recursively |
| `version` | `0.2.0` | adocfmt version to download; override to pin to a specific release |

**Notes:**

- Java 17 is set up via [actions/setup-java](https://github.com/actions/setup-java) (Temurin distribution) as part of the action.
- Only `.adoc` and `.asciidoc` files are selected during directory traversal. Explicitly listed file paths are always processed regardless of extension.
- The action downloads the adocfmt jar from the GitHub release on first use. Subsequent runs in the same job reuse the cached jar.

## Development

- **Layout:** `adocfmt` (core library) | `adocfmt-cli` (shaded JAR)
- **Build:** `mvn verify`
- **Versioning:** Semantic. Any change to formatting output is at least a minor update as it affects `--check` builds.

## Project Info

- **License:** [Apache License, Version 2.0](LICENSE)
- **Author:** Daniel Heid
- **Contributing:** [CONTRIBUTING.md](CONTRIBUTING.md)
- **Changelog:** [CHANGELOG.md](CHANGELOG.md)
- **Code of Conduct:** [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md)