Skip to content

Commit

Permalink
Refactor to allow invoking check and fix as lib
Browse files Browse the repository at this point in the history
  • Loading branch information
cap10morgan committed Jan 20, 2024
1 parent 434408f commit 61841ae
Show file tree
Hide file tree
Showing 3 changed files with 169 additions and 105 deletions.
19 changes: 17 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,8 @@ project file. See the [configuration][] section for more details.

### Library

cljfmt can be run as a library that formats a string of Clojure code.
cljfmt can be run as a library that formats a string of Clojure code or
recursively checks paths like the CLI tool.
First, add the dependency:

```edn
Expand All @@ -143,14 +144,16 @@ First, add the dependency:

Then use the library:

#### Checking strings of code

```clojure
(require '[cljfmt.core :as fmt])

(fmt/reformat-string "(defn sum [x y]\n(+ x y))")
;; => "(defn sum [x y]\n (+ x y))"
```

To use load the configuration for the current directory:
To load the configuration for the current directory:

```clojure
(require '[cljfmt.config :as cfg])
Expand All @@ -159,6 +162,18 @@ To use load the configuration for the current directory:
;; => "(+ x\n y)"
```

#### Checking paths recursively

```clojure
(require '[cljfmt.lib :as fmt])

(fmt/check {:paths ["/path/to/check"]})
```

The `check` and `fix` fns in `cljfmt.lib` will load the configuration in the
current directory. If you don't want that, use `cljfmt.lib/check-no-config` or
`cljfmt.lib/fix-no-config` instead.

### Editor Integration

You can also use cljfmt via your editor. Several Clojure editing
Expand Down
128 changes: 128 additions & 0 deletions cljfmt/src/cljfmt/lib.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
(ns cljfmt.lib
(:require [cljfmt.config :as config]
[cljfmt.core :as cljfmt]
[cljfmt.diff :as diff]
[cljfmt.io :as io]
[clojure.java.io :as cio])
(:import (java.io File)))

(defn- grep [re dir]
(filter #(re-find re (io/relative-path % dir)) (io/list-files dir)))

(defn- find-files [{:keys [file-pattern]} f]
(let [f (io/file-entity f)]
(when (io/exists? f)
(if (io/directory? f)
(grep file-pattern f)
(list f)))))

(defn- reformat-string [options s]
((cljfmt/wrap-normalize-newlines #(cljfmt/reformat-string % options)) s))

(defn- project-path [{:keys [project-root]} file]
(->> (or project-root ".") cio/file (io/relative-path file)))

(defn- format-diff
([options file]
(let [original (io/read-file file)]
(format-diff options file original (reformat-string options original))))
([options file original revised]
(let [filename (project-path options file)
diff (diff/unified-diff filename original revised)]
(if (:ansi? options)
(diff/colorize-diff diff)
diff))))

(def ^:private zero-counts {:okay 0, :incorrect 0, :error 0})

(defn- check-one [{:keys [cljfmt/trace diff?] :as options
:or {trace (constantly nil), diff? true}} file]
(trace "Processing file:" (project-path options file))
(let [original (io/read-file file)
status {:counts zero-counts :file file}]
(try
(let [revised (reformat-string options original)]
(if (not= original revised)
(cond-> status
true (assoc-in [:counts :incorrect] 1)
diff? (assoc :diff (format-diff options file original revised)))
(assoc-in status [:counts :okay] 1)))
(catch Exception ex
(-> status
(assoc-in [:counts :error] 1)
(assoc :exception ex))))))

(defn- merge-counts
([] zero-counts)
([a] a)
([a b] (merge-with + a b)))

(defn- merge-statuses
[a b]
(let [^File file (:file b)
path (.getPath file)
diff (:diff b)
exception (:exception b)]
(cond-> a
true (update :counts #(merge-with + % (:counts b)))

(and file (= 1 (-> b :counts :incorrect)))
(assoc-in [:incorrect path] "has incorrect formatting")

(and file diff) (assoc-in [:incorrect path] diff)
(and file exception) (assoc-in [:error path] exception))))

(defn check-no-config
"The same as `check`, but ignores dotfile configuration."
[{:keys [cljfmt/report parallel? paths] :as options}]
(let [map* (if parallel? pmap map)
statuses (->> paths
(mapcat (partial find-files options))
(map* (partial check-one options))
(map (fn [status]
(when report
(let [path (project-path options (:file status))]
(report (assoc status :path path))))
status)))]
(if report
(reduce merge-counts (:counts statuses))
(reduce merge-statuses {} statuses))))

(defn check
"Checks that the Clojure paths specified by the :paths option are
correctly formatted."
[options]
(let [opts (merge (config/load-config) options)]
(check-no-config opts)))

(defn- fix-one [{:keys [cljfmt/trace] :as options} file]
(trace "Processing file:" (project-path options file))
(let [original (io/read-file file)]
(try
(let [revised (reformat-string options original)
changed? (not= original revised)]
(io/update-file file revised changed?)
(if changed?
{:file file :reformatted true}
{:file file}))
(catch Exception e
{:file file :exception e}))))

(defn- recursively-find-files [{:keys [paths] :as options}]
(mapcat #(find-files options %) (set paths)))

(defn fix-no-config
"The same as `fix`, but ignores dotfile configuration."
[{:keys [cljfmt/report] :as options}]
(let [files (recursively-find-files options)
map* (if (:parallel? options) pmap map)]
(->> files
(map* (partial fix-one options))
(map (partial report options))
dorun)))

(defn fix
"Fixes the formatting for all files specified by the :paths option."
[options]
(let [opts (merge (config/load-config) options)]
(fix-no-config opts)))
127 changes: 24 additions & 103 deletions cljfmt/src/cljfmt/tool.clj
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
(ns cljfmt.tool
(:require [cljfmt.config :as config]
[cljfmt.core :as cljfmt]
[cljfmt.diff :as diff]
[cljfmt.io :as io]
[clojure.java.io :as cio]
(:require [cljfmt.lib :as lib]
[clojure.stacktrace :as st]))

(def ^:dynamic *no-output* false)
Expand All @@ -17,66 +13,20 @@
(defn- trace [& args]
(when *verbose* (apply warn args)))

(defn- grep [re dir]
(filter #(re-find re (io/relative-path % dir)) (io/list-files dir)))

(defn- find-files [{:keys [file-pattern]} f]
(let [f (io/file-entity f)]
(when (io/exists? f)
(if (io/directory? f)
(grep file-pattern f)
(list f)))))

(defn- reformat-string [options s]
((cljfmt/wrap-normalize-newlines #(cljfmt/reformat-string % options)) s))

(defn- project-path [{:keys [project-root]} file]
(->> (or project-root ".") cio/file (io/relative-path file)))

(defn- format-diff
([options file]
(let [original (io/read-file file)]
(format-diff options file original (reformat-string options original))))
([options file original revised]
(let [filename (project-path options file)
diff (diff/unified-diff filename original revised)]
(if (:ansi? options)
(diff/colorize-diff diff)
diff))))

(def ^:private zero-counts {:okay 0, :incorrect 0, :error 0})

(defn- check-one [options file]
(trace "Processing file:" (project-path options file))
(let [original (io/read-file file)
status {:counts zero-counts :file file}]
(try
(let [revised (reformat-string options original)]
(if (not= original revised)
(-> status
(assoc-in [:counts :incorrect] 1)
(assoc :diff (format-diff options file original revised)))
(assoc-in status [:counts :okay] 1)))
(catch Exception ex
(-> status
(assoc-in [:counts :error] 1)
(assoc :exception ex))))))

(defn- print-stack-trace [ex]
(when-not *no-output*
(binding [*out* *err*]
(st/print-stack-trace ex))))

(defn- print-file-status [options status]
(let [path (project-path options (:file status))]
(when-let [ex (:exception status)]
(warn "Failed to format file:" path)
(print-stack-trace ex))
(when (:reformatted status)
(warn "Reformatted" path))
(when-let [diff (:diff status)]
(warn path "has incorrect formatting")
(println diff))))
(defn- print-file-status [{:keys [path] :as status}]
(when-let [ex (:exception status)]
(warn "Failed to format file:" path)
(print-stack-trace ex))
(when (:reformatted status)
(warn "Reformatted" path))
(when-let [diff (:diff status)]
(warn path "has incorrect formatting")
(println diff)))

(defn- exit [counts]
(when-not (zero? (:error counts 0))
Expand All @@ -94,60 +44,31 @@
(when (and (zero? incorrect) (zero? error))
(warn "All source files formatted correctly"))))

(defn- merge-counts
([] zero-counts)
([a] a)
([a b] (merge-with + a b)))

(defn check-no-config
"The same as `check`, but ignores dotfile configuration."
[options]
(let [map* (if (:parallel? options) pmap map)
counts (->> (:paths options)
(mapcat (partial find-files options))
(map* (partial check-one options))
(map (fn [status]
(print-file-status options status)
(:counts status)))
(reduce merge-counts))]
(let [options* (assoc options :cljfmt/report print-file-status
:cljfmt/trace trace)
counts (lib/check-no-config options*)]
(print-final-count counts)
(exit counts)))

(defn check
"Checks that the Clojure paths specified by the :paths option are
correctly formatted."
[options]
(let [opts (merge (config/load-config) options)]
(check-no-config opts)))

(defn- fix-one [options file]
(trace "Processing file:" (project-path options file))
(let [original (io/read-file file)]
(try
(let [revised (reformat-string options original)
changed? (not= original revised)]
(io/update-file file revised changed?)
(if changed?
{:file file :reformatted true}
{:file file}))
(catch Exception e
{:file file :exception e}))))

(defn- recursively-find-files [{:keys [paths] :as options}]
(mapcat #(find-files options %) (set paths)))
(let [options* (assoc options :cljfmt/report print-file-status
:cljfmt/trace trace)
counts (lib/check options*)]
(print-final-count counts)
(exit counts)))

(defn fix-no-config
"The same as `fix`, but ignores dotfile configuration."
[options]
(let [files (recursively-find-files options)
map* (if (:parallel? options) pmap map)]
(->> files
(map* (partial fix-one options))
(map (partial print-file-status options))
dorun)))
(let [options* (assoc options :cljfmt/report print-file-status
:cljfmt/trace trace)]
(lib/fix-no-config options*)))

(defn fix
"Fixes the formatting for all files specified by the :paths option."
[options]
(let [opts (merge (config/load-config) options)]
(fix-no-config opts)))
(let [options* (assoc options :cljfmt/report print-file-status
:cljfmt/trace trace)]
(lib/fix options*)))

0 comments on commit 61841ae

Please sign in to comment.