]> code.delx.au - gnu-emacs/blob - lisp/gnus/nnheader.el
2000-09-24 Simon Josefsson <simon@josefsson.org>
[gnu-emacs] / lisp / gnus / nnheader.el
1 ;;; nnheader.el --- header access macros for Gnus and its backends
2
3 ;; Copyright (C) 1987, 1988, 1989, 1990, 1993, 1994, 1995, 1996,
4 ;; 1997, 1998, 2000
5 ;; Free Software Foundation, Inc.
6
7 ;; Author: Masanobu UMEDA <umerin@flab.flab.fujitsu.junet>
8 ;; Lars Magne Ingebrigtsen <larsi@gnus.org>
9 ;; Maintainer: bugs@gnus.org
10 ;; Keywords: news
11
12 ;; This file is part of GNU Emacs.
13
14 ;; GNU Emacs is free software; you can redistribute it and/or modify
15 ;; it under the terms of the GNU General Public License as published by
16 ;; the Free Software Foundation; either version 2, or (at your option)
17 ;; any later version.
18
19 ;; GNU Emacs is distributed in the hope that it will be useful,
20 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
21 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 ;; GNU General Public License for more details.
23
24 ;; You should have received a copy of the GNU General Public License
25 ;; along with GNU Emacs; see the file COPYING. If not, write to the
26 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
27 ;; Boston, MA 02111-1307, USA.
28
29 ;;; Commentary:
30
31 ;;; Code:
32
33 (eval-when-compile (require 'cl))
34
35 (require 'mail-utils)
36 (require 'mm-util)
37
38 (defvar nnheader-max-head-length 4096
39 "*Max length of the head of articles.")
40
41 (defvar nnheader-head-chop-length 2048
42 "*Length of each read operation when trying to fetch HEAD headers.")
43
44 (defvar nnheader-file-name-translation-alist nil
45 "*Alist that says how to translate characters in file names.
46 For instance, if \":\" is invalid as a file character in file names
47 on your system, you could say something like:
48
49 \(setq nnheader-file-name-translation-alist '((?: . ?_)))")
50
51 (eval-and-compile
52 (autoload 'nnmail-message-id "nnmail")
53 (autoload 'mail-position-on-field "sendmail")
54 (autoload 'message-remove-header "message")
55 (autoload 'gnus-point-at-eol "gnus-util")
56 (autoload 'gnus-delete-line "gnus-util")
57 (autoload 'gnus-buffer-live-p "gnus-util"))
58
59 ;;; Header access macros.
60
61 ;; These macros may look very much like the ones in GNUS 4.1. They
62 ;; are, in a way, but you should note that the indices they use have
63 ;; been changed from the internal GNUS format to the NOV format. The
64 ;; makes it possible to read headers from XOVER much faster.
65 ;;
66 ;; The format of a header is now:
67 ;; [number subject from date id references chars lines xref extra]
68 ;;
69 ;; (That next-to-last entry is defined as "misc" in the NOV format,
70 ;; but Gnus uses it for xrefs.)
71
72 (defmacro mail-header-number (header)
73 "Return article number in HEADER."
74 `(aref ,header 0))
75
76 (defmacro mail-header-set-number (header number)
77 "Set article number of HEADER to NUMBER."
78 `(aset ,header 0 ,number))
79
80 (defmacro mail-header-subject (header)
81 "Return subject string in HEADER."
82 `(aref ,header 1))
83
84 (defmacro mail-header-set-subject (header subject)
85 "Set article subject of HEADER to SUBJECT."
86 `(aset ,header 1 ,subject))
87
88 (defmacro mail-header-from (header)
89 "Return author string in HEADER."
90 `(aref ,header 2))
91
92 (defmacro mail-header-set-from (header from)
93 "Set article author of HEADER to FROM."
94 `(aset ,header 2 ,from))
95
96 (defmacro mail-header-date (header)
97 "Return date in HEADER."
98 `(aref ,header 3))
99
100 (defmacro mail-header-set-date (header date)
101 "Set article date of HEADER to DATE."
102 `(aset ,header 3 ,date))
103
104 (defalias 'mail-header-message-id 'mail-header-id)
105 (defmacro mail-header-id (header)
106 "Return Id in HEADER."
107 `(aref ,header 4))
108
109 (defalias 'mail-header-set-message-id 'mail-header-set-id)
110 (defmacro mail-header-set-id (header id)
111 "Set article Id of HEADER to ID."
112 `(aset ,header 4 ,id))
113
114 (defmacro mail-header-references (header)
115 "Return references in HEADER."
116 `(aref ,header 5))
117
118 (defmacro mail-header-set-references (header ref)
119 "Set article references of HEADER to REF."
120 `(aset ,header 5 ,ref))
121
122 (defmacro mail-header-chars (header)
123 "Return number of chars of article in HEADER."
124 `(aref ,header 6))
125
126 (defmacro mail-header-set-chars (header chars)
127 "Set number of chars in article of HEADER to CHARS."
128 `(aset ,header 6 ,chars))
129
130 (defmacro mail-header-lines (header)
131 "Return lines in HEADER."
132 `(aref ,header 7))
133
134 (defmacro mail-header-set-lines (header lines)
135 "Set article lines of HEADER to LINES."
136 `(aset ,header 7 ,lines))
137
138 (defmacro mail-header-xref (header)
139 "Return xref string in HEADER."
140 `(aref ,header 8))
141
142 (defmacro mail-header-set-xref (header xref)
143 "Set article XREF of HEADER to xref."
144 `(aset ,header 8 ,xref))
145
146 (defmacro mail-header-extra (header)
147 "Return the extra headers in HEADER."
148 `(aref ,header 9))
149
150 (defmacro mail-header-set-extra (header extra)
151 "Set the extra headers in HEADER to EXTRA."
152 `(aset ,header 9 ',extra))
153
154 (defsubst make-mail-header (&optional init)
155 "Create a new mail header structure initialized with INIT."
156 (make-vector 10 init))
157
158 (defsubst make-full-mail-header (&optional number subject from date id
159 references chars lines xref
160 extra)
161 "Create a new mail header structure initialized with the parameters given."
162 (vector number subject from date id references chars lines xref extra))
163
164 ;; fake message-ids: generation and detection
165
166 (defvar nnheader-fake-message-id 1)
167
168 (defsubst nnheader-generate-fake-message-id ()
169 (concat "fake+none+" (int-to-string (incf nnheader-fake-message-id))))
170
171 (defsubst nnheader-fake-message-id-p (id)
172 (save-match-data ; regular message-id's are <.*>
173 (string-match "\\`fake\\+none\\+[0-9]+\\'" id)))
174
175 ;; Parsing headers and NOV lines.
176
177 (defsubst nnheader-header-value ()
178 (buffer-substring (match-end 0) (gnus-point-at-eol)))
179
180 (defun nnheader-parse-head (&optional naked)
181 (let ((case-fold-search t)
182 (cur (current-buffer))
183 (buffer-read-only nil)
184 in-reply-to lines p ref)
185 (goto-char (point-min))
186 (when naked
187 (insert "\n"))
188 ;; Search to the beginning of the next header. Error messages
189 ;; do not begin with 2 or 3.
190 (prog1
191 (when (or naked (re-search-forward "^[23][0-9]+ " nil t))
192 ;; This implementation of this function, with nine
193 ;; search-forwards instead of the one re-search-forward and
194 ;; a case (which basically was the old function) is actually
195 ;; about twice as fast, even though it looks messier. You
196 ;; can't have everything, I guess. Speed and elegance
197 ;; don't always go hand in hand.
198 (vector
199 ;; Number.
200 (if naked
201 (progn
202 (setq p (point-min))
203 0)
204 (prog1
205 (read cur)
206 (end-of-line)
207 (setq p (point))
208 (narrow-to-region (point)
209 (or (and (search-forward "\n.\n" nil t)
210 (- (point) 2))
211 (point)))))
212 ;; Subject.
213 (progn
214 (goto-char p)
215 (if (search-forward "\nsubject: " nil t)
216 (nnheader-header-value) "(none)"))
217 ;; From.
218 (progn
219 (goto-char p)
220 (if (or (search-forward "\nfrom: " nil t)
221 (search-forward "\nfrom:" nil t))
222 (nnheader-header-value) "(nobody)"))
223 ;; Date.
224 (progn
225 (goto-char p)
226 (if (search-forward "\ndate: " nil t)
227 (nnheader-header-value) ""))
228 ;; Message-ID.
229 (progn
230 (goto-char p)
231 (if (search-forward "\nmessage-id:" nil t)
232 (buffer-substring
233 (1- (or (search-forward "<" (gnus-point-at-eol) t)
234 (point)))
235 (or (search-forward ">" (gnus-point-at-eol) t) (point)))
236 ;; If there was no message-id, we just fake one to make
237 ;; subsequent routines simpler.
238 (nnheader-generate-fake-message-id)))
239 ;; References.
240 (progn
241 (goto-char p)
242 (if (search-forward "\nreferences: " nil t)
243 (nnheader-header-value)
244 ;; Get the references from the in-reply-to header if there
245 ;; were no references and the in-reply-to header looks
246 ;; promising.
247 (if (and (search-forward "\nin-reply-to: " nil t)
248 (setq in-reply-to (nnheader-header-value))
249 (string-match "<[^\n>]+>" in-reply-to))
250 (let (ref2)
251 (setq ref (substring in-reply-to (match-beginning 0)
252 (match-end 0)))
253 (while (string-match "<[^\n>]+>"
254 in-reply-to (match-end 0))
255 (setq ref2 (substring in-reply-to (match-beginning 0)
256 (match-end 0)))
257 (when (> (length ref2) (length ref))
258 (setq ref ref2)))
259 ref)
260 nil)))
261 ;; Chars.
262 0
263 ;; Lines.
264 (progn
265 (goto-char p)
266 (if (search-forward "\nlines: " nil t)
267 (if (numberp (setq lines (read cur)))
268 lines 0)
269 0))
270 ;; Xref.
271 (progn
272 (goto-char p)
273 (and (search-forward "\nxref: " nil t)
274 (nnheader-header-value)))
275
276 ;; Extra.
277 (when nnmail-extra-headers
278 (let ((extra nnmail-extra-headers)
279 out)
280 (while extra
281 (goto-char p)
282 (when (search-forward
283 (concat "\n" (symbol-name (car extra)) ": ") nil t)
284 (push (cons (car extra) (nnheader-header-value))
285 out))
286 (pop extra))
287 out))))
288 (when naked
289 (goto-char (point-min))
290 (delete-char 1)))))
291
292 (defmacro nnheader-nov-skip-field ()
293 '(search-forward "\t" eol 'move))
294
295 (defmacro nnheader-nov-field ()
296 '(buffer-substring (point) (if (nnheader-nov-skip-field) (1- (point)) eol)))
297
298 (defmacro nnheader-nov-read-integer ()
299 '(prog1
300 (if (eq (char-after) ?\t)
301 0
302 (let ((num (condition-case nil
303 (read (current-buffer))
304 (error nil))))
305 (if (numberp num) num 0)))
306 (or (eobp) (forward-char 1))))
307
308 (defmacro nnheader-nov-parse-extra ()
309 '(let (out string)
310 (while (not (memq (char-after) '(?\n nil)))
311 (setq string (nnheader-nov-field))
312 (when (string-match "^\\([^ :]+\\): " string)
313 (push (cons (intern (match-string 1 string))
314 (substring string (match-end 0)))
315 out)))
316 out))
317
318 (defmacro nnheader-nov-read-message-id ()
319 '(let ((id (nnheader-nov-field)))
320 (if (string-match "^<[^>]+>$" id)
321 id
322 (nnheader-generate-fake-message-id))))
323
324 (defun nnheader-parse-nov ()
325 (let ((eol (gnus-point-at-eol)))
326 (vector
327 (nnheader-nov-read-integer) ; number
328 (nnheader-nov-field) ; subject
329 (nnheader-nov-field) ; from
330 (nnheader-nov-field) ; date
331 (nnheader-nov-read-message-id) ; id
332 (nnheader-nov-field) ; refs
333 (nnheader-nov-read-integer) ; chars
334 (nnheader-nov-read-integer) ; lines
335 (if (eq (char-after) ?\n)
336 nil
337 (if (looking-at "Xref: ")
338 (goto-char (match-end 0)))
339 (nnheader-nov-field)) ; Xref
340 (nnheader-nov-parse-extra)))) ; extra
341
342 (defun nnheader-insert-nov (header)
343 (princ (mail-header-number header) (current-buffer))
344 (let ((p (point)))
345 (insert
346 "\t"
347 (or (mail-header-subject header) "(none)") "\t"
348 (or (mail-header-from header) "(nobody)") "\t"
349 (or (mail-header-date header) "") "\t"
350 (or (mail-header-id header)
351 (nnmail-message-id))
352 "\t"
353 (or (mail-header-references header) "") "\t")
354 (princ (or (mail-header-chars header) 0) (current-buffer))
355 (insert "\t")
356 (princ (or (mail-header-lines header) 0) (current-buffer))
357 (insert "\t")
358 (when (mail-header-xref header)
359 (insert "Xref: " (mail-header-xref header)))
360 (when (or (mail-header-xref header)
361 (mail-header-extra header))
362 (insert "\t"))
363 (when (mail-header-extra header)
364 (let ((extra (mail-header-extra header)))
365 (while extra
366 (insert (symbol-name (caar extra))
367 ": " (cdar extra) "\t")
368 (pop extra))))
369 (insert "\n")
370 (backward-char 1)
371 (while (search-backward "\n" p t)
372 (delete-char 1))
373 (forward-line 1)))
374
375 (defun nnheader-insert-header (header)
376 (insert
377 "Subject: " (or (mail-header-subject header) "(none)") "\n"
378 "From: " (or (mail-header-from header) "(nobody)") "\n"
379 "Date: " (or (mail-header-date header) "") "\n"
380 "Message-ID: " (or (mail-header-id header) (nnmail-message-id)) "\n"
381 "References: " (or (mail-header-references header) "") "\n"
382 "Lines: ")
383 (princ (or (mail-header-lines header) 0) (current-buffer))
384 (insert "\n\n"))
385
386 (defun nnheader-insert-article-line (article)
387 (goto-char (point-min))
388 (insert "220 ")
389 (princ article (current-buffer))
390 (insert " Article retrieved.\n")
391 (search-forward "\n\n" nil 'move)
392 (delete-region (point) (point-max))
393 (forward-char -1)
394 (insert "."))
395
396 (defun nnheader-nov-delete-outside-range (beg end)
397 "Delete all NOV lines that lie outside the BEG to END range."
398 ;; First we find the first wanted line.
399 (nnheader-find-nov-line beg)
400 (delete-region (point-min) (point))
401 ;; Then we find the last wanted line.
402 (when (nnheader-find-nov-line end)
403 (forward-line 1))
404 (delete-region (point) (point-max)))
405
406 (defun nnheader-find-nov-line (article)
407 "Put point at the NOV line that start with ARTICLE.
408 If ARTICLE doesn't exist, put point where that line
409 would have been. The function will return non-nil if
410 the line could be found."
411 ;; This function basically does a binary search.
412 (let ((max (point-max))
413 (min (goto-char (point-min)))
414 (cur (current-buffer))
415 (prev (point-min))
416 num found)
417 (while (not found)
418 (goto-char (/ (+ max min) 2))
419 (beginning-of-line)
420 (if (or (= (point) prev)
421 (eobp))
422 (setq found t)
423 (setq prev (point))
424 (while (and (not (numberp (setq num (read cur))))
425 (not (eobp)))
426 (gnus-delete-line))
427 (cond ((> num article)
428 (setq max (point)))
429 ((< num article)
430 (setq min (point)))
431 (t
432 (setq found 'yes)))))
433 ;; We may be at the first line.
434 (when (and (not num)
435 (not (eobp)))
436 (setq num (read cur)))
437 ;; Now we may have found the article we're looking for, or we
438 ;; may be somewhere near it.
439 (when (and (not (eq found 'yes))
440 (not (eq num article)))
441 (setq found (point))
442 (while (and (< (point) max)
443 (or (not (numberp num))
444 (< num article)))
445 (forward-line 1)
446 (setq found (point))
447 (or (eobp)
448 (= (setq num (read cur)) article)))
449 (unless (eq num article)
450 (goto-char found)))
451 (beginning-of-line)
452 (eq num article)))
453
454 ;; Various cruft the backends and Gnus need to communicate.
455
456 (defvar nntp-server-buffer nil)
457 (defvar gnus-verbose-backends 7
458 "*A number that says how talkative the Gnus backends should be.")
459 (defvar gnus-nov-is-evil nil
460 "If non-nil, Gnus backends will never output headers in the NOV format.")
461 (defvar news-reply-yank-from nil)
462 (defvar news-reply-yank-message-id nil)
463
464 (defvar nnheader-callback-function nil)
465
466 (defun nnheader-init-server-buffer ()
467 "Initialize the Gnus-backend communication buffer."
468 (save-excursion
469 (unless (gnus-buffer-live-p nntp-server-buffer)
470 (setq nntp-server-buffer (get-buffer-create " *nntpd*")))
471 (mm-enable-multibyte)
472 (set-buffer nntp-server-buffer)
473 (erase-buffer)
474 (kill-all-local-variables)
475 (setq case-fold-search t) ;Should ignore case.
476 t))
477
478 ;;; Various functions the backends use.
479
480 (defun nnheader-file-error (file)
481 "Return a string that says what is wrong with FILE."
482 (format
483 (cond
484 ((not (file-exists-p file))
485 "%s does not exist")
486 ((file-directory-p file)
487 "%s is a directory")
488 ((not (file-readable-p file))
489 "%s is not readable"))
490 file))
491
492 (defun nnheader-insert-head (file)
493 "Insert the head of the article."
494 (when (file-exists-p file)
495 (if (eq nnheader-max-head-length t)
496 ;; Just read the entire file.
497 (nnheader-insert-file-contents file)
498 ;; Read 1K blocks until we find a separator.
499 (let ((beg 0)
500 format-alist)
501 (while (and (eq nnheader-head-chop-length
502 (nth 1 (nnheader-insert-file-contents
503 file nil beg
504 (incf beg nnheader-head-chop-length))))
505 (prog1 (not (search-forward "\n\n" nil t))
506 (goto-char (point-max)))
507 (or (null nnheader-max-head-length)
508 (< beg nnheader-max-head-length))))))
509 t))
510
511 (defun nnheader-article-p ()
512 "Say whether the current buffer looks like an article."
513 (goto-char (point-min))
514 (if (not (search-forward "\n\n" nil t))
515 nil
516 (narrow-to-region (point-min) (1- (point)))
517 (goto-char (point-min))
518 (while (looking-at "[a-zA-Z][^ \t]+:.*\n\\([ \t].*\n\\)*\\|From .*\n")
519 (goto-char (match-end 0)))
520 (prog1
521 (eobp)
522 (widen))))
523
524 (defun nnheader-insert-references (references message-id)
525 "Insert a References header based on REFERENCES and MESSAGE-ID."
526 (if (and (not references) (not message-id))
527 ;; This is invalid, but not all articles have Message-IDs.
528 ()
529 (mail-position-on-field "References")
530 (let ((begin (save-excursion (beginning-of-line) (point)))
531 (fill-column 78)
532 (fill-prefix "\t"))
533 (when references
534 (insert references))
535 (when (and references message-id)
536 (insert " "))
537 (when message-id
538 (insert message-id))
539 ;; Fold long References lines to conform to RFC1036 (sort of).
540 ;; The region must end with a newline to fill the region
541 ;; without inserting extra newline.
542 (fill-region-as-paragraph begin (1+ (point))))))
543
544 (defun nnheader-replace-header (header new-value)
545 "Remove HEADER and insert the NEW-VALUE."
546 (save-excursion
547 (save-restriction
548 (nnheader-narrow-to-headers)
549 (prog1
550 (message-remove-header header)
551 (goto-char (point-max))
552 (insert header ": " new-value "\n")))))
553
554 (defun nnheader-narrow-to-headers ()
555 "Narrow to the head of an article."
556 (widen)
557 (narrow-to-region
558 (goto-char (point-min))
559 (if (search-forward "\n\n" nil t)
560 (1- (point))
561 (point-max)))
562 (goto-char (point-min)))
563
564 (defun nnheader-set-temp-buffer (name &optional noerase)
565 "Set-buffer to an empty (possibly new) buffer called NAME with undo disabled."
566 (set-buffer (get-buffer-create name))
567 (buffer-disable-undo)
568 (unless noerase
569 (erase-buffer))
570 (current-buffer))
571
572 (eval-when-compile (defvar jka-compr-compression-info-list))
573 (defvar nnheader-numerical-files
574 (if (boundp 'jka-compr-compression-info-list)
575 (concat "\\([0-9]+\\)\\("
576 (mapconcat (lambda (i) (aref i 0))
577 jka-compr-compression-info-list "\\|")
578 "\\)?")
579 "[0-9]+$")
580 "Regexp that match numerical files.")
581
582 (defvar nnheader-numerical-short-files (concat "^" nnheader-numerical-files)
583 "Regexp that matches numerical file names.")
584
585 (defvar nnheader-numerical-full-files (concat "/" nnheader-numerical-files)
586 "Regexp that matches numerical full file paths.")
587
588 (defsubst nnheader-file-to-number (file)
589 "Take a FILE name and return the article number."
590 (if (string= nnheader-numerical-short-files "^[0-9]+$")
591 (string-to-int file)
592 (string-match nnheader-numerical-short-files file)
593 (string-to-int (match-string 0 file))))
594
595 (defun nnheader-directory-files-safe (&rest args)
596 ;; It has been reported numerous times that `directory-files'
597 ;; fails with an alarming frequency on NFS mounted file systems.
598 ;; This function executes that function twice and returns
599 ;; the longest result.
600 (let ((first (apply 'directory-files args))
601 (second (apply 'directory-files args)))
602 (if (> (length first) (length second))
603 first
604 second)))
605
606 (defun nnheader-directory-articles (dir)
607 "Return a list of all article files in directory DIR."
608 (mapcar 'nnheader-file-to-number
609 (nnheader-directory-files-safe
610 dir nil nnheader-numerical-short-files t)))
611
612 (defun nnheader-article-to-file-alist (dir)
613 "Return an alist of article/file pairs in DIR."
614 (mapcar (lambda (file) (cons (nnheader-file-to-number file) file))
615 (nnheader-directory-files-safe
616 dir nil nnheader-numerical-short-files t)))
617
618 (defun nnheader-fold-continuation-lines ()
619 "Fold continuation lines in the current buffer."
620 (nnheader-replace-regexp "\\(\r?\n[ \t]+\\)+" " "))
621
622 (defun nnheader-translate-file-chars (file &optional full)
623 "Translate FILE into something that can be a file name.
624 If FULL, translate everything."
625 (if (null nnheader-file-name-translation-alist)
626 ;; No translation is necessary.
627 file
628 (let* ((i 0)
629 trans leaf path len)
630 (if full
631 ;; Do complete translation.
632 (setq leaf (copy-sequence file)
633 path ""
634 i (if (and (< 1 (length leaf)) (eq ?: (aref leaf 1)))
635 2 0))
636 ;; We translate -- but only the file name. We leave the directory
637 ;; alone.
638 (if (and (featurep 'xemacs)
639 (memq system-type '(win32 w32 mswindows windows-nt)))
640 ;; This is needed on NT and stuff, because
641 ;; file-name-nondirectory is not enough to split
642 ;; file names, containing ':', e.g.
643 ;; "d:\\Work\\News\\nntp+news.fido7.ru:fido7.ru.gnu.SCORE"
644 ;;
645 ;; we are trying to correctly split such names:
646 ;; "d:file.name" -> "a:" "file.name"
647 ;; "aaa:bbb.ccc" -> "" "aaa:bbb.ccc"
648 ;; "d:aaa\\bbb:ccc" -> "d:aaa\\" "bbb:ccc"
649 ;; etc.
650 ;; to translate then only the file name part.
651 (progn
652 (setq leaf file
653 path "")
654 (if (string-match "\\(^\\w:\\|[/\\]\\)\\([^/\\]+\\)$" file)
655 (setq leaf (substring file (match-beginning 2))
656 path (substring file 0 (match-beginning 2)))))
657 ;; Emacs DTRT, says andrewi.
658 (setq leaf (file-name-nondirectory file)
659 path (file-name-directory file))))
660 (setq len (length leaf))
661 (while (< i len)
662 (when (setq trans (cdr (assq (aref leaf i)
663 nnheader-file-name-translation-alist)))
664 (aset leaf i trans))
665 (incf i))
666 (concat path leaf))))
667
668 (defun nnheader-report (backend &rest args)
669 "Report an error from the BACKEND.
670 The first string in ARGS can be a format string."
671 (set (intern (format "%s-status-string" backend))
672 (if (< (length args) 2)
673 (car args)
674 (apply 'format args)))
675 nil)
676
677 (defun nnheader-get-report (backend)
678 "Get the most recent report from BACKEND."
679 (condition-case ()
680 (nnheader-message 5 "%s" (symbol-value (intern (format "%s-status-string"
681 backend))))
682 (error (nnheader-message 5 ""))))
683
684 (defun nnheader-insert (format &rest args)
685 "Clear the communication buffer and insert FORMAT and ARGS into the buffer.
686 If FORMAT isn't a format string, it and all ARGS will be inserted
687 without formatting."
688 (save-excursion
689 (set-buffer nntp-server-buffer)
690 (erase-buffer)
691 (if (string-match "%" format)
692 (insert (apply 'format format args))
693 (apply 'insert format args))
694 t))
695
696 (if (fboundp 'subst-char-in-string)
697 (defsubst nnheader-replace-chars-in-string (string from to)
698 (subst-char-in-string from to string))
699 (defun nnheader-replace-chars-in-string (string from to)
700 "Replace characters in STRING from FROM to TO."
701 (let ((string (substring string 0)) ;Copy string.
702 (len (length string))
703 (idx 0))
704 ;; Replace all occurrences of FROM with TO.
705 (while (< idx len)
706 (when (= (aref string idx) from)
707 (aset string idx to))
708 (setq idx (1+ idx)))
709 string)))
710
711 (defun nnheader-replace-duplicate-chars-in-string (string from to)
712 "Replace characters in STRING from FROM to TO."
713 (let ((string (substring string 0)) ;Copy string.
714 (len (length string))
715 (idx 0) prev i)
716 ;; Replace all occurrences of FROM with TO.
717 (while (< idx len)
718 (setq i (aref string idx))
719 (when (and (eq prev from) (= i from))
720 (aset string (1- idx) to)
721 (aset string idx to))
722 (setq prev i)
723 (setq idx (1+ idx)))
724 string))
725
726 (defun nnheader-file-to-group (file &optional top)
727 "Return a group name based on FILE and TOP."
728 (nnheader-replace-chars-in-string
729 (if (not top)
730 file
731 (condition-case ()
732 (substring (expand-file-name file)
733 (length
734 (expand-file-name
735 (file-name-as-directory top))))
736 (error "")))
737 ?/ ?.))
738
739 (defun nnheader-message (level &rest args)
740 "Message if the Gnus backends are talkative."
741 (if (or (not (numberp gnus-verbose-backends))
742 (<= level gnus-verbose-backends))
743 (apply 'message args)
744 (apply 'format args)))
745
746 (defun nnheader-be-verbose (level)
747 "Return whether the backends should be verbose on LEVEL."
748 (or (not (numberp gnus-verbose-backends))
749 (<= level gnus-verbose-backends)))
750
751 (defvar nnheader-pathname-coding-system 'binary
752 "*Coding system for pathname.")
753
754 (defun nnheader-group-pathname (group dir &optional file)
755 "Make pathname for GROUP."
756 (concat
757 (let ((dir (file-name-as-directory (expand-file-name dir))))
758 ;; If this directory exists, we use it directly.
759 (file-name-as-directory
760 (if (file-directory-p (concat dir group))
761 (expand-file-name group dir)
762 ;; If not, we translate dots into slashes.
763 (expand-file-name (mm-encode-coding-string
764 (nnheader-replace-chars-in-string group ?. ?/)
765 nnheader-pathname-coding-system)
766 dir))))
767 (cond ((null file) "")
768 ((numberp file) (int-to-string file))
769 (t file))))
770
771 (defun nnheader-functionp (form)
772 "Return non-nil if FORM is funcallable."
773 (or (and (symbolp form) (fboundp form))
774 (and (listp form) (eq (car form) 'lambda))))
775
776 (defun nnheader-concat (dir &rest files)
777 "Concat DIR as directory to FILES."
778 (apply 'concat (file-name-as-directory dir) files))
779
780 (defun nnheader-ms-strip-cr ()
781 "Strip ^M from the end of all lines."
782 (save-excursion
783 (goto-char (point-min))
784 (while (re-search-forward "\r$" nil t)
785 (delete-backward-char 1))))
786
787 (defun nnheader-file-size (file)
788 "Return the file size of FILE or 0."
789 (or (nth 7 (file-attributes file)) 0))
790
791 (defun nnheader-find-etc-directory (package &optional file)
792 "Go through the path and find the \".../etc/PACKAGE\" directory.
793 If FILE, find the \".../etc/PACKAGE\" file instead."
794 (let ((path load-path)
795 dir result)
796 ;; We try to find the dir by looking at the load path,
797 ;; stripping away the last component and adding "etc/".
798 (while path
799 (if (and (car path)
800 (file-exists-p
801 (setq dir (concat
802 (file-name-directory
803 (directory-file-name (car path)))
804 "etc/" package
805 (if file "" "/"))))
806 (or file (file-directory-p dir)))
807 (setq result dir
808 path nil)
809 (setq path (cdr path))))
810 result))
811
812 (defvar ange-ftp-path-format)
813 (defvar efs-path-regexp)
814 (defun nnheader-re-read-dir (path)
815 "Re-read directory PATH if PATH is on a remote system."
816 (if (and (fboundp 'efs-re-read-dir) (boundp 'efs-path-regexp))
817 (when (string-match efs-path-regexp path)
818 (efs-re-read-dir path))
819 (when (and (fboundp 'ange-ftp-re-read-dir) (boundp 'ange-ftp-path-format))
820 (when (string-match (car ange-ftp-path-format) path)
821 (ange-ftp-re-read-dir path)))))
822
823 (defvar nnheader-file-coding-system 'raw-text
824 "Coding system used in file backends of Gnus.")
825
826 (defun nnheader-insert-file-contents (filename &optional visit beg end replace)
827 "Like `insert-file-contents', q.v., but only reads in the file.
828 A buffer may be modified in several ways after reading into the buffer due
829 to advanced Emacs features, such as file-name-handlers, format decoding,
830 find-file-hooks, etc.
831 This function ensures that none of these modifications will take place."
832 (let ((coding-system-for-read nnheader-file-coding-system))
833 (mm-insert-file-contents filename visit beg end replace)))
834
835 (defun nnheader-find-file-noselect (&rest args)
836 (let ((format-alist nil)
837 (auto-mode-alist (mm-auto-mode-alist))
838 (default-major-mode 'fundamental-mode)
839 (enable-local-variables nil)
840 (after-insert-file-functions nil)
841 (enable-local-eval nil)
842 (find-file-hooks nil)
843 (coding-system-for-read nnheader-file-coding-system))
844 (apply 'find-file-noselect args)))
845
846 (defun nnheader-directory-regular-files (dir)
847 "Return a list of all regular files in DIR."
848 (let ((files (directory-files dir t))
849 out)
850 (while files
851 (when (file-regular-p (car files))
852 (push (car files) out))
853 (pop files))
854 (nreverse out)))
855
856 (defun nnheader-directory-files (&rest args)
857 "Same as `directory-files', but prune \".\" and \"..\"."
858 (let ((files (apply 'directory-files args))
859 out)
860 (while files
861 (unless (member (file-name-nondirectory (car files)) '("." ".."))
862 (push (car files) out))
863 (pop files))
864 (nreverse out)))
865
866 (defmacro nnheader-skeleton-replace (from &optional to regexp)
867 `(let ((new (generate-new-buffer " *nnheader replace*"))
868 (cur (current-buffer))
869 (start (point-min)))
870 (set-buffer cur)
871 (goto-char (point-min))
872 (while (,(if regexp 're-search-forward 'search-forward)
873 ,from nil t)
874 (insert-buffer-substring
875 cur start (prog1 (match-beginning 0) (set-buffer new)))
876 (goto-char (point-max))
877 ,(when to `(insert ,to))
878 (set-buffer cur)
879 (setq start (point)))
880 (insert-buffer-substring
881 cur start (prog1 (point-max) (set-buffer new)))
882 (copy-to-buffer cur (point-min) (point-max))
883 (kill-buffer (current-buffer))
884 (set-buffer cur)))
885
886 (defun nnheader-replace-string (from to)
887 "Do a fast replacement of FROM to TO from point to `point-max'."
888 (nnheader-skeleton-replace from to))
889
890 (defun nnheader-replace-regexp (from to)
891 "Do a fast regexp replacement of FROM to TO from point to `point-max'."
892 (nnheader-skeleton-replace from to t))
893
894 (defun nnheader-strip-cr ()
895 "Strip all \r's from the current buffer."
896 (nnheader-skeleton-replace "\r"))
897
898 (defalias 'nnheader-run-at-time 'run-at-time)
899 (defalias 'nnheader-cancel-timer 'cancel-timer)
900 (defalias 'nnheader-cancel-function-timers 'cancel-function-timers)
901
902 (when (featurep 'xemacs)
903 (require 'nnheaderxm))
904
905 (run-hooks 'nnheader-load-hook)
906
907 (provide 'nnheader)
908
909 ;;; nnheader.el ends here