diff options
author | Kyle Meyer <kyle@kyleam.com> | 2017-03-26 19:06:46 -0400 |
---|---|---|
committer | Kyle Meyer <kyle@kyleam.com> | 2017-03-26 21:21:31 -0400 |
commit | f2ea2ab6423f223b6dae3e1f8feab60da0745ec4 (patch) | |
tree | f4ca1b1e1ad59718d3ca149ab12d5f336287f722 | |
parent | 7ffea30d691cd7a46b6969767b6840dabaaea428 (diff) | |
download | snakemake-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-- | NEWS | 10 | ||||
-rw-r--r-- | snakemake.el | 109 |
2 files changed, 103 insertions, 16 deletions
@@ -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") |