From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.io!.POSTED.blaine.gmane.org!not-for-mail From: Adam Porter Newsgroups: gmane.emacs.devel Subject: [PATCH] New with-file-buffer macro Date: Thu, 31 Dec 2020 13:13:57 -0600 Message-ID: <87lfdddc0a.fsf@alphapapa.net> Mime-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" Injection-Info: ciao.gmane.io; posting-host="blaine.gmane.org:116.202.254.214"; logging-data="4781"; mail-complaints-to="usenet@ciao.gmane.io" User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/26.3 (gnu/linux) To: emacs-devel@gnu.org Original-X-From: emacs-devel-bounces+ged-emacs-devel=m.gmane-mx.org@gnu.org Thu Dec 31 20:15:44 2020 Return-path: Envelope-to: ged-emacs-devel@m.gmane-mx.org Original-Received: from lists.gnu.org ([209.51.188.17]) by ciao.gmane.io with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1kv3QF-000188-0H for ged-emacs-devel@m.gmane-mx.org; Thu, 31 Dec 2020 20:15:43 +0100 Original-Received: from localhost ([::1]:55806 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1kv3QE-0000Qu-2W for ged-emacs-devel@m.gmane-mx.org; Thu, 31 Dec 2020 14:15:42 -0500 Original-Received: from eggs.gnu.org ([2001:470:142:3::10]:40906) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1kv3Oi-0008Mr-7s for emacs-devel@gnu.org; Thu, 31 Dec 2020 14:14:08 -0500 Original-Received: from ciao.gmane.io ([116.202.254.214]:53174) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1kv3Og-0006uA-4P for emacs-devel@gnu.org; Thu, 31 Dec 2020 14:14:08 -0500 Original-Received: from list by ciao.gmane.io with local (Exim 4.92) (envelope-from ) id 1kv3Od-0009ap-Nt for emacs-devel@gnu.org; Thu, 31 Dec 2020 20:14:03 +0100 X-Injected-Via-Gmane: http://gmane.org/ Received-SPF: pass client-ip=116.202.254.214; envelope-from=ged-emacs-devel@m.gmane-mx.org; helo=ciao.gmane.io X-Spam_score_int: -16 X-Spam_score: -1.7 X-Spam_bar: - X-Spam_report: (-1.7 / 5.0 requ) BAYES_00=-1.9, HEADER_FROM_DIFFERENT_DOMAINS=0.249, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=no autolearn_force=no X-Spam_action: no action X-BeenThere: emacs-devel@gnu.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: "Emacs development discussions." List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: emacs-devel-bounces+ged-emacs-devel=m.gmane-mx.org@gnu.org Original-Sender: "Emacs-devel" Xref: news.gmane.io gmane.emacs.devel:262195 Archived-At: --=-=-= Content-Type: text/plain 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 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=0001-lisp-subr.el-with-file-buffer-New-macro.patch Content-Description: Patch adding with-file-buffer macro >From 37df936cc1c9e3f630a060c8347a4aeba46fc7d0 Mon Sep 17 00:00:00 2001 From: Adam Porter 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. * 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 --=-=-=--