emacs-elpa-diffs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[nongnu] elpa/reformatter 2fd158102f 54/81: Merge pull request #27 from


From: ELPA Syncer
Subject: [nongnu] elpa/reformatter 2fd158102f 54/81: Merge pull request #27 from purcell/files-in-place
Date: Tue, 5 Sep 2023 04:03:37 -0400 (EDT)

branch: elpa/reformatter
commit 2fd158102f713090521e80b0a098f8666f6b42cf
Merge: 9d277200c1 aeef16ff67
Author: Steve Purcell <steve@sanityinc.com>
Commit: GitHub <noreply@github.com>

    Merge pull request #27 from purcell/files-in-place
    
    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



reply via email to

[Prev in Thread] Current Thread [Next in Thread]