From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.io!.POSTED.blaine.gmane.org!not-for-mail From: Arthur Miller Newsgroups: gmane.emacs.devel Subject: Re: [PATCH] New with-file-buffer macro Date: Fri, 01 Jan 2021 15:32:39 +0100 Message-ID: References: <87lfdddc0a.fsf@alphapapa.net> Mime-Version: 1.0 Content-Type: text/plain Injection-Info: ciao.gmane.io; posting-host="blaine.gmane.org:116.202.254.214"; logging-data="2391"; mail-complaints-to="usenet@ciao.gmane.io" User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/28.0.50 (gnu/linux) Cc: emacs-devel@gnu.org To: Adam Porter Original-X-From: emacs-devel-bounces+ged-emacs-devel=m.gmane-mx.org@gnu.org Fri Jan 01 15:49:48 2021 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 1kvLkR-0000Vp-UE for ged-emacs-devel@m.gmane-mx.org; Fri, 01 Jan 2021 15:49:48 +0100 Original-Received: from localhost ([::1]:33408 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1kvLkR-00052m-0h for ged-emacs-devel@m.gmane-mx.org; Fri, 01 Jan 2021 09:49:47 -0500 Original-Received: from eggs.gnu.org ([2001:470:142:3::10]:49122) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1kvLiY-0004Rl-3H for emacs-devel@gnu.org; Fri, 01 Jan 2021 09:47:50 -0500 Original-Received: from mail-oln040092073028.outbound.protection.outlook.com ([40.92.73.28]:7886 helo=EUR04-HE1-obe.outbound.protection.outlook.com) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1kvLiU-0000u1-TW for emacs-devel@gnu.org; Fri, 01 Jan 2021 09:47:49 -0500 ARC-Seal: i=1; a=rsa-sha256; s=arcselector9901; d=microsoft.com; cv=none; b=J/Rw4hBgu0JMXRfnTpX/7FlnhLj6L6tVDHMHFR/wOT1zPKlLVA+keSbPZKR4J/pSCpYm5CYYZLT55TfqZ6q6PgPHWd4pH1ebjNNezwPaRbdokZ/4Mnea9++oGNuOpHYEGQ3RWjm94x2IVGutfOHiIChz3bRKckqsWSI/O1/bY3uqnkf5Y+6pdJOluVgBDHoPqVneO3Tw+5hAMd0IEq+94A+dk27GVz92Z0ew/SuxXXmpn8Ua8Fk69A654XKKZqdd7vvBi6jXI8dny9LsiOks2y+QDW8A4YZ++qdGzB91MAbVrMkuBJafykvXNxE16H4qz2H0SmN+Myp5ffn87AY2yQ== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=microsoft.com; s=arcselector9901; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-SenderADCheck; bh=A8tRaZRGvhd6XkVH2cSh7vuzuihExurAkF6n9LbAmwE=; b=MwxxzM9FiJedKCyPvun62705xtlwqimFWro0gpEQTcIONKq9Ka2/5uucYsnO6uL0sMm1+0Hiq/CfTKKSPHH0uKNfaypxx+tLfUmXPYdfNtN2PvvZhh/mffObGG9/B47+aGJUsl6wf9ZZDf1KraBu9IcPPMTlLl/sR9ljVIzQE3Y3Cqo0Okb2drua0eQA5e+ZMKkP+cFLdHc/ABYvXak20IJKWrcuszC4uVc1QutZ0/7bigm2rSxrCR5TMs8d+W1kGolTDQgiO3i78j4rn24DaFymZJx8buOOel3CXw0X8zL3tqIakVbc0R/YITL0vyGaDKivQE5Ct/f2dS/5BloLCg== ARC-Authentication-Results: i=1; mx.microsoft.com 1; spf=none; dmarc=none; dkim=none; arc=none DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=live.com; s=selector1; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-SenderADCheck; bh=A8tRaZRGvhd6XkVH2cSh7vuzuihExurAkF6n9LbAmwE=; b=o4M3JZy2bmdSI0ps+f80r3qrcTmfNkpNq3XA5dV7e2Xglrxyve1fRaMKvv0mzPYu+oNIzge4r6wXnh5cx/W5Hjlpg/Y63meq4mXxFYgeunvKJ7QIrdLfCKkZdxcNHsAHkPIpZQUXfwsjDCe7J5a9ILFDZjKg0xwgqjqayWsGCTvTeH3CHSSv0xSEqdeAaM0EesEBOhge87DEmbscDyc7FPK1vFAbKVfLorHjepZC0rJxB+W+J00RuTH11Xb1SLbkpEEgGGf+p8Sr6B/xiGqwaH5/p6vZmyyfsZMytnhFrkfBkGp5QChQ5xgdPrrESgUbv32nDr58yoBN59fNEd38dQ== Original-Received: from VI1EUR04FT055.eop-eur04.prod.protection.outlook.com (2a01:111:e400:7e0e::50) by VI1EUR04HT008.eop-eur04.prod.protection.outlook.com (2a01:111:e400:7e0e::303) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.3700.27; Fri, 1 Jan 2021 14:32:41 +0000 Original-Received: from AM0PR06MB6577.eurprd06.prod.outlook.com (2a01:111:e400:7e0e::4f) by VI1EUR04FT055.mail.protection.outlook.com (2a01:111:e400:7e0e::173) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.3721.20 via Frontend Transport; Fri, 1 Jan 2021 14:32:41 +0000 X-IncomingTopHeaderMarker: OriginalChecksum:C1365FFE51786B3714AB62FA555788D3CE624590DC10619C8D18DEF2C889C704; UpperCasedChecksum:48AA5C8467B0673DFF050690403D9C7A6D3344BC8555DC4F7FC1054D1DEBC131; SizeAsReceived:7354; Count:46 Original-Received: from AM0PR06MB6577.eurprd06.prod.outlook.com ([fe80::9487:8c7d:da00:4993]) by AM0PR06MB6577.eurprd06.prod.outlook.com ([fe80::9487:8c7d:da00:4993%7]) with mapi id 15.20.3721.022; Fri, 1 Jan 2021 14:32:41 +0000 In-Reply-To: <87lfdddc0a.fsf@alphapapa.net> (Adam Porter's message of "Thu, 31 Dec 2020 13:13:57 -0600") X-TMN: [puhu/GywE4NG+REovJ27as1GyZDYyRZk] X-ClientProxiedBy: AM5PR1001CA0006.EURPRD10.PROD.OUTLOOK.COM (2603:10a6:206:2::19) To AM0PR06MB6577.eurprd06.prod.outlook.com (2603:10a6:208:19a::23) X-Microsoft-Original-Message-ID: <87h7o07mns.fsf@live.com> X-MS-Exchange-MessageSentRepresentingType: 1 Original-Received: from pascal.homepc (90.230.29.56) by AM5PR1001CA0006.EURPRD10.PROD.OUTLOOK.COM (2603:10a6:206:2::19) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.3721.20 via Frontend Transport; Fri, 1 Jan 2021 14:32:40 +0000 X-MS-PublicTrafficType: Email X-IncomingHeaderCount: 46 X-EOPAttributedMessage: 0 X-MS-Office365-Filtering-Correlation-Id: 319d3062-5c37-4b15-ba78-08d8ae621850 X-MS-TrafficTypeDiagnostic: VI1EUR04HT008: X-Microsoft-Antispam: BCL:0; X-Microsoft-Antispam-Message-Info: mP+smwV8ttFPcV197JZWpL4vVn4wwg+SHWtx3sebzLj6mWXq+srcWAEjiobzEkz5RHSUseV3G3lZLUB7ZvJg+kuykL45RD4aZ4OAXf3atDDmP56UWDX+OVAMcjNXv6jXmaBblMjWei5sh/9FIS6C46z2R8zFMM2DdIStzwsCSI/M/gpDGYPy1ddy7XtD3XdgPdfVgTUDKBJTlSqte06ySZ80yE+jhGV3LXpbo8igUFGVz/6P4Y9NWn7WM6gNrVGR X-MS-Exchange-AntiSpam-MessageData: Wk1khFnH5SYD7np+w6SOSmtZimHUJHqpzSjh1SjzNObYj4Q554ufVl7UrC3dzzBRQk1Avyx9lIf7ExWcGxtbagFWWXrVNGL539WNeCdE4DzDKA94Xo0YuYm1e+nDxnRer6jtuaIglPafryyEf244aw== X-OriginatorOrg: live.com X-MS-Exchange-CrossTenant-OriginalArrivalTime: 01 Jan 2021 14:32:40.9771 (UTC) X-MS-Exchange-CrossTenant-FromEntityHeader: Hosted X-MS-Exchange-CrossTenant-Id: 84df9e7f-e9f6-40af-b435-aaaaaaaaaaaa X-MS-Exchange-CrossTenant-Network-Message-Id: 319d3062-5c37-4b15-ba78-08d8ae621850 X-MS-Exchange-CrossTenant-AuthSource: VI1EUR04FT055.eop-eur04.prod.protection.outlook.com X-MS-Exchange-CrossTenant-AuthAs: Anonymous X-MS-Exchange-CrossTenant-FromEntityHeader: Internet X-MS-Exchange-CrossTenant-RMS-PersistedConsumerOrg: 00000000-0000-0000-0000-000000000000 X-MS-Exchange-Transport-CrossTenantHeadersStamped: VI1EUR04HT008 Received-SPF: pass client-ip=40.92.73.28; envelope-from=arthur.miller@live.com; helo=EUR04-HE1-obe.outbound.protection.outlook.com X-Spam_score_int: -20 X-Spam_score: -2.1 X-Spam_bar: -- X-Spam_report: (-2.1 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, FREEMAIL_FROM=0.001, MSGID_FROM_MTA_HEADER=0.001, RCVD_IN_DNSWL_NONE=-0.0001, RCVD_IN_MSPIKE_H2=-0.001, SPF_HELO_PASS=-0.001, SPF_PASS=-0.001 autolearn=ham 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:262252 Archived-At: Adam Porter writes: > 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 > > 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 I am not an expert in Lisp; but I personally think those 'with-*' macros are more idiomatic ot Lisp. They are also communicate clearly the scope where a variable (in this case buffer or file are used). I understand Eli's remark that thay don't remove much to the burden of user in LoCs the user have to write, but they do document the user intention and helps writing better self-documenting code. I compare this personally to std::copy from C++ vs raw for-loop. std::copy does not make us write less code; usually if the loop body is big we will refactor it in a callback, but it does document the intention. Elisp is not as efficient as c++ compiler to eliminate the overhead, but macros will be expanded when byte-compiled, so it really is just the memory overhead of defining the body of mcaro. I personally use, beside the macro I posted in documentation thread, those two macros, which are admittedly bad, but works for me personally: #+BEGIN_SRC elisp (defmacro with-file-prepend (file &rest body) (declare (indent 1) (debug t)) `(let ((buffer (get-buffer-create ,file))) (unwind-protect (prog1 (with-current-buffer buffer ,@body (goto-char (point-max)) (if (file-readable-p ,file) (insert-file-contents ,file)) (with-current-buffer buffer (write-region nil nil ,file nil 0))) (and (buffer-name buffer) (kill-buffer buffer)))))) (defmacro with-file-append (file &rest body) (declare (indent 1) (debug t)) `(let ((buffer (get-buffer-create ,file))) (unwind-protect (prog1 (with-current-buffer buffer (if (file-readable-p ,file) (insert-file-contents ,file)) (goto-char (point-max)) ,@body) (with-current-buffer buffer (write-region nil nil ,file nil 0))) (and (buffer-name buffer) (kill-buffer buffer))))) #+END_SRC I can then use them like: #+BEGIN_SRC elisp (with-file-prepend "early-init.el" (insert (concat ";;; early-init.el -*- lexical-binding: t; -*-\n" ";;; This file is machine generated by init file generator, don't edit\n" ";;; manually, edit instead file init.org and generate new init file from it\n\n"))) (with-file-append "init.el" (insert (concat "\n;; Local Variables:\n" ";; byte-compile-warnings: (not free-vars unresolved))\n" ";; End:\n"))) #+END_SRC I don't suggest to use/includ those, just that I find it personally handy, and more idiomatic then using say f.el or write-region directly which is kind-of more C/Java-like APIs. I personally am not qualified to say if plist implementation of 'with-file-buffer' macro is good or not; but I think the intention is certainly good. If you are already buffing string library, make you could buff file API too.