*** empty log message ***
[gnus] / lisp / nndoc.el
1 ;; Copyright (C) 1995,96 Free Software Foundation, Inc.
2
3 ;; Author: Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
4 ;;      Masanobu UMEDA <umerin@flab.flab.fujitsu.junet>
5 ;; Keywords: news
6
7 ;; This file is part of GNU Emacs.
8
9 ;; GNU Emacs is free software; you can redistribute it and/or modify
10 ;; it under the terms of the GNU General Public License as published by
11 ;; the Free Software Foundation; either version 2, or (at your option)
12 ;; any later version.
13
14 ;; GNU Emacs is distributed in the hope that it will be useful,
15 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
16 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 ;; GNU General Public License for more details.
18
19 ;; You should have received a copy of the GNU General Public License
20 ;; along with GNU Emacs; see the file COPYING.  If not, write to the
21 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
22 ;; Boston, MA 02111-1307, USA.
23
24 ;;; Commentary:
25
26 ;;; Code:
27
28 (require 'nnheader)
29 (require 'message)
30 (require 'nnmail)
31 (require 'nnoo)
32 (eval-when-compile (require 'cl))
33
34 (nnoo-declare nndoc)
35
36 (defvoo nndoc-article-type 'guess
37   "*Type of the file.
38 One of `mbox', `babyl', `digest', `news', `rnews', `mmdf', `forward',
39 `mime-digest', `standard-digest', `slack-digest', `clari-briefs' or
40 `guess'.")
41
42 (defvoo nndoc-post-type 'mail
43   "*Whether the nndoc group is `mail' or `post'.")
44
45 (defvar nndoc-type-alist 
46   `((mmdf 
47      (article-begin .  "^\^A\^A\^A\^A\n")
48      (body-end .  "^\^A\^A\^A\^A\n"))
49     (news
50      (article-begin . "^Path:"))
51     (rnews
52      (article-begin . "^#! *rnews +\\([0-9]+\\) *\n")
53      (body-end-function . nndoc-rnews-body-end))
54     (mbox 
55      (article-begin-function . nndoc-mbox-article-begin)
56      (body-end-function . nndoc-mbox-body-end))
57     (babyl 
58      (article-begin . "\^_\^L *\n")
59      (body-end . "\^_")
60      (body-begin-function . nndoc-babyl-body-begin)
61      (head-begin-function . nndoc-babyl-head-begin))
62     (forward
63      (article-begin . "^-+ Start of forwarded message -+\n+")
64      (body-end . "^-+ End of forwarded message -+$")
65      (prepare-body-function . nndoc-unquote-dashes))
66     (clari-briefs
67      (article-begin . "^ \\*")
68      (body-end . "^\t------*[ \t]^*\n^ \\*")
69      (body-begin . "^\t")
70      (head-end . "^\t")
71      (generate-head-function . nndoc-generate-clari-briefs-head)
72      (article-transform-function . nndoc-transform-clari-briefs))
73     (mime-digest
74      (article-begin . "")
75      (head-end . "^ ?$")
76      (body-end . "")
77      (file-end . "")
78      (subtype digest guess))
79     (standard-digest
80      (first-article . ,(concat "^" (make-string 70 ?-) "\n\n+"))
81      (article-begin . ,(concat "^\n" (make-string 30 ?-) "\n\n+"))
82      (prepare-body-function . nndoc-unquote-dashes)
83      (body-end-function . nndoc-digest-body-end)
84      (head-end . "^ ?$")
85      (body-begin . "^ ?\n")
86      (file-end . "^End of .*digest.*[0-9].*\n\\*\\*\\|^End of.*Digest *$")
87      (subtype digest guess))
88     (slack-digest
89      (article-begin . "^------------------------------*[\n \t]+")
90      (head-end . "^ ?$")
91      (body-end-function . nndoc-digest-body-end)
92      (body-begin . "^ ?$")
93      (file-end . "^End of")
94      (prepare-body-function . nndoc-unquote-dashes)
95      (subtype digest guess))
96     (lanl-gov-announce
97       (article-begin . "^\\\\\\\\\n")
98       (head-begin . "^Paper.*:")
99       (head-end   . "\\(^\\\\\\\\.*\n\\|-----------------\\)")
100       (body-begin . "")  
101       (body-end   . "-------------------------------------------------")     
102       (file-end   . "^Title: Recent Seminal")
103       (generate-head-function . nndoc-generate-lanl-gov-head)
104       (article-transform-function . nndoc-transform-lanl-gov-announce)
105       (subtype preprints guess))
106     (guess 
107      (guess . t)
108      (subtype nil))
109     (digest
110      (guess . t)
111      (subtype nil))
112     (preprints
113      (guess . t)
114      (subtype nil))
115     ))
116
117 \f
118
119 (defvoo nndoc-file-begin nil)
120 (defvoo nndoc-first-article nil)
121 (defvoo nndoc-article-end nil)
122 (defvoo nndoc-article-begin nil)
123 (defvoo nndoc-head-begin nil)
124 (defvoo nndoc-head-end nil)
125 (defvoo nndoc-file-end nil)
126 (defvoo nndoc-body-begin nil)
127 (defvoo nndoc-body-end-function nil)
128 (defvoo nndoc-body-begin-function nil)
129 (defvoo nndoc-head-begin-function nil)
130 (defvoo nndoc-body-end nil)
131 (defvoo nndoc-dissection-alist nil)
132 (defvoo nndoc-prepare-body-function nil)
133 (defvoo nndoc-generate-head-function nil)
134 (defvoo nndoc-article-transform-function nil)
135 (defvoo nndoc-article-begin-function nil)
136
137 (defvoo nndoc-status-string "")
138 (defvoo nndoc-group-alist nil)
139 (defvoo nndoc-current-buffer nil
140   "Current nndoc news buffer.")
141 (defvoo nndoc-address nil)
142
143 (defconst nndoc-version "nndoc 1.0"
144   "nndoc version.")
145
146 \f
147
148 ;;; Interface functions
149
150 (nnoo-define-basics nndoc)
151
152 (deffoo nndoc-retrieve-headers (articles &optional newsgroup server fetch-old)
153   (when (nndoc-possibly-change-buffer newsgroup server)
154     (save-excursion
155       (set-buffer nntp-server-buffer)
156       (erase-buffer)
157       (let (article entry)
158         (if (stringp (car articles))
159             'headers
160           (while articles
161             (when (setq entry (cdr (assq (setq article (pop articles))
162                                          nndoc-dissection-alist)))
163               (insert (format "221 %d Article retrieved.\n" article))
164               (if nndoc-generate-head-function
165                   (funcall nndoc-generate-head-function article)
166                 (insert-buffer-substring
167                  nndoc-current-buffer (car entry) (nth 1 entry)))
168               (goto-char (point-max))
169               (or (= (char-after (1- (point))) ?\n) (insert "\n"))
170               (insert (format "Lines: %d\n" (nth 4 entry)))
171               (insert ".\n")))
172
173           (nnheader-fold-continuation-lines)
174           'headers)))))
175
176 (deffoo nndoc-request-article (article &optional newsgroup server buffer)
177   (nndoc-possibly-change-buffer newsgroup server)
178   (save-excursion
179     (let ((buffer (or buffer nntp-server-buffer))
180           (entry (cdr (assq article nndoc-dissection-alist)))
181           beg)
182       (set-buffer buffer)
183       (erase-buffer)
184       (if (stringp article)
185           nil
186         (insert-buffer-substring 
187          nndoc-current-buffer (car entry) (nth 1 entry))
188         (insert "\n")
189         (setq beg (point))
190         (insert-buffer-substring 
191          nndoc-current-buffer (nth 2 entry) (nth 3 entry))
192         (goto-char beg)
193         (when nndoc-prepare-body-function
194           (funcall nndoc-prepare-body-function))
195         (when nndoc-article-transform-function
196           (funcall nndoc-article-transform-function article))
197         t))))
198
199 (deffoo nndoc-request-group (group &optional server dont-check)
200   "Select news GROUP."
201   (let (number)
202     (cond 
203      ((not (nndoc-possibly-change-buffer group server))
204       (nnheader-report 'nndoc "No such file or buffer: %s"
205                        nndoc-address))
206      (dont-check
207       (nnheader-report 'nndoc "Selected group %s" group)
208       t)
209      ((zerop (setq number (length nndoc-dissection-alist)))
210       (nndoc-close-group group)
211       (nnheader-report 'nndoc "No articles in group %s" group))
212      (t
213       (nnheader-insert "211 %d %d %d %s\n" number 1 number group)))))
214
215 (deffoo nndoc-request-type (group &optional article)
216   (cond ((not article) 'unknown)
217         (nndoc-post-type nndoc-post-type)
218         (t 'unknown)))
219
220 (deffoo nndoc-close-group (group &optional server)
221   (nndoc-possibly-change-buffer group server)
222   (and nndoc-current-buffer
223        (buffer-name nndoc-current-buffer)
224        (kill-buffer nndoc-current-buffer))
225   (setq nndoc-group-alist (delq (assoc group nndoc-group-alist)
226                                 nndoc-group-alist))
227   (setq nndoc-current-buffer nil)
228   (nnoo-close-server 'nndoc server)
229   (setq nndoc-dissection-alist nil)
230   t)
231
232 (deffoo nndoc-request-list (&optional server)
233   nil)
234
235 (deffoo nndoc-request-newgroups (date &optional server)
236   nil)
237
238 (deffoo nndoc-request-list-newsgroups (&optional server)
239   nil)
240
241 \f
242 ;;; Internal functions.
243
244 (defun nndoc-possibly-change-buffer (group source)
245   (let (buf)
246     (cond 
247      ;; The current buffer is this group's buffer.
248      ((and nndoc-current-buffer
249            (buffer-name nndoc-current-buffer)
250            (eq nndoc-current-buffer 
251                (setq buf (cdr (assoc group nndoc-group-alist))))))
252      ;; We change buffers by taking an old from the group alist.
253      ;; `source' is either a string (a file name) or a buffer object. 
254      (buf
255       (setq nndoc-current-buffer buf))
256      ;; It's a totally new group.    
257      ((or (and (bufferp nndoc-address)
258                (buffer-name nndoc-address))
259           (and (stringp nndoc-address)
260                (file-exists-p nndoc-address)
261                (not (file-directory-p nndoc-address))))
262       (push (cons group (setq nndoc-current-buffer 
263                               (get-buffer-create 
264                                (concat " *nndoc " group "*"))))
265             nndoc-group-alist)
266       (setq nndoc-dissection-alist nil)
267       (save-excursion
268         (set-buffer nndoc-current-buffer)
269         (buffer-disable-undo (current-buffer))
270         (erase-buffer)
271         (if (stringp nndoc-address)
272             (insert-file-contents nndoc-address)
273           (insert-buffer-substring nndoc-address)))))
274     ;; Initialize the nndoc structures according to this new document.
275     (when (and nndoc-current-buffer
276                (not nndoc-dissection-alist))
277       (save-excursion
278         (set-buffer nndoc-current-buffer)
279         (nndoc-set-delims)
280         (nndoc-dissect-buffer)))
281     (unless nndoc-current-buffer
282       (nndoc-close-server))
283     ;; Return whether we managed to select a file.
284     nndoc-current-buffer))
285
286 ;;;
287 ;;; Deciding what document type we have
288 ;;;
289
290 (defun nndoc-set-delims ()
291   "Set the nndoc delimiter variables according to the type of the document."
292   (let ((vars '(nndoc-file-begin 
293                 nndoc-first-article 
294                 nndoc-article-end nndoc-head-begin nndoc-head-end
295                 nndoc-file-end nndoc-article-begin
296                 nndoc-body-begin nndoc-body-end-function nndoc-body-end
297                 nndoc-prepare-body-function nndoc-article-transform-function
298                 nndoc-generate-head-function nndoc-body-begin-function
299                 nndoc-head-begin-function)))
300     (while vars
301       (set (pop vars) nil)))
302   (let (defs)
303     ;; Guess away until we find the real file type.
304     (while (assq 'guess (setq defs (cdr (assq nndoc-article-type 
305                                               nndoc-type-alist))))
306       (setq nndoc-article-type (nndoc-guess-type nndoc-article-type)))
307     ;; Set the nndoc variables.
308     (while defs
309       (set (intern (format "nndoc-%s" (caar defs)))
310            (cdr (pop defs))))))
311
312 (defun nndoc-guess-type (subtype)
313   (let ((alist nndoc-type-alist)
314         results result entry)
315     (while (and (not result)
316                 (setq entry (pop alist)))
317       (when (memq subtype (or (cdr (assq 'subtype entry)) '(guess)))
318         (goto-char (point-min))
319         (when (numberp (setq result (funcall (intern
320                                               (format "nndoc-%s-type-p" 
321                                                       (car entry))))))
322           (push (cons result entry) results)
323           (setq result nil))))
324     (unless (or result results)
325       (error "Document is not of any recognized type"))
326     (if result
327         (car entry)
328       (cadar (sort results (lambda (r1 r2) (< (car r1) (car r2))))))))
329
330 ;;; 
331 ;;; Built-in type predicates and functions
332 ;;;
333
334 (defun nndoc-mbox-type-p ()
335   (when (looking-at message-unix-mail-delimiter)
336     t))
337
338 (defun nndoc-mbox-article-begin ()
339   (when (re-search-forward (concat "^" message-unix-mail-delimiter) nil t)
340     (goto-char (match-beginning 0))))
341
342 (defun nndoc-mbox-body-end ()
343   (let ((beg (point))
344         len end)
345     (when
346         (save-excursion
347           (and (re-search-backward 
348                 (concat "^" message-unix-mail-delimiter) nil t)
349                (setq end (point))
350                (search-forward "\n\n" beg t)
351                (re-search-backward
352                 "^Content-Length:[ \t]*\\([0-9]+\\) *$" end t)
353                (setq len (string-to-int (match-string 1)))
354                (search-forward "\n\n" beg t)
355                (or (= (setq len (+ (point) len)) (point-max))
356                    (and (< len (point-max))
357                         (goto-char len)
358                         (looking-at message-unix-mail-delimiter)))))
359       (goto-char len))))
360
361 (defun nndoc-mmdf-type-p ()
362   (when (looking-at "\^A\^A\^A\^A$")
363     t))
364
365 (defun nndoc-news-type-p ()
366   (when (looking-at "^Path:.*\n")
367     t))
368
369 (defun nndoc-rnews-type-p ()
370   (when (looking-at "#! *rnews")
371     t))
372
373 (defun nndoc-rnews-body-end ()
374   (and (re-search-backward nndoc-article-begin nil t)
375        (forward-line 1)
376        (goto-char (+ (point) (string-to-int (match-string 1))))))
377
378 (defun nndoc-babyl-type-p ()
379   (when (re-search-forward "\^_\^L *\n" nil t)
380     t))
381
382 (defun nndoc-babyl-body-begin ()
383   (re-search-forward "^\n" nil t)
384   (when (looking-at "\*\*\* EOOH \*\*\*")
385     (let ((next (or (save-excursion
386                       (re-search-forward nndoc-article-begin nil t))
387                     (point-max))))
388       (unless (re-search-forward "^\n" next t)
389         (goto-char next)
390         (forward-line -1)
391         (insert "\n")
392         (forward-line -1)))))
393
394 (defun nndoc-babyl-head-begin ()
395   (when (re-search-forward "^[0-9].*\n" nil t)
396     (when (looking-at "\*\*\* EOOH \*\*\*")
397       (forward-line 1))
398     t))
399
400 (defun nndoc-forward-type-p ()
401   (when (and (re-search-forward "^-+ Start of forwarded message -+\n+" nil t)
402              (not (re-search-forward "^Subject:.*digest" nil t)))
403     t))
404
405 (defun nndoc-clari-briefs-type-p ()
406   (when (let ((case-fold-search nil))
407           (re-search-forward "^\t[^a-z]+ ([^a-z]+) --" nil t))
408     t))
409
410 (defun nndoc-transform-clari-briefs (article)
411   (goto-char (point-min))
412   (when (looking-at " *\\*\\(.*\\)\n")
413     (replace-match "" t t))
414   (nndoc-generate-clari-briefs-head article))
415
416 (defun nndoc-generate-clari-briefs-head (article)
417   (let ((entry (cdr (assq article nndoc-dissection-alist)))
418         subject from)
419     (save-excursion
420       (set-buffer nndoc-current-buffer)
421       (save-restriction
422         (narrow-to-region (car entry) (nth 3 entry))
423         (goto-char (point-min))
424         (when (looking-at " *\\*\\(.*\\)$")
425           (setq subject (match-string 1))
426           (when (string-match "[ \t]+$" subject)
427             (setq subject (substring subject 0 (match-beginning 0)))))
428         (when
429             (let ((case-fold-search nil))
430               (re-search-forward
431                "^\t\\([^a-z]+\\(,[^(]+\\)? ([^a-z]+)\\) --" nil t))
432           (setq from (match-string 1)))))
433     (insert "From: " "clari@clari.net (" (or from "unknown") ")"
434             "\nSubject: " (or subject "(no subject)") "\n")))
435
436 (defun nndoc-mime-digest-type-p ()
437   (let ((case-fold-search t)
438         boundary-id b-delimiter entry)
439     (when (and
440            (re-search-forward
441             (concat "^Content-Type: *multipart/digest;[ \t\n]*[ \t]"
442                     "boundary=\"\\([^\"\n]*[^\" \t\n]\\)\"")
443             nil t)
444            (match-beginning 1))
445       (setq boundary-id (match-string 1)
446             b-delimiter (concat "\n--" boundary-id "[\n \t]+"))
447       (setq entry (assq 'mime-digest nndoc-type-alist))
448       (setcdr entry
449               (list
450                (cons 'head-end "^ ?$")
451                (cons 'body-begin "^ ?\n")
452                (cons 'article-begin b-delimiter)
453                (cons 'body-end-function 'nndoc-digest-body-end)
454                (cons 'file-end (concat "\n--" boundary-id "--[ \t]*$"))))
455       t)))
456
457 (defun nndoc-standard-digest-type-p ()
458   (when (and (re-search-forward (concat "^" (make-string 70 ?-) "\n\n") nil t)
459              (re-search-forward 
460               (concat "\n\n" (make-string 30 ?-) "\n\n") nil t))
461     t))
462
463 (defun nndoc-digest-body-end ()
464   (and (re-search-forward nndoc-article-begin nil t)
465        (goto-char (match-beginning 0))))
466
467 (defun nndoc-slack-digest-type-p ()
468   0)
469
470 (defun nndoc-lanl-gov-announce-type-p ()
471   (when (let ((case-fold-search nil))
472           (re-search-forward "^\\\\\\\\\nPaper: [a-z-]+/[0-9]+" nil t))
473     t))
474
475 (defun nndoc-transform-lanl-gov-announce (article)
476   (goto-char (point-max))
477   (when (re-search-backward "^\\\\\\\\ +(\\([^ ]*\\) , *\\([^ ]*\\))" nil t)
478     (replace-match "\n\nGet it at \\1 (\\2)" t nil))
479   ;;  (when (re-search-backward "^\\\\\\\\$" nil t)
480   ;;    (replace-match "" t t))
481   )
482  
483 (defun nndoc-generate-lanl-gov-head (article)
484   (let ((entry (cdr (assq article nndoc-dissection-alist)))
485         (e-mail "no address given")
486         subject from)
487     (save-excursion
488       (set-buffer nndoc-current-buffer)
489       (save-restriction
490         (narrow-to-region (car entry) (nth 1 entry))
491         (goto-char (point-min))
492         (when (looking-at "^Paper.*: \\([a-z-]+/[0-9]+\\)")
493           (setq subject (concat " (" (match-string 1) ")"))
494           (when (re-search-forward "^From: \\([^ ]+\\)" nil t)
495             (setq e-mail (match-string 1)))
496           (when (re-search-forward "^Title: \\([^\f]*\\)\nAuthors?: \\(.*\\)"
497                                    nil t)
498             (setq subject (concat (match-string 1) subject))
499             (setq from (concat (match-string 2) " <" e-mail ">"))))
500         ))
501     (while (and from (string-match "(\[^)\]*)" from))
502       (setq from (replace-match "" t t from)))
503     (insert "From: "  (or from "unknown")
504             "\nSubject: " (or subject "(no subject)") "\n")))
505  
506
507
508 ;;;
509 ;;; Functions for dissecting the documents
510 ;;;
511
512 (defun nndoc-search (regexp)
513   (prog1
514       (re-search-forward regexp nil t)
515     (beginning-of-line)))
516
517 (defun nndoc-dissect-buffer ()
518   "Go through the document and partition it into heads/bodies/articles."
519   (let ((i 0)
520         (first t)
521         head-begin head-end body-begin body-end)
522     (setq nndoc-dissection-alist nil)
523     (save-excursion
524       (set-buffer nndoc-current-buffer)
525       (goto-char (point-min))
526       ;; Find the beginning of the file.
527       (when nndoc-file-begin
528         (nndoc-search nndoc-file-begin))
529       ;; Go through the file.
530       (while (if (and first nndoc-first-article)
531                  (nndoc-search nndoc-first-article)
532                (nndoc-article-begin))
533         (setq first nil)
534         (cond (nndoc-head-begin-function
535                (funcall nndoc-head-begin-function))
536               (nndoc-head-begin 
537                (nndoc-search nndoc-head-begin)))
538         (if (or (>= (point) (point-max))
539                 (and nndoc-file-end
540                      (looking-at nndoc-file-end)))
541             (goto-char (point-max))
542           (setq head-begin (point))
543           (nndoc-search (or nndoc-head-end "^$"))
544           (setq head-end (point))
545           (if nndoc-body-begin-function
546               (funcall nndoc-body-begin-function)
547             (nndoc-search (or nndoc-body-begin "^\n")))
548           (setq body-begin (point))
549           (or (and nndoc-body-end-function
550                    (funcall nndoc-body-end-function))
551               (and nndoc-body-end
552                    (nndoc-search nndoc-body-end))
553               (nndoc-article-begin)
554               (progn
555                 (goto-char (point-max))
556                 (when nndoc-file-end
557                   (and (re-search-backward nndoc-file-end nil t)
558                        (beginning-of-line)))))
559           (setq body-end (point))
560           (push (list (incf i) head-begin head-end body-begin body-end
561                       (count-lines body-begin body-end))
562                 nndoc-dissection-alist))))))
563
564 (defun nndoc-article-begin ()
565   (if nndoc-article-begin-function
566       (funcall nndoc-article-begin-function)
567     (condition-case ()
568         (nndoc-search nndoc-article-begin)
569       (error nil))))
570
571 (defun nndoc-unquote-dashes ()
572   "Unquote quoted non-separators in digests."
573   (while (re-search-forward "^- -"nil t)
574     (replace-match "-" t t)))
575
576 ;;;###autoload
577 (defun nndoc-add-type (definition &optional position)
578   "Add document DEFINITION to the list of nndoc document definitions.
579 If POSITION is nil or `last', the definition will be added
580 as the last checked definition, if t or `first', add as the
581 first definition, and if any other symbol, add after that
582 symbol in the alist."
583   ;; First remove any old instances.
584   (setq nndoc-type-alist
585         (delq (assq (car definition) nndoc-type-alist)
586               nndoc-type-alist))
587   ;; Then enter the new definition in the proper place.
588   (cond
589    ((or (null position) (eq position 'last))
590     (setq nndoc-type-alist (nconc nndoc-type-alist (list definition))))
591    ((or (eq position t) (eq position 'first))
592     (push definition nndoc-type-alist))
593    (t
594     (let ((list (memq (assq position nndoc-type-alist)
595                       nndoc-type-alist)))
596       (unless list
597         (error "No such position: %s" position))
598       (setcdr list (cons definition (cdr list)))))))
599
600 (provide 'nndoc)
601
602 ;;; nndoc.el ends here