diff options
| author | Craig Jennings <c@cjennings.net> | 2026-06-20 11:32:21 -0400 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2026-06-20 11:32:21 -0400 |
| commit | 5b5d24b5d14b9828b2acc2e39b4530be9091e512 (patch) | |
| tree | 7f754389fd8d62d148a76ce21b7b055177d5a463 | |
| parent | ed7e6b2a89fc31d2e23f2c53f8af49d3ff00586d (diff) | |
| download | dotemacs-5b5d24b5d14b9828b2acc2e39b4530be9091e512.tar.gz dotemacs-5b5d24b5d14b9828b2acc2e39b4530be9091e512.zip | |
refactor: extract shared format-region helper into system-lib
prog-json and prog-yaml each carried a byte-identical cj/--<lang>-format-region that runs a formatter over the buffer via call-process-region and replaces it on exit 0. Hoist it to system-lib as cj/format-region-with-program with a generic output buffer, and point both formatters at it. Adds the first direct unit coverage of the helper (Normal, Boundary, Error).
| -rw-r--r-- | modules/prog-json.el | 31 | ||||
| -rw-r--r-- | modules/prog-yaml.el | 31 | ||||
| -rw-r--r-- | modules/system-lib.el | 24 | ||||
| -rw-r--r-- | tests/test-system-lib--format-region-with-program.el | 68 |
4 files changed, 100 insertions, 54 deletions
diff --git a/modules/prog-json.el b/modules/prog-json.el index 953b5f79b..e7abd1828 100644 --- a/modules/prog-json.el +++ b/modules/prog-json.el @@ -9,7 +9,7 @@ ;; Eager reason: none necessary; currently eager but should load by JSON major ;; mode (Phase 6 deferral candidate). ;; Top-level side effects: one add-hook, package configuration via use-package. -;; Runtime requires: none (configures packages via use-package). +;; Runtime requires: system-lib (cj/format-region-with-program). ;; Direct test load: yes. ;; ;; JSON editing with tree-sitter highlighting, one-key formatting, and @@ -27,6 +27,8 @@ ;;; Code: +(require 'system-lib) + (defvar json-ts-mode-map) ;; -------------------------------- JSON Mode ---------------------------------- @@ -41,38 +43,13 @@ ;; -------------------------------- Formatting --------------------------------- ;; pretty-print with sorted keys, bound to standard format key -(defun cj/--json-format-region (program &rest args) - "Replace the buffer with PROGRAM ARGS run over its contents, via argv. -Runs PROGRAM (with ARGS) on the whole buffer through -`call-process-region' — no shell, so no quoting or word-splitting. -The buffer is replaced only when PROGRAM exits zero; on a non-zero -exit the buffer is left untouched and an error is signalled with -the program's stderr text. Point is preserved as closely as the -reformatted size allows. Returns t on success." - (let* ((point (point)) - (src (current-buffer)) - (out (generate-new-buffer " *json-format-out*")) - (status (apply #'call-process-region - (point-min) (point-max) program - nil out nil args))) - (unwind-protect - (if (and (integerp status) (zerop status)) - (progn - (with-current-buffer src - (replace-buffer-contents out) - (goto-char (min point (point-max)))) - t) - (user-error "%s failed: %s" program - (string-trim (with-current-buffer out (buffer-string))))) - (kill-buffer out)))) - (defun cj/json-format-buffer () "Format the current JSON buffer with sorted keys. Uses jq if available for reliable formatting, otherwise falls back to the built-in `json-pretty-print-buffer-ordered'." (interactive) (if (executable-find "jq") - (cj/--json-format-region "jq" "--sort-keys" ".") + (cj/format-region-with-program "jq" "--sort-keys" ".") (json-pretty-print-buffer-ordered))) (defun cj/json-setup () diff --git a/modules/prog-yaml.el b/modules/prog-yaml.el index c2bb559b1..e07cf510e 100644 --- a/modules/prog-yaml.el +++ b/modules/prog-yaml.el @@ -9,7 +9,7 @@ ;; Eager reason: none necessary; currently eager but should load by YAML major ;; mode (Phase 6 deferral candidate). ;; Top-level side effects: one add-hook, package configuration via use-package. -;; Runtime requires: none (configures packages via use-package). +;; Runtime requires: system-lib (cj/format-region-with-program). ;; Direct test load: yes. ;; ;; YAML editing with tree-sitter highlighting and one-key formatting. @@ -24,6 +24,8 @@ ;;; Code: +(require 'system-lib) + ;; -------------------------------- YAML Mode ---------------------------------- ;; tree-sitter mode for YAML files (built-in, Emacs 29+) ;; NOTE: No :mode directive — treesit-auto (in prog-general.el) handles @@ -36,37 +38,12 @@ ;; -------------------------------- Formatting --------------------------------- ;; normalize indentation and style, bound to standard format key -(defun cj/--yaml-format-region (program &rest args) - "Replace the buffer with PROGRAM ARGS run over its contents, via argv. -Runs PROGRAM (with ARGS) on the whole buffer through -`call-process-region' — no shell, so no quoting or word-splitting. -The buffer is replaced only when PROGRAM exits zero; on a non-zero -exit the buffer is left untouched and an error is signalled with -the program's stderr text. Point is preserved as closely as the -reformatted size allows. Returns t on success." - (let* ((point (point)) - (src (current-buffer)) - (out (generate-new-buffer " *yaml-format-out*")) - (status (apply #'call-process-region - (point-min) (point-max) program - nil out nil args))) - (unwind-protect - (if (and (integerp status) (zerop status)) - (progn - (with-current-buffer src - (replace-buffer-contents out) - (goto-char (min point (point-max)))) - t) - (user-error "%s failed: %s" program - (string-trim (with-current-buffer out (buffer-string))))) - (kill-buffer out)))) - (defun cj/yaml-format-buffer () "Format the current YAML buffer with prettier. Preserves point position as closely as possible." (interactive) (if (executable-find "prettier") - (cj/--yaml-format-region "prettier" "--parser" "yaml") + (cj/format-region-with-program "prettier" "--parser" "yaml") (user-error "prettier not found; install with: npm install -g prettier"))) (defun cj/yaml-setup () diff --git a/modules/system-lib.el b/modules/system-lib.el index ed98a476e..49bb6cd1a 100644 --- a/modules/system-lib.el +++ b/modules/system-lib.el @@ -164,5 +164,29 @@ contributes its own modes regardless of load order." (setq font-lock-global-modes (cj/--font-lock-global-modes-excluding font-lock-global-modes mode)))) +(defun cj/format-region-with-program (program &rest args) + "Replace the current buffer with PROGRAM ARGS run over its contents, via argv. +Runs PROGRAM (with ARGS) on the whole buffer through `call-process-region' +-- no shell, so no quoting or word-splitting. The buffer is replaced only +when PROGRAM exits zero; on a non-zero exit the buffer is left untouched and +a `user-error' is signalled with the program's stderr text. Point is +preserved as closely as the reformatted size allows. Returns t on success." + (let* ((point (point)) + (src (current-buffer)) + (out (generate-new-buffer " *format-out*")) + (status (apply #'call-process-region + (point-min) (point-max) program + nil out nil args))) + (unwind-protect + (if (and (integerp status) (zerop status)) + (progn + (with-current-buffer src + (replace-buffer-contents out) + (goto-char (min point (point-max)))) + t) + (user-error "%s failed: %s" program + (string-trim (with-current-buffer out (buffer-string))))) + (kill-buffer out)))) + (provide 'system-lib) ;;; system-lib.el ends here diff --git a/tests/test-system-lib--format-region-with-program.el b/tests/test-system-lib--format-region-with-program.el new file mode 100644 index 000000000..29b392b84 --- /dev/null +++ b/tests/test-system-lib--format-region-with-program.el @@ -0,0 +1,68 @@ +;;; test-system-lib--format-region-with-program.el --- Tests for cj/format-region-with-program -*- lexical-binding: t; -*- + +;;; Commentary: +;; `cj/format-region-with-program' runs an external formatter over the whole +;; buffer via `call-process-region' (argv, no shell) and replaces the buffer +;; only when the program exits zero. Extracted from the byte-identical +;; per-language helpers in prog-json.el / prog-yaml.el, so this is the first +;; direct unit coverage of the logic. call-process-region is mocked at the +;; boundary (the established pattern in test-prog-json--json-format-buffer.el). + +;;; Code: + +(require 'ert) +(require 'cl-lib) + +(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory)) +(require 'system-lib) + +(ert-deftest test-system-lib-format-region-with-program-replaces-on-success () + "Normal: on exit 0 the buffer is replaced with the program's output, returns t." + (cl-letf (((symbol-function 'call-process-region) + (lambda (_start _end _prog &rest rest) + (with-current-buffer (nth 1 rest) (insert "FORMATTED")) + 0))) + (with-temp-buffer + (insert "raw") + (should (eq t (cj/format-region-with-program "fmt"))) + (should (equal "FORMATTED" (buffer-string)))))) + +(ert-deftest test-system-lib-format-region-with-program-forwards-argv () + "Normal: PROGRAM and ARGS reach call-process-region as argv (no shell)." + (let (got-prog got-args) + (cl-letf (((symbol-function 'call-process-region) + (lambda (_start _end prog &rest rest) + (setq got-prog prog + got-args (nthcdr 3 rest)) + (with-current-buffer (nth 1 rest) (insert "x")) + 0))) + (with-temp-buffer + (cj/format-region-with-program "jq" "--sort-keys" "."))) + (should (equal "jq" got-prog)) + (should (equal '("--sort-keys" ".") got-args)))) + +(ert-deftest test-system-lib-format-region-with-program-empty-output () + "Boundary: empty program output empties the buffer and still returns t." + (cl-letf (((symbol-function 'call-process-region) + (lambda (_start _end _prog &rest _rest) 0))) ; writes nothing + (with-temp-buffer + (insert "raw") + (should (eq t (cj/format-region-with-program "fmt"))) + (should (equal "" (buffer-string)))))) + +(ert-deftest test-system-lib-format-region-with-program-nonzero-untouched () + "Error: a non-zero exit leaves the buffer untouched and signals user-error +carrying the program's stderr text." + (cl-letf (((symbol-function 'call-process-region) + (lambda (_start _end _prog &rest rest) + (with-current-buffer (nth 1 rest) (insert "boom: bad input")) + 1))) + (with-temp-buffer + (insert "raw") + (let ((err (should-error (cj/format-region-with-program "fmt") + :type 'user-error))) + (should (string-match-p "boom: bad input" (error-message-string err)))) + (should (equal "raw" (buffer-string)))))) + +(provide 'test-system-lib--format-region-with-program) +;;; test-system-lib--format-region-with-program.el ends here |
