Skip to content

Instantly share code, notes, and snippets.

@laurio
Created April 5, 2021 08:42
Show Gist options
  • Save laurio/01530ea7700752885df21e92bb926f75 to your computer and use it in GitHub Desktop.
Save laurio/01530ea7700752885df21e92bb926f75 to your computer and use it in GitHub Desktop.
babashka script for reordering requires and imports in clojure files
#!/usr/bin/env bb
(ns reorder
(:require [clojure.java.io :as io]
[clojure.string :as string]
[rewrite-clj.node :as n]
[rewrite-clj.zip :as z]
[rewrite-clj.zip.subedit :as zsub])
(:import (java.io File StringWriter)))
(defn- top? [loc]
(= :forms (z/tag (z/up loc))))
(defn- find-namespace [zloc]
(-> zloc
(z/find z/up top?)
(z/leftmost)
(z/find-value z/next 'ns) ; go to ns
(z/up))) ; ns form
(defn- process-clean-ns
[ns-loc removed-nodes col form-type]
(let [sep (n/whitespace-node (apply str (repeat col " ")))
single-space (n/whitespace-node " ")
forms (->> removed-nodes
z/node
n/children
(remove n/printable-only?)
(sort-by (comp (fn [sexpr]
(if (symbol? sexpr)
(str sexpr)
(str (first sexpr)))) n/sexpr))
(map-indexed (fn [idx node]
(if (zero? idx)
[single-space node]
[(n/newlines 1) sep node])))
(apply concat)
(cons (n/keyword-node form-type)))]
(if (empty? (z/child-sexprs removed-nodes))
(z/subedit-> ns-loc
(z/find-value z/next form-type)
(z/up)
z/remove)
(z/subedit-> ns-loc
(z/find-value z/next form-type)
(z/up)
(z/replace (n/list-node forms))))))
(defn- clean-requires
[ns-loc]
(if-let [require-loc (z/find-value (zsub/subzip ns-loc) z/next :require)]
(let [col (-> require-loc z/node meta :end-col)
removed-nodes (z/remove require-loc)]
(process-clean-ns ns-loc removed-nodes col :require))
ns-loc))
(defn- clean-imports
[ns-loc]
(if-let [import-loc (z/find-value (zsub/subzip ns-loc) z/next :import)]
(let [col (-> import-loc z/node meta :end-col)
removed-nodes (z/remove import-loc)]
(process-clean-ns ns-loc removed-nodes col :import))
ns-loc))
(defn- clean-ns
[zloc]
(let [safe-loc (z/of-string zloc)]
(some-> (find-namespace safe-loc)
(clean-requires)
(clean-imports))))
(def reordered-files-count (atom 0))
(defn reorder-file-ns! [^File f]
(let [original-content (slurp f)]
(when (or (string/includes? original-content ":require")
(string/includes? original-content ":import"))
(let [refactored-content (with-open [w (StringWriter.)]
(some-> original-content
clean-ns
(z/print-root w))
(str w))]
(when-not (or (string/blank? refactored-content)
(= refactored-content original-content))
(swap! reordered-files-count inc)
(-> f str println)
(spit f refactored-content))))))
(defn reorder-dir-namespaces! [^String dir]
(doseq [^File f (-> dir io/file file-seq)
:when (and (.isFile f)
(->> f
.getName
(re-matches #"(.+)\.clj[cs]?")))]
(try
(reorder-file-ns! f)
(catch Exception e
(-> f str println)
(throw e)))))
(defn- main [args]
(run! reorder-dir-namespaces!
(or args
["src" "spec" "test"]))
(let [reordered-count @reordered-files-count]
(when (zero? reordered-count)
(println "No requires reordered"))
(System/exit reordered-count)))
(when (= *file* (System/getProperty "babashka.file"))
(main *command-line-args*))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment