@vindex $$
@item $$
-This is the result of the last command. In case of an external
-command, it is @code{t} or @code{nil}.
+This is the result of the last command. For external commands, it is
+@code{t} if the exit code was 0 or @code{nil} otherwise.
+@vindex eshell-lisp-form-nil-is-failure
@vindex $?
@item $?
This variable contains the exit code of the last command. If the last
command was a Lisp function, it is 0 for successful completion or 1
-otherwise.
+otherwise. If @code{eshell-lisp-form-nil-is-failure} is
+non-@code{nil}, then a command with a Lisp form, like
+@samp{(@var{command} @var{args}@dots{})}, that returns @code{nil} will
+set this variable to 2.
@vindex $COLUMNS
@vindex $LINES
This can take a few different forms. If @var{conditional} is a dollar
expansion, the condition is satisfied if the result is a
non-@code{nil} value. If @var{conditional} is a @samp{@{
-@var{subcommand} @}}, the condition is satisfied if the
-@var{subcommand}'s exit status is 0.
+@var{subcommand} @}} or @samp{(@var{lisp form})}, the condition is
+satisfied if the command's exit status is 0.
@table @code
error, and now expand to all directories recursively (following
symlinks in the latter case).
++++
+*** Lisp forms in Eshell now treat a 'nil' result as a failed exit status.
+When executing a command that looks like '(lisp form)', Eshell will
+set the exit status (available in the '$?' variable) to 2. This
+allows commands like that to be used as conditionals. To change this
+behavior, customize the new 'eshell-lisp-form-nil-is-failure' option.
+
** Shell
---
Such arguments will be passed to `read', and then evaluated."
:type 'regexp)
+(defcustom eshell-lisp-form-nil-is-failure t
+ "If non-nil, Lisp forms like (COMMAND ARGS) treat a nil result as failure."
+ :type 'boolean)
+
(defcustom eshell-pre-command-hook nil
"A hook run before each interactive command is invoked."
:type 'hook)
(defun eshell-lisp-command (object &optional args)
"Insert Lisp OBJECT, using ARGS if a function."
(catch 'eshell-external ; deferred to an external command
+ (setq eshell-last-command-status 0
+ eshell-last-arguments args)
(let* ((eshell-ensure-newline-p (eshell-interactive-output-p))
+ (command-form-p (functionp object))
(result
- (if (functionp object)
- (progn
- (setq eshell-last-arguments args
- eshell-last-command-name
+ (if command-form-p
+ (let ((numeric (not (get object
+ 'eshell-no-numeric-conversions)))
+ (fname-args (get object 'eshell-filename-arguments)))
+ (when (or numeric fname-args)
+ (while args
+ (let ((arg (car args)))
+ (cond
+ ((and numeric (stringp arg) (> (length arg) 0)
+ (text-property-any 0 (length arg)
+ 'number t arg))
+ ;; If any of the arguments are flagged as
+ ;; numbers waiting for conversion, convert
+ ;; them now.
+ (setcar args (string-to-number arg)))
+ ((and fname-args (stringp arg)
+ (string-equal arg "~"))
+ ;; If any of the arguments match "~",
+ ;; prepend "./" to treat it as a regular
+ ;; file name.
+ (setcar args (concat "./" arg)))))
+ (setq args (cdr args))))
+ (setq eshell-last-command-name
(concat "#<function " (symbol-name object) ">"))
- (let ((numeric (not (get object
- 'eshell-no-numeric-conversions)))
- (fname-args (get object 'eshell-filename-arguments)))
- (when (or numeric fname-args)
- (while args
- (let ((arg (car args)))
- (cond ((and numeric (stringp arg) (> (length arg) 0)
- (text-property-any 0 (length arg)
- 'number t arg))
- ;; If any of the arguments are
- ;; flagged as numbers waiting for
- ;; conversion, convert them now.
- (setcar args (string-to-number arg)))
- ((and fname-args (stringp arg)
- (string-equal arg "~"))
- ;; If any of the arguments match "~",
- ;; prepend "./" to treat it as a
- ;; regular file name.
- (setcar args (concat "./" arg)))))
- (setq args (cdr args)))))
(eshell-apply object eshell-last-arguments))
- (setq eshell-last-arguments args
- eshell-last-command-name "#<Lisp object>")
+ (setq eshell-last-command-name "#<Lisp object>")
(eshell-eval object))))
(if (and eshell-ensure-newline-p
(save-excursion
(goto-char eshell-last-output-end)
(not (bolp))))
(eshell-print "\n"))
- (eshell-close-handles 0 (list 'quote result)))))
+ (eshell-close-handles
+ ;; If `eshell-lisp-form-nil-is-failure' is non-nil, Lisp forms
+ ;; that succeeded but have a nil result should have an exit
+ ;; status of 2.
+ (when (and eshell-lisp-form-nil-is-failure
+ (not command-form-p)
+ (= eshell-last-command-status 0)
+ (not result))
+ 2)
+ (list 'quote result)))))
(defalias 'eshell-lisp-command* #'eshell-lisp-command)
(defvar eshell-last-output-end) ;Defined in esh-mode.el.
(eshell-update-markers eshell-last-output-end)
;; Simulate the effect of eshell-sentinel.
- (eshell-close-handles (if (numberp exit-status) exit-status -1))
+ (eshell-close-handles
+ (if (numberp exit-status) exit-status -1)
+ (list 'quote (and (numberp exit-status) (= exit-status 0))))
(eshell-kill-process-function command exit-status)
(or (bound-and-true-p eshell-in-pipeline-p)
(setq eshell-last-sync-output-start nil))
(when (buffer-live-p (process-buffer proc))
(with-current-buffer (process-buffer proc)
(unwind-protect
- (let ((entry (assq proc eshell-process-list)))
-; (if (not entry)
-; (error "Sentinel called for unowned process `%s'"
-; (process-name proc))
- (when entry
- (unwind-protect
- (progn
- (unless (string= string "run")
- ;; Write the exit message if the status is
- ;; abnormal and the process is already writing
- ;; to the terminal.
- (when (and (eq proc (eshell-tail-process))
- (not (string-match "^\\(finished\\|exited\\)"
- string)))
- (funcall (process-filter proc) proc string))
- (let ((handles (nth 1 entry))
- (str (prog1 (nth 3 entry)
- (setf (nth 3 entry) nil)))
- (status (process-exit-status proc)))
- ;; If we're in the middle of handling output
- ;; from this process then schedule the EOF for
- ;; later.
- (letrec ((finish-io
- (lambda ()
- (if (nth 4 entry)
- (run-at-time 0 nil finish-io)
- (when str
- (ignore-error 'eshell-pipe-broken
- (eshell-output-object
- str nil handles)))
- (eshell-close-handles
- status 'nil handles)))))
- (funcall finish-io)))))
- (eshell-remove-process-entry entry))))
+ (when-let ((entry (assq proc eshell-process-list)))
+ (unwind-protect
+ (unless (string= string "run")
+ ;; Write the exit message if the status is
+ ;; abnormal and the process is already writing
+ ;; to the terminal.
+ (when (and (eq proc (eshell-tail-process))
+ (not (string-match "^\\(finished\\|exited\\)"
+ string)))
+ (funcall (process-filter proc) proc string))
+ (let ((handles (nth 1 entry))
+ (str (prog1 (nth 3 entry)
+ (setf (nth 3 entry) nil)))
+ (status (process-exit-status proc)))
+ ;; If we're in the middle of handling output
+ ;; from this process then schedule the EOF for
+ ;; later.
+ (letrec ((finish-io
+ (lambda ()
+ (if (nth 4 entry)
+ (run-at-time 0 nil finish-io)
+ (when str
+ (ignore-error 'eshell-pipe-broken
+ (eshell-output-object
+ str nil handles)))
+ (eshell-close-handles
+ status (list 'quote (= status 0))
+ handles)))))
+ (funcall finish-io))))
+ (eshell-remove-process-entry entry)))
(eshell-kill-process-function proc string)))))
(defun eshell-process-interact (func &optional all query)
"{ setq eshell-test-value (cdr eshell-test-value) }")
"(1 2)\n(2)\n"))))
+(ert-deftest esh-cmd-test/while-loop-lisp-form ()
+ "Test invocation of a while loop using a Lisp form."
+ (with-temp-eshell
+ (let ((eshell-test-value 0))
+ (eshell-command-result-p
+ (concat "while (/= eshell-test-value 3) "
+ "{ setq eshell-test-value (1+ eshell-test-value) }")
+ "1\n2\n3\n"))))
+
(ert-deftest esh-cmd-test/while-loop-ext-cmd ()
"Test invocation of a while loop using an external command."
(skip-unless (executable-find "["))
"{ setq eshell-test-value t }")
"t\n"))))
+(ert-deftest esh-cmd-test/until-loop-lisp-form ()
+ "Test invocation of an until loop using a Lisp form."
+ (skip-unless (executable-find "["))
+ (with-temp-eshell
+ (let ((eshell-test-value 0))
+ (eshell-command-result-p
+ (concat "until (= eshell-test-value 3) "
+ "{ setq eshell-test-value (1+ eshell-test-value) }")
+ "1\n2\n3\n"))))
+
(ert-deftest esh-cmd-test/until-loop-ext-cmd ()
"Test invocation of an until loop using an external command."
(skip-unless (executable-find "["))
(eshell-command-result-p "if $eshell-test-value {echo yes} {echo no}"
"no\n"))))
+(ert-deftest esh-cmd-test/if-else-statement-lisp-form ()
+ "Test invocation of an if/else statement using a Lisp form."
+ (with-temp-eshell
+ (eshell-command-result-p "if (zerop 0) {echo yes} {echo no}"
+ "yes\n")
+ (eshell-command-result-p "if (zerop 1) {echo yes} {echo no}"
+ "no\n")
+ (let ((debug-on-error nil))
+ (eshell-command-result-p "if (zerop \"foo\") {echo yes} {echo no}"
+ "no\n"))))
+
+(ert-deftest esh-cmd-test/if-else-statement-lisp-form-2 ()
+ "Test invocation of an if/else statement using a Lisp form.
+This tests when `eshell-lisp-form-nil-is-failure' is nil."
+ (let ((eshell-lisp-form-nil-is-failure nil))
+ (with-temp-eshell
+ (eshell-command-result-p "if (zerop 0) {echo yes} {echo no}"
+ "yes\n")
+ (eshell-command-result-p "if (zerop 1) {echo yes} {echo no}"
+ "yes\n")
+ (let ((debug-on-error nil))
+ (eshell-command-result-p "if (zerop \"foo\") {echo yes} {echo no}"
+ "no\n")))))
+
(ert-deftest esh-cmd-test/if-else-statement-ext-cmd ()
"Test invocation of an if/else statement using an external command."
(skip-unless (executable-find "["))
(eshell-command-result-p "unless $eshell-test-value {echo no} {echo yes}"
"no\n"))))
+(ert-deftest esh-cmd-test/unless-else-statement-lisp-form ()
+ "Test invocation of an unless/else statement using a Lisp form."
+ (with-temp-eshell
+ (eshell-command-result-p "unless (zerop 0) {echo no} {echo yes}"
+ "yes\n")
+ (eshell-command-result-p "unless (zerop 1) {echo no} {echo yes}"
+ "no\n")
+ (let ((debug-on-error nil))
+ (eshell-command-result-p "unless (zerop \"foo\") {echo no} {echo yes}"
+ "no\n"))))
+
(ert-deftest esh-cmd-test/unless-else-statement-ext-cmd ()
"Test invocation of an unless/else statement using an external command."
(skip-unless (executable-find "["))
(eshell-command-result-p "echo $INSIDE_EMACS[, 1]"
"eshell")))
+(ert-deftest esh-var-test/last-status-var-lisp-command ()
+ "Test using the \"last exit status\" ($?) variable with a Lisp command"
+ (with-temp-eshell
+ (eshell-command-result-p "zerop 0; echo $?"
+ "t\n0\n")
+ (eshell-command-result-p "zerop 1; echo $?"
+ "0\n")
+ (let ((debug-on-error nil))
+ (eshell-command-result-p "zerop foo; echo $?"
+ "1\n"))))
+
+(ert-deftest esh-var-test/last-status-var-lisp-form ()
+ "Test using the \"last exit status\" ($?) variable with a Lisp form"
+ (let ((eshell-lisp-form-nil-is-failure t))
+ (with-temp-eshell
+ (eshell-command-result-p "(zerop 0); echo $?"
+ "t\n0\n")
+ (eshell-command-result-p "(zerop 1); echo $?"
+ "2\n")
+ (let ((debug-on-error nil))
+ (eshell-command-result-p "(zerop \"foo\"); echo $?"
+ "1\n")))))
+
+(ert-deftest esh-var-test/last-status-var-lisp-form-2 ()
+ "Test using the \"last exit status\" ($?) variable with a Lisp form.
+This tests when `eshell-lisp-form-nil-is-failure' is nil."
+ (let ((eshell-lisp-form-nil-is-failure nil))
+ (with-temp-eshell
+ (eshell-command-result-p "(zerop 0); echo $?"
+ "0\n")
+ (eshell-command-result-p "(zerop 0); echo $?"
+ "0\n")
+ (let ((debug-on-error nil))
+ (eshell-command-result-p "(zerop \"foo\"); echo $?"
+ "1\n")))))
+
+(ert-deftest esh-var-test/last-status-var-ext-cmd ()
+ "Test using the \"last exit status\" ($?) variable with an external command"
+ (skip-unless (executable-find "["))
+ (with-temp-eshell
+ (eshell-command-result-p "[ foo = foo ]; echo $?"
+ "0\n")
+ (eshell-command-result-p "[ foo = bar ]; echo $?"
+ "1\n")))
+
(ert-deftest esh-var-test/last-result-var ()
"Test using the \"last result\" ($$) variable"
(with-temp-eshell
(eshell-command-result-p "+ 1 2; + $$ 2"
"3\n5\n")))
-(ert-deftest esh-var-test/last-result-var2 ()
+(ert-deftest esh-var-test/last-result-var-twice ()
"Test using the \"last result\" ($$) variable twice"
(with-temp-eshell
(eshell-command-result-p "+ 1 2; + $$ $$"
"3\n6\n")))
+(ert-deftest esh-var-test/last-result-var-ext-cmd ()
+ "Test using the \"last result\" ($$) variable with an external command"
+ (skip-unless (executable-find "["))
+ (with-temp-eshell
+ (eshell-command-result-p "[ foo = foo ]; format \"%s\" $$"
+ "t\n")
+ (eshell-command-result-p "[ foo = bar ]; format \"%s\" $$"
+ "nil\n")))
+
(ert-deftest esh-var-test/last-result-var-split-indices ()
"Test using the \"last result\" ($$) variable with split indices"
(with-temp-eshell