aboutsummaryrefslogtreecommitdiff
path: root/modules/dwim-shell-config.el
diff options
context:
space:
mode:
Diffstat (limited to 'modules/dwim-shell-config.el')
-rw-r--r--modules/dwim-shell-config.el182
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 ()