Colin's LaTeX Tidy — somewhere between a clang and a splat.
📖 Documentation
A configurable LaTeX source formatter with opinions.
Every rule has a weight; you set a threshold.
A weight of 0 disables a rule. What happens otherwise depends on the
combination:
- clang -- weight >= threshold and fixable: auto-fixed
- clunk -- weight >= threshold and not fixable: needs your attention
- splat -- 0 < weight < threshold: advisory, take it or leave it
- off -- weight <= 0: disabled
Prosaically, clat is Colin's LaTeX Tool. But the name also has a bit of
printer's noise in it.
In the "Aeolus" episode of Ulysses -- "How a great daily organ is turned out" -- Bloom moves through the newspaper office amid the machinery of print: clanking, rhythmic, three-four time; thump, thump, thump. My great-great-grandfather, a Dublin printer, is mentioned there too, though some editions mangle Caprani as Cuprani.
So clat is meant to sound a little like that composing-room racket: a small,
opinionated machine for turning untidy copy into clean type.
The usual LaTeX formatting tools — latexindent, tex-fmt, prettier-plugin-latex — are excellent at indentation and line wrapping. They normalise whitespace, align tables, and keep columns tidy.
clat operates at a different layer. It doesn't touch indentation. Instead, it applies content-aware style rules that those tools don't cover:
| What you need | Tool |
|---|---|
| Indentation, line wrapping, alignment | latexindent, tex-fmt |
| Style conventions and writing hygiene | clat |
For example, clat can:
- Merge stray
\labels onto their\sectionlines - Split prose to one sentence per line
- Replace
{\bf text}with\textbf{text} - Configure inline and display math delimiter preferences separately
- Ensure
~before\ref,\cite, and similar commands - Replace
...with\dots - Normalise number-unit spacing (
100\,kN) - Keep table row endings (
\\) with row content and horizontal rules on their own lines - Detect hardcoded references (
Figure 3instead of\cref{...}) - Add trailing punctuation to display equations
- Convert superscript ordinals (
1\textsuperscript{st}→1st) - And more
None of these are available in latexindent, tex-fmt, or prettier-plugin-latex. clat is complementary — you can (and should) run both. A typical pipeline:
tex-fmt main.tex # indentation, line wrapping
clat main.tex # style rulesThe weight/threshold system (clang/clunk/splat/off) also gives you granular control over which rules auto-fix, which flag for manual review, which are just advisory noise, and which are disabled — something the existing tools don't offer.
pip install clat-tidy
The command is clat. (The distribution is published as clat-tidy because
clat was already taken on PyPI; everything you type is still clat.)
For local development with conda:
conda env create -f environment.yml
conda activate clat
Or install into an existing environment:
pip install -e '.[docs]'
Requires Python >= 3.8. On Python < 3.11, tomli is installed automatically.
# Format files in place
clat main.tex appendix.tex
# Format a multi-file document by following LaTeX inputs/includes
clat -r main.tex
# Dry run (report without fixing)
clat --check main.tex
# Dry run a multi-file document
clat --check -r main.tex
# List all rules
clat list
# Override threshold for one run
clat --threshold 3 main.tex
# Limit formatting to a single pass instead of fixed-point sweeps
clat --max-iter 1 main.texRun clat list to see all rules with their numbers, weights, and current category.
| # | Rule | Default | Fixable | Description |
|---|---|---|---|---|
| 1 | labels_inline | 8 | yes | Merge \label onto the same line as \section |
| 2 | decorative_comments | 6 | yes | Strip decorative comment separators (%%=== etc.) |
| 3 | heading_spacing | 7 | yes | Two blank lines before headings, none after |
| 4 | equation_separators | 7 | yes | Insert % lines around display-math environments |
| 5 | equation_punctuation | 6 | yes | Add trailing comma or period to display equations |
| 6 | float_indentation | 5 | yes | Tab-indent content inside figure/table/list environments |
| 7 | one_sentence_per_line | 8 | yes | Split sentences onto individual lines |
| 8 | math_delimiters_inline | 5 | yes | Replace \(...\) with $...$ |
| 9 | math_delimiters_display | 0 | yes | Replace \[...\] with $$...$$ |
| 10 | math_delimiters_equation | 0 | yes | Replace \[...\] or $$...$$ with equation |
| 11 | tilde_before_refs | 7 | yes | Ensure ~ before \ref, \cite etc. |
| 12 | number_unit_spacing | 6 | yes | Normalise number-unit spacing (100\,kN) |
| 13 | old_font_commands | 5 | yes | Replace {\bf text} with \textbf{text} etc. |
| 14 | ellipsis | 4 | yes | Replace ... with \dots |
| 15 | ordinal_suffixes | 8 | yes | Convert superscript ordinals to plain text (1st, 2nd) |
| 16 | table_line_endings | 7 | yes | Keep table \\ on row lines and rules on own lines |
| 17 | abbreviation_spacing | 7 | yes | Force interword space after e.g., i.e., et al. |
| 18 | long_file | 3 | no | Warn if file exceeds 2000 lines |
| 19 | hardcoded_refs | 6 | no | Detect Figure 3 instead of \cref{...} |
| 20 | manual_sizing | 3 | no | Detect \big, \Big etc. |
| 21 | float_after_heading | 4 | no | Detect float placed directly after a heading |
Config is read from .clat.toml in the project directory, falling back to
~/.config/clat/config.toml.
clat set --initThis generates .clat.toml with all rules and their default weights:
threshold = 5
# Contents of these environments are left untouched by every rule.
protected_environments = ["tikzpicture", "pgfpicture", "axis", "tikzcd"]
# Rule ids listed here still run inside protected environments.
unprotected_rules = []
[weights]
labels_inline = 8 # Merge \label onto the same line as \section (fixable)
decorative_comments = 6 # Strip decorative comment separators (fixable)
heading_spacing = 7 # Two blank lines before headings, none after (fixable)
# ... all 21 rules listedclat masks picture/plot environments — TikZ, pgfplots, tikz-cd by default — out
before any rule runs and restores them verbatim, so their contents are never
reformatted. Adjust protected_environments to add or remove environments (set
it to [] to disable), or list a rule id in unprotected_rules to let just
that rule run inside them. See the configuration docs.
Edit the file directly, or use the CLI:
clat set --threshold 8# Prefer rule ids for scripts/configuration
clat set ellipsis 9 # set ellipsis to weight 9
clat set hardcoded_refs 2 # demote hardcoded refs to a splat
clat set math_delimiters_display 5 # enable display math delimiter conversion
clat set math_delimiters_equation 5 # convert display delimiters to equation
# List numbers are also accepted for interactive use
clat set 14 9clat set --resetOverride threshold for a single run without modifying the config:
clat --threshold 3 main.texclat <files...> Format .tex files in place
clat -r <root.tex...> Recursively format LaTeX inputs/includes
clat --recursive <root.tex> Long form of -r
clat --check <files...> Dry run: report issues without fixing
clat --check -r <root.tex> Dry run a multi-file document
clat -o out.tex in.tex Write to a different file
clat --threshold N <files> Override threshold for this run
clat --max-iter N <files> Maximum fixable-rule sweeps (default 5)
clat list List all rules with weights and categories
clat list --config path Use a specific config file
clat set <rule-id|rule#> <weight>
Set a rule weight in .clat.toml (0 disables)
clat set --threshold N Set the threshold in .clat.toml
clat set --init Create .clat.toml with defaults
clat set --reset Restore .clat.toml to defaults
clat set --config path Target a specific config file
clat --version Show version
Use -r or --recursive when a root .tex file assembles a document from
other source files:
clat -r main.texRecursive mode follows \input, \include, \subfile, \import,
\subimport, \includefrom, and \subincludefrom commands. Paths are
resolved relative to the file containing the command, .tex is appended when
no extension is given, duplicate files are skipped, and missing .tex inputs
are reported through the normal formatter output.
Each rule is either fixable (clat can rewrite the source) or unfixable
(clat can only detect the issue). clat applies enabled fixable rules repeatedly
until a full sweep makes no text changes, up to --max-iter sweeps (default
5). Then detect-only rules run once on the final text. At runtime, each rule's
weight is compared to the threshold:
weight >= threshold + fixable => clang (auto-fixed)
weight >= threshold + NOT fixable => clunk (needs manual fix)
0 < weight < threshold => splat (advisory)
weight <= 0 => off (disabled)
Fixable rules below threshold are still applied (the text is still fixed),
but reported as splats rather than clangs. Set a rule's weight to 0 to
disable it entirely. Use --max-iter 1 for single-pass behaviour.
pip install -e .
python -m pytest tests/ -vMIT