Clojure binding to ruff (https://github.com/astral-sh/ruff) for Python code FORMATTING and LINTING, through the JDK Foreign Function & Memory API.
ruff does not publish a C-ABI library — only a CLI. So this binds a tiny
first-party cdylib, ruff-c (native/ruff-c, a thin extern "C" wrapper over
ruff's ruff_python_formatter + ruff_linter crates), exactly the way
clj-fff binds fff-c. Both run IN-PROCESS via a downcall — no subprocess, no
CLI, no config discovery. format takes Python source and returns it
reformatted (long calls/collections wrapped multiline, black-compatible
style); lint returns ruff's diagnostics as plain Clojure maps.
Run the JVM with --enable-native-access=ALL-UNNAMED so the foreign linker
may load the library without a restricted-method warning.
The library is resolved ONCE, lazily, the first time it's needed:
com.blockether.ruff.native.path system property
— an explicit path to the cdylib (used verbatim).prebuilds/<platform>/<lib>, shipped by
com.blockether/ruff-native-<platform> (extracted to the cache dir).
This is the native-image path.ruff-native-<platform> jar resolved through
clojure.tools.deps (honouring Maven repos/mirrors/settings.xml),
extracted + cached. Disable with RUFF_DISABLE_DOWNLOAD=1.<platform> ∈ { linux-x64 linux-arm64 darwin-arm64 darwin-x64 windows-x64 }.
Clojure binding to ruff (https://github.com/astral-sh/ruff) for Python code
FORMATTING and LINTING, through the JDK Foreign Function & Memory API.
ruff does not publish a C-ABI library — only a CLI. So this binds a tiny
first-party cdylib, `ruff-c` (native/ruff-c, a thin `extern "C"` wrapper over
ruff's `ruff_python_formatter` + `ruff_linter` crates), exactly the way
clj-fff binds `fff-c`. Both run IN-PROCESS via a downcall — no subprocess, no
CLI, no config discovery. `format` takes Python source and returns it
reformatted (long calls/collections wrapped multiline, black-compatible
style); `lint` returns ruff's diagnostics as plain Clojure maps.
Run the JVM with `--enable-native-access=ALL-UNNAMED` so the foreign linker
may load the library without a restricted-method warning.
The library is resolved ONCE, lazily, the first time it's needed:
1. RUFF_NATIVE_PATH env / `com.blockether.ruff.native.path` system property
— an explicit path to the cdylib (used verbatim).
2. A bundled classpath resource `prebuilds/<platform>/<lib>`, shipped by
`com.blockether/ruff-native-<platform>` (extracted to the cache dir).
This is the native-image path.
3. A runtime download: the `ruff-native-<platform>` jar resolved through
`clojure.tools.deps` (honouring Maven repos/mirrors/settings.xml),
extracted + cached. Disable with RUFF_DISABLE_DOWNLOAD=1.
`<platform>` ∈ { linux-x64 linux-arm64 darwin-arm64 darwin-x64 windows-x64 }.(available?)True if the ruff native library can be resolved + linked on this platform.
True if the ruff native library can be resolved + linked on this platform.
(format code)(format code {:keys [line-length]})Format Python code and return the reformatted source as a String.
Options: :line-length int — wrap width (0 / omitted => ruff default 88).
Runs entirely in-process (no subprocess, no pyproject.toml / ruff.toml
discovery): output depends only on code and :line-length. Throws ex-info
when ruff can't format the input (syntactically invalid Python); callers that
want a verbatim fallback should use format-or or catch.
Format Python `code` and return the reformatted source as a String. Options: :line-length int — wrap width (0 / omitted => ruff default 88). Runs entirely in-process (no subprocess, no pyproject.toml / ruff.toml discovery): output depends only on `code` and `:line-length`. Throws ex-info when ruff can't format the input (syntactically invalid Python); callers that want a verbatim fallback should use `format-or` or catch.
(format-or code)(format-or code opts)Like format, but returns code unchanged if ruff is unavailable or fails
(the convenient display-side default — never lose the original source).
Like `format`, but returns `code` unchanged if ruff is unavailable or fails (the convenient display-side default — never lose the original source).
(lint code)(lint code {:keys [line-length select ignore preview]})Lint Python code with ruff and return a vector of diagnostic maps, in ruff's
own order:
{:code "F401" :message "os imported but unused"
:row 1 :col 1 :end-row 1 :end-col 10 :is-fixable true}
Rows and columns are 1-based; :end-row/:end-col are exclusive; :code is
the noqa code (F401) when the rule has one, else the rule id.
Options: :line-length int — width for line-length rules (0/omitted => 88). :select selectors — REPLACE the default rule set ("F,E501", :F401, ["F" "B"]…). Omitted => ruff's default selection (E4, E7, E9, F …). :ignore selectors — disable these on top of the selection. :preview boolean — also run preview rules.
Runs entirely in-process; no # noqa-file discovery and no pyproject.toml /
ruff.toml lookup, so the result depends only on code and these options
(inline # noqa comments in the source ARE honoured). Throws ex-info when
ruff cannot lint the input (unknown selector, or unparsable source); use
lint-or for the never-throw variant.
Lint Python `code` with ruff and return a vector of diagnostic maps, in ruff's
own order:
{:code "F401" :message "`os` imported but unused"
:row 1 :col 1 :end-row 1 :end-col 10 :is-fixable true}
Rows and columns are 1-based; `:end-row`/`:end-col` are exclusive; `:code` is
the noqa code (`F401`) when the rule has one, else the rule id.
Options:
:line-length int — width for line-length rules (0/omitted => 88).
:select selectors — REPLACE the default rule set ("F,E501",
:F401, ["F" "B"]…). Omitted => ruff's
default selection (E4, E7, E9, F …).
:ignore selectors — disable these on top of the selection.
:preview boolean — also run preview rules.
Runs entirely in-process; no `# noqa`-file discovery and no `pyproject.toml` /
`ruff.toml` lookup, so the result depends only on `code` and these options
(inline `# noqa` comments in the source ARE honoured). Throws ex-info when
ruff cannot lint the input (unknown selector, or unparsable source); use
`lint-or` for the never-throw variant.(lint-or code)(lint-or code opts)(lint-or code opts default)Like lint, but returns default (nil unless given) if ruff is unavailable
or fails — the display-side variant that never throws.
Like `lint`, but returns `default` (nil unless given) if ruff is unavailable or fails — the display-side variant that never throws.
(version)The bundled ruff release string (clj-ruff-cdylib (ruff X.Y.Z)).
The bundled ruff release string (`clj-ruff-cdylib (ruff X.Y.Z)`).
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |