diff options
| author | Craig Jennings <c@cjennings.net> | 2026-05-10 13:34:24 -0500 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2026-05-10 13:34:24 -0500 |
| commit | 3c840b0569ba3461cd61eabc32919f6899a25163 (patch) | |
| tree | e601b0080d7e923d3c3e66446d9d01c474923314 | |
| parent | 1f907e40bb2124084e336d429cd60830df6aa369 (diff) | |
| download | dotemacs-3c840b0569ba3461cd61eabc32919f6899a25163.tar.gz dotemacs-3c840b0569ba3461cd61eabc32919f6899a25163.zip | |
refactor(dirvish): extract playlist filter and sanitize helpers
`cj/dired-create-playlist-from-marked' had its audio-file filtering and trailing-`.m3u' stripping inline among the dired marking, prompting, overwrite-loop, and file-write logic. Lift each into its own pure helper:
- `cj/--playlist-filter-audio (files extensions)' returns only files whose extension matches one of EXTENSIONS (lowercase, no dot). Case-insensitive on the file side.
- `cj/--playlist-sanitize-name (name)' strips a trailing `.m3u' suffix; embedded `.m3u' that isn't at the end is preserved.
Ten Normal/Boundary tests cover keep-only-audio, case-insensitivity, files-without-extension excluded, empty inputs, and the sanitize edge cases (bare name, embedded `.m3u', empty string, just `.m3u').
| -rw-r--r-- | modules/dirvish-config.el | 38 | ||||
| -rw-r--r-- | tests/test-dirvish-config-playlist.el | 82 |
2 files changed, 106 insertions, 14 deletions
diff --git a/modules/dirvish-config.el b/modules/dirvish-config.el index 22d3f07c..438d21fa 100644 --- a/modules/dirvish-config.el +++ b/modules/dirvish-config.el @@ -60,19 +60,32 @@ "List of audio file extensions (lowercase, no dot). Used to filter files for M3U playlists.") +(defun cj/--playlist-filter-audio (files extensions) + "Return the elements of FILES whose extension matches EXTENSIONS. + +Pure helper used by `cj/dired-create-playlist-from-marked'. EXTENSIONS +is a list of lowercase extension strings (no dot). A file with no +extension never matches. Comparison downcases the file's extension so +mixed-case names match." + (cl-remove-if-not + (lambda (f) + (let ((ext (file-name-extension f))) + (and ext (member (downcase ext) extensions)))) + files)) + +(defun cj/--playlist-sanitize-name (name) + "Strip a trailing `.m3u' suffix from NAME and return the result. +Pure helper. An embedded `.m3u' that isn't at the end stays put." + (replace-regexp-in-string "\\.m3u\\'" "" name)) + (defun cj/dired-create-playlist-from-marked () "Create an .m3u playlist file from marked files in Dired (or Dirvish). Filters for audio files, prompts for the playlist name, and saves the resulting .m3u in the directory specified by =music-dir=. Interactive use only." (interactive) (let* ((marked-files (dired-get-marked-files)) - (audio-files - (cl-remove-if-not - (lambda (f) - (let ((ext (file-name-extension f))) - (and ext - (member (downcase ext) cj/audio-file-extensions)))) - marked-files)) + (audio-files (cj/--playlist-filter-audio + marked-files cj/audio-file-extensions)) (count (length audio-files))) (if (zerop count) (user-error "No audio files marked (extensions: %s)" @@ -81,14 +94,11 @@ Filters for audio files, prompts for the playlist name, and saves the resulting (playlist-path nil) (done nil)) (while (not done) - (setq base-name (read-string - (format "Playlist name (without .m3u): "))) - ;; Sanitize: strip any trailing .m3u - (setq base-name (replace-regexp-in-string "\\.m3u\\'" "" base-name)) + (setq base-name (cj/--playlist-sanitize-name + (read-string "Playlist name (without .m3u): "))) (setq playlist-path (expand-file-name (concat base-name ".m3u") music-dir)) (cond ((not (file-exists-p playlist-path)) - ;; Safe to write (setq done t)) (t (let ((choice (read-char-choice @@ -99,11 +109,11 @@ Filters for audio files, prompts for the playlist name, and saves the resulting (?o (setq done t)) (?c (user-error "Cancelled playlist creation")) (?r (setq done nil))))))) - ;; Actually write the file (with-temp-file playlist-path (dolist (af audio-files) (insert af "\n"))) - (message "Wrote playlist %s with %d tracks" (file-name-nondirectory playlist-path) count))))) + (message "Wrote playlist %s with %d tracks" + (file-name-nondirectory playlist-path) count))))) ;;; ----------------------------------- Dired ----------------------------------- diff --git a/tests/test-dirvish-config-playlist.el b/tests/test-dirvish-config-playlist.el new file mode 100644 index 00000000..3876a177 --- /dev/null +++ b/tests/test-dirvish-config-playlist.el @@ -0,0 +1,82 @@ +;;; test-dirvish-config-playlist.el --- Tests for the playlist helpers -*- lexical-binding: t; -*- + +;;; Commentary: +;; `cj/--playlist-filter-audio' and `cj/--playlist-sanitize-name' are +;; the two pure pieces under `cj/dired-create-playlist-from-marked'. +;; The interactive command does the dired marking, prompting, +;; overwrite-confirmation loop, and the file write -- the helpers stay +;; testable in isolation. + +;;; 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) + +;;; --------------------------- filter-audio -------------------------- + +(ert-deftest test-cj--playlist-filter-audio-keeps-only-audio () + "Normal: a mixed list returns only the entries with audio extensions." + (should (equal (cj/--playlist-filter-audio + '("/m/song.mp3" "/d/notes.org" "/m/track.flac" "/d/run.sh") + '("mp3" "flac")) + '("/m/song.mp3" "/m/track.flac")))) + +(ert-deftest test-cj--playlist-filter-audio-case-insensitive-extension () + "Boundary: uppercase / mixed-case extensions are matched against the +lowercase extension list." + (should (equal (cj/--playlist-filter-audio + '("/m/A.MP3" "/m/B.Flac" "/m/c.OGG") + '("mp3" "flac" "ogg")) + '("/m/A.MP3" "/m/B.Flac" "/m/c.OGG")))) + +(ert-deftest test-cj--playlist-filter-audio-files-without-extension-excluded () + "Boundary: a file with no extension can't be an audio file." + (should (equal (cj/--playlist-filter-audio + '("/m/README" "/m/song.mp3" "/m/Makefile") + '("mp3")) + '("/m/song.mp3")))) + +(ert-deftest test-cj--playlist-filter-audio-empty-input-empty-output () + "Boundary: no input files -> no output." + (should-not (cj/--playlist-filter-audio '() '("mp3" "flac")))) + +(ert-deftest test-cj--playlist-filter-audio-empty-extensions-empty-output () + "Boundary: no allowed extensions -> nothing matches." + (should-not (cj/--playlist-filter-audio + '("/m/song.mp3" "/m/track.flac") + '()))) + +;;; ---------------------- sanitize-playlist-name --------------------- + +(ert-deftest test-cj--playlist-sanitize-name-strips-trailing-m3u () + "Normal: a `.m3u' suffix is stripped." + (should (equal (cj/--playlist-sanitize-name "summer.m3u") "summer"))) + +(ert-deftest test-cj--playlist-sanitize-name-bare-name-unchanged () + "Normal: a name without `.m3u' is returned unchanged." + (should (equal (cj/--playlist-sanitize-name "summer") "summer"))) + +(ert-deftest test-cj--playlist-sanitize-name-embedded-m3u-untouched () + "Boundary: `.m3u' that isn't at the end is kept (it's part of the name)." + (should (equal (cj/--playlist-sanitize-name "my.m3u.draft") + "my.m3u.draft"))) + +(ert-deftest test-cj--playlist-sanitize-name-empty-string-unchanged () + "Boundary: empty string returns empty string." + (should (equal (cj/--playlist-sanitize-name "") ""))) + +(ert-deftest test-cj--playlist-sanitize-name-only-suffix () + "Boundary: a name that's just `.m3u' becomes empty after stripping." + (should (equal (cj/--playlist-sanitize-name ".m3u") ""))) + +(provide 'test-dirvish-config-playlist) +;;; test-dirvish-config-playlist.el ends here |
