[Home]

Generating Clojure import lines using SLIME

Updated! I've reworked/rewritten this code to add import expressions to (ns ...) declarations. The new version requires a recent version of swank-clojure (at the time of writing, the lein branch of technomancy's swank-clojure fork on github).

You can find my new version here. See the top of the file for usage information.

For completeness I've left the original version of my code below.

Often when I'm in a frenzy of coding and suddenly want to use some random class, I find manually adding the right (import ...) line to be a bit of a drag. I've just come up with a horrible hack that I thought I'd share.

My hack is just a function that takes a regexp, snuffles around the Java classpath, finds matching classes and inserts (import ...) statements into the current buffer. For example:

M-x clj-import RET IndexWriter RET

inserts:

(import '(org.apache.lucene.index IndexWriter IndexWriter$MaxFieldLength))

Or silly things like:

M-x clj-import RET (xom|lucene).*(IndexWriter|Element$) RET

yield:

(import '(org.apache.lucene.index IndexWriter IndexWriter$MaxFieldLength)
        '(nu.xom Element))

Handy!

There are two parts to this hack: some Clojure code that goes into ~/.clojure/user.clj (or wherever gets loaded on startup), and some elisp code. The Clojure code is just:

(defn find-classes [regex]
  (let [search-path-properities ["java.class.path" "sun.boot.class.path"]]
    (for [search-path search-path-properities
          jar (filter #(.endsWith % ".jar")
                      (.split (System/getProperty search-path)
                              (System/getProperty "path.separator")))
          entry (try (filter #(.endsWith (.getName %) ".class")
                             (enumeration-seq (.entries (new java.util.jar.JarFile jar))))
                     (catch Exception _))
          name [(.. entry getName (replaceAll "\\.class$" ""))]
          :when (re-find regex name)]
      name)))

And the elisp:

(defun clj-import (re)
  (interactive "sClass regex?: ")
  (slime-eval-with-transcript
   `(swank:interactive-eval ,(format "(user/find-classes #\"%s\")" re))
   nil t
   `(lambda (s)
      (with-current-buffer ,(current-buffer)
        (let ((classes (car (read-from-string s))))
          (insert (clj-format-import classes)))))))



(defun clj-format-import (classes)
  (let ((packages (make-hash-table :test 'equal)))
    (mapc (lambda (class)
            (let ((pkg (file-name-directory class))
                  (name (file-name-nondirectory class)))
              (puthash pkg (cons name (gethash pkg packages '()))
                       packages )))
          classes)
    (let ((imports '()))
      (maphash (lambda (pkg names)
                 (push (format "'(%s %s)"
                               (replace-regexp-in-string
                                "/" "."
                                (replace-regexp-in-string "/$" "" pkg))
                               (mapconcat 'identity names " "))
                       imports))
               packages)
      (format "(import %s)\n"
              (mapconcat 'identity imports "\n        ")))))

Pretty gross! But then who of us isn't?