Command-Line Interface¶
disarm provides a command-line tool for transliteration, slugification, normalization, and text processing. It reads from arguments or stdin and writes to stdout, making it composable with other Unix tools.
Installation¶
pip install disarm
After installation, the disarm command is available:
disarm t "café"
# cafe
You can also run it as a Python module:
python -m disarm t "café"
Commands¶
Every command has a short alias for faster typing in pipelines.
| Command | Alias | Description |
|---|---|---|
transliterate |
t |
Convert Unicode text to ASCII |
slugify |
s |
Generate URL-safe slugs |
normalize |
n |
Apply Unicode normalization |
pipeline |
p |
Run multi-step text processing |
demojize |
d |
Expand emoji to text descriptions |
transliterate (t)¶
Convert Unicode text to ASCII using language-aware transliteration tables.
disarm t "café résumé"
# cafe resume
disarm t "Москва"
# Moskva
disarm t "北京市"
# bei jing shi
Options:
--lang CODE- Apply language-specific transliteration rules. Use
autofor script-based detection.
disarm t --lang de "Ärger über Ölförderung"
# Aerger ueber Oelfoerderung
disarm t --lang auto "Москва"
# Moskva
--target CODE- Reverse transliteration — convert romanized Latin text back to a native script. Mutually exclusive with
--lang.
disarm t --target ru "Moskva"
# Москва
disarm t --target el "Athina"
# Αθηνα
--tones- Include tone marks in Chinese pinyin output.
disarm t --tones "北京"
# běi jīng
--strict-iso9- Use the scholarly ASCII (ISO 9-style) transliteration for Cyrillic. NOTE: ASCII digraphs (zh/ch/sh), not the diacritic ISO 9:1995 standard.
disarm t --strict-iso9 "Юрий"
# Ûrij
--gost7034- Use GOST R 7.0.34 transliteration for Cyrillic.
slugify (s)¶
Generate URL-safe slugs from Unicode text.
disarm s "Hello, World!"
# hello-world
disarm s "Ärger im Büro"
# arger-im-buro
disarm s --lang de "Ärger im Büro"
# aerger-im-buero
Options:
--lang CODE- Language-specific transliteration before slugification.
--separator CHAR- Separator character (default:
-).
disarm s --separator "_" "Hello World"
# hello_world
--max-length N- Maximum slug length.
disarm s --max-length 10 "A very long blog post title"
# a-very-lon
normalize (n)¶
Apply Unicode normalization.
disarm n "café"
# café (NFC — composed form, the default)
disarm n --form NFKC "fi"
# fi
disarm n --form NFD "é"
# é (two codepoints: e + combining acute accent)
Options:
--form {NFC,NFD,NFKC,NFKD}- Normalization form (default:
NFC).
pipeline (p)¶
Run multiple processing steps in a single pass.
disarm p --steps "normalize,fold_case,transliterate" "Héllo WÖRLD"
# hello world
disarm p --steps "normalize,strip_accents,fold_case" "Café Résumé"
# cafe resume
Options:
--steps STEPS- Comma-separated list of processing steps (required).
Available steps: normalize, transliterate, fold_case, collapse_whitespace, strip_accents, confusables, strip_control, strip_zero_width, demojize.
--form FORM- Normalization form when using the
normalizestep.
demojize (d)¶
Expand emoji to their text descriptions.
disarm d "Hello 😀 World 🌍"
# Hello grinning face World globe showing Europe-Africa
scan (sc)¶
Walk files and directories and report every anomaly inspect_anomalies finds, located by
line and column.
disarm scan src/
# src/auth.py:41:17: bidi: "user\u202egpj.exe" contains the bidi override U+202E
# src/i18n.py:3:1: invisible: "ad\u200bmin" contains U+200B ZERO WIDTH SPACE
# scanned 212 file(s), 2 finding(s)
disarm scan . --fail # exit 1 if anything is found — for CI
disarm scan src/ --json # machine-readable, with line and column
inspect_anomalies has always returned everything a scanner needs — a kind, a span, evidence
and a plain-language reason — and until #704 there was no way to point it at a file.
What the walk does, and does not do:
- Respects git's ignore rules, all three sources. git reads
.gitignorein the scanned directory and every parent up to the repository root,.git/info/exclude, and the globalcore.excludesFile. A scanner that reads only the nearest file gives different answers fordisarm scan src/anddisarm scan .on one tree. disarm asksgit check-ignorerather than reimplementing the rule, so the two cannot disagree.--no-gitignoreturns it off; outside a repository there is nothing to ask and the scan simply has no ignore rules. - Skips directories that hold no hand-written source —
node_modules,__pycache__,.venv,.terraformand the like.build,dist,out,target,binandvendorare not skipped: they are generated in some projects and hand-written in others, and a scanner that skips them by name reports clean on a tree it never read. - Never follows symlinks, so a scan stays inside the tree it was pointed at.
- Skips binary and non-UTF-8 files silently. They are not errors; they are not text.
Options:
--json- Emit
{"findings": [...], "scanned": N, "unreadable": [...]}. Each finding carriespath,line,column,kind,reasonandtoken.lineandcolumnare 1-based, andcolumncounts characters — what an editor's gutter shows — not the byte offsets the library reports. --fail- Exit
1when anything is found. Without it a scan with findings still exits0, so the command can be used to look without gating. --no-gitignore- Scan everything under the paths, ignoring git's rules.
Exit codes — something found is not something failed to read, and the codes keep them apart:
| Code | Meaning |
|---|---|
| 0 | Scanned; nothing found, or found without --fail |
| 1 | Findings, with --fail |
| 2 | Invalid arguments (argparse) |
| 3 | A path could not be read — reported on stderr, scan of the rest still printed |
Piping and stdin¶
All commands accept input from stdin when no positional argument is given. This makes disarm composable with other tools:
# Process a file
cat names.txt | disarm t
# Chain with other commands
echo "Ünïcödé Tëxt" | disarm t
# Unicode Text
# Slugify each line of a file
while IFS= read -r line; do
echo "$line" | disarm s
done < titles.txt
# Use with xargs
cat words.txt | xargs -I{} disarm t "{}"
# Combine with sort/uniq for deduplication
cat entries.txt | disarm t | sort -u
Exit codes¶
| Code | Meaning |
|---|---|
| 0 | Success |
| 1 | No input provided (no argument and no stdin) |
| 2 | Invalid arguments (unknown command, bad option) |
| 3 | scan only: a path could not be read (see scan) |