]> code.delx.au - gnu-emacs-elpa/blobdiff - async.el
Added `async-start-process'
[gnu-emacs-elpa] / async.el
index 6b3831c5c3ee9eca9433ae6062d8ef314ce0d742..070803b7f8e87e625e4ba5edca143b671f79c70f 100644 (file)
--- a/async.el
+++ b/async.el
@@ -4,9 +4,9 @@
 
 ;; Author: John Wiegley <jwiegley@gmail.com>
 ;; Created: 18 Jun 2012
-;; Version: 1.0
+;; Version: 1.1
 ;; Keywords: async
-;; X-URL: https://github.com/jwiegley/async
+;; X-URL: https://github.com/jwiegley/emacs-async
 
 ;; This program is free software; you can redistribute it and/or
 ;; modify it under the terms of the GNU General Public License as
 \f
 ;;; Commentary:
 
-;; Adds the ability to process Lisp concurrently, with a very simple syntax:
-;;
-;;   (async-start
-;;      ;; What to do in the child process
-;;      (lambda ()
-;;        (message "This is a test")
-;;        (sleep-for 3)
-;;        222)
-;;
-;;      ;; What to do when it finishes
-;;      (lambda (result)
-;;        (message "Async process done, result should be 222: %s" result)))
-;;
-;; If you omit the callback function, `async-start' will return a process
-;; object that you can call `async-get' on when you're ready to wait for the
-;; result value:
-;;
-;;   (let ((proc (async-start
-;;                  ;; What to do in the child process
-;;                  (lambda ()
-;;                    (message "This is a test")
-;;                    (sleep-for 3)
-;;                    222))))
-;;       (message "I'm going to do some work here")
-;;       ;; ....
-;;       (message "Async process done, result should be 222: %s"
-;;                (async-get proc)))
-;;
-;; If you don't want to use a callback, and you don't care about any return
-;; value form the child proces, pass the `ignore' symbol as the second
-;; argument:
-;;
-;;   (async-start
-;;    (lambda ()
-;;      (delete-file "a remote file on a slow link" nil))
-;;      'ignore)
+;; Adds the ability to call asynchronous functions and process with ease.  See
+;; the documentation for `async-start' and `async-start-process'.
 \f
 ;;; Code:
 
   :group 'emacs)
 
 (defvar async-callback nil)
+(defvar async-callback-for-process nil)
 (defvar async-callback-value nil)
 (defvar async-callback-value-set nil)
 
-(defmacro async-inject-environment
+(defmacro async-inject-variables
   (include-regexp &optional predicate exclude-regexp)
-  "Inject a part of the parent environment into an async function."
+  "Return a `setq' form that replicates part of the calling environment.
+It sets the value for every variable matching INCLUDE-REGEXP and
+also PREDICATE.  It will not perform injection for any variable
+matching EXCLUDE-REGEXP (if present).  It is intended to be used
+as follows:
+
+    (async-start
+       `(lambda ()
+          (require 'smtpmail)
+          (with-temp-buffer
+            (insert ,(buffer-substring-no-properties (point-min) (point-max)))
+            ;; Pass in the variable environment for smtpmail
+            ,(async-inject-variables \"\\`\\(smtpmail\\|\\(user-\\)?mail\\)-\")
+            (smtpmail-send-it)))
+       'ignore)"
   `'(setq
      ,@(let (bindings)
          (mapatoms
                           bindings (cons sym bindings)))))))
          bindings)))
 
+(defalias 'async-inject-environment 'async-inject-variables)
+
 (defun async-when-done (proc &optional change)
   "Process sentinal used to retrieve the value from the child process."
   (when (eq 'exit (process-status proc))
     (with-current-buffer (process-buffer proc)
       (if (= 0 (process-exit-status proc))
-          (progn
+          (if async-callback-for-process
+              (if async-callback
+                  (prog1
+                      (funcall async-callback proc)
+                    (kill-buffer (current-buffer)))
+                (set (make-local-variable 'async-callback-value) proc)
+                (set (make-local-variable 'async-callback-value-set) t))
             (goto-char (point-max))
             (backward-sexp)
             (let ((result (read (current-buffer))))
   "Called from the child Emacs process' command-line."
   (condition-case err
       (prin1 (funcall (eval (read nil))))
-    (signal
-     (prin1 `(async-signal . ,err)))
     (error
      (prin1 `(async-signal . ,err)))))
 
-(defun async-ready (proc)
-  "Wait until PROC has successfully completed."
-  (and (eq 'exit (process-status proc))
+(defun async-ready (future)
+  "Query a FUTURE to see if the ready is ready -- i.e., if no blocking
+would result from a call to `async-get' on that FUTURE."
+  (and (eq 'exit (process-status future))
        async-callback-value-set))
 
-(defun async-wait (proc)
-  "Wait until PROC has successfully completed."
-  (while (not (async-ready proc))
+(defun async-wait (future)
+  "Wait for FUTURE to become ready."
+  (while (not (async-ready future))
     (sit-for 0 50)))
 
-(defun async-get (proc)
-  "Wait until PROC has successfully completed."
-  (async-wait proc)
-  (with-current-buffer (process-buffer proc)
+(defun async-get (future)
+  "Get the value from an asynchronously function when it is ready.
+FUTURE is returned by `async-start' or `async-start-process' when
+its FINISH-FUNC is nil."
+  (async-wait future)
+  (with-current-buffer (process-buffer future)
     (prog1
         async-callback-value
       (kill-buffer (current-buffer)))))
 
+;;;###autoload
+(defmacro async-start-process (name program finish-func &rest program-args)
+  "Start the executable PROGRAM asynchronously.  See `async-start'.
+PROGRAM is passed PROGRAM-ARGS, calling FINISH-FUNC with the
+process object when done.  If FINISH-FUNC is nil, the future
+object will return the process object when the program is
+finished."
+(let ((bufvar (make-symbol "buf"))
+      (procvar (make-symbol "proc")))
+  `(let* ((,bufvar (generate-new-buffer ,(concat "*" name "*")))
+          (,procvar
+           ,`(apply #'start-process ,name ,bufvar ,program
+                    (quote ,program-args))))
+     (with-current-buffer ,bufvar
+       (set (make-local-variable 'async-callback) ,finish-func)
+       (set-process-sentinel ,procvar #'async-when-done)
+       ,(unless (string= name "emacs")
+          '(set (make-local-variable 'async-callback-for-process) t))
+       ,procvar))))
+
+;;;###autoload
 (defmacro async-start (start-func &optional finish-func)
-  "Fork execution of `start-func' into its own Emacs process.
-`start-func' must be a `read'-able symbol or lambda form.  It
-cannot be a byte-compiled lambda.
-
-`finish-func' is called with the result of `start-func' when that
-process has completed.  If it is nil, `async-start' will return a
-process object that you can block on with `async-future-get' in
-order to wait for the result of `start-func'.  This would allow
-you to start some expensive background processing at the
-beginning of a command, then wait for the result only when you're
-ready to use it."
-  (let ((bufvar (make-symbol "buf"))
-        (procvar (make-symbol "proc")))
-    (require 'find-func)
-    `(let* ((,bufvar (generate-new-buffer "*emacs*"))
-            (,procvar
-             (start-process "emacs" ,bufvar
-                            (expand-file-name invocation-name
-                                              invocation-directory)
-                            "-Q" "-l" (find-library-name "async")
-                            "-batch" "-f" "async-batch-invoke")))
-       (with-current-buffer ,bufvar
-         (set (make-local-variable 'async-callback) ,finish-func)
-         (set-process-sentinel ,procvar #'async-when-done)
+  "Execute START-FUNC (often a lambda) in a subordinate Emacs process.
+When done, the return value is passed to FINISH-FUNC.  Example:
+
+    (async-start
+       ;; What to do in the child process
+       (lambda ()
+         (message \"This is a test\")
+         (sleep-for 3)
+         222)
+
+       ;; What to do when it finishes
+       (lambda (result)
+         (message \"Async process done, result should be 222: %s\"
+                  result)))
+
+If FINISH-FUNC is nil or missing, a future is returned that can
+be inspected using `async-get', blocking until the value is
+ready.  Example:
+
+    (let ((proc (async-start
+                   ;; What to do in the child process
+                   (lambda ()
+                     (message \"This is a test\")
+                     (sleep-for 3)
+                     222))))
+
+        (message \"I'm going to do some work here\") ;; ....
+
+        (message \"Waiting on async process, result should be 222: %s\"
+                 (async-get proc)))
+
+If you don't want to use a callback, and you don't care about any
+return value form the child process, pass the `ignore' symbol as
+the second argument (if you don't, and never call `async-get', it
+will leave *emacs* process buffers hanging around):
+
+    (async-start
+     (lambda ()
+       (delete-file \"a remote file on a slow link\" nil))
+     'ignore)
+
+Note: Even when FINISH-FUNC is present, a future is still
+returned except that it yields no value (since the value is
+passed to FINISH-FUNC).  Call `async-get' on such a future always
+returns nil.  It can still be useful, however, as an argument to
+`async-ready' or `async-wait'."
+  (require 'find-func)
+  (let ((procvar (make-symbol "proc")))
+    `(let ((,procvar
+            (async-start-process "emacs" (expand-file-name invocation-name
+                                                           invocation-directory)
+                                 ,finish-func
+                                 "-Q" "-l" ,(find-library-name "async")
+                                 "-batch" "-f" "async-batch-invoke")))
+       (with-current-buffer (process-buffer ,procvar)
          (with-temp-buffer
            (let ((print-escape-newlines t))
              (prin1 (list 'quote ,start-func) (current-buffer)))
@@ -227,6 +269,17 @@ ready to use it."
      (message "Async process done, result should be 222: %s" result)))
   (message "Starting async-test-1...done"))
 
+(defun async-test-4 ()
+  (interactive)
+  (message "Starting async-test-4...")
+  (async-start-process "sleep" "sleep"
+                       ;; What to do when it finishes
+                       (lambda (proc)
+                         (message "Sleep done, exit code was %d"
+                                  (process-exit-status proc)))
+                       "3")
+  (message "Starting async-test-4...done"))
+
 (provide 'async)
 
 ;;; async.el ends here