Polyglot AST subtree DRY analyzer. Detects duplicate code across files using Jaccard similarity over normalized AST fingerprints.
A Rust port of unclebob/dry4go, extended to work across Rust, JavaScript, TypeScript, and Python (dry4go is Go-only).
# Rust
cargo install drywall
# Python
pip install drywall
# or, without installing:
uvx drywall ./src
# Node.js
npm install --save-dev drywall-cli
# then: npx drywall ./src, or add a package.json scriptOr download the binary for your platform from the latest release:
| Platform | Binary |
|---|---|
| Linux x86_64 | drywall-linux-x86_64 |
| macOS Apple Silicon | drywall-macos-aarch64 |
| macOS Intel | drywall-macos-x86_64 |
# Linux
curl -sL https://github.com/gabadi/drywall/releases/latest/download/drywall-linux-x86_64 -o drywall
chmod +x ./drywall
# macOS Apple Silicon
curl -sL https://github.com/gabadi/drywall/releases/latest/download/drywall-macos-aarch64 -o drywall
chmod +x ./drywall
# macOS Intel
curl -sL https://github.com/gabadi/drywall/releases/latest/download/drywall-macos-x86_64 -o drywall
chmod +x ./drywallThe Linux binary is statically linked (musl) — no runtime dependencies.
drywall [--threshold 0.82] ./src--threshold is the Jaccard similarity cutoff (default 0.82, same as dry4go). Pairs at or above the threshold are reported. Exit codes: 0 = no duplicates, 1 = duplicates found, 2 = bad arguments.
Add to any project's CI pipeline:
- name: Install drywall
run: |
curl -sL https://github.com/gabadi/drywall/releases/latest/download/drywall-linux-x86_64 -o drywall
chmod +x ./drywall
- name: DRY check
run: ./drywall --threshold 0.82 ./src--threshold sets the Jaccard similarity cutoff (default 0.82). Pairs at or above the threshold are flagged. The step fails (exit 1) if any duplicates are found.
Requires Rust 1.96+:
cargo build --release
./target/release/drywall ./src- Bump the version in
Cargo.toml. - Commit, tag, and push:
git commit -am "chore: release vX.Y.Z" git tag vX.Y.Z git push origin main git push origin vX.Y.Z - The
releaseworkflow triggers on the tag push: it builds the platform binaries, creates the GitHub Release, and publishes to crates.io and PyPI automatically (crates.io viaCARGO_REGISTRY_TOKEN, PyPI via trusted publishing). - Publish to npm manually (not automated — this account requires a
one-time password on every publish, which CI can't provide):
npm will prompt for an OTP once per package (4 packages total).
./scripts/publish-npm.sh vX.Y.Z