[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[nongnu] elpa/reformatter aeef16ff67 53/81: Support formatters that oper
From: |
ELPA Syncer |
Subject: |
[nongnu] elpa/reformatter aeef16ff67 53/81: Support formatters that operate on files in place |
Date: |
Tue, 5 Sep 2023 04:03:37 -0400 (EDT) |
branch: elpa/reformatter
commit aeef16ff67ba85e0f92a3f81825eed5444a63d28
Author: Steve Purcell <steve@sanityinc.com>
Commit: Steve Purcell <steve@sanityinc.com>
Support formatters that operate on files in place
---
.github/workflows/test.yml | 4 +-
.gitignore | 1 +
Makefile | 5 +-
README.md | 6 ++
reformatter-tests.el | 74 ++++++++++++++++++++
reformatter.el | 167 +++++++++++++++++++++++++++++++--------------
6 files changed, 203 insertions(+), 54 deletions(-)
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index be81b0f4d0..0e3179682e 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -24,10 +24,12 @@ jobs:
- 27.1
- snapshot
steps:
+ - uses: cachix/install-nix-action@v9
- uses: purcell/setup-emacs@master
with:
version: ${{ matrix.emacs_version }}
-
- uses: actions/checkout@v1
+ - name: Install deps for tests
+ run: nix-env -iA nixpkgs.shfmt
- name: Run tests
run: make
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000000..aee2ce51fe
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+/*.elc
diff --git a/Makefile b/Makefile
index 739fec45b3..717f9ad603 100644
--- a/Makefile
+++ b/Makefile
@@ -14,11 +14,14 @@ INIT_PACKAGES="(progn \
(package-install pkg))) \
)"
-all: compile package-lint clean-elc
+all: compile package-lint test clean-elc
package-lint:
${EMACS} -Q --eval ${INIT_PACKAGES} -batch -f
package-lint-batch-and-exit reformatter.el
+test:
+ ${EMACS} -Q --eval ${INIT_PACKAGES} -batch -l reformatter.el -l
reformatter-tests.el -f ert-run-tests-batch-and-exit
+
compile: clean-elc
${EMACS} -Q --eval ${INIT_PACKAGES} -L . -batch -f batch-byte-compile
*.el
diff --git a/README.md b/README.md
index 8f204a5238..341d4422fc 100644
--- a/README.md
+++ b/README.md
@@ -10,6 +10,12 @@ reformat the current buffer using a command-line program,
together
with an optional minor mode which can apply this command automatically
on save.
+By default, reformatter.el expects programs to read from stdin and
+write to stdout, and you should prefer this mode of operation where
+possible. If this isn't possible with your particular formatting
+program, refer to the options for `reformatter-define`, and see the
+examples in the package's tests.
+
In its initial release it supports only reformatters which can read
from stdin and write to stdout, but a more versatile interface will
be provided as development continues.
diff --git a/reformatter-tests.el b/reformatter-tests.el
new file mode 100644
index 0000000000..7b126fe818
--- /dev/null
+++ b/reformatter-tests.el
@@ -0,0 +1,74 @@
+;;; reformatter-tests.el --- Test suite for reformatter -*- lexical-binding:
t; -*-
+
+;; Copyright (C) 2020 Steve Purcell
+
+;; Author: Steve Purcell <steve@sanityinc.com>
+;; Keywords:
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; Just a few basic regression tests
+
+;;; Code:
+
+(require 'reformatter)
+(require 'ert)
+
+(defgroup reformatter-tests nil "Reformatter tests" :group 'test)
+
+;; We use `shfmt' because it can operate in a few modes
+
+;; Pure stdin/stdout
+(reformatter-define reformatter-tests-shfmt-stdio
+ :program "shfmt"
+ :args nil
+ :mode nil)
+
+(ert-deftest reformatter-tests-pure-stdio-no-args ()
+ (with-temp-buffer
+ (insert "[ foo ] && echo yes\n")
+ (reformatter-tests-shfmt-stdio-buffer)
+ (should (equal "[ foo ] && echo yes\n" (buffer-string)))))
+
+;; Read from stdin/stdout
+(reformatter-define reformatter-tests-shfmt-tempfile-in-stdout
+ :program "shfmt"
+ :stdin nil
+ :args (list input-file))
+
+(ert-deftest reformatter-tests-tempfile-in-stdout ()
+ (with-temp-buffer
+ (insert "[ foo ] && echo yes\n")
+ (reformatter-tests-shfmt-tempfile-in-stdout)
+ (should (equal "[ foo ] && echo yes\n" (buffer-string)))))
+
+;; Modify a file in place
+(reformatter-define reformatter-tests-shfmt-in-place
+ :program "shfmt"
+ :stdin nil
+ :stdout nil
+ :args (list "-w" input-file))
+
+(ert-deftest reformatter-tests-tempfile-in-place ()
+ (with-temp-buffer
+ (insert "[ foo ] && echo yes\n")
+ (reformatter-tests-shfmt-in-place)
+ (should (equal "[ foo ] && echo yes\n" (buffer-string)))))
+
+
+
+(provide 'reformatter-tests)
+;;; reformatter-tests.el ends here
diff --git a/reformatter.el b/reformatter.el
index da6fbe373f..cb8d06e277 100644
--- a/reformatter.el
+++ b/reformatter.el
@@ -28,9 +28,11 @@
;; together with an optional minor mode which can apply this command
;; automatically on save.
-;; In its initial release it supports only reformatters which read
-;; from stdin and write to stdout, but a more versatile interface will
-;; be provided as development continues.
+;; By default, reformatter.el expects programs to read from stdin and
+;; write to stdout, and you should prefer this mode of operation where
+;; possible. If this isn't possible with your particular formatting
+;; program, refer to the options for `reformatter-define', and see the
+;; examples in the package's tests.
;; As an example, let's define a reformat command that applies the
;; "dhall format" command. We'll assume here that we've already defined a
@@ -74,8 +76,62 @@
(require 'cl-lib))
(require 'ansi-color)
+(defun reformatter--do-region (name beg end program args stdin stdout
input-file exit-code-success-p display-errors)
+ "Do the work of reformatter called NAME.
+Reformats the current buffer's region from BEG to END using
+PROGRAM and ARGS. For args STDIN, STDOUT, INPUT-FILE,
+EXIT-CODE-SUCCESS-P and DISPLAY-ERRORS see the documentation of
+the `reformatter-define' macro."
+ (cl-assert input-file)
+ (cl-assert (functionp exit-code-success-p))
+ (when (and input-file
+ (buffer-file-name)
+ (string= (file-truename input-file)
+ (file-truename (buffer-file-name))))
+ (error "The reformatter must not operate on the current file in-place"))
+ (let* ((stderr-file (make-temp-file (symbol-name name)))
+ (stdout-file (make-temp-file (symbol-name name)))
+ ;; Setting this coding system might not universally be
+ ;; the best default, but was apparently necessary for
+ ;; some hand-rolled reformatter functions that this
+ ;; library was written to replace.
+ (coding-system-for-read 'utf-8)
+ (coding-system-for-write 'utf-8))
+ (unwind-protect
+ (progn
+ (write-region beg end input-file nil :quiet)
+ (let* ((error-buffer (get-buffer-create (format "*%s errors*" name)))
+ (retcode
+ (apply 'call-process program
+ (when stdin input-file)
+ (list (list :file stdout-file) stderr-file)
+ nil
+ args)))
+ (with-current-buffer error-buffer
+ (let ((inhibit-read-only t))
+ (insert-file-contents stderr-file nil nil nil t)
+ (ansi-color-apply-on-region (point-min) (point-max)))
+ (special-mode))
+ (if (funcall exit-code-success-p retcode)
+ (progn
+ (save-restriction
+ ;; This replacement method minimises
+ ;; disruption to marker positions and the
+ ;; undo list
+ (narrow-to-region beg end)
+ (reformatter-replace-buffer-contents-from-file (if stdout
+
stdout-file
+
input-file)))
+ ;; If there are no errors then we hide the error buffer
+ (delete-windows-on error-buffer))
+ (if display-errors
+ (display-buffer error-buffer)
+ (message (concat (symbol-name name) " failed: see %s")
(buffer-name error-buffer))))))
+ (delete-file stderr-file)
+ (delete-file stdout-file))))
+
;;;###autoload
-(cl-defmacro reformatter-define (name &key program args (mode t) lighter
keymap group (exit-code-success-p 'zerop))
+(cl-defmacro reformatter-define (name &key program args (mode t) (stdin t)
(stdout t) input-file lighter keymap group (exit-code-success-p 'zerop))
"Define a reformatter command with NAME.
When called, the reformatter will use PROGRAM and any ARGS to
@@ -87,35 +143,66 @@ displayed to the user.
The macro accepts the following keyword arguments:
-:program (required)
+PROGRAM (required)
Provides a form which should evaluate to a string at runtime,
e.g. a literal string, or the name of a variable which holds
the program path.
-:args
+ARGS
+
+ Command-line arguments for the program. If provided, this is a
+ form which evaluates to a list of strings at runtime. Default
+ is the empty list. This form is evaluated at runtime so that
+ you can use buffer-local variables to influence the args passed
+ to the reformatter program: the variable `input-file' will be
+ lexically bound to the path of a file containing the text to be
+ reformatted: see the keyword options INPUT-FILE, STDIN and
+ STDOUT for more information.
+
+STDIN
+
+ When non-nil (the default), the program is passed the input
+ data on stdin. Set this to nil when your reformatter can only
+ operate on files in place. In such a case, your ARGS should
+ include a reference to the `input-file' variable, which will be
+ bound to an input path when evaluated.
- If provided, this is a form which evaluates to a list of
- strings at runtime. Default is the empty list. This form is
- evaluated at runtime so that you can use buffer-local variables
- to influence the args passed to the reformatter program: note
- that you should not abuse this in order to inspect
- `buffer-file-name', because your reformatter should not require
- that a buffer be backed by a file on disk.
+STDOUT
-:mode
+ When non-nil (the default), the program is expected to write
+ the reformatted text to stdout. Set this to nil if your
+ reformatter can only operate on files in place, in which case
+ the contents of the temporary input file will be used as the
+ replacement text.
+
+INPUT-FILE
+
+ Sometimes your reformatter program might expect files to be in
+ a certain directory or have a certain file extension. This option
+ lets you handle that.
+
+ If provided, it is a form which will be evaluated before each
+ run of the formatter, and is expected to return a temporary
+ file path suitable for holding the region to be reformatted.
+ It must not produce the same path as the current buffer's file
+ if that is set: you shouldn't be operating directly on the
+ buffer's backing file. The temporary input file will be
+ deleted automatically.
+
+MODE
Unless nil, also generate a minor mode that will call the
reformatter command from `before-save-hook' when enabled.
Default is t.
-:group
+GROUP
If provided, this is the custom group used for any generated
modes or custom variables. Don't forget to declare this group
using a `defgroup' form.
-:lighter
+LIGHTER
If provided, this is a mode lighter string which will be used
for the \"-on-save\" minor mode. It should have a leading
@@ -123,12 +210,12 @@ The macro accepts the following keyword arguments:
generated custom variable which specifies the mode lighter.
Default is nil, ie. no lighter.
-:keymap
+KEYMAP
If provided, this is the symbol name of the \"-on-save\" mode's
keymap, which you must declare yourself. Default is no keymap.
-:exit-code-success-p
+EXIT-CODE-SUCCESS-P
If provided, this is a function object callable with `funcall'
which accepts an integer process exit code, and returns non-nil
@@ -176,41 +263,16 @@ might use:
When called interactively, or with prefix argument
DISPLAY-ERRORS, shows a buffer if the formatting fails."
(interactive "rp")
- (let* ((err-file (make-temp-file ,(symbol-name name)))
- (out-file (make-temp-file ,(symbol-name name)))
- ;; Setting this coding system might not universally be
- ;; the best default, but was apparently necessary for
- ;; some hand-rolled reformatter functions that this
- ;; library was written to replace.
- (coding-system-for-read 'utf-8)
- (coding-system-for-write 'utf-8))
+ (let ((input-file (or ,input-file (make-temp-file ,(symbol-name
name)))))
+ ;; Evaluate args with input-file bound
(unwind-protect
- (let* ((error-buffer (get-buffer-create ,(format "*%s errors*"
name)))
- (retcode
- (apply 'call-process-region beg end ,program
- nil (list (list :file out-file) err-file)
- nil
- ,args)))
- (with-current-buffer error-buffer
- (let ((inhibit-read-only t))
- (insert-file-contents err-file nil nil nil t)
- (ansi-color-apply-on-region (point-min) (point-max)))
- (special-mode))
- (if (funcall #',exit-code-success-p retcode)
- (progn
- (save-restriction
- ;; This replacement method minimises
- ;; disruption to marker positions and the
- ;; undo list
- (narrow-to-region beg end)
- (reformatter-replace-buffer-contents-from-file
out-file))
- ;; If there are no errors then we hide the error buffer
- (delete-windows-on error-buffer))
- (if display-errors
- (display-buffer error-buffer)
- (message ,(concat (symbol-name name) " failed: see %s")
(buffer-name error-buffer)))))
- (delete-file err-file)
- (delete-file out-file))))
+ (progn
+ (reformatter--do-region
+ ',name beg end
+ ,program ,args ,stdin ,stdout input-file
+ #',exit-code-success-p display-errors))
+ (when (file-exists-p input-file)
+ (delete-file input-file)))))
(defun ,buffer-fn-name (&optional display-errors)
"Reformats the current buffer.
@@ -225,6 +287,7 @@ DISPLAY-ERRORS, shows a buffer if the formatting fails."
,minor-mode-form)))
+
(defun reformatter-replace-buffer-contents-from-file (file)
"Replace the accessible portion of the current buffer with the contents of
FILE."
;; While the function `replace-buffer-contents' exists in recent
- [nongnu] elpa/reformatter 6c5e7f64c5 41/81: Never use `replace-buffer-contents', (continued)
- [nongnu] elpa/reformatter 6c5e7f64c5 41/81: Never use `replace-buffer-contents', ELPA Syncer, 2023/09/05
- [nongnu] elpa/reformatter 7219a0804c 48/81: Support for formatters which succeed with non-zero exit codes, ELPA Syncer, 2023/09/05
- [nongnu] elpa/reformatter 00413b21ec 28/81: Add reminder to use a `defgroup` form, ELPA Syncer, 2023/09/05
- [nongnu] elpa/reformatter dc6278a6b1 49/81: Merge pull request #23 from purcell/non-zero-exit, ELPA Syncer, 2023/09/05
- [nongnu] elpa/reformatter c9450a39c3 32/81: Merge pull request #8 from wbolster/patch-1, ELPA Syncer, 2023/09/05
- [nongnu] elpa/reformatter 48605c92a7 42/81: Add CI, ELPA Syncer, 2023/09/05
- [nongnu] elpa/reformatter e15598a0cc 40/81: Prefer `delete-trailing-whitespace` to the aggressive `whitespace-cleanup`, ELPA Syncer, 2023/09/05
- [nongnu] elpa/reformatter 1bf00b2aa3 43/81: Add FUNDING.yml, ELPA Syncer, 2023/09/05
- [nongnu] elpa/reformatter 16a7b32736 25/81: Prefer zerop to (eq 0 ...), ELPA Syncer, 2023/09/05
- [nongnu] elpa/reformatter ac980a8797 27/81: Fix typo in comment, ELPA Syncer, 2023/09/05
- [nongnu] elpa/reformatter aeef16ff67 53/81: Support formatters that operate on files in place,
ELPA Syncer <=
- [nongnu] elpa/reformatter 27348d5da3 31/81: Use string as modeline lighter format in README, ELPA Syncer, 2023/09/05
- [nongnu] elpa/reformatter 897f3ba503 67/81: Handle slashes in names of created functions, ELPA Syncer, 2023/09/05
- [nongnu] elpa/reformatter 45c0add950 57/81: Move conditional input file path to compile time, ELPA Syncer, 2023/09/05
- [nongnu] elpa/reformatter 5aa8c18679 59/81: Specify "from" in nix-env invocation, ELPA Syncer, 2023/09/05
- [nongnu] elpa/reformatter 452a99b556 66/81: Add Emacs 28.1 to CI matrix, ELPA Syncer, 2023/09/05
- [nongnu] elpa/reformatter 394b3a6606 55/81: Add reformatter-temp-file-in-current-directory, ELPA Syncer, 2023/09/05
- [nongnu] elpa/reformatter 576d339aa8 60/81: Add Emacs 27.2 to CI matrix, ELPA Syncer, 2023/09/05
- [nongnu] elpa/reformatter 19582a205e 62/81: Use Emacs > 21 define-minor-mode call convention, ELPA Syncer, 2023/09/05
- [nongnu] elpa/reformatter b57f5d4800 65/81: Merge pull request #33 from bcc32/display-call-process-signal-error-in-buffer, ELPA Syncer, 2023/09/05
- [nongnu] elpa/reformatter 7ba5486696 56/81: Clarify that extension does not include the dot, ELPA Syncer, 2023/09/05