Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
tag: release-0.1
Fetching contributors…

Cannot retrieve contributors at this time

file 220 lines (186 sloc) 7.365 kb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220
;;; elein.el --- running leiningen commands from emacs

;; Copyright (C) 2010, 2011 R.W van 't Veer

;; Author: R.W. van 't Veer
;; Created: 2 Aug 2010
;; Keywords: tools processes
;; Version: 0.1
;; URL: https://github.com/remvee/elein

;; This program is free software; you can redistribute it and/or
;; modify it under the terms of the GNU General Public License
;; as published by the Free Software Foundation; either version 3
;; of the License, or (at your option) any later version.
;;
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs; see the file COPYING. If not, write to the
;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
;; Boston, MA 02110-1301, USA.

;;; Commentary:
;; Provides support for running leiningen commands like swank and test.

;;; Code:

(eval-when-compile (require 'cl))

(defgroup elein nil
  "running leiningen commands from emacs"
  :prefix "elein-"
  :group 'applications)

(defcustom elein-lein "lein"
  "Leiningen 'lein' command."
  :type 'string
  :group 'elein)

(defcustom elein-standalone-swank-command "~/.lein/bin/swank-clojure"
  "Leiningen 'swank-clojure' command for standalone execution."
  :type 'string
  :group 'elein)

(defcustom elein-swank-buffer-name "*elein-swank*"
  "Buffer name for swank process."
  :type 'string
  :group 'elein)

(defcustom elein-swank-port 4005
  "Swank port to listen."
  :type 'integer
  :group 'elein)

(defcustom elein-swank-host "127.0.0.1"
  "Swank address to listen."
  :type 'string
  :group 'elein)

(defcustom elein-swank-options ":encoding '\"utf-8\"'"
  "Swank options."
  :type 'string
  :group 'elein)

(defun elein-project-root ()
  "Look for project.clj file to find project root."
  (locate-dominating-file default-directory "project.clj"))

(defmacro elein-in-project-root (body)
  "Wrap BODY to make `default-directory' the project root."
  (let ((dir (gensym)))
    `(let ((,dir (elein-project-root)))
       (if ,dir
         (let ((default-directory ,dir)) ,body)
         (error "No leiningen project root found")))))

(defvar elein-task-alist nil
  "Holds cached task list by directory name. The car of the
value is the mtime of the project.clj file and the cdr is the
task list itself.")

(defun elein-project-clj-mtime ()
  "Get mtime from the project.clj in the current project."
  (nth 5 (elein-in-project-root
          (file-attributes "project.clj"))))

(defun elein-list-tasks ()
  "Collect tasks for current project."
  (let* ((root (elein-project-root))
         (cached (assoc root elein-task-alist)))
    (if (and cached (equal (elein-project-clj-mtime) (cadr cached)))
      (cddr cached)
      (let ((tasks (elein-in-project-root
                    (let ((output (shell-command-to-string (concat elein-lein " help")))
                          (result nil)
                          (offset 0))
                      (while (string-match "^ \\(.*\\)" output offset)
                        (setq result (cons (match-string 1 output) result))
                        (setq offset (match-end 0)))
                      (sort result (lambda (a b) (string< a b)))))))
        (setq elein-task-alist (cons (cons root (cons (elein-project-clj-mtime)
                                                      tasks))
                                     elein-task-alist))
        tasks))))

(defun elein-swank-command ()
  "Build lein swank command from customization variables."
  (format "%s swank %d %s %s"
          elein-lein
          elein-swank-port
          elein-swank-host
          elein-swank-options))

(defun elein-standalone-swank-command ()
  "Build projectless lein swank command."
  (unless (file-exists-p (expand-file-name elein-standalone-swank-command))
    (error "can not find %s; use 'lein install swank-clojure VERSION' to install it"
           elein-standalone-swank-command))
  (format "%s %d :host %s %s"
          (expand-file-name elein-standalone-swank-command)
          elein-swank-port
          elein-swank-host
          elein-swank-options))

(defun elein-burried-shell-command (command buffer)
  "Same as `shell-command' but run process asynchronously, do not
show output and burry the given BUFFER."
  (flet ((display-buffer (buffer-or-name &optional not-this-window frame) nil))
    (bury-buffer buffer)
    (shell-command (concat command "&") buffer)))

(defun elein-swank-process-filter (process output)
  "Swank process filter to launch `slime-connect' when process is ready."
  (with-current-buffer elein-swank-buffer-name (insert output))

  (when (string-match "Connection opened on local port +\\([0-9]+\\)" output)
    (slime-set-inferior-process
     (slime-connect "localhost" (match-string 1 output))
     process)
    (set-process-filter process nil)))

;;;###autoload
(defun elein-swank (&optional prefix)
  "Launch lein swank and connect slime to it. Interactively, a
PREFIX means launch a standalone swank session without a
project."
  (interactive "P")
  (let ((buffer (get-buffer-create elein-swank-buffer-name)))
    (if prefix
      (elein-burried-shell-command (elein-standalone-swank-command) buffer)
      (elein-in-project-root
       (elein-burried-shell-command (elein-swank-command) buffer)))

    (set-process-filter (get-buffer-process buffer) #'elein-swank-process-filter)

    (message "Starting swank..")))

;;;###autoload
(defun elein-kill-swank ()
  "Kill swank process started by lein swank."
  (interactive)
  (let ((process (get-buffer-process "*elein-swank*")))
    (when process
      (ignore-errors (slime-quit-lisp))
      (let ((timeout 10))
        (while (and (> timeout 0)
                    (eql 'run (process-status process)))
          (sit-for 1)
          (decf timeout)))
      (ignore-errors (kill-buffer "*elein-swank*")))))

;;;###autoload
(defun elein-reswank ()
  "Kill current lisp, restart lein swank and connect slime to it."
  (interactive)
  (elein-kill-swank)
  (elein-swank))

;;;###autoload
(defun elein-run-cmd (args)
  "Run 'lein ARGS' using `compile' in the project root directory."
  (interactive "sArguments: ")
  (elein-in-project-root (compile (concat elein-lein " " args))))

;;;###autoload
(defun elein-run-task (task)
  "Run 'lein TASK' using `compile' in the project root directory."
  (interactive (list (completing-read "Task: " (elein-list-tasks))))
  (elein-run-cmd task))

(defmacro elein-defun-run-task (task)
  "Define shortcut function for `elein-run-task' with argument TASK."
  `(defun ,(intern (concat "elein-" task)) ()
     ,(concat "Run 'lein " task "' in the project root directory.")
     (interactive)
     (elein-run-task ,task)))

;; define interactive elein-TASK commands for common tasks
(dolist (task '(classpath
                clean
                compile
                deps
                help
                install
                jar
                new
                pom
                repl
                test
                uberjar
                upgrade
                version))
  (eval `(elein-defun-run-task ,(symbol-name task))))

(provide 'elein)

;;; elein.el ends here
Something went wrong with that request. Please try again.