|
From: Lawrence M. <we...@gm...> - 2004-05-07 21:37:57
|
Lets face it, ERC's backend server handling, and the way it
deals with displaying messages (both client- and server-side) is
a bit of a mess. In an attempt, therefore, to procrastinate
actual real work that I have to do, I thought I'd see if I could
try and tidy things up a bit.
This is very much a work-in-progress, so I'm soliciting ideas as
I'm not really sure all of mine are good.
Basically, I'm starting at the bottom and trying to clean things
up as I go. This includes, where appropriate, defining
(hopefully) useful abstractions, as well as just cleaning code.
Here is what I have so far, the main change is the new macro
DEFERC-RESPONSE-HANDLER. This defines hook and function
handlers for numeric (and non-numeric) server responses. It
defines aliases, if we have a numeric and non-numeric name (or
use the same handler for more than one response), and pushes the
response (as a string) into a hashtable.
An example usage, for a MOTD response (which is the default for
a bunch of numeric replies):
(deferc-response-handler (INVITE)
"Handle invitation messages.
PARSED has the form [\"INVITE\" \"who\" \"whom\" \"where-to\"]."
nil
(let* ((target (erc-response-target parsed))
(chnl (erc-response-channel parsed))
(sndr (erc-parse-user (erc-response-sender parsed)))
(nick (nth 0 sndr))
(login (nth 1 sndr))
(host (nth 2 sndr)))
(setq invitation chnl)
(when (string= target (erc-current-nick))
(erc-display-message
parsed 'notice 'active
'INVITE ?n nick ?u login ?h host ?c chnl))))
ERC-CALL-HOOKS is then simplified a great deal, rather than
looking to see if a response has an associated hook, we just do
(gethash RESPONSE erc-server-responses). Falling back, as
before, on ERC-DEFAULT-SERVER-HOOK.
Further changes I've made so far, cleaning up of
`erc-parse-line-from-server', and actually documenting what all
the bits are :).
A few queries.
o Should I endeavour to be backwards compatible, or just make
this, should it actually reach completion, be a major version
upgrade, complete with masses of incompatibilites?
o Is this actually worth doing? It probably /should/ be done,
is it worth the effort? :)
Anyway, that's enough comments, on with the code:
;;; erc-backend.el --- Backend network handlers and communication for ERC
;; Copyright (C) 2004 Lawrence Mitchell <we...@gm...>
;; Filename: erc-backend.el
;; Version: 1
;; Author: Lawrence Mitchell <we...@gm...>
;; Created: 2004-05-7
;; Keywords: IRC chat client internet
;;; Commentary:
;;
;;; History:
;;
;;; Code:
(require 'erc)
(defun erc-parse-server-response (process response)
"Parse a server PROCESS's IRC RESPONSE."
(unless (string= response "") ; don't care about empty strings.
(save-match-data
(let ((message (make-vector 32 nil))
(contents nil)
(n 2))
;; Everything after the second colon is the contents of the
;; message.
(and (string-match ":[^:]*:\\(.*\\)" response)
(setq contents (match-string 1 response))
(setq response (replace-match "" nil t response 1)))
;; If the message starts with a colon, then everything up to
;; the first space is the sender of the message (be it nick
;; or server). If it doesn't, just use the current server.
(if (string-match "^:\\([^ ]*\\) " response)
(progn
(aset message 1 (match-string 1 response))
(setq response (replace-match "" nil t response)))
(aset message 1 erc-session-server))
;; The next part of the message, up to the space, is the
;; command. (e.g. PRIVMSG).
(when (string-match "\\([^ ]+\\) " response)
(aset message 0 (match-string 1 response))
(setq response (replace-match "" nil t response)))
;; Everything up to the next colon are arguments to the
;; command.
(while (not (string-match "^:" response))
(when (string-match "\\([^ ]+\\) " response)
(aset message n (match-string 1 response))
(setq response (replace-match "" nil t response)))
(incf n))
(when contents
;; add the contents to the result
(aset message n contents))
(erc-handle-parsed-server-response process message)))))
(defsubst erc-response-command (response)
"Return the command in RESPONSE."
(aref response 0))
(defsubst erc-response-target (response)
"Return the target in RESPONSE."
(aref response 2))
(defsubst erc-response-sender (response)
"Return the sender of RESPONSE."
(aref response 1))
(defsubst erc-response-channel (response)
"Return the channel in RESPONSE."
(aref response 3))
(defun erc-handle-parsed-server-response (process parsed-response)
"Handle a pre-parsed PARSED-RESPONSE from PROCESS.
Hands off to helper functions via `erc-call-hooks'."
(if (member (erc-response-command parsed-response) erc-prevent-duplicates)
(let ((m (mapconcat 'identity parsed-response "")))
;; duplicate supression
(if (< (or (gethash m erc-duplicates) 0)
(- (erc-current-time erc-duplicate-timeout)))
(erc-call-hooks process parsed-response))
(puthash m (erc-current-time) erc-duplicates))
;; Hand off to the relevant handler.
(erc-call-hooks process parsed-response)))
(defun erc-call-hooks (process message)
"Call hooks associated with MESSAGE in PROCESS.
Finds hooks by looking in the `erc-server-responses' hashtable."
(let ((hook (gethash (erc-response-command message)
erc-server-responses
'erc-default-server-functions)))
(run-hook-with-args-until-success hook process message)
(with-current-buffer (erc-server-buffer)
(run-hook-with-args 'erc-timer-hook (erc-current-time)))))
(defvar erc-server-responses (make-hash-table :test #'equal)
"Hashtable mapping server responses to their handler hooks.")
(defmacro* deferc-response-handler ((name &rest aliases)
&optional extra-fn-doc extra-var-doc
&rest fn-body)
"Define an ERC handler hook/function pair.
NAME is the reponse name as sent by the server (see the IRC RFC for
meanings).
This creates:
o a hook variable `erc-server-NAME-functions' initialised to 'erc-server-NAME.
o a function `erc-server-NAME' with body FN-BODY.
If ALIASES is non-nil, each alias in ALIASES is `defalias'ed to
`erc-server-NAME', and `defvaralias'ed to `erc-server-NAME-functions'.
FN-BODY is the body of `erc-server-NAME' it may refer to the two
function arguments PROC and PARSED.
If EXTRA-FN-DOC is non-nil, it is inserted at the beginning of the
defined function's docstring.
If EXTRA-VAR-DOC is non-nil, it is inserted at the beginning of the
defined variable's docstring.
As an example:
(deferc-response-handler (311 WHOIS WI) nil nil
(do-stuff-with-whois proc parsed))
Would expand to:
(prog2
(defvar erc-server-311-functions nil
\"Hook called upon receiving a 311 server response.
Each function is called with two arguments, the process associated
with the response and the parsed response.
See also `erc-server-311'.\")
(defun erc-server-311 (proc parsed)
\"Handler for a 311 server response.
PROC is the server process which returned the response.
PARSED is the actual response.
If you want to add responses don't modify this function, but rather
add things to `erc-server-311-functions' instead.\"
(do-stuff))
(puthash \"311\" 'erc-server-311-functions erc-server-responses)
(puthash \"WHOIS\" 'erc-server-311-functions erc-server-responses)
(puthash \"WI\" 'erc-server-311-functions erc-server-responses)
(defalias 'erc-server-WHOIS 'erc-server-311)
(defvaralias 'erc-server-WHOIS-functions 'erc-server-311-functions))
(defalias 'erc-server-WI 'erc-server-311)
(defvaralias 'erc-server-WI-functions 'erc-server-311-functions)))
\(fn (NAME &rest ALIASES) &optional EXTRA-FN-DOC EXTRA-VAR-DOC &rest FN-BODY)"
(if (numberp name) (setq name (format "%03i" name)))
(setq aliases (mapcar (lambda (a)
(if (numberp a)
(format "%03i" a)
a))
aliases))
(let* ((hook-name (intern (format "erc-server-%s-functions" name)))
(fn-name (intern (format "erc-server-%s" name)))
(hook-doc (format "%sHook called upon receiving a %s server response.
Each function is called with two arguments, the process associated
with the response and the parsed response.
See also `%s'."
(if extra-var-doc
(concat extra-var-doc "\n")
"")
name fn-name))
(fn-doc (format "%sHandler for a %s server response.
PROC is the server process which returned the response.
PARSED is the actual response as a vector.
If you want to add responses don't modify this function, but rather
add things to `%s' instead."
(if extra-fn-doc
(concat extra-fn-doc "\n")
"")
name hook-name))
(alternates
(when aliases
(loop for alias in aliases
collect (list
(intern (format "erc-server-%s" alias))
(intern (format "erc-server-%s-functions"
alias)))))))
`(prog2
(defvar ,hook-name ',fn-name ,hook-doc)
(defun ,fn-name (proc parsed)
,fn-doc
,@fn-body)
,@(loop for a in (cons name aliases)
collect `(puthash ,(format "%s" a) ',hook-name
erc-server-responses))
,@(loop for (f v) in alternates
nconc (list `(defalias ',f ',fn-name)
`(defvaralias ',v ',hook-name))))))
(provide 'erc-backend)
;;; erc-backend.el ends here
|