1 ;;; nnheader.el --- header access macros for Gnus and its backends
2 ;; Copyright (C) 1987,88,89,90,93,94,95,96 Free Software Foundation, Inc.
4 ;; Author: Masanobu UMEDA <umerin@flab.flab.fujitsu.junet>
5 ;; Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
8 ;; This file is part of GNU Emacs.
10 ;; GNU Emacs is free software; you can redistribute it and/or modify
11 ;; it under the terms of the GNU General Public License as published by
12 ;; the Free Software Foundation; either version 2, or (at your option)
15 ;; GNU Emacs is distributed in the hope that it will be useful,
16 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
17 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 ;; GNU General Public License for more details.
20 ;; You should have received a copy of the GNU General Public License
21 ;; along with GNU Emacs; see the file COPYING. If not, write to the
22 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
23 ;; Boston, MA 02111-1307, USA.
27 ;; These macros may look very much like the ones in GNUS 4.1. They
28 ;; are, in a way, but you should note that the indices they use have
29 ;; been changed from the internal GNUS format to the NOV format. The
30 ;; makes it possible to read headers from XOVER much faster.
32 ;; The format of a header is now:
33 ;; [number subject from date id references chars lines xref]
35 ;; (That last entry is defined as "misc" in the NOV format, but Gnus
36 ;; uses it for xrefs.)
43 (eval-when-compile (require 'cl))
45 (defvar nnheader-max-head-length 4096
46 "*Max length of the head of articles.")
48 (defvar nnheader-file-name-translation-alist nil
49 "*Alist that says how to translate characters in file names.
50 For instance, if \":\" is illegal as a file character in file names
51 on your system, you could say something like:
53 \(setq nnheader-file-name-translation-alist '((?: . ?_)))")
55 ;;; Header access macros.
57 (defmacro mail-header-number (header)
58 "Return article number in HEADER."
61 (defmacro mail-header-set-number (header number)
62 "Set article number of HEADER to NUMBER."
63 `(aset ,header 0 ,number))
65 (defmacro mail-header-subject (header)
66 "Return subject string in HEADER."
69 (defmacro mail-header-set-subject (header subject)
70 "Set article subject of HEADER to SUBJECT."
71 `(aset ,header 1 ,subject))
73 (defmacro mail-header-from (header)
74 "Return author string in HEADER."
77 (defmacro mail-header-set-from (header from)
78 "Set article author of HEADER to FROM."
79 `(aset ,header 2 ,from))
81 (defmacro mail-header-date (header)
82 "Return date in HEADER."
85 (defmacro mail-header-set-date (header date)
86 "Set article date of HEADER to DATE."
87 `(aset ,header 3 ,date))
89 (defalias 'mail-header-message-id 'mail-header-id)
90 (defmacro mail-header-id (header)
91 "Return Id in HEADER."
94 (defalias 'mail-header-set-message-id 'mail-header-set-id)
95 (defmacro mail-header-set-id (header id)
96 "Set article Id of HEADER to ID."
97 `(aset ,header 4 ,id))
99 (defmacro mail-header-references (header)
100 "Return references in HEADER."
103 (defmacro mail-header-set-references (header ref)
104 "Set article references of HEADER to REF."
105 `(aset ,header 5 ,ref))
107 (defmacro mail-header-chars (header)
108 "Return number of chars of article in HEADER."
111 (defmacro mail-header-set-chars (header chars)
112 "Set number of chars in article of HEADER to CHARS."
113 `(aset ,header 6 ,chars))
115 (defmacro mail-header-lines (header)
116 "Return lines in HEADER."
119 (defmacro mail-header-set-lines (header lines)
120 "Set article lines of HEADER to LINES."
121 `(aset ,header 7 ,lines))
123 (defmacro mail-header-xref (header)
124 "Return xref string in HEADER."
127 (defmacro mail-header-set-xref (header xref)
128 "Set article xref of HEADER to xref."
129 `(aset ,header 8 ,xref))
131 (defun make-mail-header (&optional init)
132 "Create a new mail header structure initialized with INIT."
133 (make-vector 9 init))
135 ;; Various cruft the backends and Gnus need to communicate.
137 (defvar nntp-server-buffer nil)
138 (defvar gnus-verbose-backends 7
139 "*A number that says how talkative the Gnus backends should be.")
140 (defvar gnus-nov-is-evil nil
141 "If non-nil, Gnus backends will never output headers in the NOV format.")
142 (defvar news-reply-yank-from nil)
143 (defvar news-reply-yank-message-id nil)
145 (defvar nnheader-callback-function nil)
147 (defun nnheader-init-server-buffer ()
148 "Initialize the Gnus-backend communication buffer."
150 (setq nntp-server-buffer (get-buffer-create " *nntpd*"))
151 (set-buffer nntp-server-buffer)
152 (buffer-disable-undo (current-buffer))
154 (kill-all-local-variables)
155 (setq case-fold-search t) ;Should ignore case.
158 ;;; Virtual server functions.
160 (defun nnheader-set-init-variables (server defs)
163 ;; First we set the server variables in the sequence required. We
164 ;; use the definitions from the `defs' list where that is
168 (if (setq val (assq (caar s) defs))
172 ;; The we go through the defs list and set any variables that were
173 ;; not set in the first sweep.
175 (if (not (assq (caar defs) server))
177 (if (and (symbolp (nth 1 (car defs)))
178 (not (boundp (nth 1 (car defs)))))
180 (eval (nth 1 (car defs))))))
181 (setq defs (cdr defs)))))
183 (defun nnheader-save-variables (server)
186 (push (list (caar server) (symbol-value (caar server))) out)
187 (setq server (cdr server)))
190 (defun nnheader-restore-variables (state)
192 (set (caar state) (nth 1 (car state)))
193 (setq state (cdr state))))
195 (defun nnheader-change-server (backend server defs)
196 (nnheader-init-server-buffer)
197 (let ((current-server (intern (format "%s-current-server" backend)))
198 (alist (intern (format "%s-server-alist" backend)))
199 (variables (intern (format "%s-server-variables" backend))))
201 (when (and (symbol-value current-server)
202 (not (equal server (symbol-value current-server))))
204 (cons (list (symbol-value current-server)
205 (nnheader-save-variables (symbol-value variables)))
206 (symbol-value alist))))
207 (let ((state (assoc server (symbol-value alist))))
209 (nnheader-set-init-variables (symbol-value variables) defs)
210 (nnheader-restore-variables (nth 1 state))
211 (set alist (delq state (symbol-value alist)))))
212 (set current-server server)
215 ;;; Various functions the backends use.
217 (defun nnheader-insert-head (file)
218 "Insert the head of the article."
219 (if (eq nnheader-max-head-length t)
220 ;; Just read the entire file.
221 (insert-file-contents-literally file)
222 ;; Read 1K blocks until we find a separator.
225 (while (and (eq chop (nth 1 (insert-file-contents-literally
226 file nil beg (incf beg chop))))
227 (prog1 (not (search-forward "\n\n" nil t))
228 (goto-char (point-max)))
229 (or (null nnheader-max-head-length)
230 (< beg nnheader-max-head-length)))))))
232 (defun nnheader-article-p ()
233 "Say whether the current buffer looks like an article."
234 (goto-char (point-min))
235 (if (not (search-forward "\n\n" nil t))
237 (narrow-to-region (point-min) (1- (point)))
238 (goto-char (point-min))
239 (while (looking-at "[A-Z][^ \t]+:.*\n\\([ \t].*\n\\)*\\|From .*\n")
240 (goto-char (match-end 0)))
245 (defun nnheader-insert-references (references message-id)
246 "Insert a References header based on REFERENCES and MESSAGE-ID."
247 (if (and (not references) (not message-id))
248 () ; This is illegal, but not all articles have Message-IDs.
249 (mail-position-on-field "References")
250 (let ((begin (save-excursion (beginning-of-line) (point)))
253 (if references (insert references))
254 (if (and references message-id) (insert " "))
255 (if message-id (insert message-id))
256 ;; Fold long References lines to conform to RFC1036 (sort of).
257 ;; The region must end with a newline to fill the region
258 ;; without inserting extra newline.
259 (fill-region-as-paragraph begin (1+ (point))))))
261 (defun nnheader-remove-header (header &optional is-regexp first)
263 If FIRST, only remove the first instance if the header.
264 Return the number of headers removed."
265 (goto-char (point-min))
266 (let ((regexp (if is-regexp header (concat "^" header ":")))
270 (while (and (re-search-forward regexp nil t)
277 ;; There might be a continuation header, so we have to search
278 ;; until we find a new non-continuation line.
279 (if (re-search-forward "^[^ \t]" nil t)
284 (defun nnheader-replace-header (header new-value)
285 "Remove HEADER and insert the NEW-VALUE."
288 (nnheader-narrow-to-headers)
290 (nnheader-remove-header header)
291 (goto-char (point-max))
292 (insert header ": " new-value "\n")))))
294 (defun nnheader-narrow-to-headers ()
295 "Narrow to the head of an article."
298 (goto-char (point-min))
299 (if (search-forward "\n\n" nil t)
302 (goto-char (point-min)))
304 (defun nnheader-set-temp-buffer (name)
305 "Set-buffer to an empty (possibly new) buffer called NAME with undo disabled."
306 (set-buffer (get-buffer-create name))
307 (buffer-disable-undo (current-buffer))
311 (defmacro nnheader-temp-write (file &rest forms)
312 "Create a new buffer, evaluate FORM there, and write the buffer to FILE."
314 (let ((nnheader-temp-file ,file)
315 (nnheader-temp-cur-buffer
316 (nnheader-set-temp-buffer
317 (generate-new-buffer-name " *nnheader temp*"))))
318 (when (and nnheader-temp-file
319 (not (file-directory-p (file-name-directory
320 nnheader-temp-file))))
321 (make-directory (file-name-directory nnheader-temp-file) t))
326 (when nnheader-temp-file
327 (set-buffer nnheader-temp-cur-buffer)
328 (write-region (point-min) (point-max)
329 nnheader-temp-file nil 'nomesg)))
330 (when (buffer-name nnheader-temp-cur-buffer)
331 (kill-buffer nnheader-temp-cur-buffer))))))
333 (put 'nnheader-temp-write 'lisp-indent-function 1)
334 (put 'nnheader-temp-write 'lisp-indent-hook 1)
335 (put 'nnheader-temp-write 'edebug-form-spec '(file &rest form))
337 (defvar jka-compr-compression-info-list)
338 (defvar nnheader-numerical-files
339 (if (boundp 'jka-compr-compression-info-list)
340 (concat "\\([0-9]+\\)\\("
341 (mapconcat (lambda (i) (aref i 0))
342 jka-compr-compression-info-list "\\|")
345 "Regexp that match numerical files.")
347 (defvar nnheader-numerical-short-files (concat "^" nnheader-numerical-files)
348 "Regexp that matches numerical file names.")
350 (defvar nnheader-numerical-full-files (concat "/" nnheader-numerical-files)
351 "Regexp that matches numerical full file paths.")
353 (defun nnheader-file-to-number (file)
354 "Take a file name and return the article number."
355 (if (not (boundp 'jka-compr-compression-info-list))
357 (string-match nnheader-numerical-short-files file)
358 (string-to-int (match-string 0 file))))
360 (defun nnheader-directory-articles (dir)
361 "Return a list of all article files in a directory."
362 (mapcar 'nnheader-file-to-number
363 (directory-files dir nil nnheader-numerical-short-files t)))
365 (defun nnheader-article-to-file-alist (dir)
366 "Return an alist of article/file pairs in DIR."
367 (mapcar (lambda (file) (cons (nnheader-file-to-number file) file))
368 (directory-files dir nil nnheader-numerical-short-files t)))
370 (defun nnheader-fold-continuation-lines ()
371 "Fold continuation lines in the current buffer."
372 (goto-char (point-min))
373 (while (re-search-forward "\\(\r?\n[ \t]+\\)+" nil t)
374 (replace-match " " t t)))
376 (defun nnheader-translate-file-chars (file)
377 (if (null nnheader-file-name-translation-alist)
378 ;; No translation is necessary.
380 ;; We translate -- but only the file name. We leave the directory
382 (let* ((new (file-name-nondirectory file))
387 (when (setq trans (cdr (assq (aref new i)
388 nnheader-file-name-translation-alist)))
391 (concat (file-name-directory file) new))))
393 (defun nnheader-report (backend &rest args)
394 "Report an error from the BACKEND.
395 The first string in ARGS can be a format string."
396 (set (intern (format "%s-status-string" backend))
397 (if (< (length args) 2)
399 (apply 'format args)))
402 (defun nnheader-get-report (backend)
403 (message "%s" (symbol-value (intern (format "%s-status-string" backend)))))
405 (defun nnheader-insert (format &rest args)
406 "Clear the communicaton buffer and insert FORMAT and ARGS into the buffer.
407 If FORMAT isn't a format string, it and all ARGS will be inserted
410 (set-buffer nntp-server-buffer)
412 (if (string-match "%" format)
413 (insert (apply 'format format args))
414 (apply 'insert format args))
417 (defun nnheader-mail-file-mbox-p (file)
418 "Say whether FILE looks like an Unix mbox file."
419 (when (and (file-exists-p file)
420 (file-readable-p file)
421 (file-regular-p file))
423 (nnheader-set-temp-buffer " *mail-file-mbox-p*")
424 (insert-file-contents-literally file)
425 (goto-char (point-min))
427 (looking-at rmail-unix-mail-delimiter)
428 (kill-buffer (current-buffer))))))
430 (defun nnheader-replace-chars-in-string (string from to)
431 "Replace characters in STRING from FROM to TO."
432 (let ((string (substring string 0)) ;Copy string.
433 (len (length string))
435 ;; Replace all occurrences of FROM with TO.
437 (if (= (aref string idx) from)
438 (aset string idx to))
442 (defun nnheader-file-to-group (file &optional top)
443 "Return a group name based on FILE and TOP."
444 (nnheader-replace-chars-in-string
448 (substring (expand-file-name file)
451 (file-name-as-directory top))))
455 (defun nnheader-message (level &rest args)
456 "Message if the Gnus backends are talkative."
457 (if (or (not (numberp gnus-verbose-backends))
458 (<= level gnus-verbose-backends))
459 (apply 'message args)
460 (apply 'format args)))
462 (defun nnheader-be-verbose (level)
463 "Return whether the backends should be verbose on LEVEL."
464 (or (not (numberp gnus-verbose-backends))
465 (<= level gnus-verbose-backends)))
467 (defun nnheader-group-pathname (group dir &optional file)
468 "Make pathname for GROUP."
470 (let ((dir (file-name-as-directory (expand-file-name dir))))
471 ;; If this directory exists, we use it directly.
472 (if (file-directory-p (concat dir group))
473 (concat dir group "/")
474 ;; If not, we translate dots into slashes.
475 (concat dir (nnheader-replace-chars-in-string group ?. ?/) "/")))
476 (cond ((null file) "")
477 ((numberp file) (int-to-string file))
480 (defun nnheader-functionp (form)
481 "Return non-nil if FORM is funcallable."
482 (or (and (symbolp form) (fboundp form))
483 (and (listp form) (eq (car form) 'lambda))))
485 (fset 'nnheader-find-file-noselect 'find-file-noselect)
489 ;;; nnheader.el ends here