summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--modules/mail-config.el19
-rw-r--r--modules/system-lib.el19
-rw-r--r--tests/test-system-lib-executable-find-or-warn.el80
3 files changed, 104 insertions, 14 deletions
diff --git a/modules/mail-config.el b/modules/mail-config.el
index f19adf80..7a41e942 100644
--- a/modules/mail-config.el
+++ b/modules/mail-config.el
@@ -25,6 +25,7 @@
;;; Code:
(require 'user-constants)
+(require 'system-lib)
;; cj/custom-keymap's real binding is in keybindings.el, which init.el loads
;; first. The use-package org-msg :preface below wraps in eval-and-compile, so
@@ -63,27 +64,17 @@ transport details in debug buffers."
(interactive)
(cj/set-smtpmail-debug (not smtpmail-debug-info)))
-(defun cj/mail--executable-or-warn (program feature)
- "Return PROGRAM's executable path, or warn that FEATURE is unavailable."
- (or (executable-find program)
- (progn
- (display-warning
- 'mail-config
- (format "%s not found; %s unavailable" program feature)
- :warning)
- nil)))
-
(defun cj/mail--mbsync-command ()
"Return the mu4e mail sync command, or nil if mbsync is unavailable."
- (when-let ((mbsync (cj/mail--executable-or-warn
- "mbsync" "mu4e mail synchronization")))
+ (when-let ((mbsync (cj/executable-find-or-warn
+ "mbsync" "mu4e mail synchronization" 'mail-config)))
(concat (shell-quote-argument mbsync) " -a")))
(defun cj/mail-configure-smtpmail ()
"Configure SMTP mail transport when msmtp is available."
(setq smtpmail-debug-info cj/smtpmail-debug-enabled)
- (if-let ((msmtp (cj/mail--executable-or-warn
- "msmtp" "SMTP mail sending")))
+ (if-let ((msmtp (cj/executable-find-or-warn
+ "msmtp" "SMTP mail sending" 'mail-config)))
(setq sendmail-program msmtp
send-mail-function 'message-send-mail-with-sendmail
message-send-mail-function 'message-send-mail-with-sendmail
diff --git a/modules/system-lib.el b/modules/system-lib.el
index 4c2f17ef..f932353f 100644
--- a/modules/system-lib.el
+++ b/modules/system-lib.el
@@ -17,6 +17,25 @@ PROGRAM should be a string naming an executable program."
(not (string-empty-p program))
(executable-find program)))
+(defun cj/executable-find-or-warn (program feature &optional group)
+ "Return PROGRAM's executable path, or warn that FEATURE is unavailable.
+
+When PROGRAM is in PATH, return its absolute path. When it isn't,
+emit a `display-warning' naming PROGRAM and FEATURE so the user gets
+a clear hint about what won't work, and return nil.
+
+GROUP is the symbol passed to `display-warning' for filtering and
+defaults to `cj/system-lib'. Callers should pass their own module
+symbol (for example `mail-config') so per-feature warning filters
+keep working."
+ (or (executable-find program)
+ (progn
+ (display-warning
+ (or group 'cj/system-lib)
+ (format "%s not found; %s unavailable" program feature)
+ :warning)
+ nil)))
+
(defun cj/log-silently (format-string &rest args)
"Append formatted message (FORMAT-STRING with ARGS) to *Messages* buffer.
This does so without echoing in the minibuffer."
diff --git a/tests/test-system-lib-executable-find-or-warn.el b/tests/test-system-lib-executable-find-or-warn.el
new file mode 100644
index 00000000..e9a540bd
--- /dev/null
+++ b/tests/test-system-lib-executable-find-or-warn.el
@@ -0,0 +1,80 @@
+;;; test-system-lib-executable-find-or-warn.el --- Tests for cj/executable-find-or-warn -*- lexical-binding: t; -*-
+
+;;; Commentary:
+;; `cj/executable-find-or-warn' generalizes the mail-specific
+;; `cj/mail--executable-or-warn' pattern. Returns the program's
+;; executable path when found. When the program is missing, returns
+;; nil AND emits a `display-warning' naming the feature that's
+;; unavailable, so the user gets a clear hint about what won't work.
+;; The optional GROUP argument controls `display-warning' grouping so
+;; per-feature filters keep working.
+
+;;; Code:
+
+(require 'ert)
+(require 'cl-lib)
+
+(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory))
+(require 'system-lib)
+
+(defmacro test-system-lib-find-or-warn--with-stubs (executable-fn warning-capture &rest body)
+ "Run BODY with `executable-find' and `display-warning' stubbed.
+
+EXECUTABLE-FN replaces `executable-find' (takes a string program name,
+returns path or nil). WARNING-CAPTURE is a symbol bound in BODY's scope
+to a list of (TYPE MESSAGE LEVEL) entries collected from
+`display-warning' calls."
+ (declare (indent 2))
+ `(let ((,warning-capture nil))
+ (cl-letf (((symbol-function 'executable-find) ,executable-fn)
+ ((symbol-function 'display-warning)
+ (lambda (type message &optional level)
+ (push (list type message level) ,warning-capture))))
+ ,@body)))
+
+(ert-deftest test-cj-executable-find-or-warn-program-found-returns-path ()
+ "Normal: when the program exists, return its path and emit no warning."
+ (test-system-lib-find-or-warn--with-stubs
+ (lambda (prog) (when (string= prog "mbsync") "/usr/bin/mbsync"))
+ warnings
+ (let ((result (cj/executable-find-or-warn "mbsync" "mu4e mail sync")))
+ (should (equal result "/usr/bin/mbsync"))
+ (should-not warnings))))
+
+(ert-deftest test-cj-executable-find-or-warn-program-missing-returns-nil ()
+ "Normal: when the program is missing, return nil."
+ (test-system-lib-find-or-warn--with-stubs
+ (lambda (_prog) nil)
+ _warnings
+ (should-not (cj/executable-find-or-warn "missing-prog" "fake feature"))))
+
+(ert-deftest test-cj-executable-find-or-warn-missing-emits-warning ()
+ "Normal: a missing program produces a warning that names PROGRAM and FEATURE."
+ (test-system-lib-find-or-warn--with-stubs
+ (lambda (_prog) nil)
+ warnings
+ (cj/executable-find-or-warn "missing-prog" "fake feature")
+ (should (= 1 (length warnings)))
+ (let ((entry (car warnings)))
+ (should (string-match-p "missing-prog" (nth 1 entry)))
+ (should (string-match-p "fake feature" (nth 1 entry)))
+ (should (eq :warning (nth 2 entry))))))
+
+(ert-deftest test-cj-executable-find-or-warn-default-group ()
+ "Boundary: with no GROUP arg, the warning uses the default `cj/system-lib' symbol."
+ (test-system-lib-find-or-warn--with-stubs
+ (lambda (_prog) nil)
+ warnings
+ (cj/executable-find-or-warn "missing-prog" "feature")
+ (should (eq 'cj/system-lib (car (car warnings))))))
+
+(ert-deftest test-cj-executable-find-or-warn-explicit-group ()
+ "Normal: an explicit GROUP arg flows through to `display-warning'."
+ (test-system-lib-find-or-warn--with-stubs
+ (lambda (_prog) nil)
+ warnings
+ (cj/executable-find-or-warn "missing-prog" "feature" 'mail-config)
+ (should (eq 'mail-config (car (car warnings))))))
+
+(provide 'test-system-lib-executable-find-or-warn)
+;;; test-system-lib-executable-find-or-warn.el ends here