Clojure binding to ruff for Python code formatting and linting, through the JDK Foreign Function & Memory API (FFM).
ruff ships a CLI, not a C-ABI library — so clj-ruff binds a tiny first-party
cdylib (native/ruff-c, a thin extern "C" wrapper over ruff's
ruff_python_formatter and ruff_linter crates) and calls it in-process
via a downcall. No subprocess, no CLI, no pyproject.toml / ruff.toml
discovery: output depends only on the source and the options you pass. This mirrors how
clj-fff and
rift-clojure bind their native
libraries.
;; deps.edn
com.blockether/ruff {:mvn/version "RELEASE"}
Run the JVM with --enable-native-access=ALL-UNNAMED so the foreign linker can
load the library without a restricted-method warning.
The native library is resolved lazily on first use:
RUFF_NATIVE_PATH (env) / -Dcom.blockether.ruff.native.path — explicit path
to the cdylib.com.blockether/ruff-native-<platform>
(the native-image path).ruff-native-<platform> via clojure.tools.deps
(honours your Maven repos/mirrors/settings.xml). Disable with
RUFF_DISABLE_DOWNLOAD=1.<platform> ∈ linux-x64 linux-arm64 darwin-arm64 darwin-x64 windows-x64.
For a slim deploy, also add the one native artifact for your target, e.g.
com.blockether/ruff-native-darwin-arm64 {:mvn/version "RELEASE"}.
(require '[com.blockether.ruff :as ruff])
(ruff/format "result=some_function(a_long_argument, another_long_argument, and_one_more, keyword=value)"
{:line-length 60})
;; =>
;; result = some_function(
;; a_long_argument,
;; another_long_argument,
;; and_one_more,
;; keyword=value,
;; )
(ruff/format "x={'a':1,'b':2}") ; => "x = {\"a\": 1, \"b\": 2}\n" (ruff default width 88)
(ruff/format-or "def (((broken" {}) ; => "def (((broken" (verbatim fallback; never throws)
(ruff/version) ; => "0.2.0 (ruff 0.16.0)"
(ruff/available?) ; => true
format throws ex-info on syntactically invalid Python; format-or returns
the input unchanged instead — the convenient display-side default.
| key | meaning |
|---|---|
:line-length | wrap width (0 / omitted → ruff default 88) |
(ruff/lint "import os\nx = 1\n")
;; => [{:code "F401" :message "`os` imported but unused"
;; :row 1 :col 8 :end-row 1 :end-col 10 :is-fixable true}]
(ruff/lint "if x == None: pass" {:select "E711"})
(ruff/lint code {:select ["F" "E7"] :ignore "F401" :line-length 120 :preview true})
(ruff/lint-or code {} []) ; never throws; returns the default on failure
Rows/columns are 1-based, :end-row/:end-col exclusive, :code is the
noqa code (F401) when the rule has one and otherwise the rule id. With no
:select you get ruff's own default rule set; :select replaces it and
:ignore subtracts from whatever is selected. Inline # noqa comments in the
source are honoured; no config file is ever read. Syntax errors come back as
ordinary diagnostics with code invalid-syntax (they do not throw) — an
unknown rule selector does throw, and lint-or swallows it.
| key | meaning |
|---|---|
:select | replace the default rule set — "F,E501", :F401, ["F" "B"], "ALL" |
:ignore | disable these selectors on top of the selection |
:line-length | limit for E501 / wrap-width rules (0 / omitted → 88) |
:preview | also run ruff's preview rules |
This jar ships META-INF/native-image/com.blockether/ruff/ (FFM downcalls +
the prebuilds/** resource glob), auto-applied on the native-image classpath.
Bundle the matching ruff-native-<platform> jar (so the cdylib is embedded) or
set RUFF_NATIVE_PATH at run time. The binding uses Arena.ofAuto /
Arena.ofConfined (never Arena.ofShared), so it is safe inside an image that
also embeds GraalPy/Truffle.
# build the cdylib for this host and stage it
scripts/build-natives.sh darwin-arm64
clojure -X:test # run tests against the staged cdylib
clojure -T:build jar # main jar
clojure -T:build native-jar :platform darwin-arm64 # per-platform native jar
The bundled ruff release is pinned in native/ruff-c/Cargo.toml. clj-ruff's own
(Clojars) version in resources/VERSION is independent of it.
MIT.
Can you improve this documentation?Edit on GitHub
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 |