diff options
| author | Craig Jennings <c@cjennings.net> | 2026-06-20 16:14:38 -0400 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2026-06-20 16:14:38 -0400 |
| commit | f93b461565c49a53c94c4dc218307c9b8f56675e (patch) | |
| tree | fcd9c2508bc4cca12b930718558de38c646efca6 | |
| parent | 2c910e012e2d1f7e118add072cf3468d086cbaac (diff) | |
| download | dotemacs-f93b461565c49a53c94c4dc218307c9b8f56675e.tar.gz dotemacs-f93b461565c49a53c94c4dc218307c9b8f56675e.zip | |
refactor(dwim-shell): extract the branching command-string builders
Lift the command-string construction out of three :config commands whose
templates branch — video-trim (Beginning/End/Both), tar-gzip (single vs multi),
text-to-speech (darwin say vs espeak) — into top-level pure builders
cj/dwim-shell--video-trim-command / --tar-gzip-command / --text-to-speech-command,
leaving thin interactive wrappers that prompt and delegate. The builders are now
testable under make test (the :config defuns aren't), mirroring the existing
dated-backup/zip-single builders. Adds 8 Normal/Boundary/Error tests.
| -rw-r--r-- | modules/dwim-shell-config.el | 76 | ||||
| -rw-r--r-- | tests/test-dwim-shell-config-command-fixes.el | 55 |
2 files changed, 101 insertions, 30 deletions
diff --git a/modules/dwim-shell-config.el b/modules/dwim-shell-config.el index ad17ea913..230a8532c 100644 --- a/modules/dwim-shell-config.el +++ b/modules/dwim-shell-config.el @@ -210,6 +210,41 @@ 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 '<<f>>' '<<f>>.%s.bak'" (format-time-string "%Y%m%d_%H%M%S"))) +(defun cj/dwim-shell--tar-gzip-command (single-p) + "Return the tar-gzip command template. +SINGLE-P non-nil names the archive after the lone file (=<fne>.tar.gz=); +otherwise a shared =archive.tar.gz= over all marked files." + (if single-p + "tar czf '<<fne>>.tar.gz' '<<f>>'" + "tar czf '<<archive.tar.gz(u)>>' '<<*>>'")) + +(defun cj/dwim-shell--text-to-speech-command (system voice) + "Return the text-to-speech command template for SYSTEM using VOICE. +SYSTEM is a `system-type' symbol: `darwin' synthesizes with `say' and VOICE; +any other system uses `espeak' (VOICE unused)." + (if (eq system 'darwin) + (format "say -v %s -o '<<fne>>.aiff' -f '<<f>>'" voice) + "espeak -f '<<f>>' -w '<<fne>>.wav'")) + +(defun cj/dwim-shell--video-trim-command (trim-type start end) + "Return the ffmpeg video-trim command template for TRIM-TYPE. +TRIM-TYPE is \"Beginning\", \"End\", or \"Both\". START trims that many +seconds off the front, END off the back (each ignored for the side it does +not apply to). Signals a `user-error' when a used second count is negative." + (pcase trim-type + ("Beginning" + (when (< start 0) (user-error "Seconds must be non-negative")) + (format "ffmpeg -i '<<f>>' -y -ss %d -c:v copy -c:a copy '<<fne>>_trimmed.<<e>>'" + start)) + ("End" + (when (< end 0) (user-error "Seconds must be non-negative")) + (format "ffmpeg -sseof -%d -i '<<f>>' -y -c:v copy -c:a copy '<<fne>>_trimmed.<<e>>'" + end)) + ("Both" + (when (or (< start 0) (< end 0)) (user-error "Seconds must be non-negative")) + (format "ffmpeg -i '<<f>>' -y -ss %d -sseof -%d -c:v copy -c:a copy '<<fne>>_trimmed.<<e>>'" + start end)))) + ;; ----------------------------- Dwim Shell Command ---------------------------- (use-package dwim-shell-command @@ -357,9 +392,8 @@ Otherwise, unzip it to an appropriately named subdirectory " "Tar gzip all marked files into archive.tar.gz." (interactive) (dwim-shell-command-on-marked-files - "Tar gzip" (if (eq 1 (seq-length (dwim-shell-command--files))) - "tar czf '<<fne>>.tar.gz' '<<f>>'" - "tar czf '<<archive.tar.gz(u)>>' '<<*>>'") + "Tar gzip" (cj/dwim-shell--tar-gzip-command + (eq 1 (seq-length (dwim-shell-command--files)))) :utils "tar")) (defun cj/dwim-shell-commands-epub-to-org () @@ -448,34 +482,18 @@ process list, and the file is removed only after the spawned process exits." "Trim video with options for beginning, end, or both." (interactive) (let* ((trim-type (completing-read "Trim from: " - '("Beginning" "End" "Both") - nil t)) - (command (pcase trim-type - ("Beginning" - (let ((seconds (read-number "Seconds to trim from beginning: " 5))) - (when (< seconds 0) - (user-error "Seconds must be non-negative")) - (format "ffmpeg -i '<<f>>' -y -ss %d -c:v copy -c:a copy '<<fne>>_trimmed.<<e>>'" - seconds))) - ("End" - (let ((seconds (read-number "Seconds to trim from end: " 5))) - (when (< seconds 0) - (user-error "Seconds must be non-negative")) - (format "ffmpeg -sseof -%d -i '<<f>>' -y -c:v copy -c:a copy '<<fne>>_trimmed.<<e>>'" - seconds))) - ("Both" - (let ((start (read-number "Seconds to trim from beginning: " 5)) - (end (read-number "Seconds to trim from end: " 5))) - (when (or (< start 0) (< end 0)) - (user-error "Seconds must be non-negative")) - (format "ffmpeg -i '<<f>>' -y -ss %d -sseof -%d -c:v copy -c:a copy '<<fne>>_trimmed.<<e>>'" - start end)))))) - (dwim-shell-command-on-marked-files + '("Beginning" "End" "Both") + nil t)) + (start (if (member trim-type '("Beginning" "Both")) + (read-number "Seconds to trim from beginning: " 5) 0)) + (end (if (member trim-type '("End" "Both")) + (read-number "Seconds to trim from end: " 5) 0)) + (command (cj/dwim-shell--video-trim-command trim-type start end))) + (dwim-shell-command-on-marked-files (format "Trim video (%s)" trim-type) command :silent-success t :utils "ffmpeg"))) - (defun cj/dwim-shell-commands-drop-audio-from-video () "Drop audio from all marked videos." (interactive) @@ -694,9 +712,7 @@ all marked files rather than once per file." "en"))) (dwim-shell-command-on-marked-files "Text to speech" - (if (eq system-type 'darwin) - (format "say -v %s -o '<<fne>>.aiff' -f '<<f>>'" voice) - "espeak -f '<<f>>' -w '<<fne>>.wav'") + (cj/dwim-shell--text-to-speech-command system-type voice) :utils (if (eq system-type 'darwin) "say" "espeak")))) (defun cj/dwim-shell-commands-remove-empty-directories () diff --git a/tests/test-dwim-shell-config-command-fixes.el b/tests/test-dwim-shell-config-command-fixes.el index 2f49a868f..2cc3ae72b 100644 --- a/tests/test-dwim-shell-config-command-fixes.el +++ b/tests/test-dwim-shell-config-command-fixes.el @@ -29,5 +29,60 @@ so the substitution can't sit dead inside single quotes." (should (string-match-p "\\.[0-9]\\{8\\}_[0-9]\\{6\\}\\.bak'" cmd)) (should-not (string-match-p "\\$(date" cmd)))) +;;; ----------------------- tar-gzip command builder -------------------------- + +(ert-deftest test-dwim-tar-gzip-command-single-names-after-file () + "Normal: a single marked file names the archive <fne>.tar.gz over <<f>>." + (let ((cmd (cj/dwim-shell--tar-gzip-command t))) + (should (string-match-p "'<<fne>>\\.tar\\.gz'" cmd)) + (should (string-match-p "'<<f>>'" cmd)))) + +(ert-deftest test-dwim-tar-gzip-command-multi-uses-shared-archive () + "Boundary: multiple files tar into a shared archive.tar.gz over <<*>>." + (let ((cmd (cj/dwim-shell--tar-gzip-command nil))) + (should (string-match-p "archive\\.tar\\.gz" cmd)) + (should (string-match-p "'<<\\*>>'" cmd)))) + +;;; --------------------- text-to-speech command builder ---------------------- + +(ert-deftest test-dwim-text-to-speech-command-darwin-uses-say-voice () + "Normal: on darwin the command uses `say' with the chosen voice." + (let ((cmd (cj/dwim-shell--text-to-speech-command 'darwin "Samantha"))) + (should (string-match-p "\\`say -v Samantha " cmd)) + (should (string-match-p "'<<fne>>\\.aiff'" cmd)))) + +(ert-deftest test-dwim-text-to-speech-command-linux-uses-espeak () + "Boundary: a non-darwin system uses `espeak' and ignores the voice." + (let ((cmd (cj/dwim-shell--text-to-speech-command 'gnu/linux "ignored"))) + (should (string-match-p "\\`espeak " cmd)) + (should (string-match-p "'<<fne>>\\.wav'" cmd)) + (should-not (string-match-p "ignored" cmd)))) + +;;; ----------------------- video-trim command builder ------------------------ + +(ert-deftest test-dwim-video-trim-command-beginning-uses-ss () + "Normal: trimming the beginning emits a leading -ss with the start seconds." + (let ((cmd (cj/dwim-shell--video-trim-command "Beginning" 7 0))) + (should (string-match-p "-ss 7 " cmd)) + (should-not (string-match-p "-sseof" cmd)))) + +(ert-deftest test-dwim-video-trim-command-end-uses-sseof () + "Normal: trimming the end emits -sseof with the end seconds, no -ss." + (let ((cmd (cj/dwim-shell--video-trim-command "End" 0 9))) + (should (string-match-p "-sseof -9 " cmd)) + (should-not (string-match-p "-ss [0-9]" cmd)))) + +(ert-deftest test-dwim-video-trim-command-both-uses-ss-and-sseof () + "Normal: trimming both ends emits both -ss start and -sseof end." + (let ((cmd (cj/dwim-shell--video-trim-command "Both" 3 4))) + (should (string-match-p "-ss 3 " cmd)) + (should (string-match-p "-sseof -4 " cmd)))) + +(ert-deftest test-dwim-video-trim-command-negative-seconds-errors () + "Error: a negative second count for the used side signals a user-error." + (should-error (cj/dwim-shell--video-trim-command "Beginning" -1 0) :type 'user-error) + (should-error (cj/dwim-shell--video-trim-command "End" 0 -1) :type 'user-error) + (should-error (cj/dwim-shell--video-trim-command "Both" 0 -2) :type 'user-error)) + (provide 'test-dwim-shell-config-command-fixes) ;;; test-dwim-shell-config-command-fixes.el ends here |
