diff --git a/haskell-process.el b/haskell-process.el index bf306341a..9f9bbe89a 100644 --- a/haskell-process.el +++ b/haskell-process.el @@ -111,6 +111,16 @@ See `haskell-process-do-cabal' for more details." :type '(choice (const auto) (const ghci) (const cabal-repl) (const cabal-dev) (const cabal-ghci)) :group 'haskell-interactive) +(defcustom haskell-process-wrapper-function + #'identity + "A default wrapper function to deal with an eventual haskell-process-wrapper. + +If no wrapper is needed, then using 'identify function is sufficient. +Otherwise, define a function which takes a list of arguments. +For example: + (lambda (argv) (append (list \"nix-shell\" \"default.nix\" \"--command\" ) + (list (mapconcat 'identity argv \" \"))))") + (defcustom haskell-process-log nil "Enable debug logging to \"*haskell-process-log*\" buffer." @@ -1010,6 +1020,36 @@ from `module-buffer'." ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Building the process +(defun haskell-process-compute-process-log-and-command (session hptype) + "Compute the log and process to start command for the SESSION from the HPTYPE. +Do not actually start any process. +HPTYPE is the result of calling `'haskell-process-type`' function." + (let ((session-name (haskell-session-name session))) + (cl-ecase hptype + ('ghci + (append (list (format "Starting inferior GHCi process %s ..." haskell-process-path-ghci) + session-name + nil) + (apply haskell-process-wrapper-function (list (cons haskell-process-path-ghci haskell-process-args-ghci))))) + ('cabal-repl + (append (list (format "Starting inferior `cabal repl' process using %s ..." haskell-process-path-cabal) + session-name + nil) + (apply haskell-process-wrapper-function (list (cons haskell-process-path-cabal (cons "repl" haskell-process-args-cabal-repl)))) + (let ((target (haskell-session-target session))) + (if target (list target) nil)))) + ('cabal-ghci + (append (list (format "Starting inferior cabal-ghci process using %s ..." haskell-process-path-cabal-ghci) + session-name + nil) + (apply haskell-process-wrapper-function (list (list haskell-process-path-cabal-ghci))))) + ('cabal-dev + (let ((dir (concat (haskell-session-cabal-dir session) "/cabal-dev"))) + (append (list (format "Starting inferior cabal-dev process %s -s %s ..." haskell-process-path-cabal-dev dir) + session-name + nil) + (apply haskell-process-wrapper-function (list (cons haskell-process-path-cabal-dev (list "ghci" "-s" dir)))))))))) + ;;;###autoload (defun haskell-process-start (session) "Start the inferior Haskell process." @@ -1026,58 +1066,14 @@ from `module-buffer'." (haskell-process-set-session process session) (haskell-process-set-cmd process nil) (haskell-process-set (haskell-session-process session) 'is-restarting nil) - (let ((default-directory (haskell-session-cabal-dir session))) + (let ((default-directory (haskell-session-cabal-dir session)) + (log-and-process-to-start (haskell-process-compute-process-log-and-command session (haskell-process-type)))) (haskell-session-pwd session) (haskell-process-set-process process - (cl-ecase (haskell-process-type) - ('ghci - (haskell-process-log - (propertize (format "Starting inferior GHCi process %s ..." - haskell-process-path-ghci) - 'face font-lock-comment-face)) - (apply #'start-process - (append (list (haskell-session-name session) - nil - haskell-process-path-ghci) - haskell-process-args-ghci))) - ('cabal-repl - (haskell-process-log - (propertize - (format "Starting inferior `cabal repl' process using %s ..." - haskell-process-path-cabal) - 'face font-lock-comment-face)) - - (apply #'start-process - (append (list (haskell-session-name session) - nil - haskell-process-path-cabal) - '("repl") haskell-process-args-cabal-repl - (let ((target (haskell-session-target session))) - (if target (list target) nil))))) - ('cabal-ghci - (haskell-process-log - (propertize - (format "Starting inferior cabal-ghci process using %s ..." - haskell-process-path-cabal-ghci) - 'face font-lock-comment-face)) - (start-process (haskell-session-name session) - nil - haskell-process-path-cabal-ghci)) - ('cabal-dev - (let ((dir (concat (haskell-session-cabal-dir session) - "/cabal-dev"))) - (haskell-process-log - (propertize (format "Starting inferior cabal-dev process %s -s %s ..." - haskell-process-path-cabal-dev - dir) - 'face font-lock-comment-face)) - (start-process (haskell-session-name session) - nil - haskell-process-path-cabal-dev - "ghci" - "-s" - dir)))))) + (progn + (haskell-process-log (propertize (car log-and-process-to-start) 'face font-lock-comment-face)) + (apply #'start-process (cdr log-and-process-to-start))))) (progn (set-process-sentinel (haskell-process-process process) 'haskell-process-sentinel) (set-process-filter (haskell-process-process process) 'haskell-process-filter)) (haskell-process-send-startup process) diff --git a/tests/haskell-process-tests.el b/tests/haskell-process-tests.el new file mode 100644 index 000000000..084fd1a6f --- /dev/null +++ b/tests/haskell-process-tests.el @@ -0,0 +1,105 @@ +;;; haskell-process-tests.el + +;;; Code: + +(require 'ert) +(require 'haskell-process) + +;; HACK how to install deps in haskell-mode +(progn (require 'package) + (package-initialize) + (add-to-list 'package-archives '("melpa" . "http://melpa.milkbox.net/packages/")) + (package-refresh-contents) + (package-install 'el-mock)) + +(require 'el-mock) + +(ert-deftest haskell-process-wrapper-command-function-identity () + "No wrapper, return directly the command." + (should (equal '("ghci") + (progn + (custom-set-variables '(haskell-process-wrapper-function #'identity)) + (apply haskell-process-wrapper-function (list '("ghci"))))))) + +(ert-deftest haskell-process-wrapper-function-non-identity () + "Wrapper as a string, return the wrapping command as a string." + (should (equal '("nix-shell" "default.nix" "--command" "cabal\\ run") + (progn + (custom-set-variables '(haskell-process-wrapper-function (lambda (argv) + (append '("nix-shell" "default.nix" "--command") + (list (shell-quote-argument argv)))))) + (apply haskell-process-wrapper-function (list "cabal run")))))) + +(ert-deftest test-haskell-process--compute-process-log-and-command-ghci () + (should (equal '("Starting inferior GHCi process ghci ..." "dumses1" nil "ghci" "-ferror-spans") + (let ((haskell-process-path-ghci "ghci") + (haskell-process-args-ghci '("-ferror-spans"))) + (custom-set-variables '(haskell-process-wrapper-function #'identity)) + (mocklet (((haskell-session-name "dummy-session") => "dumses1")) + (haskell-process-compute-process-log-and-command "dummy-session" 'ghci)))))) + +(ert-deftest test-haskell-process--with-wrapper-compute-process-log-and-command-ghci () + (should (equal '("Starting inferior GHCi process ghci ..." "dumses1" nil "nix-shell" "default.nix" "--command" "ghci\\ -ferror-spans") + (let ((haskell-process-path-ghci "ghci") + (haskell-process-args-ghci '("-ferror-spans"))) + (custom-set-variables '(haskell-process-wrapper-function + (lambda (argv) (append (list "nix-shell" "default.nix" "--command" ) + (list (shell-quote-argument (mapconcat 'identity argv " "))))))) + (mocklet (((haskell-session-name "dummy-session") => "dumses1")) + (haskell-process-compute-process-log-and-command "dummy-session" 'ghci)))))) + +(ert-deftest test-haskell-process--compute-process-log-and-command-cabal-repl () + (should (equal '("Starting inferior `cabal repl' process using cabal ..." "dumses2" nil "cabal" "repl" "--ghc-option=-ferror-spans" "dumdum-session") + (let ((haskell-process-path-cabal "cabal") + (haskell-process-args-cabal-repl '("--ghc-option=-ferror-spans"))) + (custom-set-variables '(haskell-process-wrapper-function #'identity)) + (mocklet (((haskell-session-name "dummy-session2") => "dumses2") + ((haskell-session-target "dummy-session2") => "dumdum-session")) + (haskell-process-compute-process-log-and-command "dummy-session2" 'cabal-repl)))))) + +(ert-deftest test-haskell-process--with-wrapper-compute-process-log-and-command-cabal-repl () + (should (equal '("Starting inferior `cabal repl' process using cabal ..." "dumses2" nil "nix-shell" "default.nix" "--command" "cabal\\ repl\\ --ghc-option\\=-ferror-spans" "dumdum-session") + (let ((haskell-process-path-cabal "cabal") + (haskell-process-args-cabal-repl '("--ghc-option=-ferror-spans"))) + (custom-set-variables '(haskell-process-wrapper-function + (lambda (argv) (append (list "nix-shell" "default.nix" "--command" ) + (list (shell-quote-argument (mapconcat 'identity argv " "))))))) + (mocklet (((haskell-session-name "dummy-session2") => "dumses2") + ((haskell-session-target "dummy-session2") => "dumdum-session")) + (haskell-process-compute-process-log-and-command "dummy-session2" 'cabal-repl)))))) + +(ert-deftest test-haskell-process--compute-process-log-and-command-cabal-ghci () + (should (equal '("Starting inferior cabal-ghci process using cabal-ghci ..." "dumses3" nil "cabal-ghci") + (let ((haskell-process-path-ghci "ghci")) + (custom-set-variables '(haskell-process-wrapper-function #'identity)) + (mocklet (((haskell-session-name "dummy-session3") => "dumses3")) + (haskell-process-compute-process-log-and-command "dummy-session3" 'cabal-ghci)))))) + +(ert-deftest test-haskell-process--with-wrapper-compute-process-log-and-command-cabal-ghci () + (should (equal '("Starting inferior cabal-ghci process using cabal-ghci ..." "dumses3" nil "nix-shell" "default.nix" "--command" "cabal-ghci") + (let ((haskell-process-path-ghci "ghci")) + (custom-set-variables '(haskell-process-wrapper-function + (lambda (argv) (append (list "nix-shell" "default.nix" "--command" ) + (list (shell-quote-argument (mapconcat 'identity argv " "))))))) + (mocklet (((haskell-session-name "dummy-session3") => "dumses3")) + (haskell-process-compute-process-log-and-command "dummy-session3" 'cabal-ghci)))))) + +(ert-deftest test-haskell-process--compute-process-log-and-command-cabal-dev () + (should (equal '("Starting inferior cabal-dev process cabal-dev -s directory/cabal-dev ..." "dumses4" nil "cabal-dev" "ghci" "-s" "directory/cabal-dev") + (let ((haskell-process-path-cabal-dev "cabal-dev")) + (custom-set-variables '(haskell-process-wrapper-function #'identity)) + (mocklet (((haskell-session-name "dummy-session4") => "dumses4") + ((haskell-session-cabal-dir "dummy-session4") => "directory")) + (haskell-process-compute-process-log-and-command "dummy-session4" 'cabal-dev)))))) + +(ert-deftest test-haskell-process--with-wrapper-compute-process-log-and-command-cabal-dev () + (should (equal '("Starting inferior cabal-dev process cabal-dev -s directory/cabal-dev ..." "dumses4" nil "run-with-docker" "cabal-dev\\ ghci\\ -s\\ directory/cabal-dev") + (let ((haskell-process-path-cabal-dev "cabal-dev")) + (custom-set-variables '(haskell-process-wrapper-function + (lambda (argv) (append (list "run-with-docker") + (list (shell-quote-argument (mapconcat 'identity argv " "))))))) + (mocklet (((haskell-session-name "dummy-session4") => "dumses4") + ((haskell-session-cabal-dir "dummy-session4") => "directory")) + (haskell-process-compute-process-log-and-command "dummy-session4" 'cabal-dev)))))) + +;;; haskell-process-tests.el ends here