Improve source NUH handling in ERC

* lisp/erc/erc.el (erc--user-nuh-message-types): New variable.
(erc--shuffle-nuh-nickward, erc--interpret-nuh): Replace former with
latter, whose behavior is easier to predict.
* test/lisp/erc/erc-tests.el (erc--interpret-nuh): New test.
This commit is contained in:
F. Jason Park 2025-11-28 16:21:57 -08:00
parent aa31628584
commit 76f5181bc6
2 changed files with 55 additions and 5 deletions

View file

@ -7991,11 +7991,28 @@ See associated unit test for precise behavior."
(match-string 2 string)
(match-string 3 string))))
(defun erc--shuffle-nuh-nickward (nick login host)
"Interpret results of `erc--parse-nuh', promoting loners to nicks."
(cond (nick (cl-assert (null login)) (list nick login host))
((and (null login) host) (list host nil nil))
((and login (null host)) (list login nil nil))))
(defvar erc--user-nuh-message-types
'(PRIVMSG JOIN PART QUIT NICK KICK TOPIC AWAY ACCOUNT TAGMSG))
(defun erc--interpret-nuh (nuh &optional cmd noerrorp)
"Return new NUH triple with non-nil nickname or host component, or signal.
If CMD is null or appears in `erc--user-nuh-message-types', promote a
lone host to a lone nick. With NOERRORP, return a copy of NUH instead
of signaling."
(pcase-let ((`(,nick ,login ,host) nuh))
(cond (nick (list nick login host))
((and (null login) host)
(if (or (null cmd) (memq cmd erc--user-nuh-message-types))
(list host nil nil)
(list nil nil host)))
((and login
(let ((types (or (erc--get-isupport-entry 'CHANTYPES 'single)
erc--fallback-channel-prefixes)))
(not (seq-some (lambda (c) (seq-contains-p types c #'eq))
login))))
(list login nil host))
(noerrorp (list nick login host))
(t (error "Failed to interpret: %s" nuh)))))
(defun erc-extract-nick (string)
"Return the nick corresponding to a user specification STRING.

View file

@ -674,6 +674,39 @@
;; No fallback behavior.
(should-not (erc--parse-nuh "abc\nde!fg@xy")))
;; NUH interpretation rules:
;;
;; 1. "a@b" or "a!b" - "a" is the nick and "b" is the host. Can't have
;; a login without a nick and a host.
;;
;; 2. "a" - either a nick or a host, depending on message type. The
;; presence of a "." does not imply a host because some IRC-adjacent
;; bridges allow nicks to contain dots, and a host can be a host
;; name, like "localhost" without a domain structure. Nick-only
;; types include PRIVMSG, JOIN, PART, QUIT, NICK, KICK, TOPIC, AWAY,
;; ACCOUNT, and TAGMSG. MODE can be either but is usually a nick
;; unless recovering from a netsplit or as a response to a ChanServ
;; OP. NOTICE can be either but is always a nick when directed to a
;; channel.
;;
;; 3. "a!", "a!@", "a@", "!a@", "@a", etc. are pathological.
;;
(ert-deftest erc--interpret-nuh ()
(should (equal (erc--interpret-nuh (erc--parse-nuh "a@b"))
'("a" nil "b")))
(should (equal (erc--interpret-nuh (erc--parse-nuh "a!b"))
'("a" nil "b")))
(should (equal (erc--interpret-nuh (erc--parse-nuh "B..o..b"))
'("B..o..b" nil nil)))
(should (equal (erc--interpret-nuh (erc--parse-nuh "gnu.org"))
'("gnu.org" nil nil)))
(should (equal (erc--interpret-nuh (erc--parse-nuh "localhost"))
'("localhost" nil nil)))
;; Reject login containing CHANTYPE chars.
(should (equal (erc--parse-nuh "a&b@c") '(nil "a&b" "c")))
(should-error (erc--interpret-nuh '(nil "a&b" "c"))))
(ert-deftest erc--parsed-prefix ()
;; Effectively a no-op in a non-ERC buffer.
(should-not (erc--parsed-prefix))