From unknown Thu Jun 19 14:04:27 2025 Content-Disposition: inline Content-Transfer-Encoding: quoted-printable MIME-Version: 1.0 X-Mailer: MIME-tools 5.509 (Entity 5.509) Content-Type: text/plain; charset=utf-8 From: bug#57955 <57955@debbugs.gnu.org> To: bug#57955 <57955@debbugs.gnu.org> Subject: Status: 29.0.50; Allow session-local ERC modules Reply-To: bug#57955 <57955@debbugs.gnu.org> Date: Thu, 19 Jun 2025 21:04:27 +0000 retitle 57955 29.0.50; Allow session-local ERC modules reassign 57955 emacs submitter 57955 "J.P." severity 57955 wishlist tag 57955 patch thanks From debbugs-submit-bounces@debbugs.gnu.org Tue Sep 20 09:06:01 2022 Received: (at submit) by debbugs.gnu.org; 20 Sep 2022 13:06:01 +0000 Received: from localhost ([127.0.0.1]:56551 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1oacwq-0005Xc-Is for submit@debbugs.gnu.org; Tue, 20 Sep 2022 09:06:01 -0400 Received: from lists.gnu.org ([209.51.188.17]:45430) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1oacwo-0005XU-7h for submit@debbugs.gnu.org; Tue, 20 Sep 2022 09:05:59 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]:33714) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1oacwi-0004Tw-9o for bug-gnu-emacs@gnu.org; Tue, 20 Sep 2022 09:05:57 -0400 Received: from mail-108-mta51.mxroute.com ([136.175.108.51]:41155) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1oacwe-0008N5-LQ for bug-gnu-emacs@gnu.org; Tue, 20 Sep 2022 09:05:51 -0400 Received: from mail-111-mta2.mxroute.com ([136.175.111.2] filter006.mxroute.com) (Authenticated sender: mN4UYu2MZsgR) by mail-108-mta51.mxroute.com (ZoneMTA) with ESMTPSA id 1835b0115be0002b7a.001 for (version=TLSv1/SSLv3 cipher=ECDHE-RSA-AES128-GCM-SHA256); Tue, 20 Sep 2022 13:05:41 +0000 X-Zone-Loop: 95530989caf8d8ece97d249344da982d9342f7cb2253 X-Originating-IP: [136.175.111.2] DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=neverwas.me ; s=x; h=Content-Type:MIME-Version:Message-ID:Date:Subject:To:From:Sender: Reply-To:Cc:Content-Transfer-Encoding:Content-ID:Content-Description: Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID: In-Reply-To:References:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=3F0SHVhmuVURosZQjIbN1WZS8Cle7NN3rYjG3zwaITc=; b=E7+S5ZNHiUSfKy1+I2JITpIgLk k9RHuII9p3oXNP1JxEMoHuki2AFwk+IFKeXLeGFxyg3wMdnoLXpC6eMRA2pYDpvM+qX10W1vsIm3x XEQD2B0ujRKIvS0QUSjzt3P+4jzNsnHyHo4UUNmMfT4uTyo2mmruuStEmwf0sbe210OUBY3a/eySu pIeB1p7KkmwHpzd05r6wxBXARYzFXONRUd7FT/dCNu2La2XAYCY5wuQpovhA80h96jkMcRj1hKWfY VA08WpOh1BT69HhF9Ro551Q5QZe2gdvP5uCJ0WNQMW4lE1eabYrS3DgaWhy4zJvvxs9jOVKyLz2iO a2/aoXxw==; From: "J.P." To: bug-gnu-emacs@gnu.org Subject: 29.0.50; Allow session-local ERC modules X-Debbugs-CC: emacs-erc@gnu.org Date: Tue, 20 Sep 2022 06:05:38 -0700 Message-ID: <8735cm2o2l.fsf@neverwas.me> User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/29.0.50 (gnu/linux) MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" X-Authenticated-Id: masked@neverwas.me Received-SPF: pass client-ip=136.175.108.51; envelope-from=jp@neverwas.me; helo=mail-108-mta51.mxroute.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, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-Spam-Score: -1.4 (-) X-Debbugs-Envelope-To: submit X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: debbugs-submit-bounces@debbugs.gnu.org Sender: "Debbugs-submit" X-Spam-Score: -2.4 (--) --=-=-= Content-Type: text/plain Tags: patch Hi people, Since its inception, ERC has aimed to support local modules, that is, modules local to a connection. (If you need convincing of this, take a look at `define-erc-module'.) This makes sense for a good many reasons, chief among them simplified semantics when arranging for buffer-local variables and hooks. Ancillary benefits include let-binding `erc-modules' around entry-point invocations and selectively disabling modules for particular sessions (e.g., after capability negotiation). Unfortunately, this dream of ERC's authors was never fully realized. Take a look at `erc-open', where you'll find would-be local vars being set too early and thus clobbered when `erc-mode' (the major mode) is activated. Various kludges have come along to circumvent this. For example, the log module uses `erc-connect-pre-hook' to conduct its buffer-local business. But it shouldn't have to. ERC can do better. This patch aims to address this problem by partially changing the purpose of the function `erc-update-modules' in a backward compatible way. Instead of activating local modules immediately, it now returns them in a list to be activated at a time and place of the caller's choosing. The most opportune place for this, in terms of `erc-open', is after all the core local variables have been determined, which exposes them to module setup code. As a bonus, the major mode hook is likewise deferred to this point. This patch also reworks the module-to-features map and preferred-name migration logic, which was incomplete. As far as third-party packages are concerned, it's only been tested with erc-hl-nicks, thus far, but it "should" work with others too. (Please let me know if that's a lie.) Thanks, J.P. P.S. BTW, if anyone is friendly with the hl-nicks author, please ask them to reach out regarding an unrelated custom.el issue; attempts to contact them via GitHub have so far proven unsuccessful. In GNU Emacs 29.0.50 (build 2, x86_64-pc-linux-gnu, GTK+ Version 3.24.34, cairo version 1.17.6) of 2022-09-19 built on localhost Repository revision: 132d5cb0a3ec94afbb49772631861e00160ffffb Repository branch: master Windowing system distributor 'The X.Org Foundation', version 11.0.12014000 System Description: Fedora Linux 36 (Workstation Edition) Configured using: 'configure --enable-check-lisp-object-type --enable-checking=yes,glyphs 'CFLAGS=-O0 -g3' PKG_CONFIG_PATH=:/usr/lib64/pkgconfig:/usr/share/pkgconfig' Configured features: ACL CAIRO DBUS FREETYPE GIF GLIB GMP GNUTLS GPM GSETTINGS HARFBUZZ JPEG JSON LCMS2 LIBOTF LIBSELINUX LIBSYSTEMD LIBXML2 M17N_FLT MODULES NOTIFY INOTIFY PDUMPER PNG RSVG SECCOMP SOUND SQLITE3 THREADS TIFF TOOLKIT_SCROLL_BARS WEBP X11 XDBE XIM XINPUT2 XPM GTK3 ZLIB Important settings: value of $LANG: en_US.UTF-8 value of $XMODIFIERS: @im=ibus locale-coding-system: utf-8-unix Major mode: Lisp Interaction Minor modes in effect: tooltip-mode: t global-eldoc-mode: t eldoc-mode: t show-paren-mode: t electric-indent-mode: t mouse-wheel-mode: t tool-bar-mode: t menu-bar-mode: t file-name-shadow-mode: t global-font-lock-mode: t font-lock-mode: t blink-cursor-mode: t line-number-mode: t indent-tabs-mode: t transient-mark-mode: t auto-composition-mode: t auto-encryption-mode: t auto-compression-mode: t Load-path shadows: None found. Features: (shadow sort mail-extr emacsbug message mailcap yank-media puny dired dired-loaddefs rfc822 mml mml-sec password-cache epa derived epg rfc6068 epg-config gnus-util text-property-search time-date subr-x mm-decode mm-bodies mm-encode mail-parse rfc2231 mailabbrev gmm-utils mailheader cl-loaddefs cl-lib sendmail rfc2047 rfc2045 ietf-drums mm-util mail-prsvr mail-utils rmc iso-transl tooltip eldoc paren electric uniquify ediff-hook vc-hooks lisp-float-type elisp-mode mwheel term/x-win x-win term/common-win x-dnd tool-bar dnd fontset image regexp-opt fringe tabulated-list replace newcomment text-mode lisp-mode prog-mode register page tab-bar menu-bar rfn-eshadow isearch easymenu timer select scroll-bar mouse jit-lock font-lock syntax font-core term/tty-colors frame minibuffer nadvice seq simple cl-generic indonesian philippine cham georgian utf-8-lang misc-lang vietnamese tibetan thai tai-viet lao korean japanese eucjp-ms cp51932 hebrew greek romanian slovak czech european ethiopic indian cyrillic chinese composite emoji-zwj charscript charprop case-table epa-hook jka-cmpr-hook help abbrev obarray oclosure cl-preloaded button loaddefs faces cus-face macroexp files window text-properties overlay sha1 md5 base64 format env code-pages mule custom widget keymap hashtable-print-readable backquote threads dbusbind inotify lcms2 dynamic-setting system-font-setting font-render-setting cairo move-toolbar gtk x-toolkit xinput2 x multi-tty make-network-process emacs) Memory information: ((conses 16 36059 6198) (symbols 48 5107 0) (strings 32 13115 1641) (string-bytes 1 372299) (vectors 16 9247) (vector-slots 8 146583 10252) (floats 8 21 25) (intervals 56 220 0) (buffers 1000 10)) --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=0002-Support-local-ERC-modules-in-erc-mode-buffers.patch >From b88bcadffba84b64ae91d45b84736313ac49dfef Mon Sep 17 00:00:00 2001 From: "F. Jason Park" Date: Mon, 12 Jul 2021 03:44:28 -0700 Subject: [PATCH 2/4] Support local ERC modules in erc-mode buffers * lisp/erc/erc.el (erc-migrate-modules): add some missing mappings. (erc--module-name-migrations, erc--features-to-modules, erc--modules-to-features): add alists to support simplified module-name migrations. (erc-update-modules): Change return value to a list of minor-mode commands for local modules that need deferred activation, if any. Use `custom-variable-p' to detect flavor. Currently, all modules are global, meaning so are their accompanying minor modes. (erc-open): Defer enabling of local modules via `erc-update-modules' until after buffer is initialized with other local vars. Also defer major mode hooks so they can detect things like whether the buffer is a server or target buffer. (define-erc-modules): Don't enable local modules (minor modes) unless `erc-mode' is the major mode. And don't disable them unless the minor mode is actually active. Also, don't mutate `erc-modules' when dealing with a local module. It's believed that the original authors wanted this functionality. --- lisp/erc/erc.el | 108 ++++++++++++++++++++++++------------- test/lisp/erc/erc-tests.el | 47 ++++++++++++++++ 2 files changed, 119 insertions(+), 36 deletions(-) diff --git a/lisp/erc/erc.el b/lisp/erc/erc.el index 20f22c896f..8fa9d0c8a3 100644 --- a/lisp/erc/erc.el +++ b/lisp/erc/erc.el @@ -1390,7 +1390,9 @@ define-erc-module This will define a minor mode called erc-NAME-mode, possibly an alias erc-ALIAS-mode, as well as the helper functions -erc-NAME-enable, and erc-NAME-disable. +erc-NAME-enable, and erc-NAME-disable. Beware that for global +modules, these helpers, as well as the minor-mode toggle, all mutate +the user option `erc-modules'. Example: @@ -1426,16 +1428,21 @@ define-erc-module ,(format "Enable ERC %S mode." name) (interactive) - (add-to-list 'erc-modules (quote ,name)) - (setq ,mode t) - ,@enable-body) + (unless ,local-p + (cl-pushnew (erc--normalize-module-symbol ',name) erc-modules)) + (when (or ,(not local-p) (eq major-mode 'erc-mode)) + (setq ,mode t) + ,@enable-body)) (defun ,disable () ,(format "Disable ERC %S mode." name) (interactive) - (setq erc-modules (delq (quote ,name) erc-modules)) - (setq ,mode nil) - ,@disable-body) + (unless ,local-p + (setq erc-modules (delq (erc--normalize-module-symbol ',name) + erc-modules))) + (when (or ,(not local-p) ,mode) + (setq ,mode nil) + ,@disable-body)) ,(when (and alias (not (eq name alias))) `(defalias ',(intern @@ -2030,14 +2037,40 @@ erc-default-nicks (defvar-local erc-nick-change-attempt-count 0 "Used to keep track of how many times an attempt at changing nick is made.") +(defconst erc--features-to-modules + '((erc-pcomplete completion pcomplete) + (erc-capab capab-identify) + (erc-join autojoin) + (erc-page page ctcp-page) + (erc-sound sound ctcp-sound) + (erc-stamp stamp timestamp) + (erc-services services nickserv)) + "Migration alist mapping a library feature to module names. +Keys need not be unique: a library may define more than one +module.") + +(defconst erc--modules-to-features + (cl-loop for (feature . names) in erc--features-to-modules + append (mapcar (lambda (name) (cons name feature)) names)) + "Migration alist mapping a module's name to library feature.") + +(defconst erc--module-name-migrations + (let (pairs) + (pcase-dolist (`(,_ ,canonical . ,rest) erc--features-to-modules) + (dolist (obsolete rest) + (push (cons obsolete canonical) pairs))) + pairs) + "Association list of obsolete module names to canonical names.") + +(defun erc--normalize-module-symbol (module) + "Canonicalize symbol MODULE for `erc-modules'." + (or (cdr (assq module erc--module-name-migrations)) module)) + (defun erc-migrate-modules (mods) "Migrate old names of ERC modules to new ones." ;; modify `transforms' to specify what needs to be changed ;; each item is in the format '(old . new) - (let ((transforms '((pcomplete . completion)))) - (delete-dups - (mapcar (lambda (m) (or (cdr (assoc m transforms)) m)) - mods)))) + (delete-dups (mapcar #'erc--normalize-module-symbol mods))) (defcustom erc-modules '(netsplit fill button match track completion readonly networks ring autojoin noncommands irccontrols @@ -2116,27 +2149,22 @@ erc-modules :group 'erc) (defun erc-update-modules () - "Run this to enable erc-foo-mode for all modules in `erc-modules'." - (let (req) + "Enable global minor mode for all global modules in `erc-modules'. +Return minor-mode commands for all local modules, possibly for +deferred invocation, as done by `erc-open' whenever a new ERC +buffer is created. Local modules were introduced in ERC 5.6." + (let (local-modules) (dolist (mod erc-modules) - (setq req (concat "erc-" (symbol-name mod))) - (cond - ;; yuck. perhaps we should bring the filenames into sync? - ((string= req "erc-capab-identify") - (setq req "erc-capab")) - ((string= req "erc-completion") - (setq req "erc-pcomplete")) - ((string= req "erc-pcomplete") - (setq mod 'completion)) - ((string= req "erc-autojoin") - (setq req "erc-join"))) - (condition-case nil - (require (intern req)) - (error nil)) + (require (or (alist-get mod erc--modules-to-features) + (intern (concat "erc-" (symbol-name mod)))) + nil 'noerror) ; some modules don't have a corresponding feature (let ((sym (intern-soft (concat "erc-" (symbol-name mod) "-mode")))) - (if (fboundp sym) + (unless (and sym (fboundp sym)) + (error "`%s' is not a known ERC module" mod)) + (if (custom-variable-p sym) (funcall sym 1) - (error "`%s' is not a known ERC module" mod)))))) + (push sym local-modules)))) + local-modules)) (defun erc-setup-buffer (buffer) "Consults `erc-join-buffer' to find out how to display `BUFFER'." @@ -2192,18 +2220,22 @@ erc-open (let* ((target (and channel (erc--target-from-string channel))) (buffer (erc-get-buffer-create server port nil target id)) (old-buffer (current-buffer)) - old-point + (old-recon-count erc-server-reconnect-count) + (old-point nil) + (delayed-modules nil) (continued-session (and erc--server-reconnecting (with-suppressed-warnings ((obsolete erc-reuse-buffers)) erc-reuse-buffers)))) (when connect (run-hook-with-args 'erc-before-connect server port nick)) - (erc-update-modules) (set-buffer buffer) (setq old-point (point)) - (let ((old-recon-count erc-server-reconnect-count)) - (erc-mode) - (setq erc-server-reconnect-count old-recon-count)) + (setq delayed-modules (erc-update-modules)) + + (delay-mode-hooks (erc-mode)) + + (setq erc-server-reconnect-count old-recon-count) + (when (setq erc-server-connected (not connect)) (setq erc-server-announced-name (buffer-local-value 'erc-server-announced-name old-buffer))) @@ -2266,6 +2298,12 @@ erc-open (setq erc-dbuf (when erc-log-p (get-buffer-create (concat "*ERC-DEBUG: " server "*")))) + + (erc-determine-parameters server port nick full-name user passwd) + + (save-excursion (run-mode-hooks)) + (dolist (mod delayed-modules) (funcall mod +1)) + ;; set up prompt (unless continued-session (goto-char (point-max)) @@ -2277,8 +2315,6 @@ erc-open (erc-display-prompt) (goto-char (point-max))) - (erc-determine-parameters server port nick full-name user passwd) - ;; Saving log file on exit (run-hook-with-args 'erc-connect-pre-hook buffer) diff --git a/test/lisp/erc/erc-tests.el b/test/lisp/erc/erc-tests.el index b2ed29e80e..d3d319ab22 100644 --- a/test/lisp/erc/erc-tests.el +++ b/test/lisp/erc/erc-tests.el @@ -975,4 +975,51 @@ erc-message (kill-buffer "ExampleNet") (kill-buffer "#chan"))) +(ert-deftest erc-migrate-modules () + (should (equal (erc-migrate-modules '(autojoin timestamp button)) + '(autojoin stamp button))) + ;; Default unchanged + (should (equal (erc-migrate-modules erc-modules) erc-modules))) + +(ert-deftest erc-update-modules () + (let* (calls + (erc-modules '(fake-foo fake-bar))) + (cl-letf (((symbol-function 'require) + (lambda (s &rest _) (push s calls))) + ((symbol-function 'erc-fake-foo-mode) + (lambda (n) (push (cons 'fake-foo n) calls))) + ;; Here, foo is a global module (minor mode) + ((get 'erc-fake-foo-mode 'standard-value) #'ignore) + ((symbol-function 'erc-fake-bar-mode) + (lambda (n) (push (cons 'fake-bar n) calls))) + ((symbol-function 'erc-autojoin-mode) + (lambda (n) (push (cons 'autojoin n) calls))) + ((get 'erc-autojoin-mode 'standard-value) #'ignore) + ((symbol-function 'erc-networks-mode) + (lambda (n) (push (cons 'networks n) calls))) + ((symbol-function 'erc-completion-mode) + (lambda (n) (push (cons 'completion n) calls))) + ((get 'erc-completion-mode 'standard-value) #'ignore)) + + (ert-info ("Locals") + (should (equal (erc-update-modules) + '(erc-fake-bar-mode))) + ;; Bar still required + (should (equal (nreverse calls) '(erc-fake-foo + (fake-foo . 1) + erc-fake-bar))) + (setq calls nil)) + + (ert-info ("Module name overrides") + (setq erc-modules '(completion autojoin networks)) + (should-not (erc-update-modules)) ; no locals + (should (equal (nreverse calls) + '(erc-pcomplete + (completion . 1) + erc-join + (autojoin . 1) + erc-networks + (networks . 1)))) + (setq calls nil))))) + ;;; erc-tests.el ends here -- 2.37.2 --=-=-=-- From debbugs-submit-bounces@debbugs.gnu.org Tue Sep 20 13:43:52 2022 Received: (at 57955) by debbugs.gnu.org; 20 Sep 2022 17:43:52 +0000 Received: from localhost ([127.0.0.1]:59550 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1oahHj-0003NM-QC for submit@debbugs.gnu.org; Tue, 20 Sep 2022 13:43:51 -0400 Received: from mout.gmx.net ([212.227.17.21]:56053) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1oahHe-0003N6-3J for 57955@debbugs.gnu.org; Tue, 20 Sep 2022 13:43:50 -0400 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=gmx.net; s=badeba3b8450; t=1663695816; bh=Dm0aKnKgQdM2xZe7C5wf0rOXyCyfGDfR+czeSn5KpnM=; h=X-UI-Sender-Class:From:To:Cc:Subject:In-Reply-To:References:Date; b=I61CM3mNdsxCffhq29AoNlTWnxklaXAsUQ2uXef0ca19XDV8R3nSmD+EPacaO/L/o zFAuB8iZqT/EncjBPk4C9esVrjM5hN+KDb+UVlpo1t4a4alpXOUlKAzirxBzVcoYgO +/h5235e1kCqyxj4EVVY0GfjhzZi9vwOe1+WZGmk= X-UI-Sender-Class: 01bb95c1-4bf8-414a-932a-4f6e2808ef9c Received: from gandalf.gmx.de ([79.140.125.182]) by mail.gmx.net (mrgmx105 [212.227.17.168]) with ESMTPSA (Nemesis) id 1ML9yc-1osm613O8A-00IGfp; Tue, 20 Sep 2022 19:43:35 +0200 From: Michael Albinus To: "J.P." Subject: Re: bug#57955: 29.0.50; Allow session-local ERC modules In-Reply-To: <8735cm2o2l.fsf__21117.726997339$1663692322$gmane$org@neverwas.me> (J. P.'s message of "Tue, 20 Sep 2022 06:05:38 -0700") References: <8735cm2o2l.fsf__21117.726997339$1663692322$gmane$org@neverwas.me> Date: Tue, 20 Sep 2022 19:43:34 +0200 Message-ID: <87r106eybd.fsf@gmx.de> User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/29.0.50 (gnu/linux) MIME-Version: 1.0 Content-Type: text/plain X-Provags-ID: V03:K1:YVqWuGgOcu3teWsi7WATMCF0D4/J+t020qlQKEwMW46vQDPpzuc bVXPQpLGUJM0qp5X1Can6/d02mcGOzarO8mCjT+alZ3vhHYP3Uf7sbbfxZanVOkrA8+kMKx rJCkpbWkI+Crb0cywkH8FqqjFh5EW7hUuNUkH4PrpIja6Tm1anbGI6if0bsoBrWXMqYGak+ RMWACwF1yiMuJqkJ4hORQ== X-Spam-Flag: NO X-UI-Out-Filterresults: notjunk:1;V03:K0:/Cs0C6xBtaI=:nI5/EjB5B0wnD1ByaHtdWa eMCwk/kNSAlC+/Qk1Rcr0Lftq/07IdWe0lUeyGw8IwxU3JZeUkXtHurhye5JoOVNqLZjE1Eay 1xRawyDGpbqpnCTK4l2s3xooRVrz0njnbBncI5RXv9dQKaTWhhmf/+kgzdOEDOEFd2P8AVNvf qg83egLyckS4FhYAJBGsmADlbOsjK0B0I72PdcTh0Jcz5EQJ4PajB8e58KpO6vN8O83+rTol/ NdJkjvqKe6neIUrH3n2j53U1Pj9JjlHr36BVkOFQ1yzgh1xKmE52LoujxPNJ2AHAvlfHlJkD2 /+FF9W4/CJ5ngnkbQ0t/0kYctn6sD2uzD0GlPMJl6I0IzFAexlx132P1dXobU0ZCI7RIq1KKC 3Te9v7W7vFsiJVtoyMRJEYjx/hTbCdCk/5Jsf/ni3eFEiri5k18XRTL9vJp6skcEHMqUz8GDW 66Hcv65tkSHoK9bHndWcd0Rdxye6MQpmUsIzFUemGYJgNQeQgk1pEaelSGK82fM55+/olAGQU n8IaP3Cej5qPDifYRjTpZfhwFluwjt2TVj1lVHPnTKEAWaB0wOArx3leMZzRwH32gaQZaX635 dPF9c4LWR2LlOzlVCv19MP4RKFhMpb0vN/RwoRUTQ9o8F3tn4jZpbq94jwZp4M2qdp96Y7utD tEgCJ7ZOOgoIjyxSTfq6fEhRL1O4AkrJ0+PXFnHzwQ4KHUy5lUwq3Cv3zJnp4U8ey5hN0nYmo dxVoARcFXu8ku1rwgpdsgtnRYxB9TziDGVRjCg3KvMIIEJSiZ4lbTajGbAaYiOob4SLm5ICaw 9qcPiCVWwNDAp/LaQsREzB1CERPgOZyALVrchyhK8eW2ybl/s5kScKCLO1BYD//I/oRmSlErV 33v5mF2lSAdmxqAQOAzI/TbN2NcNSv/gpkW/m3lFf1KupnnKt8AQlSjjlGWfMoZMia+mCmgF5 JjBBU5FidXkkL9iG5mQJOOA18e3nWLIkdd9zbEcufolpitkLeHkgo2WhKNbEq44YCVjtJmaR9 q2Z+tZl6b9qO0y2Jcv66Bw4Mmm9yNnnQq2zsIlop47MQNA4VAh2h5nriQI3MHxhb+fyFSD8ns 2wBAJNMo3y6ngz5Jade0vuapwj3Kb59DbGDTn7WIyJDv0auoLYaJIzYPwQ+CMql/HwR3vm+P1 usT+aPbCV36FnlPkxBZEPlKtI2 X-Spam-Score: -0.7 (/) X-Debbugs-Envelope-To: 57955 Cc: 57955@debbugs.gnu.org, emacs-erc@gnu.org X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: debbugs-submit-bounces@debbugs.gnu.org Sender: "Debbugs-submit" X-Spam-Score: -1.7 (-) "J.P." writes: > Hi people, Hi, > Since its inception, ERC has aimed to support local modules, that is, > modules local to a connection. (If you need convincing of this, take a > look at `define-erc-module'.) This makes sense for a good many reasons, > chief among them simplified semantics when arranging for buffer-local > variables and hooks. Ancillary benefits include let-binding > `erc-modules' around entry-point invocations and selectively disabling > modules for particular sessions (e.g., after capability negotiation). Without knowing erc in general and your patch in detail: this sounds like you could profit from connection-local variables. Did you check this? > Thanks, > J.P. Best regards, Michael. From debbugs-submit-bounces@debbugs.gnu.org Wed Sep 21 09:16:05 2022 Received: (at 57955) by debbugs.gnu.org; 21 Sep 2022 13:16:05 +0000 Received: from localhost ([127.0.0.1]:32957 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1oaza9-0007P3-4H for submit@debbugs.gnu.org; Wed, 21 Sep 2022 09:16:05 -0400 Received: from mail-108-mta83.mxroute.com ([136.175.108.83]:44859) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1oaza6-0007ON-Jd for 57955@debbugs.gnu.org; Wed, 21 Sep 2022 09:16:03 -0400 Received: from mail-111-mta2.mxroute.com ([136.175.111.2] filter006.mxroute.com) (Authenticated sender: mN4UYu2MZsgR) by mail-108-mta83.mxroute.com (ZoneMTA) with ESMTPSA id 1836030c9440002b7a.001 for <57955@debbugs.gnu.org> (version=TLSv1/SSLv3 cipher=ECDHE-RSA-AES128-GCM-SHA256); Wed, 21 Sep 2022 13:15:53 +0000 X-Zone-Loop: ecf00f9e2a5950d8ef4ef8ed05eea8c4a625c2f935a4 X-Originating-IP: [136.175.111.2] DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=neverwas.me ; s=x; h=Content-Type:MIME-Version:Message-ID:Date:References:In-Reply-To: Subject:Cc:To:From:Sender:Reply-To:Content-Transfer-Encoding:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=uYmYjzylxd0k0B59Yss4q7xmduVPxvmXcx40DeCI/A8=; b=IuKKCrc57rfZwehIh+mSYotgAl 3oTuhnn0pDPOBDyIogPMidyepK3WUG1fahpU0R9F6GfJTdBp5VrvM9moqU8tNePtyIhIdkZ4bH2q5 lR7n2ip/ITxUxl5v8eOxWO+jcc7m1QNJPlYkMJK2VNZZQsRL27r1MlB8xU4ZahCMtoI+iz4UgUrPz nTyj8lilabrBvVr6sh7k/zTS4+FE126rxEIJR3GKC019aPP3K4jXly2jZd/RDslPIngD5nmtPmS0R B/T2Om5GYnquIccyMgyiIzJIsxdTLIGjKjCK/Q2sRUk9Tymq0orA9gloCb4oFwpE+NciBcEKuEylB gpt759ow==; From: "J.P." To: Michael Albinus Subject: Re: bug#57955: 29.0.50; Allow session-local ERC modules In-Reply-To: <87r106eybd.fsf@gmx.de> (Michael Albinus's message of "Tue, 20 Sep 2022 19:43:34 +0200") References: <8735cm2o2l.fsf__21117.726997339$1663692322$gmane$org@neverwas.me> <87r106eybd.fsf@gmx.de> Date: Wed, 21 Sep 2022 06:15:48 -0700 Message-ID: <87czboswaj.fsf@neverwas.me> User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/29.0.50 (gnu/linux) MIME-Version: 1.0 Content-Type: text/plain X-Authenticated-Id: masked@neverwas.me X-Spam-Score: 0.0 (/) X-Debbugs-Envelope-To: 57955 Cc: 57955@debbugs.gnu.org, emacs-erc@gnu.org X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: debbugs-submit-bounces@debbugs.gnu.org Sender: "Debbugs-submit" X-Spam-Score: -1.0 (-) Hi Michael, Michael Albinus writes: > Without knowing erc in general and your patch in detail: this sounds > like you could profit from connection-local variables. Did you check > this? Not quite yet (only superficially). At first glance, I'm not sure they're a perfect fit for this specific issue, but I'll definitely investigate further. Either way, I'm thinking they'd be a great solution (or inspiration) for an initiative we have on the horizon, namely, devising a means of applying user options in a more granular, contextual manner [1]. Also (if you happen to recall), a few of our recent exchanges ended with me pledging to follow through on one thing or another. And yet, most of those promises remain unfulfilled. Please know that I do plan on addressing them "eventually" and that I very much appreciate your help (and your patience). Thanks, J.P. [1] In case you're interested, by "context," I'm referring to various logical (somewhat overlapping) IRC boundaries, such as - message: event-local, i.e., source-wise and message-type-wise - channel: target-local - network: connection-local Basically, I'm looking for something akin to a "context variable" facility, except not so much for managing concurrency but instead for transparently stashing and restoring message-processing environments matched against headers and protocol state. More info: https://lists.gnu.org/archive/html/emacs-erc/2021-10/msg00003.html From debbugs-submit-bounces@debbugs.gnu.org Thu Oct 13 09:47:41 2022 Received: (at control) by debbugs.gnu.org; 13 Oct 2022 13:47:41 +0000 Received: from localhost ([127.0.0.1]:60020 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1oiyYm-0005Ct-SP for submit@debbugs.gnu.org; Thu, 13 Oct 2022 09:47:41 -0400 Received: from mail-oa1-f46.google.com ([209.85.160.46]:42811) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1oiyYj-0005Bp-I0 for control@debbugs.gnu.org; Thu, 13 Oct 2022 09:47:37 -0400 Received: by mail-oa1-f46.google.com with SMTP id 586e51a60fabf-1370acb6588so2315816fac.9 for ; Thu, 13 Oct 2022 06:47:37 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20210112; h=to:subject:message-id:date:mime-version:from:from:to:cc:subject :date:message-id:reply-to; bh=KBUKUVbeyiQvHc9ZT70wIvwoZHXIZUX1HLKUWrp4Hik=; b=gbUoiC3LSwnhM9a0+5iY8isdydj369UNX2hWQiiw9nssKPpdKrYOvWpakBPFCSBKNd K7BMi/ROSDtfJowNrIjtTuPLCh4NPGnCWrw7mlQQ6olzPluALzYBgLfHQhWLUjRxVWcn rUpsHQetFmPvBaoCWMBy5s2F4tx95lg6vOT8/9tbWfO+iMg53sfRy2wPMCW7YMxH7pTb NNmJlIyGn3ClaiQ+Vdvg+bpPIXRIbVRVU8e6ZptJT7Vd922bzcjpA2D6qYGq9G63v98/ qEe+L3qzJXCijQc3xcKkLnZUXH/+h7RyyyT+TAehYNWWzInTxguNiuzh4eKS8lMKHNgp 8QOQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=to:subject:message-id:date:mime-version:from:x-gm-message-state :from:to:cc:subject:date:message-id:reply-to; bh=KBUKUVbeyiQvHc9ZT70wIvwoZHXIZUX1HLKUWrp4Hik=; b=ev9RQIwVISHhzB5YVXOcJm+zkyP4QXnw9xTQujtOT0vs28ZkcIdwR0rfKx/jw9DVJu WfYzGYHuApiUI8UGqFRsgRNXrud6aEj3xGjClD5QM2zrAa2qYFj7QtdMYY4wmrEoglpx BKlma81UvW/TpmtllHHzQkMd8pYm/BRarsf+gpc5oc6X/rd0yPoUUCzPPCARxeP4Zxl7 rswxDeRHVziTLdLl11kpyvujWbekmcv21E31bj8Y6PLknkkg70Ch1SVQUR0xwIWyyC8e h7hgSdXvs2Lm34HJRzJ+EifhvjKmUHy409pN0+nPit/pxcV2O5FMqZfeg0v6phf5D4G8 SitQ== X-Gm-Message-State: ACrzQf28JylIPr2OhIjkImDB844J2Z4ejFgqoXLmT450StOJiLnCp7oH lR8V73UL7R7cBrXpsResNCRNzT22zx3fHT8jcQ8ZMZsZ X-Google-Smtp-Source: AMsMyM6CdyG3oWf2avHjnkPmJyRk4L1L4i4mc0vFQg1o23hnzoQTmcOc+8TWn0pEMKPP32uDjRFpSe0epWjxCpMqAWM= X-Received: by 2002:a05:6870:9126:b0:132:b724:e96c with SMTP id o38-20020a056870912600b00132b724e96cmr5567260oae.199.1665668852005; Thu, 13 Oct 2022 06:47:32 -0700 (PDT) Received: from 753933720722 named unknown by gmailapi.google.com with HTTPREST; Thu, 13 Oct 2022 15:47:31 +0200 From: Stefan Kangas X-Hashcash: 1:20:221013:control@debbugs.gnu.org::FZjGc0Mt1HEY4lyQ:0L5V MIME-Version: 1.0 Date: Thu, 13 Oct 2022 15:47:31 +0200 Message-ID: Subject: control message for bug #57955 To: control@debbugs.gnu.org Content-Type: text/plain; charset="UTF-8" X-Spam-Score: 0.0 (/) X-Debbugs-Envelope-To: control X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: debbugs-submit-bounces@debbugs.gnu.org Sender: "Debbugs-submit" X-Spam-Score: -1.0 (-) severity 57955 wishlist quit From debbugs-submit-bounces@debbugs.gnu.org Wed Oct 26 09:16:50 2022 Received: (at 57955) by debbugs.gnu.org; 26 Oct 2022 13:16:50 +0000 Received: from localhost ([127.0.0.1]:53682 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1ongH4-0000Sl-6E for submit@debbugs.gnu.org; Wed, 26 Oct 2022 09:16:50 -0400 Received: from mail-108-mta45.mxroute.com ([136.175.108.45]:37769) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1ongH1-0000SX-Ge for 57955@debbugs.gnu.org; Wed, 26 Oct 2022 09:16:48 -0400 Received: from mail-111-mta2.mxroute.com ([136.175.111.2] filter006.mxroute.com) (Authenticated sender: mN4UYu2MZsgR) by mail-108-mta45.mxroute.com (ZoneMTA) with ESMTPSA id 184147007ab0006e99.001 for <57955@debbugs.gnu.org> (version=TLSv1/SSLv3 cipher=ECDHE-RSA-AES128-GCM-SHA256); Wed, 26 Oct 2022 13:16:37 +0000 X-Zone-Loop: 4a0a201dae4af5f41845ead15c07cf38e6bfac3c2532 X-Originating-IP: [136.175.111.2] DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=neverwas.me ; s=x; h=Content-Type:MIME-Version:Message-ID:Date:References:In-Reply-To: Subject:Cc:To:From:Sender:Reply-To:Content-Transfer-Encoding:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=CIhv93Zo/2qAW0hOFr1//5x7OWqrNs2ykxUaDtuvrXs=; b=gWyYl3Ckt/nPdwJqK+4eL0lPGQ hYk71qXXEKabNDNiGaSnvWpZAfNzleguALuojPbpVYoKKTc+e0bmIDDbqQxFTo8a8Ntms9dw+arri x/KxBieWZhaMQBUiEmOlFRUF3qgZrZHu3v4dAc/rIv2FWrUbxbxEk0KK4fQa23Osfxv1AkGLs4UeK 3dWwxoJmMMvvWQAisF+PR84hCEob9OFWEQp+nXqPbh+9y3zVf714GKL07QfMlLn+Jp40bWK4sd/wc LPzOfJ71f7lWBwmlO/iVKN8BEekdGO1KaGyZNSYn55Kpq6uuI5GU2ne32/ZumKlJankmE6vSMZ/xE IDW5DKHQ==; From: "J.P." To: 57955@debbugs.gnu.org Subject: Re: bug#57955: 29.0.50; Allow session-local ERC modules In-Reply-To: <8735cm2o2l.fsf@neverwas.me> (J. P.'s message of "Tue, 20 Sep 2022 06:05:38 -0700") References: <8735cm2o2l.fsf@neverwas.me> Date: Wed, 26 Oct 2022 06:16:34 -0700 Message-ID: <87ilk64te5.fsf@neverwas.me> User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/29.0.50 (gnu/linux) MIME-Version: 1.0 Content-Type: text/plain X-Authenticated-Id: masked@neverwas.me X-Spam-Score: 0.0 (/) X-Debbugs-Envelope-To: 57955 Cc: emacs-erc@gnu.org X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: debbugs-submit-bounces@debbugs.gnu.org Sender: "Debbugs-submit" X-Spam-Score: -1.0 (-) I'd like to propose that these changes be included in ERC 5.5 and Emacs 29. If anyone has any concerns, please speak up. Otherwise, expect this patch to be installed at some point as part of the larger SASL change set in bug#29108. Thanks. From debbugs-submit-bounces@debbugs.gnu.org Tue Nov 15 10:07:47 2022 Received: (at 57955) by debbugs.gnu.org; 15 Nov 2022 15:07:47 +0000 Received: from localhost ([127.0.0.1]:55032 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1ouxXO-0005RS-MT for submit@debbugs.gnu.org; Tue, 15 Nov 2022 10:07:47 -0500 Received: from mail-108-mta21.mxroute.com ([136.175.108.21]:33419) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1ouxXM-0005R8-Sk for 57955@debbugs.gnu.org; Tue, 15 Nov 2022 10:07:45 -0500 Received: from mail-111-mta2.mxroute.com ([136.175.111.2] filter006.mxroute.com) (Authenticated sender: mN4UYu2MZsgR) by mail-108-mta21.mxroute.com (ZoneMTA) with ESMTPSA id 1847bd4cd350006e99.001 for <57955@debbugs.gnu.org> (version=TLSv1/SSLv3 cipher=ECDHE-RSA-AES128-GCM-SHA256); Tue, 15 Nov 2022 15:07:34 +0000 X-Zone-Loop: d6bc325bcd98d4672404c58013ad53008af696ea2eda X-Originating-IP: [136.175.111.2] DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=neverwas.me ; s=x; h=Content-Type:MIME-Version:Message-ID:Date:References:In-Reply-To: Subject:Cc:To:From:Sender:Reply-To:Content-Transfer-Encoding:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=ecqrj5IdVRIe14g3iEgOIzOeoCewoUxWcVZ/fcHnZws=; b=LWfw2Ue641IKls0Exwk1kBdvZt MDU/4Ku8uE6zN6oIBQQCOZx4FBkKQXtQo6lGdo2uOghzC5Xbq2+Jbc6sXMst6lYjBwsyfZ6KK4l6X uXHxXpU22QGOJB2Edw69crjjkxPA+Jw9Lhxf8WeAxv9Sw/UyDw7vjDxVz4Hy1rihoD2h7GrOEf5rs lO4rTZZZX0+b+WMajcpYmhyAD3j3HLZMU0xYzctuP/pJL/bgCrqZoW5L/6EtS3TpwcBYgeeoR5Ad6 tOs9tC9EV2J38cBRsRYaU4WJulP5ziDB5mb3iblxEYmMxQv7X1mQ/2HtfQNlXocSeetatlv9LmwpY nMWUcSIA==; From: "J.P." To: 57955@debbugs.gnu.org Subject: Re: bug#57955: 29.0.50; Allow session-local ERC modules In-Reply-To: <87ilk64te5.fsf@neverwas.me> (J. P.'s message of "Wed, 26 Oct 2022 06:16:34 -0700") References: <8735cm2o2l.fsf@neverwas.me> <87ilk64te5.fsf@neverwas.me> Date: Tue, 15 Nov 2022 07:07:30 -0800 Message-ID: <877czww91p.fsf@neverwas.me> User-Agent: Gnus/5.13 (Gnus v5.13) MIME-Version: 1.0 Content-Type: text/plain X-Authenticated-Id: masked@neverwas.me X-Spam-Score: 0.0 (/) X-Debbugs-Envelope-To: 57955 Cc: emacs-erc@gnu.org X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: debbugs-submit-bounces@debbugs.gnu.org Sender: "Debbugs-submit" X-Spam-Score: -1.0 (-) "J.P." writes: > I'd like to propose that these changes be included in ERC 5.5 and Emacs > 29. If anyone has any concerns, please speak up. Otherwise, expect this > patch to be installed at some point as part of the larger SASL change > set in bug#29108. Thanks. Brief update. The scope of this change has shrunk considerably. The deferred loading and migrations stuff still applies, but the main user-facing aspect, namely, support for let-binding a module's options on entry-point invocation, has been abandoned (for now). After looking into connection-local variables a bit, following Michael's suggestion up thread, I have come to the opinion that the let-binding idea was not fully formed and that options granularity is worthy of more meditation and discussion and thus not a realistic goal for Emacs 5.5. Thanks. From debbugs-submit-bounces@debbugs.gnu.org Mon May 22 00:05:59 2023 Received: (at 57955) by debbugs.gnu.org; 22 May 2023 04:05:59 +0000 Received: from localhost ([127.0.0.1]:33129 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1q0wo3-0001Ak-Ds for submit@debbugs.gnu.org; Mon, 22 May 2023 00:05:59 -0400 Received: from mail-108-mta207.mxroute.com ([136.175.108.207]:35453) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1q0wo0-0001AX-K6 for 57955@debbugs.gnu.org; Mon, 22 May 2023 00:05:57 -0400 Received: from mail-111-mta2.mxroute.com ([136.175.111.2] filter006.mxroute.com) (Authenticated sender: mN4UYu2MZsgR) by mail-108-mta207.mxroute.com (ZoneMTA) with ESMTPSA id 18841a2795800074ee.001 for <57955@debbugs.gnu.org> (version=TLSv1/SSLv3 cipher=ECDHE-RSA-AES128-GCM-SHA256); Mon, 22 May 2023 04:05:46 +0000 X-Zone-Loop: c09725f1806c2b4fafb2a61e6bebbf0e3e71fdfc4686 X-Originating-IP: [136.175.111.2] DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=neverwas.me ; s=x; h=Content-Type:MIME-Version:Message-ID:Date:References:In-Reply-To: Subject:Cc:To:From:Sender:Reply-To:Content-Transfer-Encoding:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=KBMSoaWQjPPzt3V32DvDvA6u8dRyHBgb4ZqHVhMdzVk=; b=O0vcXgBzie5aTwUVmnjgbTLZm3 ISCL2/H1HRSIvWOI94HgfZRuAp2crpykDm2aPwwVChL1hXmv99pfQNLhw9CSISciJS/qxLf9d/1Kt LSiWr/vF8SkuK1/gQwdUVjYngMItn7mlRBMuaZj96hQmJVKbFhpO/FTpqZZbML4yRnzGx0F11Gha6 +3ru7oMRu/7gL1GSfXw59XASn2fVHkQz6VpXv+0+0wzmLaOfoIDHz0cS6sqYP40pir10LJsVinzbm 1Rd8ALDzFiYEqWVupte3VJX8aKImJmdCT1k8sV9Aj/fVR3+jV5xckL4RA9xBSjYmgDK3teH/2YbA0 Rc0KGrsQ==; From: "J.P." To: 57955@debbugs.gnu.org Subject: Re: bug#57955: 29.0.50; Allow session-local ERC modules In-Reply-To: <877czww91p.fsf@neverwas.me> (J. P.'s message of "Tue, 15 Nov 2022 07:07:30 -0800") References: <8735cm2o2l.fsf@neverwas.me> <87ilk64te5.fsf@neverwas.me> <877czww91p.fsf@neverwas.me> Date: Sun, 21 May 2023 21:05:41 -0700 Message-ID: <87a5xx57u2.fsf@neverwas.me> User-Agent: Gnus/5.13 (Gnus v5.13) MIME-Version: 1.0 Content-Type: text/plain X-Authenticated-Id: masked@neverwas.me X-Spam-Score: 0.0 (/) X-Debbugs-Envelope-To: 57955 Cc: emacs-erc@gnu.org X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: debbugs-submit-bounces@debbugs.gnu.org Sender: "Debbugs-submit" X-Spam-Score: -1.0 (-) "J.P." writes: > Brief update. The scope of this change has shrunk considerably. The > deferred loading and migrations stuff still applies, but the main > user-facing aspect, namely, support for let-binding a module's options > on entry-point invocation, has been abandoned (for now). > > After looking into connection-local variables a bit, following Michael's > suggestion up thread, I have come to the opinion that the let-binding > idea was not fully formed and that options granularity is worthy of more > meditation and discussion and thus not a realistic goal for Emacs 5.5. To help with managing local modules, I've added some (possibly temporary) convenience functions and other supporting items as part of bug#60936. The most useful are: * macro `erc--restore-initialize-priors' This restores local variables from a previous session on major-mode hook or slightly later. The effect is similar to that provided by the `permanent-local' property, which we may end up settling for if the dream of context-local user options evaporates for good. This may also be of interest to global modules intended primarily for interactive use, such as the proposed `bufbar' and `nickbar' (bug#63595). * variable `erc--updating-modules-p' This is non-nil when running `erc-update-modules' in `erc-open'. It allows global modules to suppress superfluous buffer initialization pre-major-mode while still making that same init code available on demand for interactive invocations and indirect activation by dependent modules. Please keep in mind that these may change or disappear at any time. From debbugs-submit-bounces@debbugs.gnu.org Mon Oct 09 00:02:37 2023 Received: (at 57955) by debbugs.gnu.org; 9 Oct 2023 04:02:38 +0000 Received: from localhost ([127.0.0.1]:58992 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1qphTY-00045s-MF for submit@debbugs.gnu.org; Mon, 09 Oct 2023 00:02:37 -0400 Received: from mail-108-mta86.mxroute.com ([136.175.108.86]:41923) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1qphTV-00045d-4d for 57955@debbugs.gnu.org; Mon, 09 Oct 2023 00:02:35 -0400 Received: from mail-111-mta2.mxroute.com ([136.175.111.2] filter006.mxroute.com) (Authenticated sender: mN4UYu2MZsgR) by mail-108-mta86.mxroute.com (ZoneMTA) with ESMTPSA id 18b12996ebc0004ae0.001 for <57955@debbugs.gnu.org> (version=TLSv1.3 cipher=TLS_AES_256_GCM_SHA384); Mon, 09 Oct 2023 04:02:07 +0000 X-Zone-Loop: 3ad96b0e1df54b961179d737b4bc93288afbc30966c0 X-Originating-IP: [136.175.111.2] DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=neverwas.me ; s=x; h=Content-Type:MIME-Version:Message-ID:Date:References:In-Reply-To: Subject:Cc:To:From:Sender:Reply-To:Content-Transfer-Encoding:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=TL+K1cWLw7HBgRyuisjQ2/yDLTGzAyMWhdSlIlKFVDI=; b=dXlquyCKIyasJhXoBm+7QAA1tk T9lNtLYDdz3ppzGj5Pyw+gtHsYxcpMj/3Hqw7PcgT8alkMgj5n0KNse5PBou4f8ORNgXzsdEhSVKa J9e7XsGVo0I904mG1Xy/e+bbZs1PhYNfqfaxdB9em28G1rel0DNWMUy6ZEDi00F2GEoQuaUXgWf5T FC63FGC7BLzzhBG4v0pbhzrErsd5jWqer7fpFvTlLUr2tt8kj/C/uTvbibt1MAhre+7DrnFHr8XGs 1JOS1qMUXoMlhxCFMpsogH8/+zVPGgZA2Lr98WyMO7w38hO1rRwkqmYrbZFVvOKhC5jMiOslEUZBl QCJqlfxQ==; From: "J.P." To: 57955@debbugs.gnu.org Subject: Re: bug#57955: 29.0.50; Allow session-local ERC modules In-Reply-To: <8735cm2o2l.fsf@neverwas.me> (J. P.'s message of "Tue, 20 Sep 2022 06:05:38 -0700") References: <8735cm2o2l.fsf@neverwas.me> Date: Sun, 08 Oct 2023 21:02:02 -0700 Message-ID: <87o7h8jvet.fsf@neverwas.me> User-Agent: Gnus/5.13 (Gnus v5.13) MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" X-Authenticated-Id: masked@neverwas.me X-Spam-Score: -0.5 (/) X-Debbugs-Envelope-To: 57955 Cc: emacs-erc@gnu.org X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: debbugs-submit-bounces@debbugs.gnu.org Sender: "Debbugs-submit" X-Spam-Score: -1.5 (-) --=-=-= Content-Type: text/plain It turns out these changes removed some behavior involving the loading of modules that's long been part of ERC's implicit interface. Basically, when encountering a third-party module `mymod', ERC has always attempted to `require' the (possibly nonexistent) feature `erc-mymod', and to do so unconditionally. However, this bug changed that policy to instead only attempt such loading if the corresponding command `erc-mymod-mode' is undefined. That was likely unwise because some packages do questionable things like ;;;###autoload (eval-after-load 'erc '(define-erc-module mymod nil "Doc" () ())) which fails to provide the vital `symbol-file' association between the minor-mode command and the library (because the command itself isn't autoloaded). Such uses ignore the decades-old example in the doc string of `define-erc-module', which clearly recommends ;;;###autoload(autoload 'erc-mymode-mode "erc-mymode") (define-erc-module mymod nil "Doc" () ()) as the surefire approach. That said, in cases where the library's name matches the (prefixed) module name, no autoload cookie is necessary. Unfortunately, many of these packages are in maintenance mode, with authors unwilling to respond to suggestions to update such aberrant uses. Thus, I think it's in ERC's best interest to accommodate them as it always has. Please see the second patch below. Thanks. --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=0001-5.6-Honor-nil-values-in-erc-restore-initialize-prior.patch >From 01c7045757c6109e07053dcf3a712b74a1d34d41 Mon Sep 17 00:00:00 2001 From: "F. Jason Park" Date: Thu, 5 Oct 2023 00:16:46 -0700 Subject: [PATCH 1/2] [5.6] Honor nil values in erc--restore-initialize-priors * lisp/erc/erc.el (erc--restore-initialize-priors): Don't produce invalid empty `setq' when given VARS that initialize to nil. This function is mainly used by local modules, which were first introduced in 5.5 (bug#57955). * test/lisp/erc/erc-tests.el (erc--restore-initialize-priors): Fix expected expansion. (Bug#60936) --- lisp/erc/erc.el | 17 ++++++++--------- test/lisp/erc/erc-tests.el | 17 +++++++---------- 2 files changed, 15 insertions(+), 19 deletions(-) diff --git a/lisp/erc/erc.el b/lisp/erc/erc.el index fb236f1f189..16651b41eef 100644 --- a/lisp/erc/erc.el +++ b/lisp/erc/erc.el @@ -1366,16 +1366,15 @@ erc--target-priors (defmacro erc--restore-initialize-priors (mode &rest vars) "Restore local VARS for MODE from a previous session." (declare (indent 1)) - (let ((existing (make-symbol "existing")) + (let ((priors (make-symbol "priors")) + (initp (make-symbol "initp")) ;; - restore initialize) - (while-let ((k (pop vars)) (v (pop vars))) - (push `(,k (alist-get ',k ,existing)) restore) - (push `(,k ,v) initialize)) - `(if-let* ((,existing (or erc--server-reconnecting erc--target-priors)) - ((alist-get ',mode ,existing))) - (setq ,@(mapcan #'identity (nreverse restore))) - (setq ,@(mapcan #'identity (nreverse initialize)))))) + forms) + (while-let ((k (pop vars))) + (push `(,k (if ,initp (alist-get ',k ,priors) ,(pop vars))) forms)) + `(let* ((,priors (or erc--server-reconnecting erc--target-priors)) + (,initp (and ,priors (alist-get ',mode ,priors)))) + (setq ,@(mapcan #'identity (nreverse forms)))))) (defun erc--target-from-string (string) "Construct an `erc--target' variant from STRING." diff --git a/test/lisp/erc/erc-tests.el b/test/lisp/erc/erc-tests.el index 8a68eca6196..64b503832f3 100644 --- a/test/lisp/erc/erc-tests.el +++ b/test/lisp/erc/erc-tests.el @@ -796,18 +796,15 @@ erc--valid-local-channel-p (should (erc--valid-local-channel-p "&local"))))) (ert-deftest erc--restore-initialize-priors () - ;; This `pcase' expands to 100+k. Guess we could do something like - ;; (and `(,_ ((,e . ,_) . ,_) . ,_) v) first and then return a - ;; (equal `(if-let* ((,e ...)...)...) v) to cut it down to < 1k. (should (pcase (macroexpand-1 '(erc--restore-initialize-priors erc-my-mode foo (ignore 1 2 3) - bar #'spam)) - (`(if-let* ((,e (or erc--server-reconnecting erc--target-priors)) - ((alist-get 'erc-my-mode ,e))) - (setq foo (alist-get 'foo ,e) - bar (alist-get 'bar ,e)) - (setq foo (ignore 1 2 3) - bar #'spam)) + bar #'spam + baz nil)) + (`(let* ((,p (or erc--server-reconnecting erc--target-priors)) + (,q (and ,p (alist-get 'erc-my-mode ,p)))) + (setq foo (if ,q (alist-get 'foo ,p) (ignore 1 2 3)) + bar (if ,q (alist-get 'bar ,p) #'spam) + baz (if ,q (alist-get 'baz ,p) nil))) t)))) (ert-deftest erc--target-from-string () -- 2.41.0 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=0002-5.6-Sort-and-dedupe-when-loading-modules-in-erc-open.patch >From 8e85f0748859590f2e94339e3dab300f9de9f728 Mon Sep 17 00:00:00 2001 From: "F. Jason Park" Date: Fri, 6 Oct 2023 17:34:04 -0700 Subject: [PATCH 2/2] [5.6] Sort and dedupe when loading modules in erc-open * doc/misc/erc.texi: Add new subheading "Module Loading" under "Modules" chapter. * lisp/erc/erc.el (erc--sort-modules): New utility function to sort and dedupe modules. (erc-modules): In `custom-set' function, factor out collation step into separate utility `erc--sort-modules'. (erc-update-modules): Call `erc--update-modules' with `erc-modules'. (erc--aberrant-modules): New variable, a list of symbols whose modules are suspected of being incorrectly defined. (erc--find-mode): Make heuristic more robust by always checking for a mode activation command rather than just a state variable. This fixes a compatibility bug, new in 5.6, affecting third-party modules that autoload their module definition instead of its corresponding activation-toggle command. (erc--update-modules): Add new positional argument `modules'. (erc--setup-buffer-hook): Add new default member, `erc--warn-about-aberrant-modules'. (erc-open): Pass sorted `erc-modules' to `erc--update-modules'. * test/lisp/erc/erc-tests.el (erc--sort-modules): New test. (erc-tests--update-modules): New fixture. (erc--update-modules): Remove and rework as three separate tests dedicated to specific contexts. The existing one had poor coverage and was difficult if not impossible to follow. (erc--update-modules/unknown, erc--update-modules/local, erc--update-modules/realistic): New tests. (Bug#57955) --- doc/misc/erc.texi | 35 ++++++++ lisp/erc/erc.el | 52 ++++++++---- test/lisp/erc/erc-tests.el | 163 ++++++++++++++++++++++++++----------- 3 files changed, 183 insertions(+), 67 deletions(-) diff --git a/doc/misc/erc.texi b/doc/misc/erc.texi index 3297d8b17f0..3bfa240cacc 100644 --- a/doc/misc/erc.texi +++ b/doc/misc/erc.texi @@ -653,6 +653,41 @@ Modules @code{erc-modules}. +@anchor{Module Loading} +@subheading Module Loading +@cindex module loading + +ERC loads internal modules in alphabetical order and third-party +modules as they appear in @code{erc-modules}. When defining your own +module, take care to ensure ERC can find it. An easy way to do that +is by mimicking the example in the doc string for +@code{define-erc-module}. For historical reasons, ERC also falls back +to @code{require}ing features. For example, if some module +@code{} in @code{erc-modules} lacks a corresponding +@code{erc--mode} command, ERC will attempt to load the library +@code{erc-} prior to connecting. If this fails, ERC signals an +error. Users wanting to define modules in an init files should +@code{(provide 'erc-)} somewhere to placate ERC. Dynamically +generating modules on the fly is not supported. + +Sometimes, packages attempt to autoload a module's definition instead +of its minor-mode command, which breaks the link between the library +and the module. This means that enabling the mode by invoking its +command toggle isn't enough to load its defining library. Such +packages should instead only supply autoload cookies featuring an +explicit @code{autoload} form for their module's minor-mode command. +As mentioned above, packages can also usually avoid autoload cookies +entirely so long as their module's prefixed name matches that of its +defining library and the latter's provided feature. + +Packages have also been seen to specify unnecessary top-level +@code{eval-after-load} forms, which end up being ineffective in most +cases. Another unfortunate practice is mutating @code{erc-modules} +itself in an autoloaded form. Doing this tricks Customize into +displaying the widget for @code{erc-modules} incorrectly, with +built-in modules moved from the predefined checklist to the +user-provided free-form area. + @c PRE5_4: Document every option of every module in its own subnode diff --git a/lisp/erc/erc.el b/lisp/erc/erc.el index 16651b41eef..60d745e1268 100644 --- a/lisp/erc/erc.el +++ b/lisp/erc/erc.el @@ -2004,6 +2004,13 @@ erc-migrate-modules ;; each item is in the format '(old . new) (delete-dups (mapcar #'erc--normalize-module-symbol mods))) +(defun erc--sort-modules (modules) + (let (built-in third-party) + (dolist (mod modules) + (setq mod (erc--normalize-module-symbol mod)) + (cl-pushnew mod (if (get mod 'erc--module) built-in third-party))) + `(,@(sort built-in #'string-lessp) ,@(nreverse third-party)))) + (defcustom erc-modules '( autojoin button completion fill imenu irccontrols list match menu move-to-prompt netsplit networks noncommands readonly ring stamp track) @@ -2039,16 +2046,10 @@ erc-modules (when (symbol-value f) (funcall f 0)) (kill-local-variable f))))))))) - (let (built-in third-party) - (dolist (v val) - (setq v (erc--normalize-module-symbol v)) - (if (get v 'erc--module) - (push v built-in) - (push v third-party))) - ;; Calling `set-default-toplevel-value' complicates testing - (set sym (append (sort built-in #'string-lessp) - (nreverse third-party)))) + ;; Calling `set-default-toplevel-value' complicates testing. + (set sym (erc--sort-modules val)) ;; this test is for the case where erc hasn't been loaded yet + ;; FIXME explain how this ^ can occur or remove comment. (when (fboundp 'erc-update-modules) (unless erc--inside-mode-toggle-p (erc-update-modules)))) @@ -2112,15 +2113,29 @@ erc-modules (defun erc-update-modules () "Enable minor mode for every module in `erc-modules'. Except ignore all local modules, which were introduced in ERC 5.5." - (erc--update-modules) + (erc--update-modules erc-modules) nil) +(defvar erc--aberrant-modules nil + "Modules suspected of being improperly loaded.") + +(defun erc--warn-about-aberrant-modules () + (when erc--aberrant-modules + (erc-button--display-error-notice-with-keys-and-warn + "The following modules exhibited strange loading behavior: " + (mapconcat (lambda (s) (format "`%s'" s)) erc--aberrant-modules ", ") + ". Please contact ERC with \\[erc-bug] if you believe this to be untrue." + " See Info:\"(erc) Module Loading\" for more.") + (setq erc--aberrant-modules nil))) + (defun erc--find-mode (sym) (setq sym (erc--normalize-module-symbol sym)) - (if-let* ((mode (intern-soft (concat "erc-" (symbol-name sym) "-mode"))) - ((or (boundp mode) - (and (fboundp mode) - (autoload-do-load (symbol-function mode) mode))))) + (if-let ((mode (intern-soft (concat "erc-" (symbol-name sym) "-mode"))) + ((and (fboundp mode) + (autoload-do-load (symbol-function mode) mode))) + ((or (get sym 'erc--module) + (symbol-file mode) + (ignore (cl-pushnew sym erc--aberrant-modules))))) mode (and (require (or (get sym 'erc--feature) (intern (concat "erc-" (symbol-name sym)))) @@ -2129,9 +2144,9 @@ erc--find-mode (fboundp mode) mode))) -(defun erc--update-modules () +(defun erc--update-modules (modules) (let (local-modes) - (dolist (module erc-modules local-modes) + (dolist (module modules local-modes) (if-let ((mode (erc--find-mode module))) (if (custom-variable-p mode) (funcall mode 1) @@ -2158,7 +2173,7 @@ erc--updating-modules-p confidently call (erc-foo-mode 1) without having to learn anything about the dependency's implementation.") -(defvar erc--setup-buffer-hook nil +(defvar erc--setup-buffer-hook '(erc--warn-about-aberrant-modules) "Internal hook for module setup involving windows and frames.") (defvar erc--display-context nil @@ -2315,7 +2330,8 @@ erc-open (setq old-point (point)) (setq delayed-modules (erc--merge-local-modes (let ((erc--updating-modules-p t)) - (erc--update-modules)) + (erc--update-modules + (erc--sort-modules erc-modules))) (or erc--server-reconnecting erc--target-priors))) diff --git a/test/lisp/erc/erc-tests.el b/test/lisp/erc/erc-tests.el index 64b503832f3..0b88ad9cfa9 100644 --- a/test/lisp/erc/erc-tests.el +++ b/test/lisp/erc/erc-tests.el @@ -2293,65 +2293,130 @@ erc--find-group--real (should (eq (erc--find-group 'smiley nil) 'erc)) (should (eq (erc--find-group 'unmorse nil) 'erc))) -(ert-deftest erc--update-modules () - (let (calls - erc-modules - erc-kill-channel-hook erc-kill-server-hook erc-kill-buffer-hook) +(ert-deftest erc--sort-modules () + (should (equal (erc--sort-modules '(networks foo fill bar fill stamp bar)) + ;; Third-party mods appear in original order. + '(fill networks stamp foo bar)))) + +(defun erc-tests--update-modules (fn) + (let* ((calls nil) + (custom-modes nil) + (on-load nil) + + (get-calls (lambda () (prog1 (nreverse calls) (setq calls nil)))) + + (add-onload (lambda (m k v) + (put (intern m) 'erc--feature k) + (push (cons k (lambda () (funcall v m))) on-load))) - ;; This `lbaz' module is unknown, so ERC looks for it via the - ;; symbol proerty `erc--feature' and, failing that, by - ;; `require'ing its "erc-" prefixed symbol. - (should-not (intern-soft "erc-lbaz-mode")) + (mk-cmd (lambda (module) + (let ((mode (intern (format "erc-%s-mode" module)))) + (fset mode (lambda (n) (push (cons mode n) calls)))))) + + (mk-builtin (lambda (module-string) + (let ((s (intern module-string))) + (put s 'erc--module s)))) + + (mk-global (lambda (module) + (push (intern (format "erc-%s-mode" module)) + custom-modes)))) (cl-letf (((symbol-function 'require) (lambda (s &rest _) - (when (eq s 'erc--lbaz-feature) - (fset (intern "erc-lbaz-mode") ; local module - (lambda (n) (push (cons 'lbaz n) calls)))) - (push s calls))) - - ;; Local modules - ((symbol-function 'erc-lbar-mode) - (lambda (n) (push (cons 'lbar n) calls))) - ((get 'lbaz 'erc--feature) 'erc--lbaz-feature) - - ;; Global modules - ((symbol-function 'erc-gfoo-mode) - (lambda (n) (push (cons 'gfoo n) calls))) - ((get 'erc-gfoo-mode 'standard-value) 'ignore) + ;; Simulate library being loaded, things defined. + (when-let ((h (alist-get s on-load))) (funcall h)) + (push (cons 'req s) calls))) + + ;; Spoof global module detection. + ((symbol-function 'custom-variable-p) + (lambda (v) (memq v custom-modes)))) + + (funcall fn get-calls add-onload mk-cmd mk-builtin mk-global)) + (should-not erc--aberrant-modules))) + +(ert-deftest erc--update-modules/unknown () + (erc-tests--update-modules + + (lambda (get-calls _ mk-cmd _ mk-global) + + (ert-info ("Baseline") + (let* ((erc-modules '(foo)) + (obarray (obarray-make)) + (err (should-error (erc--update-modules erc-modules)))) + (should (equal (cadr err) "`foo' is not a known ERC module")) + (should (equal (funcall get-calls) + `((req . ,(intern-soft "erc-foo"))))))) + + ;; Module's mode command exists but lacks an associated file. + (ert-info ("Bad autoload flagged as suspect") + (should-not erc--aberrant-modules) + (let* ((erc--aberrant-modules nil) + (obarray (obarray-make)) + (erc-modules (list (intern "foo")))) + + ;; Create a mode activation command. + (funcall mk-cmd "foo") + + ;; Make the mode var global. + (funcall mk-global "foo") + + ;; No local modules to return. + (should-not (erc--update-modules erc-modules)) + (should (equal (mapcar #'prin1-to-string erc--aberrant-modules) + '("foo"))) + ;; ERC requires the library via prefixed module name. + (should (equal (mapcar #'prin1-to-string (funcall get-calls)) + `("(req . erc-foo)" "(erc-foo-mode . 1)")))))))) + +;; A local module (here, `lo2') lacks a mode toggle, so ERC tries to +;; load its defining library, first via the symbol property +;; `erc--feature', and then via an "erc-" prefixed symbol. +(ert-deftest erc--update-modules/local () + (erc-tests--update-modules + + (lambda (get-calls add-onload mk-cmd mk-builtin mk-global) + + (let* ((obarray (obarray-make 20)) + (erc-modules (mapcar #'intern '("glo" "lo1" "lo2")))) + + ;; Create a global and a local module. + (mapc mk-cmd '("glo" "lo1")) + (mapc mk-builtin '("glo" "lo1")) + (funcall mk-global "glo") + (funcall add-onload "lo2" 'explicit-feature-lib mk-cmd) + + ;; Returns local modules. + (should (equal (mapcar #'symbol-name (erc--update-modules erc-modules)) + '("erc-lo2-mode" "erc-lo1-mode"))) + + ;; Requiring `erc-lo2' defines `erc-lo2-mode'. + (should (equal (mapcar #'prin1-to-string (funcall get-calls)) + `("(erc-glo-mode . 1)" + "(req . explicit-feature-lib)"))))))) + +(ert-deftest erc--update-modules/realistic () + (let ((calls nil) + ;; Module `pcomplete' "resolves" to `completion'. + (erc-modules '(pcomplete autojoin networks))) + (cl-letf (((symbol-function 'require) + (lambda (s &rest _) (push (cons 'req s) calls))) + + ;; Spoof global module detection. + ((symbol-function 'custom-variable-p) + (lambda (v) + (memq v '(erc-autojoin-mode erc-networks-mode + erc-completion-mode)))) + ;; Mock and spy real builtins. ((symbol-function 'erc-autojoin-mode) (lambda (n) (push (cons 'autojoin n) calls))) - ((get 'erc-autojoin-mode 'standard-value) 'ignore) ((symbol-function 'erc-networks-mode) (lambda (n) (push (cons 'networks n) calls))) - ((get 'erc-networks-mode 'standard-value) 'ignore) ((symbol-function 'erc-completion-mode) - (lambda (n) (push (cons 'completion n) calls))) - ((get 'erc-completion-mode 'standard-value) 'ignore)) - - (ert-info ("Unknown module") - (setq erc-modules '(lfoo)) - (should-error (erc--update-modules)) - (should (equal (pop calls) 'erc-lfoo)) - (should-not calls)) + (lambda (n) (push (cons 'completion n) calls)))) - (ert-info ("Local modules") - (setq erc-modules '(gfoo lbar lbaz)) - ;; Don't expose the mode here - (should (equal (mapcar #'symbol-name (erc--update-modules)) - '("erc-lbaz-mode" "erc-lbar-mode"))) - ;; Lbaz required because unknown. - (should (equal (nreverse calls) '((gfoo . 1) erc--lbaz-feature))) - (fmakunbound (intern "erc-lbaz-mode")) - (unintern (intern "erc-lbaz-mode") obarray) - (setq calls nil)) - - (ert-info ("Global modules") ; `pcomplete' resolved to `completion' - (setq erc-modules '(pcomplete autojoin networks)) - (should-not (erc--update-modules)) ; no locals - (should (equal (nreverse calls) - '((completion . 1) (autojoin . 1) (networks . 1)))) - (setq calls nil))))) + (should-not (erc--update-modules erc-modules)) ; no locals + (should (equal (nreverse calls) + '((completion . 1) (autojoin . 1) (networks . 1))))))) (ert-deftest erc--merge-local-modes () (cl-letf (((get 'erc-b-mode 'erc-module) 'b) -- 2.41.0 --=-=-=-- From debbugs-submit-bounces@debbugs.gnu.org Fri Oct 13 20:23:34 2023 Received: (at 57955) by debbugs.gnu.org; 14 Oct 2023 00:23:34 +0000 Received: from localhost ([127.0.0.1]:47572 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1qrSRK-00074o-D1 for submit@debbugs.gnu.org; Fri, 13 Oct 2023 20:23:34 -0400 Received: from mail-108-mta84.mxroute.com ([136.175.108.84]:34385) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1qrSRH-00074c-1Z for 57955@debbugs.gnu.org; Fri, 13 Oct 2023 20:23:33 -0400 Received: from mail-111-mta2.mxroute.com ([136.175.111.2] filter006.mxroute.com) (Authenticated sender: mN4UYu2MZsgR) by mail-108-mta84.mxroute.com (ZoneMTA) with ESMTPSA id 18b2b90b4f3000ff68.001 for <57955@debbugs.gnu.org> (version=TLSv1.3 cipher=TLS_AES_256_GCM_SHA384); Sat, 14 Oct 2023 00:23:05 +0000 X-Zone-Loop: b387c694178fa047fe1f30818254afeaaa2586c5b10f X-Originating-IP: [136.175.111.2] DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=neverwas.me ; s=x; h=Content-Type:MIME-Version:Message-ID:Date:References:In-Reply-To: Subject:Cc:To:From:Sender:Reply-To:Content-Transfer-Encoding:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=i9RXIC0hKS2Obu/0qYImHI8lFUe/T+W+X9/B2VTISJA=; b=OQLP+elQnhaMca0cl7paQlEytW C5kX8RAVF7DBmj+4HxtYVtTeQgTz/KLn1fKgofTu/zjZcz0qQ4WbmTZ3DWLLnsiS0/1/wm+fNFqJN 1Fb7jt8fHxriyvEjgsacGFOEsM1sItf4H3zkLZGsRLXUQ6yXaCUvRiq3tgJjBqpRnjrwG0ZO/bcgr ULdP1cA+YbnlpQdo1CVfe25VWVsgXIDLPng2uuBe2fei34SregjEpNYUoPsuxSUo3/3fH2bgFW5uV yF+qm+c1/csWQadXgO6Ok+BS6Bqzg7okD6IadzyYUp6wsvfZuSF/SeA8Ew5KahutJoCSGxblM4p5G R8a7NJmw==; From: "J.P." To: 57955@debbugs.gnu.org Subject: Re: bug#57955: 29.0.50; Allow session-local ERC modules In-Reply-To: <87o7h8jvet.fsf@neverwas.me> (J. P.'s message of "Sun, 08 Oct 2023 21:02:02 -0700") References: <8735cm2o2l.fsf@neverwas.me> <87o7h8jvet.fsf@neverwas.me> Date: Fri, 13 Oct 2023 17:23:01 -0700 Message-ID: <87edhy9hne.fsf@neverwas.me> User-Agent: Gnus/5.13 (Gnus v5.13) MIME-Version: 1.0 Content-Type: text/plain X-Authenticated-Id: masked@neverwas.me X-Spam-Score: 0.0 (/) X-Debbugs-Envelope-To: 57955 Cc: emacs-erc@gnu.org X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: debbugs-submit-bounces@debbugs.gnu.org Sender: "Debbugs-submit" X-Spam-Score: -1.0 (-) I've added something similar to the proposed change as https://git.savannah.gnu.org/cgit/emacs.git/commit/?id=d46c016f If you'll recall, I initially left this bug open as a reminder that we still lack a solution to the glaring problem of configuration scoping. To summarize, this concerns the pressing need to allow users to specify different values for new (and hopefully existing) global options based on context (like network, channel, speaker, etc.). One idea bandied about has been adapting connection-local variables to be more abstract and eventually integrating them with Customize and `use-package'. As thing stand, though, each local module must itself decide whether it's session-local, buffer-local, or both. And it must contend with stashing and restoring its configured state on its own. I'll likely close this bug after opening another to address these broader configuration concerns. Thanks. From debbugs-submit-bounces@debbugs.gnu.org Wed Oct 18 09:37:07 2023 Received: (at 57955) by debbugs.gnu.org; 18 Oct 2023 13:37:07 +0000 Received: from localhost ([127.0.0.1]:33471 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1qt6jS-00008M-W9 for submit@debbugs.gnu.org; Wed, 18 Oct 2023 09:37:07 -0400 Received: from mail-108-mta96.mxroute.com ([136.175.108.96]:40753) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1qt6jN-00007u-Ue for 57955@debbugs.gnu.org; Wed, 18 Oct 2023 09:37:05 -0400 Received: from mail-111-mta2.mxroute.com ([136.175.111.2] filter006.mxroute.com) (Authenticated sender: mN4UYu2MZsgR) by mail-108-mta96.mxroute.com (ZoneMTA) with ESMTPSA id 18b43008905000ff68.001 for <57955@debbugs.gnu.org> (version=TLSv1.3 cipher=TLS_AES_256_GCM_SHA384); Wed, 18 Oct 2023 13:36:30 +0000 X-Zone-Loop: 5c73ae690846d270c71046532bffa50e01dc06039cf3 X-Originating-IP: [136.175.111.2] DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=neverwas.me ; s=x; h=Content-Type:MIME-Version:Message-ID:Date:References:In-Reply-To: Subject:Cc:To:From:Sender:Reply-To:Content-Transfer-Encoding:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=HYBTAfha2eFXb+I7AN92a1OwjBnajcfsY4pEpTSqbrM=; b=NBV5aO8Sli5I5xsg3Dw/vo/+OF HABf73ejVdI6OBnOR029ocoH50IZoMXlJ5nB68AzscZTyWJrfnO6/jj/lk3MsO4j9T0OjNHaQ2Vav ISyZf4jrGjQe2mySLT7CUTNod5XFitsbjED0XdA2Db0mXhmftf3dvqhH75RCzq8AXyRx98buIifZz RLgZrSWCrdVTfetjPsH16fbvogAAaqrnob/uqkkkbZmor3DmRcPn7b6AszrjVos0LTtxmo/SG0HfS ZJG3HW/gfMTfmRxnWLAfyniE59eAcWPVc4oof1VwTBJ9AAVJGT2V768YXqh4IU1UZbK4G0HTMkVlU YHacQFOg==; From: "J.P." To: 57955@debbugs.gnu.org Subject: Re: bug#57955: 29.0.50; Allow session-local ERC modules In-Reply-To: <87edhy9hne.fsf@neverwas.me> (J. P.'s message of "Fri, 13 Oct 2023 17:23:01 -0700") References: <8735cm2o2l.fsf@neverwas.me> <87o7h8jvet.fsf@neverwas.me> <87edhy9hne.fsf@neverwas.me> Date: Wed, 18 Oct 2023 06:36:26 -0700 Message-ID: <87cyxccasl.fsf@neverwas.me> User-Agent: Gnus/5.13 (Gnus v5.13) MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" X-Authenticated-Id: masked@neverwas.me X-Spam-Score: 0.0 (/) X-Debbugs-Envelope-To: 57955 Cc: emacs-erc@gnu.org X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: debbugs-submit-bounces@debbugs.gnu.org Sender: "Debbugs-submit" X-Spam-Score: -1.0 (-) --=-=-= Content-Type: text/plain "J.P." writes: > I've added something similar to the proposed change as > > https://git.savannah.gnu.org/cgit/emacs.git/commit/?id=d46c016f Actually, this has proved insufficient for detecting top-level and `eval-after-load' calls to `erc-update-modules'. The attached tweak hopefully addresses that. --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=0001-5.6-Warn-about-top-level-erc-update-modules-calls.patch >From 1e2b653b3b4af5f19f24b700bdd55cb30c198670 Mon Sep 17 00:00:00 2001 From: "F. Jason Park" Date: Tue, 17 Oct 2023 23:36:12 -0700 Subject: [PATCH] [5.6] Warn about top-level erc-update-modules calls * doc/misc/erc.texi (Modules): Mention risk of infinite recursion when packages run `erc-update-modules' on load. * lisp/erc/erc.el (erc--warn-about-aberrant-modules): Tweak warning message. (erc--requiring-module-mode-p): New internal variable. (erc--find-mode): Guard against recursive `require's. (Bug#57955) --- doc/misc/erc.texi | 21 ++++++++++++--------- lisp/erc/erc.el | 21 ++++++++++++++++----- 2 files changed, 28 insertions(+), 14 deletions(-) diff --git a/doc/misc/erc.texi b/doc/misc/erc.texi index 3bfa240cacc..c654c97731c 100644 --- a/doc/misc/erc.texi +++ b/doc/misc/erc.texi @@ -666,17 +666,17 @@ Modules @code{} in @code{erc-modules} lacks a corresponding @code{erc--mode} command, ERC will attempt to load the library @code{erc-} prior to connecting. If this fails, ERC signals an -error. Users wanting to define modules in an init files should +error. Users defining personal modules in an init file should @code{(provide 'erc-)} somewhere to placate ERC. Dynamically generating modules on the fly is not supported. -Sometimes, packages attempt to autoload a module's definition instead -of its minor-mode command, which breaks the link between the library -and the module. This means that enabling the mode by invoking its -command toggle isn't enough to load its defining library. Such -packages should instead only supply autoload cookies featuring an -explicit @code{autoload} form for their module's minor-mode command. -As mentioned above, packages can also usually avoid autoload cookies +Some packages attempt to autoload a module's definition instead of its +minor-mode command, which breaks the link between the library and the +module. This means that enabling the mode by invoking its command +toggle isn't enough to load its defining library. Such packages +should instead only supply autoload cookies featuring an explicit +@code{autoload} form for their module's minor-mode command. As +mentioned above, packages can also usually avoid autoload cookies entirely so long as their module's prefixed name matches that of its defining library and the latter's provided feature. @@ -686,7 +686,10 @@ Modules itself in an autoloaded form. Doing this tricks Customize into displaying the widget for @code{erc-modules} incorrectly, with built-in modules moved from the predefined checklist to the -user-provided free-form area. +user-provided free-form area. Similarly, some packages run +@code{erc-update-modules} in top-level and autoloaded forms or in +@code{after-load-functions} members, which can trigger recursive +invocations. @c PRE5_4: Document every option of every module in its own subnode diff --git a/lisp/erc/erc.el b/lisp/erc/erc.el index 5bf6496e926..f841e385ab7 100644 --- a/lisp/erc/erc.el +++ b/lisp/erc/erc.el @@ -2129,12 +2129,17 @@ erc--aberrant-modules (defun erc--warn-about-aberrant-modules () (when (and erc--aberrant-modules (not erc--target)) (erc-button--display-error-notice-with-keys-and-warn - "The following modules exhibited strange loading behavior: " + "The following modules exhibit strange loading behavior: " (mapconcat (lambda (s) (format "`%s'" s)) erc--aberrant-modules ", ") ". Please contact ERC with \\[erc-bug] if you believe this to be untrue." " See Info:\"(erc) Module Loading\" for more.") (setq erc--aberrant-modules nil))) +(defvar erc--requiring-module-mode-p nil + "Non-nil when `require'ing `erc-mymod' from `erc-update-modules'. +Used for detecting top-level calls to `erc-update-modules' by +third-party packages.") + (defun erc--find-mode (sym) (setq sym (erc--normalize-module-symbol sym)) (if-let ((mode (intern-soft (concat "erc-" (symbol-name sym) "-mode"))) @@ -2144,10 +2149,16 @@ erc--find-mode (symbol-file mode) (ignore (cl-pushnew sym erc--aberrant-modules))))) mode - (and (require (or (get sym 'erc--feature) - (intern (concat "erc-" (symbol-name sym)))) - nil 'noerror) - (setq mode (intern-soft (concat "erc-" (symbol-name sym) "-mode"))) + (and (or (and erc--requiring-module-mode-p + ;; Also likely non-nil: (eq sym (car features)) + (cl-pushnew sym erc--aberrant-modules)) + (let ((erc--requiring-module-mode-p t)) + (require (or (get sym 'erc--feature) + (intern (concat "erc-" (symbol-name sym)))) + nil 'noerror)) + (memq sym erc--aberrant-modules)) + (or mode (setq mode (intern-soft (concat "erc-" (symbol-name sym) + "-mode")))) (fboundp mode) mode))) -- 2.41.0 --=-=-=-- From debbugs-submit-bounces@debbugs.gnu.org Sat Feb 10 15:36:31 2024 Received: (at 57955) by debbugs.gnu.org; 10 Feb 2024 20:36:32 +0000 Received: from localhost ([127.0.0.1]:39622 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1rYu5O-0002i3-V6 for submit@debbugs.gnu.org; Sat, 10 Feb 2024 15:36:31 -0500 Received: from mail-108-mta88.mxroute.com ([136.175.108.88]:32967) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1rYu5M-0002hp-76 for 57955@debbugs.gnu.org; Sat, 10 Feb 2024 15:36:29 -0500 Received: from filter006.mxroute.com ([136.175.111.2] filter006.mxroute.com) (Authenticated sender: mN4UYu2MZsgR) by mail-108-mta88.mxroute.com (ZoneMTA) with ESMTPSA id 18d94bc09c30000466.001 for <57955@debbugs.gnu.org> (version=TLSv1.3 cipher=TLS_AES_256_GCM_SHA384); Sat, 10 Feb 2024 20:36:07 +0000 X-Zone-Loop: 70d817bed24fcdc788f7aa2655107322e5baed5c5460 X-Originating-IP: [136.175.111.2] DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=neverwas.me ; s=x; h=Content-Type:MIME-Version:Message-ID:Date:References:In-Reply-To: Subject:Cc:To:From:Sender:Reply-To:Content-Transfer-Encoding:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=rTa21iDIvEZMXSbp53CnbZqubi2iazjYMSl0CmNffC8=; b=arURzhWtozwsUYKof49mzXz6uy paxEC8q/4jn37uYAO/dbHTKkFtd0+pZL66OBGFZ5bJz21SD89AzsGVs2FsMS9+64bn7wYVjGpWloe mYVI7riZuqalBZFvFrNKxTBCZAPoui4ZdZC8ALZ/0kWdIPdgzvg1FG2KK/0OuZ8EgwSPAZBxcKNdb rFB0oP4WUW1vprd5KdDjra9BQcLATsRxQWRId27KTw4M0PBpLyqAm3M6LD6vyiBh+nvwHCZDwIqXI IcqRf2cBlsrNhVURSYuQpdmtNbkBWUQduY4znSPDZWSX/vyenRg1pNloCFhsMZmkfLWldNVvFVY7V +xIj8JrQ==; From: "J.P." To: 57955@debbugs.gnu.org Subject: Re: bug#57955: 29.0.50; Allow session-local ERC modules In-Reply-To: <8735cm2o2l.fsf@neverwas.me> (J. P.'s message of "Tue, 20 Sep 2022 06:05:38 -0700") References: <8735cm2o2l.fsf@neverwas.me> Date: Sat, 10 Feb 2024 12:36:04 -0800 Message-ID: <87wmrcxdjf.fsf@neverwas.me> User-Agent: Gnus/5.13 (Gnus v5.13) MIME-Version: 1.0 Content-Type: text/plain X-Authenticated-Id: masked@neverwas.me X-Spam-Score: -0.0 (/) X-Debbugs-Envelope-To: 57955 Cc: emacs-erc@gnu.org X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: debbugs-submit-bounces@debbugs.gnu.org Sender: "Debbugs-submit" X-Spam-Score: -1.0 (-) A new Emacs user recently complained about the lack of an example showing local modules being `let'-bound around calls to ERC's entry point functions in lisp code. Perhaps we should add such an example to the Modules chapter and also possibly update the "Multiple networks" example in Advanced > SASL to include a non-SASL connection: (defun my-erc-up (network) (interactive "Snetwork: ") (pcase network ('libera (let ((erc-modules (cons 'sasl erc-modules)) (erc-sasl-mechanism 'external)) (erc-tls :server "irc.libera.chat" :port 6697 :client-certificate t))) ('example (let ((erc-modules (cons 'sasl erc-modules)) (erc-sasl-auth-source-function #'erc-sasl-auth-source-password-as-host)) (erc-tls :server "irc.example.net" :port 6697 :user "alyssa" :password "Example.Net"))) (_ (call-interactively #'erc-tls)))) From debbugs-submit-bounces@debbugs.gnu.org Thu Feb 29 19:25:47 2024 Received: (at 57955) by debbugs.gnu.org; 1 Mar 2024 00:25:47 +0000 Received: from localhost ([127.0.0.1]:35720 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1rfqih-0002Tg-73 for submit@debbugs.gnu.org; Thu, 29 Feb 2024 19:25:47 -0500 Received: from mail-108-mta62.mxroute.com ([136.175.108.62]:41333) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1rfqie-0002TW-5k for 57955@debbugs.gnu.org; Thu, 29 Feb 2024 19:25:46 -0500 Received: from filter006.mxroute.com ([136.175.111.2] filter006.mxroute.com) (Authenticated sender: mN4UYu2MZsgR) by mail-108-mta62.mxroute.com (ZoneMTA) with ESMTPSA id 18df766954b0003bea.001 for <57955@debbugs.gnu.org> (version=TLSv1.3 cipher=TLS_AES_256_GCM_SHA384); Fri, 01 Mar 2024 00:25:11 +0000 X-Zone-Loop: a5a8b50a932c0bb08eff548f7cfb03a5006974127417 X-Originating-IP: [136.175.111.2] DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=neverwas.me ; s=x; h=Content-Type:MIME-Version:Message-ID:Date:References:In-Reply-To: Subject:Cc:To:From:Sender:Reply-To:Content-Transfer-Encoding:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=G+mFY9yJsyZ8xOj8KoW242x8My3XbJ2p9dGSwA9pB60=; b=MEPvDoGiDURabLqVqdARPKXQa/ UbMO2lkM2erqkNb9FkenhwECxB8TPAqHpbdK+FYYRJDQRn4e7dOa1g3Jx5KeFIlRaf9ASDauZQuWm zpsc9z2EW9ko44926jSEV0v0XsrhyEruU8qTdZb3sazKEIuiBNV6zeTdQ9LprYe1odweFloiW7G95 U1x4XF3nic9KRPmC+EnUGG0G45VkI4o+vP53RtCkt44UP2VJcXarDC+mYd6NsdAONXwwgYJF1nGgz mn/MTFlSthZQISpOm9qbYUnP1LNFpe5jodWFdL/vyBe2aZN8q8glTBxkI5iqZd31ZihaQIQ38X3zp nNUVwWAQ==; From: "J.P." To: 57955@debbugs.gnu.org Subject: Re: bug#57955: 29.0.50; Allow session-local ERC modules In-Reply-To: <87wmrcxdjf.fsf@neverwas.me> (J. P.'s message of "Sat, 10 Feb 2024 12:36:04 -0800") References: <8735cm2o2l.fsf@neverwas.me> <87wmrcxdjf.fsf@neverwas.me> Date: Thu, 29 Feb 2024 16:25:08 -0800 Message-ID: <87frxax0gr.fsf@neverwas.me> User-Agent: Gnus/5.13 (Gnus v5.13) MIME-Version: 1.0 Content-Type: text/plain X-Authenticated-Id: masked@neverwas.me X-Spam-Score: -0.0 (/) X-Debbugs-Envelope-To: 57955 Cc: emacs-erc@gnu.org X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: debbugs-submit-bounces@debbugs.gnu.org Sender: "Debbugs-submit" X-Spam-Score: -1.0 (-) "J.P." writes: > A new Emacs user recently complained about the lack of an example > showing local modules being `let'-bound around calls to ERC's entry > point functions in lisp code. Perhaps we should add such an example to > the Modules chapter and also possibly update the "Multiple networks" > example in Advanced > SASL to include a non-SASL connection: > > (defun my-erc-up (network) > (interactive "Snetwork: ") > > (pcase network > ('libera > (let ((erc-modules (cons 'sasl erc-modules)) > (erc-sasl-mechanism 'external)) > (erc-tls :server "irc.libera.chat" :port 6697 > :client-certificate t))) > ('example > (let ((erc-modules (cons 'sasl erc-modules)) > (erc-sasl-auth-source-function > #'erc-sasl-auth-source-password-as-host)) > (erc-tls :server "irc.example.net" :port 6697 > :user "alyssa" > :password "Example.Net"))) > (_ (call-interactively #'erc-tls)))) I've added an example similar to this one. From debbugs-submit-bounces@debbugs.gnu.org Sun Feb 09 15:46:24 2025 Received: (at 57955) by debbugs.gnu.org; 9 Feb 2025 20:46:25 +0000 Received: from localhost ([127.0.0.1]:46602 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1thEC7-0007Lv-AT for submit@debbugs.gnu.org; Sun, 09 Feb 2025 15:46:24 -0500 Received: from mail-108-mta67.mxroute.com ([136.175.108.67]:41989) by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.84_2) (envelope-from ) id 1thEC4-0007Lk-D8 for 57955@debbugs.gnu.org; Sun, 09 Feb 2025 15:46:22 -0500 Received: from filter006.mxroute.com ([136.175.111.3] filter006.mxroute.com) (Authenticated sender: mN4UYu2MZsgR) by mail-108-mta67.mxroute.com (ZoneMTA) with ESMTPSA id 194ec76883f000310e.001 for <57955@debbugs.gnu.org> (version=TLSv1.3 cipher=TLS_AES_256_GCM_SHA384); Sun, 09 Feb 2025 20:46:18 +0000 X-Zone-Loop: c784bf553bcc13672b3e2b1be6c92392d5b58d72a369 X-Originating-IP: [136.175.111.3] DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=neverwas.me ; s=x; h=Content-Type:MIME-Version:Message-ID:Date:References:In-Reply-To: Subject:Cc:To:From:Sender:Reply-To:Content-Transfer-Encoding:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=FCQutFn3HmiVe5xqSa1vdSt1SNkSbZTIApC4mukNJ3Q=; b=FPaCWjCyW0lJD39zzAnEDflWjf F7hECZnSkjSf5pKDNXlhaeovzzGqpcwiWf+vuTK6WRKiW6DZyTA3GsdwOYb+/YNfrzt96Rjt2FhQD G1riqo+lx5pK6+PVhcQIrhF7L9hIktz6naQLn6ip9a3gH0EcdrrH8HIiORDVUjYlpLFNS/zFFSn+t R3Syv1mvI54ocaWaHPuuz4BHjw0AiAohXoXTEQnmGSGKE7YY94iTQ5E/0oF2Gs8uLoAs//B/9NdwD LWQUg87SC6elY2T+k0up701BqOMMKnJDc//53ZLzkocQ5pYOdxjAMDlBfIRrnRLzb5QuGppoPe8WI AF3lveuQ==; From: "J.P." To: 57955@debbugs.gnu.org Subject: Re: bug#57955: 29.0.50; Allow session-local ERC modules In-Reply-To: <8735cm2o2l.fsf@neverwas.me> (J. P.'s message of "Tue, 20 Sep 2022 06:05:38 -0700") References: <8735cm2o2l.fsf@neverwas.me> Date: Sun, 09 Feb 2025 12:46:14 -0800 Message-ID: <87o6zavobt.fsf@neverwas.me> User-Agent: Gnus/5.13 (Gnus v5.13) MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" X-Authenticated-Id: masked@neverwas.me X-Spam-Score: 0.0 (/) X-Debbugs-Envelope-To: 57955 Cc: emacs-erc@gnu.org X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: debbugs-submit-bounces@debbugs.gnu.org Sender: "Debbugs-submit" X-Spam-Score: -1.0 (-) --=-=-= Content-Type: text/plain I'd like to reexamine the scope of this bug because it's a partial blocker for bug#49860 (IRCv3). You'll recall among its original goals were two overlapping concerns: a. Granular configuration of a local module's user options b. Persistence of a local module's data across reconnections One idea bandied about for addressing the first was to recommend and accommodate buffer-local options, that is, recommend that options in a local module's Custom group be explicitly declared buffer-local with the :local `defcustom' keyword and that they be given local bindings on module activation, thus initializing them with values from the current environment. While this is technically feasible, a few notable complications would need sorting out [1]. A complementary aspect addressing the second goal of convenient persistence was also previously floated and amounted to leveraging the `permanent-local' symbol property on a local module's own variables of interest to sustain them across reconnections. With local user options, this would likely involve the `permanent-only' argument to the :local `defcustom' keyword explained in (info "(elisp) Variable Definitions"). For the stated purpose of sustaining variables of interest across reconnection boundaries, this approach remains viable [2], at least for third party modules that don't know about the internal persistence mechanism [3]. So, in light of the new proposal for "scoped" configuration now officially on the table [4], it might behoove us to just pretend the granularity objective is henceforth solely the domain of that proposal's bug (bug#76019). That'll allow us, here, in this bug, to focus entirely on the second objective about persistence and to hopefully arrive at something worthy of some finality for 5.7. To that end, here's some related territory possibly worth exploring: 1. A public utility function to access the prior buffer's local variables during reconnection 2. A managed facility for declaring arbitrary persisted data with supporting CRUD operations 3. Optional helpers for an option's :set function that update persisted values in affected buffers or inform users to cycle the mode or restart the session 4. Documenting differences in how a local module's mode command variants behave with the various flavors of local modules, like session-wide, target-only, etc. 5. An advanced tutorial on how to write a local module using only the public API via a fully functional demo To get started, I've attached a PoC of a possible approach for point 2 (the CRUD thing). It turns out my having explored the idea some has led me to the opinion that it's probably better to stick to points 4 and 5 only and to let module authors deal with the rest. Basically, I'm not sure asking anyone to adopt yet another magical abstraction layer just to persist state is any less mentally taxing than asking them to wrangle it all themselves using lower level Emacs facilities, so long as we provide clear guidelines and examples with any necessary boilerplate. Of course, this observation disregards maintainability concerns, so we'd need to be pretty certain all related infrastructure is mostly here to stay (famous last words). More to come on this shortly. Thanks. [1] Possible complications with the :local `defcustom' keyword idea: . Most users are unfamiliar with buffer-local options. And, AFAICT, Customize doesn't itself prescribe how such an option's variable should be made buffer local nor how or whether relevant updates ought to be propagated across existing local bindings when the default value is updated. It would seem such concerns are the responsibility of the application. But these are *user* options, and users can't be bothered to learn the idiosyncrasies of each app just to configure it to behave as expected. They'll either move on or risk contending with unwelcome surprises. . The buffer-wise "scope" doesn't always align perfectly with contexts endemic to IRC, the most important being the connection-wise session, which spans multiple buffers (internally, those having the same `erc-networks--id'). Users on 29+ might be able to use `setopt' to update the value cleanly within a session, for example, in a server buffer, and have the change shared with all targets as well. But, users on 27 and 28 can't be expected to invoke the option's :set function outside of Custom buffers, although advanced users can manually apply updates via the module's explicit enable/disable command variants. . Modules oftentimes ignore the value of an option after initialization and instead use something derived from the original value and then progressively refined. Local modules also perform other initialization tasks based on the value of an option, such as subscribe to certain hooks. While buffer-local options may agree sufficiently with this pattern, so long as they're bound before module setup code runs in a new or reused buffer, the pattern dictates that a module capture a "snapshot" of an option's value anyway, so there's no reason to prefer buffer-local bindings over, say, more ephemeral and arguably easier to reason about `let' bindings. . Per-target options won't magically work when local in a target buffer because ERC often decides on target-related business with the server buffer current. Indeed, the target buffer in question may not even exist yet, which happens most often in response handlers, such as `erc-server-PRIVMSG'. Although this situation can be remedied, doing it in a backward compatible way seems a chore. [2] A local module's mode variable itself can't be `permanent-local' because the majority of setup it performs won't survive a major-mode reset, thus creating an "inconsistent state" during the crucial reinitialization period when modules inspect and even modify one another. It's then that they also need to possibly recall the original value of variables not owned by them or even ERC (and these definitely can't be made `permanent-local'). [3] The internal inter-session persistence mechanism consists mainly of a crude restoration ritual for transferring values from old buffers to new via the variables `erc--server-reconnecting' and `erc--target-priors'. These are bound at module initialization time to an alist containing the local variables of the "reassociated" buffer, if any. Aside from those symbol names not being great and there being no public interface, there's also no way to recover if something goes awry during (re)initialization: restarting the session from scratch won't work so long as module-managed local variables are still bound to unusable values in the old buffer. Basically, the offending local module must run its "disable body" somewhere: either in the old buffer, before reassociating, or in the new one upon failure. Clearly, a friendlier and ideally more robust user-facing solution is necessary. [4] https://debbugs.gnu.org/cgi/bugreport.cgi?bug=76019 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=0001-5.7-Skip-already-enabled-local-modules-in-erc-open.patch >From d0c47c1dddbff1bf51ff2fb62149baa6be45fc8e Mon Sep 17 00:00:00 2001 From: "F. Jason Park" Date: Mon, 3 Feb 2025 23:05:24 -0800 Subject: [PATCH 1/2] [5.7] Skip already enabled local modules in erc-open * lisp/erc/erc.el (erc-open): When activating local modules, skip those that have just been enabled by a fellow module. Do this even though their setup code is meant to be idempotent. (Bug#57955) --- lisp/erc/erc.el | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lisp/erc/erc.el b/lisp/erc/erc.el index afa8e0a7b72..0d72b46360e 100644 --- a/lisp/erc/erc.el +++ b/lisp/erc/erc.el @@ -2662,7 +2662,9 @@ erc-open (erc--initialize-markers old-point continued-session) (erc-determine-parameters server port nick full-name user passwd) (save-excursion (run-mode-hooks) - (dolist (mod (car delayed-modules)) (funcall mod +1)) + (dolist (mod (car delayed-modules)) + (unless (and (boundp mod) (symbol-value mod)) + (funcall mod +1))) (dolist (var (cdr delayed-modules)) (set var nil))) ;; Saving log file on exit -- 2.48.1 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=0002-5.7-Provide-API-for-persisting-local-module-state-in.patch >From 1a1f02e9dd21ec74bb489dedd6252a8f5e1b367d Mon Sep 17 00:00:00 2001 From: "F. Jason Park" Date: Sat, 3 Jun 2023 09:00:00 -0700 Subject: [PATCH 2/2] [5.7] Provide API for persisting local-module state in ERC * lisp/erc/erc-common.el (erc--locals-get, erc--locals-define) (erc--locals-persist): New function. * lisp/erc/erc-sasl.el (erc-sasl--options): Remove unused variable. (erc--sasl-locals): New variable and struct of the same name. (erc-sasl--get-user): Use new API. (erc-sasl-mode, erc-sasl-enable): Relocate above references that depend on the variable `erc--sasl-locals' being defined. (erc-sasl--read-password, erc-sasl--create-client) (erc-sasl--mechanism-offered-p, erc-server-908) (erc--register-connection): Use new API. * lisp/erc/erc.el (erc-open): Don't activate local modules if they're already enabled, even though their setup code is meant to be idempotent. * test/lisp/erc/erc-sasl-tests.el (erc-sasl--mechanism-offered-p) (erc-sasl--read-password--basic, erc-sasl--read-password--auth-source) (erc-sasl-create-client--plain, erc-sasl-create-client--external) (erc-sasl-create-client--scram-sha-1) (erc-sasl-create-client--scram-sha-256) (erc-sasl-create-client--scram-sha-256--no-authzid) (erc-sasl-create-client--scram-sha-512--no-authzid): Replace references to `erc-sasl--options' with `erc--sasl-locals'. * test/lisp/erc/erc-tests.el (erc-tests-foo-mode, erc-tests-foo-enable) (erc-tests-foo-disable, erc--tests-foo-locals): New variables, functions, and struct. (erc--locals-define/baseline, erc--locals-define/mutate): New tests. (Bug#57955) --- lisp/erc/erc-common.el | 40 ++++++++++++++ lisp/erc/erc-sasl.el | 93 +++++++++++++++++---------------- test/lisp/erc/erc-sasl-tests.el | 47 +++++++++-------- test/lisp/erc/erc-tests.el | 55 +++++++++++++++++++ 4 files changed, 167 insertions(+), 68 deletions(-) diff --git a/lisp/erc/erc-common.el b/lisp/erc/erc-common.el index d293e6ba878..5e0a53bcfab 100644 --- a/lisp/erc/erc-common.el +++ b/lisp/erc/erc-common.el @@ -211,6 +211,46 @@ erc--favor-changed-reverted-modules-state erc-modules) (customize-mark-as-set 'erc-modules))) +(defmacro erc--locals-get (module key) + "Return MODULE's persistent buffer-local value for KEY. +Serve as a generalized place variable." + (let* ((var (intern (format "erc--%s-locals" module))) + (accessor (intern-soft (format "%s-%s" var key)))) + `(,accessor ,var))) + +(defmacro erc--locals-define (module &rest slots) + "Define a buffer-local data type for local MODULE. +Ensure it survives (re)initialization of MODULE for the duration of an +Emacs session but is still destroyed if disabling the module. Expect +SLOTS to be of the kind used by `cl-defstruct' and MODULE to be a +\"normalized\" module symbol. Also define a permanent-local variable +named `erc--MODULE-locals' to hold the data structure and a function to +kill that variable when disabling MODULE." + (declare (indent 1)) + (let* ((type (intern (format "erc--%s-locals" module))) + (kill (intern (format "%s-kill" type))) + (mode (intern (format "erc-%s-mode" module))) + (hook (intern (format "erc-%s-mode-hook" module)))) + `(progn + (defvar-local ,type nil + ,(format "Persistent data for `%s'." mode)) + (put ',type 'permanent-local t) + (defun ,kill () + (unless (bound-and-true-p ,mode) + (kill-local-variable ',type))) + (add-hook ',hook #',kill 50) ; global + (cl-defstruct (,type (:constructor ,type)) + ,(format "Persistent data for local module %s." module) + ,@slots)))) + +(defmacro erc--locals-persist (module &rest plist) + "If needed, initialize MODULE's data in current buffer from PLIST. +Expect to run in the \"enable body\" of MODULE's minor mode command." + (declare (indent 1)) + (let ((var (intern (format "erc--%s-locals" module)))) + `(unless ,var + (setq ,var (funcall #',var ,@plist))))) + (defun erc--assemble-toggle (localp name ablsym mode val body) (let ((arg (make-symbol "arg"))) `(defun ,ablsym ,(if localp `(&optional ,arg) '()) diff --git a/lisp/erc/erc-sasl.el b/lisp/erc/erc-sasl.el index a16f554f2d1..1e746be8a62 100644 --- a/lisp/erc/erc-sasl.el +++ b/lisp/erc/erc-sasl.el @@ -112,9 +112,6 @@ erc-sasl-authzid :type '(choice (const nil) string)) -;; Analogous to what erc-backend does to persist opening params. -(defvar-local erc-sasl--options nil) - ;; Session-local (server buffer) SASL subproto state (defvar-local erc-sasl--state nil) @@ -124,12 +121,40 @@ erc-sasl--state (step nil :type vector) (pending nil :type string)) +(erc--locals-define sasl + (user erc-sasl-user) + (password erc-sasl-password) + (mechanism erc-sasl-mechanism) + ;; FIXME use truncated option names for these slots like above. + (authfn erc-sasl-auth-source-function) + (authzid erc-sasl-authzid)) + (defun erc-sasl--get-user () - (pcase (alist-get 'user erc-sasl--options) + (pcase (erc--locals-get sasl user) (:user erc-session-username) (:nick (erc-current-nick)) (v v))) +(define-erc-module sasl nil + "Non-IRCv3 SASL support for ERC. +This doesn't solicit or validate a suite of supported mechanisms." + ;; See bug#49860 for a CAP 3.2-aware WIP implementation. + ((unless erc--target + (setq erc-sasl--state (make-erc-sasl--state)) + ;; If the previous attempt failed during registration, this may be + ;; non-nil and contain erroneous values, but how can we detect that? + ;; What if the server dropped the connection for some other reason? + (erc--locals-persist sasl) + (let* ((mech (erc--locals-get sasl mechanism)) + (client (erc-sasl--create-client mech))) + (unless client + (erc-display-error-notice + nil (format "Unknown or unsupported SASL mechanism: `%s'" mech)) + (error "Unknown or unsupported SASL mechanism: `%s'" mech)) + (setf (erc-sasl--state-client erc-sasl--state) client)))) + ((kill-local-variable 'erc-sasl--state)) + localp) + (defun erc-sasl-auth-source-password-as-host (&rest plist) "Call `erc-auth-source-search' with `erc-sasl-password' as `:host'. But only do so when it's a string or a non-nil symbol, unless @@ -148,15 +173,17 @@ erc-sasl-auth-source-password-as-host (defun erc-sasl--read-password (prompt) "Return configured option or server password. If necessary, pass PROMPT to `read-passwd'." - (if-let* ((found (pcase (alist-get 'password erc-sasl--options) - ((guard (alist-get 'authfn erc-sasl--options)) - (let-alist erc-sasl--options - (let ((erc-sasl-user .user) - (erc-sasl-password .password) - (erc-sasl-mechanism .mechanism) - (erc-sasl-authzid .authzid) - (erc-sasl-auth-source-function .authfn)) - (funcall .authfn :user (erc-sasl--get-user))))) + (if-let* ((found (pcase (erc--locals-get sasl password) + ((guard (erc--locals-get sasl authfn)) + (pcase erc--sasl-locals + ((cl-struct erc--sasl-locals + (user erc-sasl-user) + (password erc-sasl-password) + (mechanism erc-sasl-mechanism) + (authzid erc-sasl-authzid) + (authfn erc-sasl-auth-source-function)) + (funcall erc-sasl-auth-source-function + :user (erc-sasl--get-user))))) (:password erc-session-password) ((and (pred stringp) v) (unless (string-empty-p v) v))))) (copy-sequence (erc--unfun found)) @@ -250,7 +277,7 @@ erc-sasl--create-client (erc-sasl--get-user) "N/A" "N/A")) (sasl-client-set-property client 'authenticator-name - (alist-get 'authzid erc-sasl--options)) + (erc--locals-get sasl authzid)) client))) (cl-defmethod erc-sasl--create-client ((_ (eql plain))) @@ -268,7 +295,7 @@ erc-sasl--create-client (mech (sasl-find-mechanism '("PLAIN"))) (client (sasl-make-client mech authc port host))) (sasl-client-set-property client 'authenticator-name - (alist-get 'authzid erc-sasl--options)) + (erc--locals-get sasl authzid)) client)) (cl-defmethod erc-sasl--create-client ((_ (eql scram-sha-256))) @@ -283,7 +310,7 @@ erc-sasl--create-client (cl-defmethod erc-sasl--create-client ((_ (eql ecdsa-nist256p-challenge))) "Create and return a new ECDSA-NIST256P-CHALLENGE client." - (let ((keyfile (cdr (assq 'password erc-sasl--options)))) + (let ((keyfile (erc--locals-get sasl password))) ;; Better to signal usage errors now than inside a process filter. (cond ((or (not (stringp keyfile)) (not (file-readable-p keyfile))) (erc-display-error-notice @@ -301,7 +328,7 @@ erc-sasl--mechanism-offered-p "Return non-nil when OFFERED appears among a list of mechanisms." (string-match-p (rx-to-string `(: (| bot ",") - ,(symbol-name (alist-get 'mechanism erc-sasl--options)) + ,(symbol-name (erc--locals-get sasl mechanism)) (| eot ","))) (downcase offered))) @@ -313,32 +340,6 @@ english (s907 . "ERR_SASLALREADY (already authenticated) %s") (s908 . "RPL_SASLMECHS (unsupported mechanism: %m) %s"))) -(define-erc-module sasl nil - "Non-IRCv3 SASL support for ERC. -This doesn't solicit or validate a suite of supported mechanisms." - ;; See bug#49860 for a CAP 3.2-aware WIP implementation. - ((unless erc--target - (setq erc-sasl--state (make-erc-sasl--state)) - ;; If the previous attempt failed during registration, this may be - ;; non-nil and contain erroneous values, but how can we detect that? - ;; What if the server dropped the connection for some other reason? - (erc--restore-initialize-priors erc-sasl-mode - erc-sasl--options `((user . ,erc-sasl-user) - (password . ,erc-sasl-password) - (mechanism . ,erc-sasl-mechanism) - (authfn . ,erc-sasl-auth-source-function) - (authzid . ,erc-sasl-authzid))) - (let* ((mech (alist-get 'mechanism erc-sasl--options)) - (client (erc-sasl--create-client mech))) - (unless client - (erc-display-error-notice - nil (format "Unknown or unsupported SASL mechanism: `%s'" mech)) - (error "Unknown or unsupported SASL mechanism: `%s'" mech)) - (setf (erc-sasl--state-client erc-sasl--state) client)))) - ((kill-local-variable 'erc-sasl--state) - (kill-local-variable 'erc-sasl--options)) - localp) - (define-erc-response-handler (AUTHENTICATE) "Begin or resume an SASL session." nil (if-let* ((response (car (erc-response.command-args parsed))) @@ -405,7 +406,7 @@ erc-sasl--destroy (define-erc-response-handler (908) "Handle a RPL_SASLMECHS response." nil (erc-display-message parsed '(notice error) 'active 's908 - ?m (alist-get 'mechanism erc-sasl--options) + ?m (erc--locals-get sasl mechanism) ?s (string-join (cdr (erc-response.command-args parsed)) " ")) (erc-sasl--destroy proc)) @@ -425,11 +426,11 @@ erc--register-connection (let ((erc-session-password (and erc-session-password (not (eq :password - (alist-get 'password erc-sasl--options))) + (erc--locals-get sasl password))) erc-session-password)) (erc-session-username ;; The username may contain a colon or a space - (if (eq :user (alist-get 'user erc-sasl--options)) + (if (eq :user (erc--locals-get sasl user)) (erc-current-nick) erc-session-username))) (cl-call-next-method)) diff --git a/test/lisp/erc/erc-sasl-tests.el b/test/lisp/erc/erc-sasl-tests.el index 29fc7bef033..e9c32f6d3aa 100644 --- a/test/lisp/erc/erc-sasl-tests.el +++ b/test/lisp/erc/erc-sasl-tests.el @@ -25,7 +25,7 @@ (require 'erc-sasl) (ert-deftest erc-sasl--mechanism-offered-p () - (let ((erc-sasl--options '((mechanism . external)))) + (let ((erc--sasl-locals (erc--sasl-locals :mechanism 'external))) (should (erc-sasl--mechanism-offered-p "foo,external")) (should (erc-sasl--mechanism-offered-p "external,bar")) (should (erc-sasl--mechanism-offered-p "foo,external,bar")) @@ -34,25 +34,25 @@ erc-sasl--mechanism-offered-p (ert-deftest erc-sasl--read-password--basic () (ert-info ("Explicit erc-sasl-password") - (let ((erc-sasl--options '((password . "foo")))) + (let ((erc--sasl-locals (erc--sasl-locals :password "foo"))) (should (string= (erc-sasl--read-password nil) "foo")))) (ert-info ("Explicit session password") (let ((erc-session-password "foo") - (erc-sasl--options '((password . :password)))) + (erc--sasl-locals (erc--sasl-locals :password :password))) (should (string= (erc-sasl--read-password nil) "foo")))) (ert-info ("Prompt when no authfn and :password resolves to nil") (let ((erc-session-password nil) - (erc-sasl--options - '((password . :password) (user . :user) (authfn)))) + (erc--sasl-locals (erc--sasl-locals :password :password + :user :user))) (should (string= (ert-simulate-keys "bar\r" (erc-sasl--read-password "?")) "bar")))) (ert-info ("Prompt when auth-source fails and `erc-session-password' null") (should-not erc-session-password) - (let ((erc-sasl--options '((password) (authfn . ignore)))) + (let ((erc--sasl-locals (erc--sasl-locals :authfn #'ignore))) (should (string= (ert-simulate-keys "baz\r" (erc-sasl--read-password "pwd:")) "baz"))))) @@ -83,36 +83,36 @@ erc-sasl--read-password--auth-source '((name . erc-sasl--read-password--auth-source))) (ert-info ("Symbol as password specifies machine") - (let ((erc-sasl--options - `((user . "bob") (password . FSF.chat) (authfn . ,fn)))) + (let ((erc--sasl-locals + (erc--sasl-locals :user "bob" :password 'FSF.chat :authfn fn))) (should (string= (erc-sasl--read-password nil) "sesame")) (should (equal (pop calls) '(:user "bob" :host "FSF.chat"))))) (ert-info (":password as password resolved to machine") (let ((erc-session-password "FSF.chat") - (erc-sasl--options - `((user . "bob") (password . :password) (authfn . ,fn)))) + (erc--sasl-locals + (erc--sasl-locals :user "bob" :password :password :authfn fn))) (should (string= (erc-sasl--read-password nil) "sesame")) (should (equal (pop calls) '(:user "bob" :host "FSF.chat"))))) (ert-info (":user resolved to `erc-session-username'") ; *1 (let ((erc-session-username "bob") - (erc-sasl--options `((user . :user) (password) (authfn . ,fn))) + (erc--sasl-locals (erc--sasl-locals :user :user :authfn fn)) (erc-networks--id (erc-networks--id-create 'GNU/chat))) (should (string= (erc-sasl--read-password nil) "spam")) (should (equal (pop calls) '(:user "bob"))))) (ert-info (":user resolved to current nick") ; *1 (let ((erc-server-current-nick "bob") - (erc-sasl--options `((user . :nick) (password) (authfn . ,fn))) + (erc--sasl-locals (erc--sasl-locals :user :nick :authfn fn)) (erc-networks--id (erc-networks--id-create 'GNU/chat))) (should (string= (erc-sasl--read-password nil) "spam")) (should (equal (pop calls) '(:user "bob"))))) (ert-info ("Symbol as password, entry lacks user field") (let ((erc-server-current-nick "fake") - (erc-sasl--options - `((user . :nick) (password . MyHost) (authfn . ,fn))) + (erc--sasl-locals + (erc--sasl-locals :user :nick :password 'MyHost :authfn fn)) (erc-networks--id (erc-networks--id-create 'GNU/chat))) (should (string= (erc-sasl--read-password nil) "123")) (should (equal (pop calls) '(:user "fake" :host "MyHost"))))) @@ -123,7 +123,7 @@ erc-sasl--read-password--auth-source (ert-deftest erc-sasl-create-client--plain () (let* ((erc-session-password "password123") (erc-session-username "tester") - (erc-sasl--options '((user . :user) (password . :password))) + (erc--sasl-locals (erc--sasl-locals :user :user :password :password)) (erc-session-port 1667) (erc-session-server "localhost") (client (erc-sasl--create-client 'plain)) @@ -137,7 +137,7 @@ erc-sasl-create-client--plain (ert-deftest erc-sasl-create-client--external () (let* ((erc-server-current-nick "tester") - (erc-sasl--options '((user . :nick) (password . :password))) + (erc--sasl-locals (erc--sasl-locals :user :nick :password :password)) (client (erc-sasl--create-client 'external)) ; unused ^ (result (sasl-next-step client nil))) (should (equal (format "%S" [ignore nil]) (format "%S" result))) @@ -147,8 +147,9 @@ erc-sasl-create-client--external (should-not (assoc-default "EXTERNAL" sasl-mechanism-alist))) (ert-deftest erc-sasl-create-client--scram-sha-1 () - (let* ((erc-sasl--options '((user . "jilles") (password . "sesame") - (authzid . "jilles"))) + (let* ((erc--sasl-locals (erc--sasl-locals :user "jilles" + :password "sesame" + :authzid "jilles")) (mock-rvs (list "c5RqLCZy0L4fGkKAZ0hujFBs" "")) (sasl-unique-id-function (lambda () (pop mock-rvs))) (client (erc-sasl--create-client 'scram-sha-1)) @@ -186,8 +187,9 @@ erc-sasl-create-client--scram-sha-256 (ert-skip "Emacs lacks sasl-scram-sha256")) (let* ((erc-server-current-nick "jilles") (erc-session-password "sesame") - (erc-sasl--options '((user . :nick) (password . :password) - (authzid . "jilles"))) + (erc--sasl-locals (erc--sasl-locals :user :nick + :password :password + :authzid "jilles")) (mock-rvs (list "c5RqLCZy0L4fGkKAZ0hujFBs" "")) (sasl-unique-id-function (lambda () (pop mock-rvs))) (client (erc-sasl--create-client 'scram-sha-256)) @@ -227,7 +229,7 @@ erc-sasl-create-client--scram-sha-256--no-authzid (ert-skip "Emacs lacks sasl-scram-sha256")) (let* ((erc-server-current-nick "jilles") (erc-session-password "sesame") - (erc-sasl--options '((user . :nick) (password . :password) (authzid))) + (erc--sasl-locals (erc--sasl-locals :user :nick)) (mock-rvs (list "c5RqLCZy0L4fGkKAZ0hujFBs" "")) (sasl-unique-id-function (lambda () (pop mock-rvs))) (client (erc-sasl--create-client 'scram-sha-256)) @@ -267,7 +269,8 @@ erc-sasl-create-client--scram-sha-512--no-authzid (ert-skip "Emacs lacks sasl-scram-sha512")) (let* ((erc-server-current-nick "jilles") (erc-session-password "sesame") - (erc-sasl--options '((user . :nick) (password . :password) (authzid))) + (erc--sasl-locals (erc--sasl-locals :user :nick + :password :password)) (mock-rvs (list "c5RqLCZy0L4fGkKAZ0hujFBs" "")) (sasl-unique-id-function (lambda () (pop mock-rvs))) (client (erc-sasl--create-client 'scram-sha-512)) diff --git a/test/lisp/erc/erc-tests.el b/test/lisp/erc/erc-tests.el index df9e4d52f77..7be9902f527 100644 --- a/test/lisp/erc/erc-tests.el +++ b/test/lisp/erc/erc-tests.el @@ -3792,6 +3792,61 @@ erc--merge-local-modes (should (equal (erc--merge-local-modes new old) '((erc-d-mode) . (erc-b-mode)))))))) +(erc--locals-define tests-foo + (a (error "Argument A required")) + (b 1 :read-only t) + (c (and erc--target 2))) + +(define-erc-module tests-foo nil "Use local persistent data." + ((erc--locals-persist tests-foo :a 0)) + ((ignore)) + localp) + +(ert-deftest erc--locals-define/baseline () + (erc-mode) + (should-not erc--tests-foo-locals) + (erc-tests-foo-mode +1) + (should erc--tests-foo-locals) + (should (= (erc--tests-foo-locals-a erc--tests-foo-locals) 0)) + (should (= (erc--tests-foo-locals-b erc--tests-foo-locals) 1)) + (should-not (erc--tests-foo-locals-c erc--tests-foo-locals)) + + ;; Local var `erc--tests-foo-locals' survives a major-mode reset. + ;; However, enabling the minor mode does not re-initialize the + ;; values: they're preserved from the previous session. + (erc-mode) + (setq erc--target (erc--target-from-string "Bob")) + (should erc--tests-foo-locals) + (erc-tests-foo-mode +1) + (should (= (erc--locals-get tests-foo a) 0)) + (should (= (erc--locals-get tests-foo b) 1)) + (should (null (erc--locals-get tests-foo c))) + + ;; Cycling the mode *does* reinitialize the data. + (erc-tests-foo-mode -1) + (should-not erc--tests-foo-locals) + (erc-tests-foo-mode +1) + (should (equal (erc--locals-get tests-foo c) 2))) + +(ert-deftest erc--locals-define/mutate () + (erc-mode) + (should-not erc--tests-foo-locals) + (erc-tests-foo-mode +1) + (should erc--tests-foo-locals) + (should (= (erc--locals-get tests-foo a) 0)) + (should (= (erc--locals-get tests-foo b) 1)) + (should-not (erc--locals-get tests-foo c)) + + ;; Writing to a read-only slot signals an error. + (should-error (setf (erc--locals-get tests-foo b) 42)) + + ;; Local macro works as a generalized variable. + (push '(y . Y) (erc--locals-get tests-foo c)) + (push '(x . X) (erc--locals-get tests-foo c)) + (should (equal (erc--locals-get tests-foo c) '((x . X) (y . Y)))) + (setf (alist-get 'y (erc--locals-get tests-foo c)) 'YY) + (should (equal (erc--locals-get tests-foo c) '((x . X) (y . YY))))) + (ert-deftest define-erc-module--global () (let ((global-module '(define-erc-module mname malias "Some docstring." -- 2.48.1 --=-=-=-- From debbugs-submit-bounces@debbugs.gnu.org Tue Feb 18 23:18:13 2025 Received: (at 57955) by debbugs.gnu.org; 19 Feb 2025 04:18:13 +0000 Received: from localhost ([127.0.0.1]:39035 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1tkbXH-0006GI-Jn for submit@debbugs.gnu.org; Tue, 18 Feb 2025 23:18:13 -0500 Received: from mail-108-mta181.mxroute.com ([136.175.108.181]:38781) by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.84_2) (envelope-from ) id 1tkbXC-0006Ff-QF for 57955@debbugs.gnu.org; Tue, 18 Feb 2025 23:18:09 -0500 Received: from filter006.mxroute.com ([136.175.111.3] filter006.mxroute.com) (Authenticated sender: mN4UYu2MZsgR) by mail-108-mta181.mxroute.com (ZoneMTA) with ESMTPSA id 1951c6d5191000310e.001 for <57955@debbugs.gnu.org> (version=TLSv1.3 cipher=TLS_AES_256_GCM_SHA384); Wed, 19 Feb 2025 04:18:01 +0000 X-Zone-Loop: 3fd62b494968851790688273915386b7bf1597ac81e8 X-Originating-IP: [136.175.111.3] DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=neverwas.me ; s=x; h=Content-Type:MIME-Version:Message-ID:Date:References:In-Reply-To: Subject:Cc:To:From:Sender:Reply-To:Content-Transfer-Encoding:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=gG/NlOkCMF2s3WXNjYKoB2vDghTIuBjvse78NGKjnE4=; b=UiXgF0Z5dUYNl0LUNKJlyRYpEk v6U5fTi0LVzTJmNR2l7/IG62FWessRECiIKw3tfqtqF89U5w3YVZ4wH0dJR+t9T7jVeBDBHNPtbst vQvI6QNZhbx3rHRZDWOd3/o6d+ICmIUZJhhvf/Pbq3enisTG7Rf2OQLPn2i0QjmXRiKsrprd1OS64 KSHPlPLXu3ZDpmFqVnLXhURfs3d00d401bLdFnpCb3fGssaZOTGRTyCMbokibGcu6fUuaD67E8jKj 9uFs/73tKrz+e51cn92Lgol8LX1706uZw5iwsmAKeSVdZZ2R7JElgSXMqg5m5XBqvtpKeB4DApnyn aGQky3nw==; From: "J.P." To: 57955@debbugs.gnu.org Subject: Re: bug#57955: 29.0.50; Allow session-local ERC modules In-Reply-To: <87o6zavobt.fsf@neverwas.me> References: <8735cm2o2l.fsf@neverwas.me> <87o6zavobt.fsf@neverwas.me> Date: Tue, 18 Feb 2025 20:17:58 -0800 Message-ID: <87v7t6in49.fsf@neverwas.me> User-Agent: Gnus/5.13 (Gnus v5.13) MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" X-Authenticated-Id: masked@neverwas.me X-Spam-Score: 0.0 (/) X-Debbugs-Envelope-To: 57955 Cc: emacs-erc@gnu.org X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: debbugs-submit-bounces@debbugs.gnu.org Sender: "Debbugs-submit" X-Spam-Score: -1.0 (-) --=-=-= Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable "J.P." writes: > So, in light of the new proposal for "scoped" configuration now > officially on the table [4], it might behoove us to just pretend the > granularity objective is henceforth solely the domain of that proposal's > bug (bug#76019). That'll allow us, here, in this bug, to focus entirely > on the second objective about persistence and to hopefully arrive at > something worthy of some finality for 5.7. To that end, here's some > related territory possibly worth exploring: > > 1. A public utility function to access the prior buffer's local > variables during reconnection > > 2. A managed facility for declaring arbitrary persisted data with > supporting CRUD operations >=20=20=20 > 3. Optional helpers for an option's :set function that update > persisted values in affected buffers or inform users to cycle the > mode or restart the session > > 4. Documenting differences in how a local module's mode command > variants behave with the various flavors of local modules, like > session-wide, target-only, etc. > > 5. An advanced tutorial on how to write a local module using only the > public API via a fully functional demo > > To get started, I've attached a PoC of a possible approach for point 2 > (the CRUD thing). It turns out my having explored the idea some has led > me to the opinion that it's probably better to stick to points 4 and 5 > only and to let module authors deal with the rest. Basically, I'm not > sure asking anyone to adopt yet another magical abstraction layer just > to persist state is any less mentally taxing than asking them to wrangle > it all themselves using lower level Emacs facilities, so long as we > provide clear guidelines and examples with any necessary boilerplate. Of > course, this observation disregards maintainability concerns, so we'd > need to be pretty certain all related infrastructure is mostly here to > stay (famous last words). More to come on this shortly. Here is an initial draft attempting to address points 4 and 5 in the list above (patch also attached): File: erc.info, Node: Module Example, Next: Module Usage, Prev: Module Loading, Up: Modules 4.3 Example =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D This is a walk-through of a working module presented in separate chunks. If you'd prefer to view it as a whole, you can install it as a third-party package through ERC's devel archive: . ;;; erc-view.el -- Automatic view-mode for ERC -*- lexical-binding: = t; -*- ;; Maintainer: The ERC Maintainers ;; Keywords: convenience ;; Version: 0.1 ;; Package-Requires: ((emacs "30.1")) ;; URL: https://gitlab.com/emacs-erc/erc-view ;;; Commentary: ;; This is a demo local module for ERC. It arranges for automatical= ly ;; enabling `view-mode' when leaving the prompt area and automatical= ly ;; disabling it when reentering. It also ensures `view-mode' stays ;; enabled or disabled when reconnecting. ;;; Code: You need to import ERC's main library somehow. The easiest way is directly, via a simple =E2=80=98(require 'erc)=E2=80=99, although this mo= dule does so indirectly because it also uses definitions from =E2=80=98erc-goodies=E2= =80=99: (require 'erc-goodies) (require 'view) Avoid headaches by aligning the name of your module with its containing library and Custom group. It's best to have one group and one module per library. (defgroup erc-view nil "Automatically enter and exit `view-mode' in ERC." :version "0.1" :group 'erc) (defcustom erc-view-enable-when-exiting-prompt t "Whether to enable `view-mode' when exiting the prompt area." :type 'boolean) (defcustom erc-view-disable-when-entering-prompt t "Whether to disable `view-mode' when entering the prompt area." :type 'boolean) (defcustom erc-view-backspace-at-prompt-scrolls-down t "Whether a \\`' at the prompt scrolls down to enter `vi= ew-mode'." :type 'boolean) You'll almost always want to define internal variables as buffer-local. (defvar-local erc-view--enabled-p nil "Current reconnect-aware activation state of `view-mode'.") In some cases, you may need a variable's value to survive the reapplication of ERC's =E2=80=98major-mode=E2=80=99 performed in each rea= ssociated buffer upon reconnecting. Do this by leveraging the =E2=80=98permanent-l= ocal=E2=80=99 symbol property. (*note (elisp)Creating Buffer-Local::.) (put 'erc-view--enabled-p 'permanent-local t) There are a few caveats regarding the durability of permanent values. By convention, disabling a module's minor mode kills local bindings. Mode commands, like =E2=80=98erc-view-mode=E2=80=99, do so in the current= buffer only, while unidirectional ones, like =E2=80=98erc-view-mode-disable=E2=80=99, = do so connection-wide. There are also occasions in which persistence is undefined, most notably when =E2=80=9Cgrafting=E2=80=9D an old buffer's contents onto a c= urrent buffer. This occurs in server buffers upon =E2=80=9Clogical connection=E2=80=9D (= at =E2=80=98MOTD=E2=80=99's end), when a user reconnects with a new invocation of an entry-point command, like =E2=80=98erc-tls=E2=80=99, instead of via the auto-reconnec= t facility or by issuing a =E2=80=98/reconnect=E2=80=99 at the prompt. Unaffected are = entry-point invocations that include an =E2=80=98:id=E2=80=99 keyword because reassoc= iation happens immediately in such cases, before ERC even initializes any modules. Grafting can also happen in target buffers, most often after a user reconnects under a new nick and conducts business in the same channels and queries as before, only to renick _back_ to the previous nick via a =E2=80=98/nick oldme=E2=80=99 or similar. As of version 5.7, ERC retains= the current buffer's permanent value in all such situations, meaning ERC ignores permanent values from previous buffers and retains default values assigned during module initialization. Moving on, if your module needs to bind keys, define its keymap _before_ the module itself, and use the standard minor-mode naming convention of =E2=80=98erc-my-module-mode-map=E2=80=99. (defvar-keymap erc-view-mode-map :doc "Keymap for `view-mode' in ERC." " " #'erc-view--enable-on-backspace) (defvar-keymap erc-view-mode-overriding-map :parent view-mode-map :doc "Overriding keymap for `view-mode' when `erc-view-mode' is ac= tive. Hitting \\`' atop a button prompts for an action by default. U= se \\`C-j' or \\`j' for scrolling up by a line." "C" nil ; View-kill-and-leave "E" #'erc-view--exit-to-bottom ; View-exit-and-edit "Q" nil ; View-quit-all "k" #'View-scroll-line-backward ; Vim backwards line "j" #'View-scroll-line-forward ; Vim forwards line "S-" #'View-scroll-line-backward) You'll almost always want to define your module as buffer-local. Do this by including a =E2=80=98localp=E2=80=99 flag as the final parameter = to =E2=80=98define-erc-module=E2=80=99, after the =E2=80=9Cdisable body=E2= =80=9D. If your module only operates in one kind of buffer, disable it elsewhere in the =E2=80=9Cenab= le body=E2=80=9D. For example, if it should only run in server buffers, dis= able it in target buffers by doing something like =E2=80=98(if (erc-target) (erc-my-module-mode -1) (erc-my-module--setup))=E2=80=99. And in all cas= es, please remember to mention the module's intended =E2=80=9Cscope=E2=80=9D = in the doc string. Some informal adjectives that may help with that are: =E2=80=A2 query-local =E2=80=A2 channel-local =E2=80=A2 target-local (query or channel) =E2=80=A2 server-local =E2=80=A2 session-local (server and target) =E2=80=A2 buffer-local (server or target) You may also wish to mention this in the Custom group's doc string. (define-erc-module view nil "Enable `view-mode' if it was on previously. This module is buffer-local. If you also use the `scrolltobottom' module, you probably want to enable the option `erc-scrolltobottom-a= ll'." ((add-hook 'view-mode-hook #'erc-view--remember 0 t) (add-hook 'post-command-hook #'erc-view--enforce-prompt-boundary = 0 t) (setf (alist-get 'view-mode minor-mode-overriding-map-alist) erc-view-mode-overriding-map) (unless (local-variable-p 'erc-view--enabled-p) (setq-local erc-view--enabled-p nil)) (view-mode (if erc-view--enabled-p +1 -1))) ((kill-local-variable 'erc-view--enabled-p) (remove-hook 'post-command-hook #'erc-view--enforce-prompt-bounda= ry t) (remove-hook 'view-mode-hook #'erc-view--remember t) (setf (alist-get 'view-mode minor-mode-overriding-map-alist nil '= remove) nil)) localp) Always define your module early, before any code that refers to its mode command or minor-mode variable. (defun erc-view--enable-on-backspace (lines) "Enable `view-mode' at the prompt by hitting \\`'." (interactive "P") (if (and erc-view-backspace-at-prompt-scrolls-down (not view-mode) (=3D (point) erc-input-marker)) (progn (view-mode +1) (View-scroll-page-backward lines)) (call-interactively #'delete-backward-char))) (defun erc-view--enforce-prompt-boundary () "Enable or disable `view-mode' when crossing prompt boundary." (when-let* ((new (if (>=3D (point) erc-input-marker) (and view-mode erc-view-disable-when-entering-prompt= -1) (and (not view-mode) erc-view-enable-when-exiting-prom= pt +1)))) (run-at-time 0 nil (lambda (buffer new) (with-current-buffer buffer (view-mode new)= )) (current-buffer) new))) (defun erc-view--exit-to-bottom () "Scroll to prompt, exit `view-mode', and move to EOB." (interactive) (let (view-no-disable-on-exit) (View-scroll-to-buffer-end) (View-exit) (goto-char (point-max)))) (defun erc-view--remember () "Remember the value of `view-mode'. Disable `erc-move-to-prompt-setup' locally when `view-mode' is enabl= ed." (cl-assert (local-variable-p 'erc-view--enabled-p)) (setq erc-view--enabled-p view-mode) (when erc-move-to-prompt-mode (if view-mode (remove-hook 'pre-command-hook #'erc-move-to-prompt t) (erc-move-to-prompt-setup)))) Don't forget to =E2=80=98provide=E2=80=99 your module so that =E2=80= =98erc-update-modules=E2=80=99 can find it. (provide 'erc-view) ;;; erc-view.el ends here Mimicking the above should just about cover most use cases. If your module isn't loading correctly, it's likely a naming, layout, or packaging issue. If you _must_ defy the convention recommended earlier regarding a library-group-module correspondence or if you've designed your module mainly to be toggled interactively rather than added to =E2=80=98erc-modules=E2=80=99, try placing a line like the following abov= e the module's definition. ;;;###autoload(autoload 'erc-my-module-mode "erc-my-module" nil t) Just remember, doing so means you'll need to (re)generate the autoload file when hacking locally (*note (emacs)Fetching Package Sources::). --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=0001-5.7-Split-ERC-module-documentation-into-subnodes.patch >From bdb72a74a30f23ba4181d054639c75ee89e54095 Mon Sep 17 00:00:00 2001 From: "F. Jason Park" Date: Wed, 12 Feb 2025 21:11:51 -0800 Subject: [PATCH 1/2] [5.7] Split ERC module documentation into subnodes * doc/misc/erc.texi: Add "Modules" section to the main detailed menu. (Modules): Promote "Local Modules" and "Module Loading" subheadings to sections and proper nodes. Rename "Local Modules" to "Module Scope" but retain anchor for compatibility. --- doc/misc/erc.texi | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/doc/misc/erc.texi b/doc/misc/erc.texi index 1c0afa3b300..818693db2f8 100644 --- a/doc/misc/erc.texi +++ b/doc/misc/erc.texi @@ -75,6 +75,11 @@ Top * Sample Session:: Example of connecting to the @samp{#emacs} channel * Special Features:: Differences from standalone IRC clients +Advanced Module Topics + +* Scope: Module Scope. Differences between module types. +* Loading: Module Loading. How ERC loads modules. + Advanced Usage * Connecting:: Ways of connecting to an IRC server. @@ -614,9 +619,13 @@ Modules At present, the only such module is @code{networks}, whose library ERC always loads anyway. +@c Advanced module topics and individual module usage. + @anchor{Local Modules} -@subheading Local Modules +@node Module Scope +@section Scope @cindex local modules +@cindex module scope @c Earlier language in code comments, commit messages, and tracker @c discussions used to describe a local module as being "active" in a @@ -697,10 +706,8 @@ Modules unlike global toggles, none of these ever mutates @code{erc-modules}. -@c FIXME add section to Advanced chapter for creating modules, and -@c move this there. -@anchor{Module Loading} -@subheading Loading +@node Module Loading +@section Loading @cindex module loading ERC loads internal modules in alphabetical order and third-party -- 2.48.1 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=0002-5.7-Add-module-example-to-ERC-s-documentation.patch >From 074bd8ea6ed4f57fde2e48c21c2df5b9813f5fbf Mon Sep 17 00:00:00 2001 From: "F. Jason Park" Date: Wed, 12 Feb 2025 21:11:51 -0800 Subject: [PATCH 2/2] [5.7] Add module example to ERC's documentation * doc/misc/erc.texi (Module Example): New section under the Modules chapter. (Bug#57955) --- doc/misc/erc.texi | 237 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 237 insertions(+) diff --git a/doc/misc/erc.texi b/doc/misc/erc.texi index 818693db2f8..3f63b530f83 100644 --- a/doc/misc/erc.texi +++ b/doc/misc/erc.texi @@ -79,6 +79,7 @@ Top * Scope: Module Scope. Differences between module types. * Loading: Module Loading. How ERC loads modules. +* Example: Module Example. An example module. Advanced Usage @@ -761,6 +762,242 @@ Module Loading incorrectly, with built-in modules moved from the predefined checklist to the user-provided free-form area. +@node Module Example +@section Example +@cindex module example + +This is a walk-through of a working module presented in separate chunks. +If you'd prefer to view it as a whole, you can install it as a +third-party package through ERC's devel archive: +@uref{https://emacs-erc.gitlab.io/bugs/archive/erc-view.html}. + +@lisp +;;; erc-view.el -- Automatic view-mode for ERC -*- lexical-binding: t; -*- + +;; Maintainer: The ERC Maintainers +;; Keywords: convenience +;; Version: 0.1 +;; Package-Requires: ((emacs "30.1")) +;; URL: https://gitlab.com/emacs-erc/erc-view + +;;; Commentary: + +;; This is a demo local module for ERC. It arranges for automatically +;; enabling `view-mode' when leaving the prompt area and automatically +;; disabling it when reentering. It also ensures `view-mode' stays +;; enabled or disabled when reconnecting. + +;;; Code: +@end lisp + +You need to import ERC's main library somehow. The easiest way is +directly, via a simple @code{(require 'erc)}, although this module does +so indirectly because it also uses definitions from @file{erc-goodies}: + +@lisp +(require 'erc-goodies) +(require 'view) +@end lisp + +Avoid headaches by aligning the name of your module with its containing +library and Custom group. It's best to have one group and one module +per library. + +@lisp +(defgroup erc-view nil + "Automatically enter and exit `view-mode' in ERC." + :version "0.1" + :group 'erc) + +(defcustom erc-view-enable-when-exiting-prompt t + "Whether to enable `view-mode' when exiting the prompt area." + :type 'boolean) + +(defcustom erc-view-disable-when-entering-prompt t + "Whether to disable `view-mode' when entering the prompt area." + :type 'boolean) + +(defcustom erc-view-backspace-at-prompt-scrolls-down t + "Whether a \\`' at the prompt scrolls down to enter `view-mode'." + :type 'boolean) +@end lisp + +You'll almost always want to define internal variables as buffer-local. + +@lisp +(defvar-local erc-view--enabled-p nil + "Current reconnect-aware activation state of `view-mode'.") +@end lisp + +@noindent +In some cases, you may need a variable's value to survive the +reapplication of ERC's @code{major-mode} performed in each reassociated +buffer upon reconnecting. Do this by leveraging the +@code{permanent-local} symbol property. (@pxref{Creating +Buffer-Local,,,elisp,}.) + +@lisp +(put 'erc-view--enabled-p 'permanent-local t) +@end lisp + +@noindent +There are a few caveats regarding the durability of permanent values. +By convention, disabling a module's minor mode kills local bindings. +Mode commands, like @code{erc-view-mode}, do so in the current buffer +only, while unidirectional ones, like @code{erc-view-mode-disable}, do +so connection-wide. + +There are also occasions in which persistence is undefined, most notably +when @dfn{grafting} an old buffer's contents onto a current buffer. +This occurs in server buffers upon @dfn{logical connection} (at +@samp{MOTD}'s end), when a user reconnects with a new invocation of an +entry-point command, like @code{erc-tls}, instead of via the +auto-reconnect facility or by issuing a @samp{/reconnect} at the prompt. +Unaffected are entry-point invocations that include an @code{:id} +keyword because reassociation happens immediately in such cases, before +ERC even initializes any modules. Grafting can also happen in target +buffers, most often after a user reconnects under a new nick and +conducts business in the same channels and queries as before, only to +renick @emph{back} to the previous nick via a @samp{/nick oldme} or +similar. As of version 5.7, ERC retains the current buffer's permanent +value in all such situations, meaning ERC ignores permanent values from +previous buffers and retains default values assigned during module +initialization. + +Moving on, if your module needs to bind keys, define its keymap +@emph{before} the module itself, and use the standard minor-mode naming +convention of @code{erc-my-module-mode-map}. + +@lisp +(defvar-keymap erc-view-mode-map + :doc "Keymap for `view-mode' in ERC." + " " #'erc-view--enable-on-backspace) + +(defvar-keymap erc-view-mode-overriding-map + :parent view-mode-map + :doc "Overriding keymap for `view-mode' when `erc-view-mode' is active. +Hitting \\`' atop a button prompts for an action by default. Use +\\`C-j' or \\`j' for scrolling up by a line." + "C" nil ; View-kill-and-leave + "E" #'erc-view--exit-to-bottom ; View-exit-and-edit + "Q" nil ; View-quit-all + "k" #'View-scroll-line-backward ; Vim backwards line + "j" #'View-scroll-line-forward ; Vim forwards line + "S-" #'View-scroll-line-backward) +@end lisp + +You'll almost always want to define your module as buffer-local. Do +this by including a @code{localp} flag as the final parameter to +@code{define-erc-module}, after the @dfn{disable body}. If your module +only operates in one kind of buffer, disable it elsewhere in the +@dfn{enable body}. For example, if it should only run in server +buffers, disable it in target buffers by doing something like @code{(if +(erc-target) (erc-my-module-mode -1) (erc-my-module--setup))}. And in +all cases, please remember to mention the module's intended @dfn{scope} +in the doc string. Some informal adjectives that may help with that +are: + +@itemize +@item query-local +@item channel-local +@item target-local (query or channel) +@item server-local +@item session-local (server and target) +@item buffer-local (server or target) +@end itemize + +@noindent +You may also wish to mention this in the Custom group's doc string. + +@lisp +(define-erc-module view nil + "Enable `view-mode' if it was on previously. +This module is buffer-local. If you also use the `scrolltobottom' +module, you probably want to enable the option `erc-scrolltobottom-all'." + ((add-hook 'view-mode-hook #'erc-view--remember 0 t) + (add-hook 'post-command-hook #'erc-view--enforce-prompt-boundary 0 t) + (setf (alist-get 'view-mode minor-mode-overriding-map-alist) + erc-view-mode-overriding-map) + (unless (local-variable-p 'erc-view--enabled-p) + (setq-local erc-view--enabled-p nil)) + (view-mode (if erc-view--enabled-p +1 -1))) + ((kill-local-variable 'erc-view--enabled-p) + (remove-hook 'post-command-hook #'erc-view--enforce-prompt-boundary t) + (remove-hook 'view-mode-hook #'erc-view--remember t) + (setf (alist-get 'view-mode minor-mode-overriding-map-alist nil 'remove) + nil)) + localp) +@end lisp + +Always define your module early, before any code that refers to its mode +command or minor-mode variable. + +@lisp +(defun erc-view--enable-on-backspace (lines) + "Enable `view-mode' at the prompt by hitting \\`'." + (interactive "P") + (if (and erc-view-backspace-at-prompt-scrolls-down (not view-mode) + (= (point) erc-input-marker)) + (progn + (view-mode +1) + (View-scroll-page-backward lines)) + (call-interactively #'delete-backward-char))) + +(defun erc-view--enforce-prompt-boundary () + "Enable or disable `view-mode' when crossing prompt boundary." + (when-let* + ((new (if (>= (point) erc-input-marker) + (and view-mode erc-view-disable-when-entering-prompt -1) + (and (not view-mode) erc-view-enable-when-exiting-prompt +1)))) + (run-at-time 0 nil (lambda (buffer new) + (with-current-buffer buffer (view-mode new))) + (current-buffer) new))) + +(defun erc-view--exit-to-bottom () + "Scroll to prompt, exit `view-mode', and move to EOB." + (interactive) + (let (view-no-disable-on-exit) + (View-scroll-to-buffer-end) + (View-exit) + (goto-char (point-max)))) + +(defun erc-view--remember () + "Remember the value of `view-mode'. +Disable `erc-move-to-prompt-setup' locally when `view-mode' is enabled." + (cl-assert (local-variable-p 'erc-view--enabled-p)) + (setq erc-view--enabled-p view-mode) + (when erc-move-to-prompt-mode + (if view-mode + (remove-hook 'pre-command-hook #'erc-move-to-prompt t) + (erc-move-to-prompt-setup)))) +@end lisp + +Don't forget to @code{provide} your module so that +@code{erc-update-modules} can find it. + +@lisp +(provide 'erc-view) + +;;; erc-view.el ends here + +@end lisp +Mimicking the above should just about cover most use cases. If your +module isn't loading correctly, it's likely a naming, layout, or +packaging issue. If you @emph{must} defy the convention recommended +earlier regarding a library-group-module correspondence or if you've +designed your module mainly to be toggled interactively rather than +added to @code{erc-modules}, try placing a line like the following above +the module's definition. + +@lisp +;;;###autoload(autoload 'erc-my-module-mode "erc-my-module" nil t) +@end lisp + +@noindent +Just remember, doing so means you'll need to (re)generate the autoload +file when hacking locally (@pxref{Fetching Package Sources,,, emacs,}). + + @c PRE5_4: Document every option of every module in its own subnode -- 2.48.1 --=-=-=--