aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKyle Meyer <kyle@kyleam.com>2017-03-26 19:06:46 -0400
committerKyle Meyer <kyle@kyleam.com>2017-03-26 21:21:31 -0400
commitf2ea2ab6423f223b6dae3e1f8feab60da0745ec4 (patch)
treef4ca1b1e1ad59718d3ca149ab12d5f336287f722
parent7ffea30d691cd7a46b6969767b6840dabaaea428 (diff)
downloadsnakemake-mode-f2ea2ab6423f223b6dae3e1f8feab60da0745ec4.tar.gz
Add a terminal interface for running Snakemake
Snakemake commands are currently executed through compile. In general, this works fine, but it doesn't work well when Snakemake should be executed in a different environment than the one in which the current Emacs session was started. For example, Guix commands like 'guix environment ...' manipulate environmental variables to expose particular software. With the current setup, snakemake-program could be set to a wrapper script that creates the environment and then calls Snakemake: guix environment -l manifest.scm --ad-hoc snakemake --pure \ -- snakemake $@ But the disadvantage of this approach is that it adds the environmental setup time to _each_ Snakemake call. To work better with tools like Guix, let's add an alternative interface that allows commands to be executed in a terminal session. Instead of the above script, snakemake-shell-file-name can be set to a script with guix environment -l manifest.scm --ad-hoc snakemake --pure Now the environmental setup cost is limited to starting the terminal.
-rw-r--r--NEWS10
-rw-r--r--snakemake.el109
2 files changed, 103 insertions, 16 deletions
diff --git a/NEWS b/NEWS
index fe95a23..3c33003 100644
--- a/NEWS
+++ b/NEWS
@@ -1,5 +1,15 @@
NEWS -- history of user-visible changes -*- mode: org; -*-
+* master (unreleased)
+
+** New features
+
+- ~snakemake-popup~ commands learned to call Snakemake through a
+ terminal rather than through ~compile~. This allows a persistent
+ environment to be maintained between Snakemake calls, which is
+ useful for running Snakemake in isolated environments created by
+ tools like Guix.
+
* v1.1.0
** New features
diff --git a/snakemake.el b/snakemake.el
index 2299182..99473fc 100644
--- a/snakemake.el
+++ b/snakemake.el
@@ -25,9 +25,9 @@
;; `snakemake-popup', which you should consider giving a global key
;; binding.
;;
-;; The popup currently includes four actions, all which lead to
-;; `compile' being called with "snakemake ...". What's different
-;; between the actions is how targets are selected.
+;; The popup currently includes four actions, all which lead to a
+;; Snakemake call. What's different between the actions is how
+;; targets are selected.
;;
;; snakemake-build-targets-at-point
;;
@@ -41,8 +41,7 @@
;;
;; snakemake-build
;;
-;; This action leads to an interactive `compile' call that allows
-;; you to edit the command before it is run.
+;; This action allows you to edit the command before running it.
;;
;; It tries to guess what the target name should be, but it
;; doesn't verify if this target is actually a build target. This
@@ -75,6 +74,10 @@
;; which to look for a Snakefile if there isn't one in the current
;; directory.
;;
+;; By default, commands are executed through `compile'. However, a
+;; terminal can be created with `snakemake-term-start', in which case
+;; commands are sent there instead.
+;;
;; In addition to the popup commands, there are commands for showing
;; and saving the dependency graph of a target. The command
;; `snakemake-graph' displays the graph in a buffer. From this
@@ -88,6 +91,7 @@
(require 'cl-lib)
(require 'compile)
+(require 'term)
(require 'magit-popup)
(require 'snakemake-mode)
@@ -105,6 +109,11 @@
:type 'string
:package-version '(snakemake-mode . "0.4.0"))
+(defcustom snakemake-shell-file-name shell-file-name
+ "Program used by `ansi-term' to start a terminal."
+ :type 'string
+ :package-version '(snakemake-mode . "1.2.0"))
+
(defcustom snakemake-file-target-program
(executable-find "snakemake-file-targets")
"Program that returns newline-delimited list of output files."
@@ -563,15 +572,72 @@ Snakemake-graph mode is a minor mode that provides a key,
(compile cmd)
(push cmd compile-history)))
+;;;; Terminal interface
+
+(defun snakemake-term--name ()
+ (concat "snakemake-terminal: " (abbreviate-file-name default-directory)))
+
+(defun snakemake-term-process ()
+ "Return the terminal process of the current directory."
+ (get-process (concat "*" (snakemake-term--name) "*")))
+
+(defun snakemake-term-start ()
+ "Start a terminal session for the current Snakefile directory.
+
+The main advantage of using a terminal is that it allows for a
+persistent environment between Snakemake calls, which is useful
+for running Snakemake in isolated environments created by tools
+like Guix.
+
+To do so, `snakemake-shell-file-name' should be set to a script
+that starts a shell with the desired environment. For example,
+to set up an enviroment with Guix, `snakemake-shell-file-name'
+could point to a script that runs
+
+ guix environment -l manifest.scm --ad-hoc snakemake --pure"
+ (interactive)
+ (let ((default-directory (snakemake-snakefile-directory)))
+ (unless (snakemake-term-process)
+ (ansi-term snakemake-shell-file-name (snakemake-term--name)))))
+
+(defun snakemake-term-send (string)
+ "Send STRING to the terminal for the current directory."
+ (let* ((proc (or (snakemake-term-process)
+ (user-error "No active terminal. Start with %s"
+ (substitute-command-keys
+ "`\\[snakemake-term-start]'"))))
+ (buf (process-buffer proc)))
+ (unless (get-buffer-window buf)
+ (display-buffer buf))
+ (term-send-string proc (concat string "\n"))
+ (with-current-buffer buf
+ (goto-char (process-mark proc)))))
+
+(defun snakemake-term-build-targets (targets args)
+ "Send 'snakemake [ARGS] -- TARGETS' to the terminal."
+ (let ((default-directory (snakemake-snakefile-directory)))
+ (snakemake-term-send (snakemake--make-command targets args))))
+
;;;; General interface
+(defun snakemake-build-targets (targets args)
+ "Run 'snakemake [ARGS] -- TARGETS'.
+If a terminal is associated with the current Snakefile directory,
+send the command there. Otherwise, run the command with
+`compile'."
+ (funcall (if (snakemake-term-process)
+ #'snakemake-term-build-targets
+ #'snakemake-compile-targets)
+ targets
+ args))
+
;;;###autoload
(defun snakemake-build-targets-at-point (&optional args)
"Build target(s) at point without any prompts.
$ snakemake [ARGS] -- <targets>"
(interactive (list (snakemake-arguments)))
- (snakemake-compile-targets
+ (snakemake-build-targets
(or (snakemake-file-targets-at-point 'check)
(snakemake-rule-at-point 'target)
(user-error "No target found at point"))
@@ -583,7 +649,7 @@ $ snakemake [ARGS] -- <targets>"
$ snakemake [ARGS] -- <file>"
(interactive (list (snakemake-arguments)))
- (snakemake-compile-targets
+ (snakemake-build-targets
(list (snakemake-read-file-target))
args))
@@ -593,24 +659,35 @@ $ snakemake [ARGS] -- <file>"
$ snakemake [ARGS] -- <rule>"
(interactive (list (snakemake-arguments)))
- (snakemake-compile-targets
+ (snakemake-build-targets
(list (snakemake-read-rule 'targets))
args))
;;;###autoload
(defun snakemake-build (&optional args)
- "Read `compile' command for building targets.
+ "Read and run a Snakemake command for building targets.
+
+If a terminal is associated with the current Snakefile directory,
+send the command there. Otherwise, run the command with
+`compile'.
+
+To start a terminal for the current Snakefile directory, run
+`\\[snakemake-term-start]'.
$ snakemake [ARGS] -- <targets>"
(interactive (list (snakemake-arguments)))
- (let ((compile-command (snakemake--make-command
- (or (snakemake-file-targets-at-point)
- (snakemake-rule-at-point)
- (list ""))
- args))
- (compilation-read-command t)
+ (let ((cmd (snakemake--make-command
+ (or (snakemake-file-targets-at-point)
+ (snakemake-rule-at-point)
+ (list ""))
+ args))
(default-directory (snakemake-snakefile-directory)))
- (call-interactively #'compile)))
+ (if (snakemake-term-process)
+ (snakemake-term-send
+ (read-from-minibuffer "Command to send to terminal: " cmd))
+ (let ((compile-command cmd)
+ (compilation-read-command t))
+ (call-interactively #'compile)))))
(define-obsolete-function-alias 'snakemake-compile 'snakemake-build "1.2.0")