From 2b0d2793730567dd1d6d5c425d73238303c1d5de Mon Sep 17 00:00:00 2001 From: Craig Jennings Date: Sat, 13 Jun 2026 13:51:56 -0500 Subject: fix(dwim-shell): valid zip name, real backup timestamp, reachable menu key Three audit fixes. Single-file zip named the archive after the input file (<>.<>), making invalid archives and a "foo." name for directories. It now builds .zip. The dated backup single-quoted $(date ...), so the stamp sat literal in the filename. Now the timestamp is interpolated in Elisp with format-time-string. The dired menu used "M-S-d", which Meta-Shift-d never emits (it sends M-D), so the menu was unreachable in plain dired. It now binds M-D, matching the dirvish sibling. Both command strings moved to top-level builders so they're unit-testable without loading the dwim-shell-command package. --- modules/dirvish-config.el | 2 +- modules/dwim-shell-config.el | 20 ++++++++++++---- tests/test-dwim-shell-config-command-fixes.el | 33 +++++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 5 deletions(-) create mode 100644 tests/test-dwim-shell-config-command-fixes.el diff --git a/modules/dirvish-config.el b/modules/dirvish-config.el index 29ba2ebac..79d6ff41c 100644 --- a/modules/dirvish-config.el +++ b/modules/dirvish-config.el @@ -27,7 +27,7 @@ ;; - p: Copy absolute file path ;; - P: Print the file at point via CUPS ;; - S: Study — start an org-drill session on the .org file at point -;; - M-S-d (Meta-Shift-d): DWIM shell commands menu +;; - M-D (Meta-Shift-d): DWIM shell commands menu ;; - TAB: Toggle subtree expansion ;; - F11: Toggle sidebar view diff --git a/modules/dwim-shell-config.el b/modules/dwim-shell-config.el index 655c8d852..ad17ea913 100644 --- a/modules/dwim-shell-config.el +++ b/modules/dwim-shell-config.el @@ -198,6 +198,18 @@ file list." (replace-regexp-in-string "'" "'\\\\''" (expand-file-name f)))) files "\n")) +(defun cj/dwim-shell--zip-single-file-command () + "Return the zip command template for a single marked file. +The archive is named =.zip=, not a reconstruction of the input filename +\(which produced invalid archives, and a `foo.' name for a directory)." + "zip -r '<>.zip' '<>'") + +(defun cj/dwim-shell--dated-backup-command () + "Return the cp command template for a timestamped backup of marked file(s). +The timestamp is interpolated here with `format-time-string' so it can't sit +dead inside the shell's single quotes the way a literal =$(date ...)= did." + (format "cp -p '<>' '<>.%s.bak'" (format-time-string "%Y%m%d_%H%M%S"))) + ;; ----------------------------- Dwim Shell Command ---------------------------- (use-package dwim-shell-command @@ -337,7 +349,7 @@ Otherwise, unzip it to an appropriately named subdirectory " (interactive) (dwim-shell-command-on-marked-files "Zip" (if (eq 1 (seq-length (dwim-shell-command--files))) - "zip -r '<>.<>' '<>'" + (cj/dwim-shell--zip-single-file-command) "zip -r '<>' '<<*>>'") :utils "zip")) @@ -547,8 +559,8 @@ clipboard contents cannot inject shell commands." (interactive) (dwim-shell-command-on-marked-files "Backup with date" - "cp -p '<>' '<>.$(date +%Y%m%d_%H%M%S).bak'" - :utils '("cp" "date"))) + (cj/dwim-shell--dated-backup-command) + :utils '("cp"))) (defun cj/dwim-shell-commands-optimize-image-for-web () "Optimize image(s) for web (reduce file size)." @@ -930,7 +942,7 @@ gpg: decryption failed: No pinentry" ;; Bind menu to keymaps after function is defined (with-eval-after-load 'dired - (keymap-set dired-mode-map "M-S-d" #'dwim-shell-commands-menu)) ;; was M-D, overrides kill-word + (keymap-set dired-mode-map "M-D" #'dwim-shell-commands-menu)) ;; Meta-Shift-d; matches the dirvish binding below (with-eval-after-load 'dirvish (keymap-set dirvish-mode-map "M-D" #'dwim-shell-commands-menu))) diff --git a/tests/test-dwim-shell-config-command-fixes.el b/tests/test-dwim-shell-config-command-fixes.el new file mode 100644 index 000000000..2f49a868f --- /dev/null +++ b/tests/test-dwim-shell-config-command-fixes.el @@ -0,0 +1,33 @@ +;;; test-dwim-shell-config-command-fixes.el --- zip/backup command builders -*- lexical-binding: t; -*- + +;;; Commentary: +;; Two audit fixes, extracted into top-level command-string builders so they're +;; testable without loading the dwim-shell-command package (the command defuns +;; that call them live inside its use-package :config, which the batch test +;; harness doesn't instantiate): +;; - cj/dwim-shell--zip-single-file-command names the archive .zip +;; - cj/dwim-shell--dated-backup-command carries a real timestamp, not "$(date)" +;; The third fix (dired menu key M-S-d -> M-D) is a keybinding inside the same +;; :config block; it's verified in the live daemon, not here. + +;;; Code: + +(require 'ert) +(require 'dwim-shell-config) + +(ert-deftest test-dwim-zip-single-file-command-names-archive-dot-zip () + "Normal: the single-file zip template names the archive .zip, with no +leftover <> that would rebuild the input filename." + (let ((cmd (cj/dwim-shell--zip-single-file-command))) + (should (string-match-p "'<>\\.zip'" cmd)) + (should-not (string-match-p "<>" cmd)))) + +(ert-deftest test-dwim-dated-backup-command-carries-real-timestamp () + "Normal: the dated-backup template interpolates a real YYYYMMDD_HHMMSS stamp, +so the substitution can't sit dead inside single quotes." + (let ((cmd (cj/dwim-shell--dated-backup-command))) + (should (string-match-p "\\.[0-9]\\{8\\}_[0-9]\\{6\\}\\.bak'" cmd)) + (should-not (string-match-p "\\$(date" cmd)))) + +(provide 'test-dwim-shell-config-command-fixes) +;;; test-dwim-shell-config-command-fixes.el ends here -- cgit v1.2.3