diff options
| -rw-r--r-- | modules/dirvish-config.el | 48 | ||||
| -rw-r--r-- | tests/test-dirvish-config-print.el | 134 |
2 files changed, 181 insertions, 1 deletions
diff --git a/modules/dirvish-config.el b/modules/dirvish-config.el index bf91ae2e..b25baca9 100644 --- a/modules/dirvish-config.el +++ b/modules/dirvish-config.el @@ -245,6 +245,52 @@ Examples: (message "Duplicated: %s → %s" (file-name-nondirectory file) new-name)))) +;;; ------------------------------ Dirvish Print File --------------------------- + +(defvar cj/dirvish-print-extensions + '("pdf" "ps" "eps" "txt" "text" "org" "md" "markdown" "log" "conf" + "el" "py" "sh" "c" "h" "json" "yaml" "yml" "csv" "tex" + "png" "jpg" "jpeg" "gif") + "File extensions `cj/dirvish-print-file' will send to the printer. +Matched case-insensitively. CUPS filters handle each of these directly, +so PDFs and images print without a separate dialog.") + +(defun cj/--printable-file-p (file) + "Return non-nil when FILE's extension is in `cj/dirvish-print-extensions'. +Match is case-insensitive; a file with no extension is not printable. +Pure helper used by `cj/dirvish-print-file'." + (when-let* ((ext (file-name-extension file))) + (and (member (downcase ext) cj/dirvish-print-extensions) t))) + +(defun cj/--print-program () + "Return the CUPS print command (`lp' preferred, `lpr' as fallback), or nil." + (or (executable-find "lp") (executable-find "lpr"))) + +(defun cj/dirvish-print-file () + "Print the file at point on the default printer via CUPS (`lp'/`lpr'). +Refuses directories and file types not in `cj/dirvish-print-extensions'. +Shadows dired's `P' (`dired-do-print') with this type-aware version." + (interactive) + (let ((file (dired-get-filename nil t))) + (unless file + (user-error "No file at point")) + (when (file-directory-p file) + (user-error "Cannot print directories")) + (unless (cj/--printable-file-p file) + (user-error "Not a printable file type: %s" (file-name-nondirectory file))) + (let ((program (or (cj/--print-program) + (user-error "No `lp' or `lpr' found — is CUPS installed?"))) + (name (file-name-nondirectory file))) + (when (y-or-n-p (format "Print %s on the default printer? " name)) + (with-temp-buffer + (let* ((code (call-process program nil t nil file)) + (out (string-trim (buffer-string)))) + (if (zerop code) + (message "Printing %s%s" name + (if (string-empty-p out) "" (concat " — " out))) + (user-error "Print failed (exit %d)%s" code + (if (string-empty-p out) "" (concat ": " out)))))))))) + ;;; ----------------------- Dirvish Open File Manager Here ---------------------- (defun cj/dirvish-open-file-manager-here () @@ -421,7 +467,7 @@ Uses feh on X11, swww on Wayland." ("o" . cj/xdg-open) ("O" . cj/open-file-with-command) ; Prompts for command to run ("p" . (lambda () (interactive) (cj/dired-copy-path-as-kill nil t))) - ("P" . (lambda () (interactive) (cj/dired-copy-path-as-kill))) + ("P" . cj/dirvish-print-file) ("r" . dirvish-rsync) ("s" . dirvish-quicksort) ("v" . dirvish-vc-menu) diff --git a/tests/test-dirvish-config-print.el b/tests/test-dirvish-config-print.el new file mode 100644 index 00000000..ab6d073f --- /dev/null +++ b/tests/test-dirvish-config-print.el @@ -0,0 +1,134 @@ +;;; test-dirvish-config-print.el --- Tests for the dirvish print command -*- lexical-binding: t; -*- + +;;; Commentary: +;; `cj/dirvish-print-file' (bound to `P' in `dirvish-mode-map') sends the +;; file at point to the default printer via CUPS (`lp', falling back to +;; `lpr'). It refuses directories and file types outside +;; `cj/dirvish-print-extensions'. These tests cover the pure predicates +;; (`cj/--printable-file-p', `cj/--print-program') directly and the command +;; with `dired-get-filename', `y-or-n-p', and `call-process' mocked. + +;;; Code: + +(require 'ert) +(require 'package) + +(setq package-user-dir (expand-file-name "elpa" user-emacs-directory)) +(package-initialize) +(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory)) +(add-to-list 'load-path (expand-file-name "elpa/dirvish-2.3.0/extensions" + user-emacs-directory)) +(require 'user-constants) +(require 'keybindings) +(require 'dirvish-config) + +;;; --------------------------- cj/--printable-file-p -------------------------- + +(ert-deftest test-dirvish-print-printable-file-p-known-extensions () + "Normal: PDF, text, and org files are printable." + (should (cj/--printable-file-p "/tmp/report.pdf")) + (should (cj/--printable-file-p "/tmp/notes.txt")) + (should (cj/--printable-file-p "/tmp/agenda.org"))) + +(ert-deftest test-dirvish-print-printable-file-p-case-insensitive () + "Boundary: extension matching ignores case." + (should (cj/--printable-file-p "/tmp/REPORT.PDF")) + (should (cj/--printable-file-p "/tmp/Notes.Txt"))) + +(ert-deftest test-dirvish-print-printable-file-p-no-extension () + "Boundary: a file with no extension is not printable." + (should-not (cj/--printable-file-p "/tmp/README")) + (should-not (cj/--printable-file-p "/home/cj/.bashrc"))) + +(ert-deftest test-dirvish-print-printable-file-p-non-printable-extension () + "Error: a video or archive is not printable." + (should-not (cj/--printable-file-p "/tmp/clip.mp4")) + (should-not (cj/--printable-file-p "/tmp/archive.tar.gz"))) + +;;; ----------------------------- cj/--print-program --------------------------- + +(ert-deftest test-dirvish-print-program-prefers-lp () + "Normal: `lp' is used when available." + (cl-letf (((symbol-function 'executable-find) + (lambda (cmd) (when (equal cmd "lp") "/usr/bin/lp")))) + (should (equal (cj/--print-program) "/usr/bin/lp")))) + +(ert-deftest test-dirvish-print-program-falls-back-to-lpr () + "Boundary: `lpr' is used when `lp' is missing." + (cl-letf (((symbol-function 'executable-find) + (lambda (cmd) (when (equal cmd "lpr") "/usr/bin/lpr")))) + (should (equal (cj/--print-program) "/usr/bin/lpr")))) + +(ert-deftest test-dirvish-print-program-none-available () + "Error: nil when neither `lp' nor `lpr' is on PATH." + (cl-letf (((symbol-function 'executable-find) (lambda (_cmd) nil))) + (should-not (cj/--print-program)))) + +;;; ---------------------------- cj/dirvish-print-file ------------------------- + +(defmacro test-dirvish-print--with-file-at-point (file &rest body) + "Run BODY with `dired-get-filename' returning FILE." + (declare (indent 1)) + `(cl-letf (((symbol-function 'dired-get-filename) (lambda (&rest _) ,file))) + ,@body)) + +(ert-deftest test-dirvish-print-file-sends-printable-file-to-printer () + "Normal: confirming the prompt runs the print program on the file." + (let (called) + (test-dirvish-print--with-file-at-point "/tmp/report.pdf" + (cl-letf (((symbol-function 'cj/--print-program) (lambda () "/usr/bin/lp")) + ((symbol-function 'y-or-n-p) (lambda (&rest _) t)) + ((symbol-function 'call-process) + (lambda (program _infile _dest _display &rest args) + (setq called (cons program args)) + 0)) + ((symbol-function 'message) (lambda (&rest _) nil))) + (cj/dirvish-print-file) + (should (equal called '("/usr/bin/lp" "/tmp/report.pdf"))))))) + +(ert-deftest test-dirvish-print-file-declining-the-prompt-prints-nothing () + "Boundary: answering no to the prompt does not invoke the print program." + (let (called) + (test-dirvish-print--with-file-at-point "/tmp/report.pdf" + (cl-letf (((symbol-function 'cj/--print-program) (lambda () "/usr/bin/lp")) + ((symbol-function 'y-or-n-p) (lambda (&rest _) nil)) + ((symbol-function 'call-process) + (lambda (&rest _) (setq called t) 0))) + (cj/dirvish-print-file) + (should-not called))))) + +(ert-deftest test-dirvish-print-file-errors-without-file () + "Error: no file at point fails clearly." + (test-dirvish-print--with-file-at-point nil + (should-error (cj/dirvish-print-file) :type 'user-error))) + +(ert-deftest test-dirvish-print-file-errors-on-directory () + "Error: a directory is not printable." + (test-dirvish-print--with-file-at-point temporary-file-directory + (should-error (cj/dirvish-print-file) :type 'user-error))) + +(ert-deftest test-dirvish-print-file-errors-on-non-printable-type () + "Error: a non-printable file type is refused before any prompt." + (test-dirvish-print--with-file-at-point "/tmp/clip.mp4" + (should-error (cj/dirvish-print-file) :type 'user-error))) + +(ert-deftest test-dirvish-print-file-errors-without-print-program () + "Error: no `lp'/`lpr' on PATH fails clearly." + (test-dirvish-print--with-file-at-point "/tmp/report.pdf" + (cl-letf (((symbol-function 'cj/--print-program) (lambda () nil))) + (should-error (cj/dirvish-print-file) :type 'user-error)))) + +(ert-deftest test-dirvish-print-file-errors-when-print-command-fails () + "Error: a non-zero print exit code surfaces as a `user-error'." + (test-dirvish-print--with-file-at-point "/tmp/report.pdf" + (cl-letf (((symbol-function 'cj/--print-program) (lambda () "/usr/bin/lp")) + ((symbol-function 'y-or-n-p) (lambda (&rest _) t)) + ((symbol-function 'call-process) (lambda (&rest _) 1))) + (should-error (cj/dirvish-print-file) :type 'user-error)))) + +(ert-deftest test-dirvish-print-file-bound-to-uppercase-p () + "Normal: `P' in `dirvish-mode-map' runs the print command." + (should (eq (keymap-lookup dirvish-mode-map "P") #'cj/dirvish-print-file))) + +(provide 'test-dirvish-config-print) +;;; test-dirvish-config-print.el ends here |
