From: Adam Porter <adam@alphapapa.net>
To: emacs-devel@gnu.org
Subject: [PATCH] New with-file-buffer macro
Date: Thu, 31 Dec 2020 13:13:57 -0600 [thread overview]
Message-ID: <87lfdddc0a.fsf@alphapapa.net> (raw)
[-- Attachment #1: Type: text/plain, Size: 4255 bytes --]
Hi,
Attached is a patch which adds a new macro, with-file-buffer. It's
similar to the macro with-temp-file, and it simplifies some common
cases, like reading a file's contents into a buffer and returning a
value from forms evaluated in its buffer, writing to a file that
shouldn't already exist, overwriting an existing file, etc.
For example, here's a recently updated function in package-build.el
which uses with-temp-buffer and write-region:
#+BEGIN_SRC elisp
(defun package-build--write-pkg-readme (name files directory)
(when-let ((commentary
(let* ((file (concat name ".el"))
(file (or (car (rassoc file files)) file))
(file (and file (expand-file-name file directory))))
(and (file-exists-p file)
(lm-commentary file)))))
(with-temp-buffer
(if (>= emacs-major-version 27)
(insert commentary)
;; Taken from 27.1's `lm-commentary'.
(insert
(replace-regexp-in-string ; Get rid of...
"[[:blank:]]*$" "" ; trailing white-space
(replace-regexp-in-string
(format "%s\\|%s\\|%s"
;; commentary header
(concat "^;;;[[:blank:]]*\\("
lm-commentary-header
"\\):[[:blank:]\n]*")
"^;;[[:blank:]]*" ; double semicolon prefix
"[[:blank:]\n]*\\'") ; trailing new-lines
"" commentary))))
(unless (= (char-before) ?\n)
(insert ?\n))
(let ((coding-system-for-write buffer-file-coding-system))
(write-region nil nil
(expand-file-name (concat name "-readme.txt")
package-build-archive-dir))))))
#+END_SRC
Here's how it could work using this new with-file-buffer macro:
#+BEGIN_SRC elisp
(defun package-build--write-pkg-readme (name files directory)
(when-let ((commentary
(let* ((file (concat name ".el"))
(file (or (car (rassoc file files)) file))
(file (and file (expand-file-name file directory))))
(and (file-exists-p file)
(lm-commentary file)))))
(let ((coding-system-for-write buffer-file-coding-system))
(with-file-buffer (expand-file-name (concat name "-readme.txt")
package-build-archive-dir)
(:insert nil :write t :overwrite t)
(if (>= emacs-major-version 27)
(insert commentary)
;; Taken from 27.1's `lm-commentary'.
(insert
(replace-regexp-in-string ; Get rid of...
"[[:blank:]]*$" "" ; trailing white-space
(replace-regexp-in-string
(format "%s\\|%s\\|%s"
;; commentary header
(concat "^;;;[[:blank:]]*\\("
lm-commentary-header
"\\):[[:blank:]\n]*")
"^;;[[:blank:]]*" ; double semicolon prefix
"[[:blank:]\n]*\\'") ; trailing new-lines
"" commentary))))
(unless (= (char-before) ?\n)
(insert ?\n))))))
#+END_SRC
This example isn't the most dramatically different, but it shows a few
improvements: the options list after the filename shows clearly what the
form does (write a file, overwriting if it already exists), whereas
calling write-region directly requires reading to the end of the body
forms (which might not fit within the window); and the options list also
makes arguments to write-region easier to use, as well as making it easy
to control write-region-inhibit-fsync and file-precious-flag. Without
this macro, these operations and options are, IMO, harder to use and
easy to overlook, so I think this would be a worthwhile addition.
If this idea seems useful, I'm sure various improvements may be made, so
I would appreciate any feedback. For example, it might be better for
":insert nil" to be the default, so ":insert t" would be specified to
read the file's contents into the buffer.
Thanks,
Adam
[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: Patch adding with-file-buffer macro --]
[-- Type: text/x-diff, Size: 5857 bytes --]
From 37df936cc1c9e3f630a060c8347a4aeba46fc7d0 Mon Sep 17 00:00:00 2001
From: Adam Porter <adam@alphapapa.net>
Date: Thu, 31 Dec 2020 12:56:25 -0600
Subject: [PATCH] * lisp/subr.el (with-file-buffer): New macro.
* lisp/subr.el (with-file-buffer): New macro.
* doc/lispref/files.texi (Writing to Files): Document it.
* etc/NEWS: Add news entry.
---
doc/lispref/files.texi | 48 ++++++++++++++++++++++++++++++++++++++++++
etc/NEWS | 6 ++++++
lisp/subr.el | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 111 insertions(+)
diff --git a/doc/lispref/files.texi b/doc/lispref/files.texi
index 6949ca2..1617bbf 100644
--- a/doc/lispref/files.texi
+++ b/doc/lispref/files.texi
@@ -708,6 +708,54 @@ Writing to Files
(@pxref{Buffer List}).
@end defmac
+@defmac with-file-buffer file options body@dots{}
+@anchor{Definition of with-file-buffer}
+Like the @code{with-temp-file} macro (which see), the
+@code{with-file-buffer} macro evaluates the @var{body} forms with a
+temporary buffer as the current buffer and then returns the value of
+the last form in @var{body}. The @var{options} offer control over
+various aspects of the operations:
+
+@table @code
+@item :insert
+When non-nil (the default, when unspecified), insert @var{file}'s contents
+before evaluating @var{body}, leaving point before the contents.
+
+@item :must-exist
+When non-nil, signal an error if @var{file} does not exist.
+
+@item :write
+When non-nil, write the contents of the buffer to @var{file} after
+evaluating @var{body}.
+
+@item :overwrite
+When nil (the default, when unspecified), signal an error instead of
+overwriting an existing file. If @code{ask}, ask for confirmation
+before overwriting an existing file. If @code{t}, overwrite
+@var{file} unconditionally.
+
+@item :visit
+Passed to function @code{write-region}, which see.
+
+@item :lockname
+Passed to function @code{write-region}, which see.
+
+@item :append
+Passed to function @code{write-region}, which see. (When using this
+option, you will probably want to specify @code{:insert nil} as well,
+otherwise the file's contents would be duplicated.)
+
+@item :fsync
+When non-nil (the default, when unspecified), bind
+@var{write-region-inhibit-fsync} (which see) to this value.
+
+@item :precious
+Bind @var{file-precious-flag} (which see) to this
+value (when unspecified, nil).
+@end table
+
+@end defmac
+
@node File Locks
@section File Locks
@cindex file locks
diff --git a/etc/NEWS b/etc/NEWS
index 62907a6..9b8288f 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -2311,6 +2311,12 @@ locales. They are also available as aliases 'ebcdic-cp-*' (e.g.,
'cp278' for 'ibm278'). There are also new charsets 'ibm2xx' to
support these coding-systems.
+---
+** New macro 'with-file-buffer'.
+The new macro 'with-file-buffer' simplifies operating on the contents
+of files and/or writing to them, compared to using 'with-temp-buffer',
+'insert-file-contents', 'write-region', and various options
+separately.
\f
* Changes in Emacs 28.1 on Non-Free Operating Systems
diff --git a/lisp/subr.el b/lisp/subr.el
index 77b142c..0f6da02 100644
--- a/lisp/subr.el
+++ b/lisp/subr.el
@@ -3992,6 +3992,63 @@ with-temp-buffer
(and (buffer-name ,temp-buffer)
(kill-buffer ,temp-buffer)))))))
+(defmacro with-file-buffer (file options &rest body)
+ "Evaluate BODY and return its value in a temp buffer for FILE.
+OPTIONS is a plist of the following options:
+
+`:insert': When non-nil (the default, when unspecified), insert
+file's contents before evaluating BODY, leaving point before the
+contents.
+
+`:must-exist': When non-nil, signal an error if no file exists at
+FILE.
+
+`:write': When non-nil, write the contents of the buffer to FILE
+after evaluating BODY.
+
+`:overwrite': When nil (the default, when unspecified), signal an
+error instead of overwriting an existing file at FILE. If `ask',
+ask for confirmation before overwriting an existing file. If t,
+overwrite a file at FILE unconditionally.
+
+`:visit': Passed to function `write-region', which see.
+
+`:lockname:' Passed to function `write-region', which see.
+
+`:append': Passed to function `write-region', which see. (When
+using this option, you will probably want to specify `:insert
+nil' as well.)
+
+`:fsync': When non-nil (the default, when unspecified), bind
+`write-region-inhibit-fsync' (which see) to this value.
+
+`:precious': Bind `file-precious-flag' (which see) to this
+value (when unspecified, nil)."
+ (declare (indent 2) (debug (stringp form body)))
+ `(let ((write-region-inhibit-fsync ,(when (plist-member options :fsync)
+ (not (plist-get options :fsync))))
+ (file-precious-flag ,(plist-get options :precious)))
+ (with-temp-buffer
+ ,(when (or (not (plist-member options :insert))
+ (plist-get options :insert))
+ `(if (file-readable-p ,file)
+ (save-excursion
+ (insert-file-contents ,file))
+ (when ,(plist-get options :must-exist)
+ (error "File not readable: %s" ,file))))
+ (prog1
+ (progn
+ ,@body)
+ ,(when (plist-get options :write)
+ `(write-region nil nil ,file
+ ,(plist-get options :append)
+ ,(plist-get options :visit)
+ ,(plist-get options :lockname)
+ ,(pcase-exhaustive (plist-get options :overwrite)
+ ('nil ''excl)
+ ((or 'ask ''ask) ''ask)
+ ('t nil))))))))
+
(defmacro with-silent-modifications (&rest body)
"Execute BODY, pretending it does not modify the buffer.
This macro is typically used around modifications of
--
2.7.4
next reply other threads:[~2020-12-31 19:13 UTC|newest]
Thread overview: 3+ messages / expand[flat|nested] mbox.gz Atom feed top
2020-12-31 19:13 Adam Porter [this message]
2020-12-31 19:48 ` [PATCH] New with-file-buffer macro Eli Zaretskii
2021-01-01 14:32 ` Arthur Miller
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=87lfdddc0a.fsf@alphapapa.net \
--to=adam@alphapapa.net \
--cc=emacs-devel@gnu.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
Code repositories for project(s) associated with this external index
https://git.savannah.gnu.org/cgit/emacs.git
https://git.savannah.gnu.org/cgit/emacs/org-mode.git
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.