nndoc.el 37 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097
  1. ;;; nndoc.el --- single file access for Gnus
  2. ;; Copyright (C) 1995-2012 Free Software Foundation, Inc.
  3. ;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
  4. ;; Masanobu UMEDA <umerin@flab.flab.fujitsu.junet>
  5. ;; Keywords: news
  6. ;; This file is part of GNU Emacs.
  7. ;; GNU Emacs is free software: you can redistribute it and/or modify
  8. ;; it under the terms of the GNU General Public License as published by
  9. ;; the Free Software Foundation, either version 3 of the License, or
  10. ;; (at your option) any later version.
  11. ;; GNU Emacs is distributed in the hope that it will be useful,
  12. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. ;; GNU General Public License for more details.
  15. ;; You should have received a copy of the GNU General Public License
  16. ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
  17. ;;; Commentary:
  18. ;; For Outlook mail boxes format, see http://mbx2mbox.sourceforge.net/
  19. ;;; Code:
  20. (require 'nnheader)
  21. (require 'message)
  22. (require 'nnmail)
  23. (require 'nnoo)
  24. (require 'gnus-util)
  25. (require 'mm-util)
  26. (eval-when-compile (require 'cl))
  27. (nnoo-declare nndoc)
  28. (defvoo nndoc-article-type 'guess
  29. "*Type of the file.
  30. One of `mbox', `babyl', `digest', `news', `rnews', `mmdf', `forward',
  31. `rfc934', `rfc822-forward', `mime-parts', `standard-digest',
  32. `slack-digest', `clari-briefs', `nsmail', `outlook', `oe-dbx',
  33. `mailman', `exim-bounce', or `guess'.")
  34. (defvoo nndoc-post-type 'mail
  35. "*Whether the nndoc group is `mail' or `post'.")
  36. (defvoo nndoc-open-document-hook 'nnheader-ms-strip-cr
  37. "Hook run after opening a document.
  38. The default function removes all trailing carriage returns
  39. from the document.")
  40. (defvar nndoc-type-alist
  41. `((mmdf
  42. (article-begin . "^\^A\^A\^A\^A\n")
  43. (body-end . "^\^A\^A\^A\^A\n"))
  44. (mime-digest
  45. (article-begin . "")
  46. (head-begin . "^ ?\n")
  47. (head-end . "^ ?$")
  48. (body-end . "")
  49. (file-end . "")
  50. (subtype digest guess))
  51. (nsmail
  52. (article-begin . "^From - "))
  53. (news
  54. (article-begin . "^Path:"))
  55. (rnews
  56. (article-begin . "^#! *rnews +\\([0-9]+\\) *\n")
  57. (body-end-function . nndoc-rnews-body-end))
  58. (mbox
  59. (article-begin-function . nndoc-mbox-article-begin)
  60. (body-end-function . nndoc-mbox-body-end))
  61. (babyl
  62. (article-begin . "\^_\^L *\n")
  63. (body-end . "\^_")
  64. (body-begin-function . nndoc-babyl-body-begin)
  65. (head-begin-function . nndoc-babyl-head-begin))
  66. (mime-parts
  67. (generate-head-function . nndoc-generate-mime-parts-head)
  68. (article-transform-function . nndoc-transform-mime-parts))
  69. (exim-bounce
  70. (article-begin . "^------ This is a copy of the message, including all the headers. ------\n\n")
  71. (body-end-function . nndoc-exim-bounce-body-end-function))
  72. (rfc934
  73. (article-begin . "^--.*\n+")
  74. (body-end . "^--.*$")
  75. (prepare-body-function . nndoc-unquote-dashes))
  76. (mailman
  77. (article-begin . "^--__--__--\n\nMessage:")
  78. (body-end . "^--__--__--$")
  79. (prepare-body-function . nndoc-unquote-dashes))
  80. (clari-briefs
  81. (article-begin . "^ \\*")
  82. (body-end . "^\t------*[ \t]^*\n^ \\*")
  83. (body-begin . "^\t")
  84. (head-end . "^\t")
  85. (generate-head-function . nndoc-generate-clari-briefs-head)
  86. (article-transform-function . nndoc-transform-clari-briefs))
  87. (standard-digest
  88. (first-article . ,(concat "^" (make-string 70 ?-) "\n *\n+"))
  89. (article-begin . ,(concat "^\n" (make-string 30 ?-) "\n *\n+"))
  90. (prepare-body-function . nndoc-unquote-dashes)
  91. (body-end-function . nndoc-digest-body-end)
  92. (head-end . "^ *$")
  93. (body-begin . "^ *\n")
  94. (file-end . "^End of .*digest.*[0-9].*\n\\*\\*\\|^End of.*Digest *$")
  95. (subtype digest guess))
  96. (slack-digest
  97. (article-begin . "^------------------------------*[\n \t]+")
  98. (head-end . "^ ?$")
  99. (body-end-function . nndoc-digest-body-end)
  100. (body-begin . "^ ?$")
  101. (file-end . "^End of")
  102. (prepare-body-function . nndoc-unquote-dashes)
  103. (subtype digest guess))
  104. (google
  105. (pre-dissection-function . nndoc-decode-content-transfer-encoding)
  106. (article-begin . "^== [0-9]+ of [0-9]+ ==$")
  107. (head-begin . "^Date:")
  108. (head-end . "^$")
  109. (body-end-function . nndoc-digest-body-end)
  110. (body-begin . "^$")
  111. (file-end . "^==============================================================================$")
  112. (prepare-body-function . nndoc-unquote-dashes)
  113. (subtype digest guess))
  114. (lanl-gov-announce
  115. (article-begin . "^\\\\\\\\\n")
  116. (head-begin . "^\\(Paper.*:\\|arXiv:\\)")
  117. (head-end . "\\(^\\\\\\\\.*\n\\|-----------------\\)")
  118. (body-begin . "")
  119. (body-end . "\\(-------------------------------------------------\\|%-%-%-%-%-%-%-%-%-%-%-%-%-%-\\|%%--%%--%%--%%--%%--%%--%%--%%--\\|%%%---%%%---%%%---%%%---\\)")
  120. (file-end . "\\(^Title: Recent Seminal\\|%%%---%%%---%%%---%%%---\\)")
  121. (generate-head-function . nndoc-generate-lanl-gov-head)
  122. (article-transform-function . nndoc-transform-lanl-gov-announce)
  123. (subtype preprints guess))
  124. (git
  125. (file-begin . "\n- Log ---.*")
  126. (article-begin . "^commit ")
  127. (head-begin . "^Author: ")
  128. (body-begin . "^$")
  129. (file-end . "\n-----------------------------------------------------------------------")
  130. (article-transform-function . nndoc-transform-git-article)
  131. (header-transform-function . nndoc-transform-git-headers))
  132. (rfc822-forward
  133. (article-begin . "^\n+")
  134. (body-end-function . nndoc-rfc822-forward-body-end-function)
  135. (generate-head-function . nndoc-rfc822-forward-generate-head)
  136. (generate-article-function . nndoc-rfc822-forward-generate-article))
  137. (outlook
  138. (article-begin-function . nndoc-outlook-article-begin)
  139. (body-end . "\0"))
  140. (oe-dbx ;; Outlook Express DBX format
  141. (dissection-function . nndoc-oe-dbx-dissection)
  142. (generate-head-function . nndoc-oe-dbx-generate-head)
  143. (generate-article-function . nndoc-oe-dbx-generate-article))
  144. (forward
  145. (article-begin . "^-+ \\(Start of \\)?forwarded message.*\n+")
  146. (body-end . "^-+ End \\(of \\)?forwarded message.*$")
  147. (prepare-body-function . nndoc-unquote-dashes))
  148. (mail-in-mail ;; Wild guess on mailer daemon's messages or others
  149. (article-begin-function . nndoc-mail-in-mail-article-begin))
  150. (guess
  151. (guess . t)
  152. (subtype nil))
  153. (digest
  154. (guess . t)
  155. (subtype nil))
  156. (preprints
  157. (guess . t)
  158. (subtype nil))))
  159. (defvar nndoc-binary-file-names ".[Dd][Bb][Xx]$"
  160. "Regexp for binary nndoc file names.")
  161. (defvoo nndoc-file-begin nil)
  162. (defvoo nndoc-first-article nil)
  163. (defvoo nndoc-article-begin nil)
  164. (defvoo nndoc-head-begin nil)
  165. (defvoo nndoc-head-end nil)
  166. (defvoo nndoc-file-end nil)
  167. (defvoo nndoc-body-begin nil)
  168. (defvoo nndoc-body-end-function nil)
  169. (defvoo nndoc-body-begin-function nil)
  170. (defvoo nndoc-head-begin-function nil)
  171. (defvoo nndoc-body-end nil)
  172. ;; nndoc-dissection-alist is a list of sublists. Each sublist holds the
  173. ;; following items. ARTICLE acts as the association key and is an ordinal
  174. ;; starting at 1. HEAD-BEGIN [0], HEAD-END [1], BODY-BEGIN [2] and BODY-END
  175. ;; [3] are positions in the `nndoc' buffer. LINE-COUNT [4] is a count of
  176. ;; lines in the body. For MIME dissections only, ARTICLE-INSERT [5] and
  177. ;; SUMMARY-INSERT [6] give headers to insert for full article or summary line
  178. ;; generation, respectively. Other headers usually follow directly from the
  179. ;; buffer. Value `nil' means no insert.
  180. (defvoo nndoc-dissection-alist nil)
  181. (defvoo nndoc-prepare-body-function nil)
  182. (defvoo nndoc-generate-head-function nil)
  183. (defvoo nndoc-article-transform-function nil)
  184. (defvoo nndoc-header-transform-function nil)
  185. (defvoo nndoc-article-begin-function nil)
  186. (defvoo nndoc-generate-article-function nil)
  187. (defvoo nndoc-dissection-function nil)
  188. (defvoo nndoc-pre-dissection-function nil)
  189. (defvoo nndoc-status-string "")
  190. (defvoo nndoc-group-alist nil)
  191. (defvoo nndoc-current-buffer nil
  192. "Current nndoc news buffer.")
  193. (defvoo nndoc-address nil)
  194. (defconst nndoc-version "nndoc 1.0"
  195. "nndoc version.")
  196. ;;; Interface functions
  197. (nnoo-define-basics nndoc)
  198. (deffoo nndoc-retrieve-headers (articles &optional newsgroup server fetch-old)
  199. (when (nndoc-possibly-change-buffer newsgroup server)
  200. (with-current-buffer nntp-server-buffer
  201. (erase-buffer)
  202. (let (article entry)
  203. (if (stringp (car articles))
  204. 'headers
  205. (while articles
  206. (when (setq entry (cdr (assq (setq article (pop articles))
  207. nndoc-dissection-alist)))
  208. (let ((start (point)))
  209. (insert (format "221 %d Article retrieved.\n" article))
  210. (if nndoc-generate-head-function
  211. (funcall nndoc-generate-head-function article)
  212. (insert-buffer-substring
  213. nndoc-current-buffer (car entry) (nth 1 entry)))
  214. (goto-char (point-max))
  215. (unless (eq (char-after (1- (point))) ?\n)
  216. (insert "\n"))
  217. (insert (format "Lines: %d\n" (nth 4 entry)))
  218. (insert ".\n")
  219. (when nndoc-header-transform-function
  220. (save-excursion
  221. (save-restriction
  222. (narrow-to-region start (point))
  223. (funcall nndoc-header-transform-function entry)))))))
  224. (nnheader-fold-continuation-lines)
  225. 'headers)))))
  226. (deffoo nndoc-request-article (article &optional newsgroup server buffer)
  227. (nndoc-possibly-change-buffer newsgroup server)
  228. (save-excursion
  229. (let ((buffer (or buffer nntp-server-buffer))
  230. (entry (cdr (assq article nndoc-dissection-alist)))
  231. beg)
  232. (set-buffer buffer)
  233. (erase-buffer)
  234. (when entry
  235. (cond
  236. ((stringp article) nil)
  237. (nndoc-generate-article-function
  238. (funcall nndoc-generate-article-function article))
  239. (t
  240. (insert-buffer-substring
  241. nndoc-current-buffer (car entry) (nth 1 entry))
  242. (insert "\n")
  243. (setq beg (point))
  244. (insert-buffer-substring
  245. nndoc-current-buffer (nth 2 entry) (nth 3 entry))
  246. (goto-char beg)
  247. (when nndoc-prepare-body-function
  248. (funcall nndoc-prepare-body-function))
  249. (when nndoc-article-transform-function
  250. (funcall nndoc-article-transform-function article))
  251. t))))))
  252. (deffoo nndoc-request-group (group &optional server dont-check info)
  253. "Select news GROUP."
  254. (let (number)
  255. (cond
  256. ((not (nndoc-possibly-change-buffer group server))
  257. (nnheader-report 'nndoc "No such file or buffer: %s"
  258. nndoc-address))
  259. (dont-check
  260. (nnheader-report 'nndoc "Selected group %s" group)
  261. t)
  262. ((zerop (setq number (length nndoc-dissection-alist)))
  263. (nndoc-close-group group)
  264. (nnheader-report 'nndoc "No articles in group %s" group))
  265. (t
  266. (nnheader-insert "211 %d %d %d %s\n" number 1 number group)))))
  267. (deffoo nndoc-retrieve-groups (groups &optional server)
  268. (dolist (group groups)
  269. (nndoc-request-group group server))
  270. t)
  271. (deffoo nndoc-request-type (group &optional article)
  272. (cond ((not article) 'unknown)
  273. (nndoc-post-type nndoc-post-type)
  274. (t 'unknown)))
  275. (deffoo nndoc-close-group (group &optional server)
  276. (nndoc-possibly-change-buffer group server)
  277. (and nndoc-current-buffer
  278. (buffer-name nndoc-current-buffer)
  279. (kill-buffer nndoc-current-buffer))
  280. (setq nndoc-group-alist (delq (assoc group nndoc-group-alist)
  281. nndoc-group-alist))
  282. (setq nndoc-current-buffer nil)
  283. (nnoo-close-server 'nndoc server)
  284. (setq nndoc-dissection-alist nil)
  285. t)
  286. (deffoo nndoc-request-list (&optional server)
  287. t)
  288. (deffoo nndoc-request-newgroups (date &optional server)
  289. nil)
  290. (deffoo nndoc-request-list-newsgroups (&optional server)
  291. nil)
  292. ;;; Internal functions.
  293. (defun nndoc-possibly-change-buffer (group source)
  294. (let (buf)
  295. (cond
  296. ;; The current buffer is this group's buffer.
  297. ((and nndoc-current-buffer
  298. (buffer-name nndoc-current-buffer)
  299. (eq nndoc-current-buffer
  300. (setq buf (cdr (assoc group nndoc-group-alist))))))
  301. ;; We change buffers by taking an old from the group alist.
  302. ;; `source' is either a string (a file name) or a buffer object.
  303. (buf
  304. (setq nndoc-current-buffer buf))
  305. ;; It's a totally new group.
  306. ((or (and (bufferp nndoc-address)
  307. (buffer-name nndoc-address))
  308. (and (stringp nndoc-address)
  309. (file-exists-p nndoc-address)
  310. (not (file-directory-p nndoc-address))))
  311. (push (cons group (setq nndoc-current-buffer
  312. (get-buffer-create
  313. (concat " *nndoc " group "*"))))
  314. nndoc-group-alist)
  315. (setq nndoc-dissection-alist nil)
  316. (with-current-buffer nndoc-current-buffer
  317. (erase-buffer)
  318. (if (and (stringp nndoc-address)
  319. (string-match nndoc-binary-file-names nndoc-address))
  320. (let ((coding-system-for-read 'binary))
  321. (mm-insert-file-contents nndoc-address))
  322. (if (stringp nndoc-address)
  323. (nnheader-insert-file-contents nndoc-address)
  324. (insert-buffer-substring nndoc-address))
  325. (run-hooks 'nndoc-open-document-hook)))))
  326. ;; Initialize the nndoc structures according to this new document.
  327. (when (and nndoc-current-buffer
  328. (not nndoc-dissection-alist))
  329. (with-current-buffer nndoc-current-buffer
  330. (nndoc-set-delims)
  331. (if (eq nndoc-article-type 'mime-parts)
  332. (nndoc-dissect-mime-parts)
  333. (nndoc-dissect-buffer))))
  334. (unless nndoc-current-buffer
  335. (nndoc-close-server))
  336. ;; Return whether we managed to select a file.
  337. nndoc-current-buffer))
  338. ;;;
  339. ;;; Deciding what document type we have
  340. ;;;
  341. (defun nndoc-set-delims ()
  342. "Set the nndoc delimiter variables according to the type of the document."
  343. (let ((vars '(nndoc-file-begin
  344. nndoc-first-article
  345. nndoc-article-begin-function
  346. nndoc-head-begin nndoc-head-end
  347. nndoc-file-end nndoc-article-begin
  348. nndoc-body-begin nndoc-body-end-function nndoc-body-end
  349. nndoc-prepare-body-function nndoc-article-transform-function
  350. nndoc-header-transform-function
  351. nndoc-generate-head-function nndoc-body-begin-function
  352. nndoc-head-begin-function
  353. nndoc-generate-article-function
  354. nndoc-dissection-function
  355. nndoc-pre-dissection-function)))
  356. (while vars
  357. (set (pop vars) nil)))
  358. (let (defs)
  359. ;; Guess away until we find the real file type.
  360. (while (assq 'guess (setq defs (cdr (assq nndoc-article-type
  361. nndoc-type-alist))))
  362. (setq nndoc-article-type (nndoc-guess-type nndoc-article-type)))
  363. ;; Set the nndoc variables.
  364. (while defs
  365. (set (intern (format "nndoc-%s" (caar defs)))
  366. (cdr (pop defs))))))
  367. (defun nndoc-guess-type (subtype)
  368. (let ((alist nndoc-type-alist)
  369. results result entry)
  370. (while (and (not result)
  371. (setq entry (pop alist)))
  372. (when (memq subtype (or (cdr (assq 'subtype entry)) '(guess)))
  373. (goto-char (point-min))
  374. ;; Remove blank lines.
  375. (while (eq (following-char) ?\n)
  376. (delete-char 1))
  377. (when (numberp (setq result (funcall (intern
  378. (format "nndoc-%s-type-p"
  379. (car entry))))))
  380. (push (cons result entry) results)
  381. (setq result nil))))
  382. (unless (or result results)
  383. (error "Document is not of any recognized type"))
  384. (if result
  385. (car entry)
  386. (cadar (last (sort results 'car-less-than-car))))))
  387. ;;;
  388. ;;; Built-in type predicates and functions
  389. ;;;
  390. (defun nndoc-mbox-type-p ()
  391. (when (looking-at message-unix-mail-delimiter)
  392. t))
  393. (defun nndoc-mbox-article-begin ()
  394. (when (re-search-forward (concat "^" message-unix-mail-delimiter) nil t)
  395. (goto-char (match-beginning 0))))
  396. (defun nndoc-mbox-body-end ()
  397. (let ((beg (point))
  398. len end)
  399. (when
  400. (save-excursion
  401. (and (re-search-backward
  402. (concat "^" message-unix-mail-delimiter) nil t)
  403. (setq end (point))
  404. (search-forward "\n\n" beg t)
  405. (re-search-backward
  406. "^Content-Length:[ \t]*\\([0-9]+\\) *$" end t)
  407. (setq len (string-to-number (match-string 1)))
  408. (search-forward "\n\n" beg t)
  409. (unless (= (setq len (+ (point) len)) (point-max))
  410. (and (< len (point-max))
  411. (goto-char len)
  412. (looking-at message-unix-mail-delimiter)))))
  413. (goto-char len))))
  414. (defun nndoc-mmdf-type-p ()
  415. (when (looking-at "\^A\^A\^A\^A$")
  416. t))
  417. (defun nndoc-news-type-p ()
  418. (when (looking-at "^Path:.*\n")
  419. t))
  420. (defun nndoc-rnews-type-p ()
  421. (when (looking-at "#! *rnews")
  422. t))
  423. (defun nndoc-rnews-body-end ()
  424. (and (re-search-backward nndoc-article-begin nil t)
  425. (forward-line 1)
  426. (goto-char (+ (point) (string-to-number (match-string 1))))))
  427. (defun nndoc-google-type-p ()
  428. (when (re-search-forward "^=3D=3D 1 of [0-9]+ =3D=3D$" nil t)
  429. t))
  430. (defun nndoc-decode-content-transfer-encoding ()
  431. (let ((encoding
  432. (save-restriction
  433. (message-narrow-to-head)
  434. (message-fetch-field "content-transfer-encoding"))))
  435. (when (and encoding
  436. (search-forward "\n\n" nil t))
  437. (save-restriction
  438. (narrow-to-region (point) (point-max))
  439. (mm-decode-content-transfer-encoding
  440. (intern (downcase (mail-header-strip encoding))))))))
  441. (defun nndoc-babyl-type-p ()
  442. (when (re-search-forward "\^_\^L *\n" nil t)
  443. t))
  444. (defun nndoc-babyl-body-begin ()
  445. (re-search-forward "^\n" nil t)
  446. (when (looking-at "\\*\\*\\* EOOH \\*\\*\\*")
  447. (let ((next (or (save-excursion
  448. (re-search-forward nndoc-article-begin nil t))
  449. (point-max))))
  450. (unless (re-search-forward "^\n" next t)
  451. (goto-char next)
  452. (forward-line -1)
  453. (insert "\n")
  454. (forward-line -1)))))
  455. (defun nndoc-babyl-head-begin ()
  456. (when (re-search-forward "^[0-9].*\n" nil t)
  457. (when (looking-at "\\*\\*\\* EOOH \\*\\*\\*")
  458. (forward-line 1))
  459. t))
  460. (defun nndoc-forward-type-p ()
  461. (when (and (re-search-forward "^-+ \\(Start of \\)?forwarded message.*\n+"
  462. nil t)
  463. (looking-at "[\r\n]*[a-zA-Z][a-zA-Z0-9-]*:\\|^>?From "))
  464. t))
  465. (defun nndoc-rfc934-type-p ()
  466. (when (and (re-search-forward "^-+ Start of forwarded.*\n+" nil t)
  467. (not (re-search-forward "^Subject:.*digest" nil t))
  468. (not (re-search-backward "^From:" nil t 2))
  469. (not (re-search-forward "^From:" nil t 2)))
  470. t))
  471. (defun nndoc-mailman-type-p ()
  472. (when (re-search-forward "^--__--__--\n+" nil t)
  473. t))
  474. (defun nndoc-rfc822-forward-type-p ()
  475. (save-restriction
  476. (message-narrow-to-head)
  477. (when (re-search-forward "^Content-Type: *message/rfc822" nil t)
  478. t)))
  479. (defun nndoc-rfc822-forward-body-end-function ()
  480. (goto-char (point-max)))
  481. (defun nndoc-rfc822-forward-generate-article (article &optional head)
  482. (let ((entry (cdr (assq article nndoc-dissection-alist)))
  483. (begin (point))
  484. encoding)
  485. (with-current-buffer nndoc-current-buffer
  486. (save-restriction
  487. (message-narrow-to-head)
  488. (setq encoding (message-fetch-field "content-transfer-encoding"))))
  489. (insert-buffer-substring nndoc-current-buffer (car entry) (nth 3 entry))
  490. (when encoding
  491. (save-restriction
  492. (narrow-to-region begin (point-max))
  493. (mm-decode-content-transfer-encoding
  494. (intern (downcase (mail-header-strip encoding))))))
  495. (when head
  496. (goto-char begin)
  497. (when (search-forward "\n\n" nil t)
  498. (delete-region (1- (point)) (point-max)))))
  499. t)
  500. (defun nndoc-rfc822-forward-generate-head (article)
  501. (nndoc-rfc822-forward-generate-article article 'head))
  502. (defun nndoc-mime-parts-type-p ()
  503. (let ((case-fold-search t)
  504. (limit (search-forward "\n\n" nil t)))
  505. (goto-char (point-min))
  506. (when (and limit
  507. (re-search-forward
  508. (concat "\
  509. ^Content-Type:[ \t]*multipart/[a-z]+ *; *\\(\\(\n[ \t]\\)?.*;\\)*"
  510. "\\(\n[ \t]\\)?[ \t]*boundary=\"?[^\"\n]*[^\" \t\n]")
  511. limit t))
  512. t)))
  513. (defun nndoc-transform-mime-parts (article)
  514. (let* ((entry (cdr (assq article nndoc-dissection-alist)))
  515. (headers (nth 5 entry)))
  516. (when headers
  517. (goto-char (point-min))
  518. (insert headers))))
  519. (defun nndoc-generate-mime-parts-head (article)
  520. (let* ((entry (cdr (assq article nndoc-dissection-alist)))
  521. (headers (nth 6 entry)))
  522. (save-restriction
  523. (narrow-to-region (point) (point))
  524. (insert-buffer-substring
  525. nndoc-current-buffer (car entry) (nth 1 entry))
  526. (goto-char (point-max)))
  527. (when headers
  528. (insert headers))))
  529. (defun nndoc-clari-briefs-type-p ()
  530. (when (let ((case-fold-search nil))
  531. (re-search-forward "^\t[^a-z]+ ([^a-z]+) --" nil t))
  532. t))
  533. (defun nndoc-transform-clari-briefs (article)
  534. (goto-char (point-min))
  535. (when (looking-at " *\\*\\(.*\\)\n")
  536. (replace-match "" t t))
  537. (nndoc-generate-clari-briefs-head article))
  538. (defun nndoc-generate-clari-briefs-head (article)
  539. (let ((entry (cdr (assq article nndoc-dissection-alist)))
  540. subject from)
  541. (with-current-buffer nndoc-current-buffer
  542. (save-restriction
  543. (narrow-to-region (car entry) (nth 3 entry))
  544. (goto-char (point-min))
  545. (when (looking-at " *\\*\\(.*\\)$")
  546. (setq subject (match-string 1))
  547. (when (string-match "[ \t]+$" subject)
  548. (setq subject (substring subject 0 (match-beginning 0)))))
  549. (when
  550. (let ((case-fold-search nil))
  551. (re-search-forward
  552. "^\t\\([^a-z]+\\(,[^(]+\\)? ([^a-z]+)\\) --" nil t))
  553. (setq from (match-string 1)))))
  554. (insert "From: " "clari@clari.net (" (or from "unknown") ")"
  555. "\nSubject: " (or subject "(no subject)") "\n")))
  556. (defun nndoc-exim-bounce-type-p ()
  557. (and (re-search-forward "^------ This is a copy of the message, including all the headers. ------" nil t)
  558. t))
  559. (defun nndoc-exim-bounce-body-end-function ()
  560. (goto-char (point-max)))
  561. (defun nndoc-mime-digest-type-p ()
  562. (let ((case-fold-search t)
  563. boundary-id b-delimiter entry)
  564. (when (and
  565. (re-search-forward
  566. (concat "^Content-Type: *multipart/digest;[ \t\n]*[ \t]"
  567. "boundary=\"?\\([^\"\n]*[^\" \t\n]\\)")
  568. nil t)
  569. (match-beginning 1))
  570. (setq boundary-id (match-string 1)
  571. b-delimiter (concat "\n--" boundary-id "[ \t]*$"))
  572. (setq entry (assq 'mime-digest nndoc-type-alist))
  573. (setcdr entry
  574. (list
  575. (cons 'head-begin "^ ?\n")
  576. (cons 'head-end "^ ?$")
  577. (cons 'body-begin "^ ?\n")
  578. (cons 'article-begin b-delimiter)
  579. (cons 'body-end-function 'nndoc-digest-body-end)
  580. (cons 'file-end (concat "^--" boundary-id "--[ \t]*$"))))
  581. t)))
  582. (defun nndoc-standard-digest-type-p ()
  583. (when (and (re-search-forward (concat "^" (make-string 70 ?-) "\n\n") nil t)
  584. (re-search-forward
  585. (concat "\n\n" (make-string 30 ?-) "\n\n") nil t))
  586. t))
  587. (defun nndoc-digest-body-end ()
  588. (and (re-search-forward nndoc-article-begin nil t)
  589. (goto-char (match-beginning 0))))
  590. (defun nndoc-slack-digest-type-p ()
  591. 0)
  592. (defun nndoc-git-type-p ()
  593. (and (search-forward "\n- Log ---" nil t)
  594. (search-forward "\ncommit " nil t)
  595. (search-forward "\nAuthor: " nil t)))
  596. (defun nndoc-transform-git-article (article)
  597. (goto-char (point-min))
  598. (when (re-search-forward "^Author: " nil t)
  599. (replace-match "From: " t t)))
  600. (defun nndoc-transform-git-headers (entry)
  601. (goto-char (point-min))
  602. (when (re-search-forward "^Author: " nil t)
  603. (replace-match "From: " t t))
  604. (let (subject)
  605. (with-current-buffer nndoc-current-buffer
  606. (goto-char (car entry))
  607. (when (search-forward "\n\n" nil t)
  608. (setq subject (buffer-substring (point) (line-end-position)))))
  609. (when subject
  610. (goto-char (point-min))
  611. (forward-line 1)
  612. (insert (format "Subject: %s\n" subject)))))
  613. (defun nndoc-lanl-gov-announce-type-p ()
  614. (when (let ((case-fold-search nil))
  615. (re-search-forward "^\\\\\\\\\n\\(Paper\\( (\\*cross-listing\\*)\\)?: [a-zA-Z-\\.]+/[0-9]+\\|arXiv:\\)" nil t))
  616. t))
  617. (defun nndoc-transform-lanl-gov-announce (article)
  618. (let ((case-fold-search nil))
  619. (goto-char (point-max))
  620. (when (re-search-backward "^\\\\\\\\ +( *\\([^ ]*\\) , *\\([^ ]*\\))" nil t)
  621. (replace-match "\n\nGet it at \\1 (\\2)" t nil))
  622. (goto-char (point-min))
  623. (while (re-search-forward "^\\\\\\\\$" nil t)
  624. (replace-match "" t nil))
  625. (goto-char (point-min))
  626. (when (re-search-forward "^replaced with revised version +\\(.*[^ ]\\) +" nil t)
  627. (replace-match "Date: \\1 (revised) " t nil))
  628. (goto-char (point-min))
  629. (unless (re-search-forward "^From" nil t)
  630. (goto-char (point-min))
  631. (when (re-search-forward "^Authors?: \\(.*\\)" nil t)
  632. (goto-char (point-min))
  633. (insert "From: " (match-string 1) "\n")))
  634. (when (re-search-forward "^arXiv:" nil t)
  635. (replace-match "Paper: arXiv:" t nil))))
  636. (defun nndoc-generate-lanl-gov-head (article)
  637. (let ((entry (cdr (assq article nndoc-dissection-alist)))
  638. (from "<no address given>")
  639. subject date)
  640. (with-current-buffer nndoc-current-buffer
  641. (save-restriction
  642. (narrow-to-region (car entry) (nth 1 entry))
  643. (goto-char (point-min))
  644. (when (looking-at "^\\(Paper.*: \\|arXiv:\\)\\([0-9a-zA-Z-\\./]+\\)")
  645. (setq subject (concat " (" (match-string 2) ")"))
  646. (when (re-search-forward "^From: \\(.*\\)" nil t)
  647. (setq from (concat "<"
  648. (cadr (funcall gnus-extract-address-components
  649. (match-string 1))) ">")))
  650. (if (re-search-forward "^Date: +\\([^(]*\\)" nil t)
  651. (setq date (match-string 1))
  652. (when (re-search-forward "^replaced with revised version +\\([^(]*\\)" nil t)
  653. (setq date (match-string 1))))
  654. (when (re-search-forward "^Title: \\([^\f]*\\)\nAuthors?: \\(.*\\)"
  655. nil t)
  656. (setq subject (concat (match-string 1) subject))
  657. (setq from (concat (match-string 2) " " from))))))
  658. (while (and from (string-match "(\[^)\]*)" from))
  659. (setq from (replace-match "" t t from)))
  660. (insert "From: " (or from "unknown")
  661. "\nSubject: " (or subject "(no subject)") "\n")
  662. (if date (insert "Date: " date))))
  663. (defun nndoc-nsmail-type-p ()
  664. (when (looking-at "From - ")
  665. t))
  666. (defun nndoc-outlook-article-begin ()
  667. (prog1 (re-search-forward "From:\\|Received:" nil t)
  668. (goto-char (match-beginning 0))))
  669. (defun nndoc-outlook-type-p ()
  670. ;; FIXME: Is JMF the magic of outlook mailbox? -- ShengHuo.
  671. (looking-at "JMF"))
  672. (defun nndoc-oe-dbx-type-p ()
  673. (looking-at (mm-string-to-multibyte "\317\255\022\376")))
  674. (defun nndoc-read-little-endian ()
  675. (+ (prog1 (char-after) (forward-char 1))
  676. (lsh (prog1 (char-after) (forward-char 1)) 8)
  677. (lsh (prog1 (char-after) (forward-char 1)) 16)
  678. (lsh (prog1 (char-after) (forward-char 1)) 24)))
  679. (defun nndoc-oe-dbx-decode-block ()
  680. (list
  681. (nndoc-read-little-endian) ;; this address
  682. (nndoc-read-little-endian) ;; next address offset
  683. (nndoc-read-little-endian) ;; blocksize
  684. (nndoc-read-little-endian))) ;; next address
  685. (defun nndoc-oe-dbx-dissection ()
  686. (let ((i 0) blk p tp)
  687. (goto-char 60117) ;; 0x0000EAD4+1
  688. (setq p (point))
  689. (unless (eobp)
  690. (setq blk (nndoc-oe-dbx-decode-block)))
  691. (while (and blk (> (car blk) 0) (or (zerop (nth 3 blk))
  692. (> (nth 3 blk) p)))
  693. (push (list (incf i) p nil nil nil 0) nndoc-dissection-alist)
  694. (while (and (> (car blk) 0) (> (nth 3 blk) p))
  695. (goto-char (1+ (nth 3 blk)))
  696. (setq blk (nndoc-oe-dbx-decode-block)))
  697. (if (or (<= (car blk) p)
  698. (<= (nth 1 blk) 0)
  699. (not (zerop (nth 3 blk))))
  700. (setq blk nil)
  701. (setq tp (+ (car blk) (nth 1 blk) 17))
  702. (if (or (<= tp p) (>= tp (point-max)))
  703. (setq blk nil)
  704. (goto-char tp)
  705. (setq p tp
  706. blk (nndoc-oe-dbx-decode-block)))))))
  707. (defun nndoc-oe-dbx-generate-article (article &optional head)
  708. (let ((entry (cdr (assq article nndoc-dissection-alist)))
  709. (cur (current-buffer))
  710. (begin (point))
  711. blk p)
  712. (with-current-buffer nndoc-current-buffer
  713. (setq p (car entry))
  714. (while (> p (point-min))
  715. (goto-char p)
  716. (setq blk (nndoc-oe-dbx-decode-block))
  717. (setq p (point))
  718. (with-current-buffer cur
  719. (insert-buffer-substring nndoc-current-buffer p (+ p (nth 2 blk))))
  720. (setq p (1+ (nth 3 blk)))))
  721. (goto-char begin)
  722. (while (re-search-forward "\r$" nil t)
  723. (delete-char -1))
  724. (when head
  725. (goto-char begin)
  726. (when (search-forward "\n\n" nil t)
  727. (setcar (cddddr entry) (count-lines (point) (point-max)))
  728. (delete-region (1- (point)) (point-max))))
  729. t))
  730. (defun nndoc-oe-dbx-generate-head (article)
  731. (nndoc-oe-dbx-generate-article article 'head))
  732. (defun nndoc-mail-in-mail-type-p ()
  733. (let (found)
  734. (save-excursion
  735. (catch 'done
  736. (while (re-search-forward "\n\n[-A-Za-z0-9]+:" nil t)
  737. (setq found 0)
  738. (forward-line)
  739. (while (looking-at "[ \t]\\|[-A-Za-z0-9]+:")
  740. (if (looking-at "[-A-Za-z0-9]+:")
  741. (setq found (1+ found)))
  742. (forward-line))
  743. (if (and (> found 0) (looking-at "\n"))
  744. (throw 'done 9999)))
  745. nil))))
  746. (defun nndoc-mail-in-mail-article-begin ()
  747. (let (point found)
  748. (if (catch 'done
  749. (while (re-search-forward "\n\n\\([-A-Za-z0-9]+:\\)" nil t)
  750. (setq found 0)
  751. (setq point (match-beginning 1))
  752. (forward-line)
  753. (while (looking-at "[ \t]\\|[-A-Za-z0-9]+:")
  754. (if (looking-at "[-A-Za-z0-9]+:")
  755. (setq found (1+ found)))
  756. (forward-line))
  757. (if (and (> found 0) (looking-at "\n"))
  758. (throw 'done t)))
  759. nil)
  760. (goto-char point))))
  761. (deffoo nndoc-request-accept-article (group &optional server last)
  762. nil)
  763. ;;;
  764. ;;; Functions for dissecting the documents
  765. ;;;
  766. (defun nndoc-search (regexp)
  767. (prog1
  768. (re-search-forward regexp nil t)
  769. (beginning-of-line)))
  770. (defun nndoc-dissect-buffer ()
  771. "Go through the document and partition it into heads/bodies/articles."
  772. (let ((i 0)
  773. (first t)
  774. art-begin head-begin head-end body-begin body-end)
  775. (setq nndoc-dissection-alist nil)
  776. (with-current-buffer nndoc-current-buffer
  777. (goto-char (point-min))
  778. ;; Remove blank lines.
  779. (while (eq (following-char) ?\n)
  780. (delete-char 1))
  781. (when nndoc-pre-dissection-function
  782. (save-excursion
  783. (funcall nndoc-pre-dissection-function)))
  784. (if nndoc-dissection-function
  785. (funcall nndoc-dissection-function)
  786. ;; Find the beginning of the file.
  787. (when nndoc-file-begin
  788. (nndoc-search nndoc-file-begin))
  789. ;; Go through the file.
  790. (while (if (and first nndoc-first-article)
  791. (nndoc-search nndoc-first-article)
  792. (if art-begin
  793. (goto-char art-begin)
  794. (nndoc-article-begin)))
  795. (setq first nil
  796. art-begin nil)
  797. (cond (nndoc-head-begin-function
  798. (funcall nndoc-head-begin-function))
  799. (nndoc-head-begin
  800. (nndoc-search nndoc-head-begin)))
  801. (if (or (eobp)
  802. (and nndoc-file-end
  803. (looking-at nndoc-file-end)))
  804. (goto-char (point-max))
  805. (setq head-begin (point))
  806. (nndoc-search (or nndoc-head-end "^$"))
  807. (setq head-end (point))
  808. (if nndoc-body-begin-function
  809. (funcall nndoc-body-begin-function)
  810. (nndoc-search (or nndoc-body-begin "^\n")))
  811. (setq body-begin (point))
  812. (or (and nndoc-body-end-function
  813. (funcall nndoc-body-end-function))
  814. (and nndoc-body-end
  815. (nndoc-search nndoc-body-end))
  816. (and (nndoc-article-begin)
  817. (setq art-begin (point)))
  818. (progn
  819. (goto-char (point-max))
  820. (when nndoc-file-end
  821. (and (re-search-backward nndoc-file-end nil t)
  822. (beginning-of-line)))))
  823. (setq body-end (point))
  824. (push (list (incf i) head-begin head-end body-begin body-end
  825. (count-lines body-begin body-end))
  826. nndoc-dissection-alist)))))
  827. (setq nndoc-dissection-alist (nreverse nndoc-dissection-alist))))
  828. (defun nndoc-article-begin ()
  829. (if nndoc-article-begin-function
  830. (funcall nndoc-article-begin-function)
  831. (ignore-errors
  832. (nndoc-search nndoc-article-begin))))
  833. (defun nndoc-unquote-dashes ()
  834. "Unquote quoted non-separators in digests."
  835. (while (re-search-forward "^- -"nil t)
  836. (replace-match "-" t t)))
  837. ;; Against compiler warnings.
  838. (defvar nndoc-mime-split-ordinal)
  839. (defun nndoc-dissect-mime-parts ()
  840. "Go through a MIME composite article and partition it into sub-articles.
  841. When a MIME entity contains sub-entities, dissection produces one article for
  842. the header of this entity, and one article per sub-entity."
  843. (setq nndoc-dissection-alist nil
  844. nndoc-mime-split-ordinal 0)
  845. (with-current-buffer nndoc-current-buffer
  846. (nndoc-dissect-mime-parts-sub (point-min) (point-max) nil nil nil)))
  847. (defun nndoc-dissect-mime-parts-sub (head-begin body-end article-insert
  848. position parent)
  849. "Dissect an entity, within a composite MIME message.
  850. The complete message or MIME entity extends from HEAD-BEGIN to BODY-END.
  851. ARTICLE-INSERT should be added at beginning for generating a full article.
  852. The string POSITION holds a dotted decimal representation of the article
  853. position in the hierarchical structure, it is nil for the outer entity.
  854. PARENT is the message-ID of the parent summary line, or nil for none."
  855. (let ((case-fold-search t)
  856. (message-id (nnmail-message-id))
  857. head-end body-begin summary-insert message-rfc822 multipart-any
  858. subject content-type type subtype boundary-regexp)
  859. ;; Gracefully handle a missing body.
  860. (goto-char head-begin)
  861. (if (or (and (eq (char-after) ?\n) (or (forward-char 1) t))
  862. (search-forward "\n\n" body-end t))
  863. (setq head-end (1- (point))
  864. body-begin (point))
  865. (setq head-end body-end
  866. body-begin body-end))
  867. (narrow-to-region head-begin head-end)
  868. ;; Save MIME attributes.
  869. (goto-char head-begin)
  870. (setq content-type (message-fetch-field "Content-Type"))
  871. (when content-type
  872. (when (string-match
  873. "^ *\\([^ \t\n/;]+\\)/\\([^ \t\n/;]+\\)" content-type)
  874. (setq type (downcase (match-string 1 content-type))
  875. subtype (downcase (match-string 2 content-type))
  876. message-rfc822 (and (string= type "message")
  877. (string= subtype "rfc822"))
  878. multipart-any (string= type "multipart")))
  879. (when (string-match ";[ \t\n]*name=\\([^ \t\n;]+\\)" content-type)
  880. (setq subject (match-string 1 content-type)))
  881. (when (string-match "boundary=\"?\\([^\"\n]*[^\" \t\n]\\)" content-type)
  882. (setq boundary-regexp (concat "^--"
  883. (regexp-quote
  884. (match-string 1 content-type))
  885. "\\(--\\)?[ \t]*\n"))))
  886. (unless subject
  887. (when (or multipart-any (not article-insert))
  888. (setq subject (message-fetch-field "Subject"))))
  889. (unless type
  890. (setq type "text"
  891. subtype "plain"))
  892. ;; Prepare the article and summary inserts.
  893. (unless article-insert
  894. (setq article-insert (buffer-string)
  895. head-end head-begin))
  896. ;; Fix MIME-Version
  897. (unless (string-match "MIME-Version:" article-insert)
  898. (setq article-insert
  899. (concat article-insert "MIME-Version: 1.0\n")))
  900. (setq summary-insert article-insert)
  901. ;; - summary Subject.
  902. (setq summary-insert
  903. (let ((line (concat "Subject: <" position
  904. (and position multipart-any ".")
  905. (and multipart-any "*")
  906. (and (or position multipart-any) " ")
  907. (cond ((string= subtype "plain") type)
  908. ((string= subtype "basic") type)
  909. (t subtype))
  910. ">"
  911. (and subject " ")
  912. subject
  913. "\n")))
  914. (if (string-match "Subject:.*\n\\([ \t].*\n\\)*" summary-insert)
  915. (replace-match line t t summary-insert)
  916. (concat summary-insert line))))
  917. ;; - summary Message-ID.
  918. (setq summary-insert
  919. (let ((line (concat "Message-ID: " message-id "\n")))
  920. (if (string-match "Message-ID:.*\n\\([ \t].*\n\\)*" summary-insert)
  921. (replace-match line t t summary-insert)
  922. (concat summary-insert line))))
  923. ;; - summary References.
  924. (when parent
  925. (setq summary-insert
  926. (let ((line (concat "References: " parent "\n")))
  927. (if (string-match "References:.*\n\\([ \t].*\n\\)*"
  928. summary-insert)
  929. (replace-match line t t summary-insert)
  930. (concat summary-insert line)))))
  931. ;; Generate dissection information for this entity.
  932. (push (list (incf nndoc-mime-split-ordinal)
  933. head-begin head-end body-begin body-end
  934. (count-lines body-begin body-end)
  935. article-insert summary-insert)
  936. nndoc-dissection-alist)
  937. ;; Recurse for all sub-entities, if any.
  938. (widen)
  939. (cond
  940. (message-rfc822
  941. (save-excursion
  942. (nndoc-dissect-mime-parts-sub body-begin body-end nil
  943. position message-id)))
  944. ((and multipart-any boundary-regexp)
  945. (let ((part-counter 0)
  946. part-begin part-end eof-flag)
  947. (while (string-match "\
  948. ^\\(Lines\\|Content-\\(Type\\|Transfer-Encoding\\|Disposition\\)\\):.*\n\\([ \t].*\n\\)*"
  949. article-insert)
  950. (setq article-insert (replace-match "" t t article-insert)))
  951. (let ((case-fold-search nil))
  952. (goto-char body-begin)
  953. (setq eof-flag (not (re-search-forward boundary-regexp body-end t)))
  954. (while (not eof-flag)
  955. (setq part-begin (point))
  956. (cond ((re-search-forward boundary-regexp body-end t)
  957. (or (not (match-string 1))
  958. (string= (match-string 1) "")
  959. (setq eof-flag t))
  960. (forward-line -1)
  961. (setq part-end (point))
  962. (forward-line 1))
  963. (t (setq part-end body-end
  964. eof-flag t)))
  965. (save-excursion
  966. (nndoc-dissect-mime-parts-sub
  967. part-begin part-end article-insert
  968. (concat position
  969. (and position ".")
  970. (format "%d" (incf part-counter)))
  971. message-id)))))))))
  972. ;;;###autoload
  973. (defun nndoc-add-type (definition &optional position)
  974. "Add document DEFINITION to the list of nndoc document definitions.
  975. If POSITION is nil or `last', the definition will be added
  976. as the last checked definition, if t or `first', add as the
  977. first definition, and if any other symbol, add after that
  978. symbol in the alist."
  979. ;; First remove any old instances.
  980. (gnus-alist-pull (car definition) nndoc-type-alist)
  981. ;; Then enter the new definition in the proper place.
  982. (cond
  983. ((or (null position) (eq position 'last))
  984. (setq nndoc-type-alist (nconc nndoc-type-alist (list definition))))
  985. ((or (eq position t) (eq position 'first))
  986. (push definition nndoc-type-alist))
  987. (t
  988. (let ((list (memq (assq position nndoc-type-alist)
  989. nndoc-type-alist)))
  990. (unless list
  991. (error "No such position: %s" position))
  992. (setcdr list (cons definition (cdr list)))))))
  993. (provide 'nndoc)
  994. ;;; nndoc.el ends here