# ErlAlign
[](https://github.com/saleyn/erlalign/actions/workflows/ci.yml)
[](https://hex.pm/packages/erlalign)
A column-aligning code formatter for Erlang source code, inspired by Go's `gofmt`. Works as a post-processor on top of the `erlfmt` formatter.
## What it does
ErlAlign scans consecutive lines that share the same indentation and pattern type, then pads them so their operators and values line up vertically. It aligns:
- **Record field assignments** - Aligns `=` operators in record definitions
- **Variable assignments** - Aligns `=` in consecutive variable declarations
- **Case/if arrows** - Aligns `->` operators in case and if expressions
- **Function guards** - Aligns guard clauses
## Features
### Record field alignment
```erlang
% before (erlfmt output)
User = #user{
name = <<"Alice">>,
age = 30,
occupation = <<"developer">>
}.
% after (with ErlAlign)
User = #user{
name = <<"Alice">>,
age = 30,
occupation = <<"developer">>
}.
```
### Variable assignment alignment
```erlang
% before
X = 1,
Foo = <<"bar">>,
SomethingLong = 42.
% after
X = 1,
Foo = <<"bar">>,
SomethingLong = 42.
```
### Case arrow alignment
```erlang
% before
case Result of
{ok, Value} -> Value;
{error, _} = Err -> Err
end.
% after
case Result of
{ok, Value} -> Value;
{error, _} = Err -> Err
end.
```
### Documentation conversion
ErlAlign also includes `erlalign_docs` module for converting EDoc `@doc` blocks to OTP-27 `-doc` attributes:
```erlang
% before (EDoc format)
%% @doc
%% Returns the user record with the given ID.
-spec user(id()) -> {ok, user()} | {error, atom()}.
user(UserID) -> ...
% after (OTP-27 format)
-doc """
Returns the user record with the given ID.
""".
-spec user(id()) -> {ok, user()} | {error, atom()}.
user(UserID) -> ...
```
## Installation
### Requirements
- Erlang/OTP 24 or later
- rebar3 3.14+
### As a rebar3 plugin
Add erlalign to your project's `rebar.config` to use it as a rebar3 plugin:
#### From GitHub (recommended for latest)
```erlang
{plugins, [
{erlalign, {git, "https://github.com/saleyn/erlalign.git", {branch, "main"}}}
]}.
```
#### From Hex.pm (when available)
```erlang
{plugins, [
{erlalign, "0.1.0"}
]}.
```
### Using with rebar3
After adding erlalign as a plugin, you can use it in your project:
#### Format your project
```bash
# Format all Erlang files in src/
rebar3 format
# Format specific directory
rebar3 format src/
# Format specific file or files
rebar3 format src/mymodule.erl src/another.erl
# Format apps and lib directories
rebar3 format apps/ lib/
```
#### Check formatting without modifying files
```bash
# Check if files need formatting
rebar3 format --check src/
# Useful in CI/CD to fail if files aren't formatted
rebar3 format --check
```
#### Preview changes
```bash
# See what would change without writing files
rebar3 format --dry-run src/
# Also good for code review
rebar3 format --dry-run apps/myapp/src/
```
#### Advanced options
```bash
# Custom line length
rebar3 format --line-length 120 src/
# Suppress output
rebar3 format --silent src/
# Combine options
rebar3 format --line-length 100 --check src/
```
#### Converting documentation
erlalign also includes a documentation converter for converting EDoc `@doc` blocks to OTP-27 `-doc` attributes:
```bash
# Convert documentation in all files
rebar3 edoc-to-doc
# Check documentation conversion without modifying
rebar3 edoc-to-doc --check
# Preview documentation changes
rebar3 edoc-to-doc --dry-run
# Keep separator lines
rebar3 edoc-to-doc --keep-separators
# Custom line length for wrapped docs
rebar3 edoc-to-doc --line-length 100
```
### Integration tips
#### Git hooks (pre-commit)
Add to your `.git/hooks/pre-commit`:
```bash
#!/bin/bash
set -e
# Check formatting before commit
rebar3 format --check
```
Make it executable:
```bash
chmod +x .git/hooks/pre-commit
```
#### GitHub Actions CI
Add to your `.github/workflows/ci.yml`:
```yaml
- name: Check formatting
run: rebar3 format --check
```
#### Gitlab CI
Add to your `.gitlab-ci.yml`:
```yaml
format_check:
script:
- rebar3 format --check
```
#### Combine with erlfmt
For maximum code cleanliness, combine erlalign with erlfmt:
```bash
# First format with erlfmt (basic formatting)
rebar3 fmt
# Then align with erlalign (column alignment)
rebar3 format
```
#### Creating a Makefile target
Add to your `Makefile`:
```makefile
.PHONY: format fmt check-fmt
fmt: format
format:
rebar3 format
check-fmt:
rebar3 format --check
```
Then use:
```bash
make format # Format code
make check-fmt # Check formatting
```
#### Configuration per project
Create a global config file at `~/.config/erlalign/.formatter.exs`:
```erlang
[
{line_length, 100},
{trim_eol_ws, true},
{eol_at_eof, off}
].
```
This configuration will be used automatically by all projects using erlalign as a plugin or binary.
## Usage
### Formatting with rebar3
```bash
# Format all Erlang files in src/
rebar3 format
# Format specific directory
rebar3 format src/
# Use custom line length
rebar3 format --line-length 120
# Check mode (fail if formatting needed)
rebar3 format --check src/
# Dry run (preview changes)
rebar3 format --dry-run src/mymodule.erl
```
#### Options
| Flag | Default | Description |
|---|---|---|
| `--line-length N` | `98` | Maximum line length for alignment decisions |
| `--check` | off | Exit with error if any file would be changed |
| `--dry-run` | off | Print what would be changed without modifying files |
| `-s, --silent` | off | Suppress output |
| `-h, --help` | | Show help message |
### Converting documentation with rebar3
```bash
# Convert all EDoc @doc blocks to -doc attributes
rebar3 edoc-to-doc
# Custom line length for wrapped docs
rebar3 edoc-to-doc --line-length 100
# Keep separator lines (don't remove %%----)
rebar3 edoc-to-doc --keep-separators
# Check mode
rebar3 edoc-to-doc --check src/
# Dry run
rebar3 edoc-to-doc --dry-run src/
```
#### Options
| Flag | Default | Description |
|---|---|---|
| `--line-length N` | `80` | Line wrap width for formatted docs |
| `--keep-separators` | off | Preserve `%%----` separator lines (removed by default) |
| `--check` | off | Check mode - fail if files would change |
| `--dry-run` | off | Preview changes without writing |
| `-s, --silent` | off | Suppress output |
| `-h, --help` | | Show help message |
### Command-line binary
Build the standalone erlalign binary:
```bash
# Build binary with make
make escriptize
# Or with rebar3
rebar3 escriptize
```
The binary will be located at `_build/default/bin/erlalign`.
#### Using the binary
Format Erlang files directly from the command line:
```bash
# Format a single file (modifies in place)
erlalign src/mymodule.erl
# Format multiple files
erlalign src/ lib/ test/
# Check formatting without modifying
erlalign --check src/
# Dry run (preview changes)
erlalign --dry-run src/mymodule.erl
# Trim trailing whitespace from end of lines (default)
erlalign --trim-eol-ws src/
# Disable trailing whitespace trimming
erlalign --no-trim-eol-ws src/
# Set line length
erlalign --line-length 120 src/
# Handle end-of-file newlines
erlalign --eol-at-eof add src/ # Add newline if missing
erlalign --eol-at-eof remove src/ # Remove trailing newline
# Convert @doc to -doc attributes (OTP 27+)
erlalign --doc src/
# Keep separator lines in documentation
erlalign --keep-separators src/
# Suppress output
erlalign --silent src/
# Show help
erlalign --help
```
#### Binary options
| Flag | Default | Description |
|---|---|---|
| `--line-length N` | `98` | Maximum line length for alignment decisions |
| `--trim-eol-ws` | on | Trim trailing whitespace from end of lines |
| `--no-trim-eol-ws` | off | Keep trailing whitespace |
| `--eol-at-eof VALUE` | off | Handle EOF newlines: `add`, `remove`, or `off` |
| `--keep-separators` | off | Preserve `%%----` separator lines in docs |
| `--doc` | off | Convert @doc to -doc attributes (OTP 27+) |
| `--check` | off | Check mode - exit with error if unchanged needed |
| `--dry-run` | off | Preview changes without writing files |
| `-s, --silent` | off | Suppress output messages |
| `-h, --help` | | Show help message |
#### Global configuration
The binary supports global configuration via `~/.config/erlalign/.formatter.exs`:
```erlang
[
{line_length, 120},
{trim_eol_ws, true},
{eol_at_eof, off}
].
```
These default values can be overridden with command-line flags.
### Programmatic usage
Use the modules directly from Erlang code:
```erlang
% Format code
{ok, Code} = file:read_file("src/mymodule.erl"),
Formatted = erlalign:format(Code, [{line_length, 100}]),
ok = file:write_file("src/mymodule.erl", Formatted).
% Convert documentation
erlalign_docs:process_file("src/mymodule.erl", [
{line_length, 100},
{remove_doc_separators, true}
]).
% Or with output to different file
erlalign_docs:process_file("src/mymodule.erl", [
{output, "src/mymodule_formatted.erl"},
{line_length, 80}
]).
```
#### Module functions
**erlalign module:**
- `format(Code)` - Format code with default options
- `format(Code, Options)` - Format code with options
- `load_global_config()` - Load global configuration from ~/.config/erlalign/.formatter.exs
**erlalign_docs module:**
- `format_code(Code)` - Convert doc blocks in code
- `format_code(Code, Options)` - Convert with options
- `process_file(Path)` - Convert a file in-place
- `process_file(Path, Options)` - Convert with options
#### Configuration
Global configuration file: `~/.config/erlalign/.formatter.exs`
```erlang
[
{line_length, 120}
].
```
## Building
```bash
rebar3 compile
```
## Testing
```bash
rebar3 eunit
```
## License
MIT License - see LICENSE file
## See Also
- **ExAlign** - Column-aligning formatter for Elixir code: https://github.com/saleyn/exalign