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