diff options
Diffstat (limited to 'modules/dwim-shell-config.el')
| -rw-r--r-- | modules/dwim-shell-config.el | 182 |
1 files changed, 66 insertions, 116 deletions
diff --git a/modules/dwim-shell-config.el b/modules/dwim-shell-config.el index ad17ea913..e8790a489 100644 --- a/modules/dwim-shell-config.el +++ b/modules/dwim-shell-config.el @@ -1,105 +1,39 @@ -;; dwim-shell-config.el --- Dired Shell Commands -*- coding: utf-8; lexical-binding: t; -*- +;;; dwim-shell-config.el --- Dired shell command menu -*- coding: utf-8; lexical-binding: t; -*- ;; ;;; Commentary: ;; ;; Layer: 3 (Domain Workflow). ;; Category: D/P. ;; Load shape: eager. -;; Eager reason: none; Dired/Dirvish shell commands, a command-loaded deferral -;; candidate. +;; Eager reason: none; Dired/Dirvish shell commands can load by command. ;; Top-level side effects: package configuration via use-package. -;; Runtime requires: cl-lib. +;; Runtime requires: cl-lib, system-lib. ;; Direct test load: yes. ;; -;; This module provides a collection of DWIM (Do What I Mean) shell commands -;; for common file operations in Dired and other buffers. It leverages the -;; `dwim-shell-command' package to execute shell commands on marked files -;; with smart templating and progress tracking. -;; -;; Features: -;; - Audio/Video conversion (mp3, opus, webp, HEVC) -;; - Image manipulation (resize, flip, format conversion) -;; - PDF operations (merge, split, password protection, OCR) -;; - Archive management (zip/unzip) -;; - Document conversion (epub to org, docx to pdf, pdf to txt) -;; - Git operations (clone from clipboard) -;; - External file opening with context awareness -;; -;; Workflow: -;; 1. *Mark files in Dired/Dirvish* -;; - Use =m= to mark individual files -;; - Use =* .= to mark by extension -;; - Use =% m= to mark by regexp -;; - Or operate on the file under cursor if nothing is marked -;; -;; 2. *Execute a DWIM command* -;; - Call the command via =M-x dwim-shell-commands-[command-name]= -;; - Or bind frequently used commands to keys -;; -;; 3. *Command execution* -;; - The command runs asynchronously in the background -;; - A =*Async Shell Command*= buffer shows progress -;; - Files are processed with smart templating (replacing =<<f>>=, =<<fne>>=, etc.) -;; -;; 4. *Results* -;; - New files appear in the Dired/Dirvish buffer -;; - Buffer auto-refreshes when command completes -;; - Errors appear in the async buffer if something fails -;; -;; Requirements: -;; The commands rely on various external utilities that need to be installed: -;; - ffmpeg: Audio/video conversion -;; - imagemagick (convert): Image manipulation -;; - qpdf: PDF operations (requires version 8.x+ for secure password handling) -;; - tesseract: OCR functionality -;; - pandoc: Document conversion -;; - atool: Archive extraction -;; - rsvg-convert: SVG to PNG conversion -;; - pdftotext: PDF text extraction -;; - git: Version control operations -;; - gpgconf: GPG agent management -;; - 7z (p7zip): Secure password-protected archives -;; -;; On Arch Linux, install the requirements with: -;; #+begin_src bash -;; sudo pacman -S --needed ffmpeg imagemagick qpdf tesseract tesseract-data-eng pandoc atool librsvg poppler git gnupg p7zip zip unzip mkvtoolnix-cli mpv ruby -;; #+end_src -;; -;; On MacOS, install the requirements with: -;; #+begin_src bash -;; brew install ffmpeg imagemagick qpdf tesseract pandoc atool librsvg poppler gnupg p7zip mkvtoolnix mpv -;; #+end_src -;; -;; Usage: -;; Commands operate on marked files in Dired or the current file in other modes. -;; The package automatically replaces standard shell commands with DWIM versions -;; for a more intuitive experience. -;; -;; Security: -;; Password-protected operations (PDF encryption, archive encryption) use secure -;; methods to avoid exposing passwords in process lists or command history: -;; - PDF operations: Use temporary files with restrictive permissions (mode 600) -;; - Archive operations: Use 7z instead of zip for better password handling -;; - Temporary password files are automatically cleaned up after use -;; - Note: Switched from zip to 7z for encryption due to zip's insecure -P flag -;; -;; Template Variables: -;; - <<f>>: Full path to file -;; - <<fne>>: File name without extension -;; - <<e>>: File extension -;; - <<b>>: Base name (file name with extension, no directory) -;; - <<d>>: Directory path -;; - <<n>>: Sequential number (for batch renaming) -;; - <<td>>: Temporary directory -;; - <<cb>>: Clipboard contents -;; - <<*>>: All marked files +;; Configures dwim-shell-command actions for marked Dired/Dirvish files: +;; media conversion, archive/PDF/document operations, external opening, and a +;; curated transient menu. Commands use dwim-shell templates for marked files or +;; the current buffer file. ;; +;; Password-bearing operations avoid command-line secrets by writing temporary +;; password files with restrictive permissions and deleting them from the process +;; sentinel after the spawned command exits. ;;; Code: (require 'cl-lib) (require 'system-lib) ;; cj/confirm-strong (permanent file destruction confirm) +;; Function declarations (lazily-loaded packages and sibling modules). +(declare-function dwim-shell-command-on-marked-files "dwim-shell-command") +(declare-function dwim-shell-command-read-file-name "dwim-shell-command") +(declare-function dwim-shell-command--files "dwim-shell-command") +(declare-function cj/xdg-open "external-open") +(declare-function dwim-shell-commands-menu "dwim-shell-config") + +;; Forward declaration: external variable provided by the dirvish package. +(defvar dirvish-mode-map) + ;; --------------------------- Password-file helpers --------------------------- (defun cj/dwim-shell--password-cleanup-callback (temp-file) @@ -210,6 +144,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 +326,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 +416,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 +646,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 () |
