-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcite.el
722 lines (607 loc) · 25.1 KB
/
cite.el
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
;;; cite.el --- Citing engine for Gnus
;; This file is NOT part of Emacs.
;; Copyright (C) 2002, 2003, 2004, 2011 Lawrence Mitchell <[email protected]>
;; Filename: cite.el
;; Version: 1.34
;; Author: Lawrence Mitchell <[email protected]>
;; Created: 2002-06-15
;; Keywords: mail news
;; COPYRIGHT NOTICE
;; This program is free software; you can redistribute it and/or
;; modify it under the terms of the GNU General Public License as
;; published by the Free Software Foundation; either version 2 of the
;; License, or (at your option) any later version.
;;
;; This program is distributed in the hope that it will be useful, but
;; WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
;; General Public License for more
;; details. http://www.gnu.org/copyleft/gpl.html
;;
;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs. If you did not, write to the Free Software
;; Foundation, Inc., 675 Mass Ave., Cambridge, MA 02139, USA.
;;; Commentary:
;; This is yet another citing engine for Emacs. It's to trivial-cite
;; what trivial-cite is to supercite (i.e. stripped down). I wrote it
;; because, well, I wanted to see if I could. Doesn't do any fancy
;; guessing of cite-prefixes, just tacks a ">" on the front of the
;; quoted article. See the docstrings of `cite-cite' and
;; `cite-parse-headers' for information on extending cite.
;; Installation:
;; To use this package, you have to make it the default citing
;; function: First make sure cite.el is somewhere on your `load-path',
;; then add: (autoload 'cite-cite "cite" "A simple cite function for
;; Emacs" nil) to your .emacs.
;;
;; If you are a Gnus user:
;; In your ~/.gnus add
;; (setq message-cite-function 'cite-cite)
;; to make message call `cite-cite' to cite articles.
;; Since `cite-cite' also generates an attribution, you probably also
;; want to do:
;; (setq news-reply-header-hook nil)
;; or at least make sure that `news-reply-header-hook' doesn't call a
;; function which creates an attribution line.
;;
;; If you are a VM user:
;; If you use Emacs' mail-mode to compose replies, add the following
;; to your ~/.vm
;; (setq mail-citation-hook 'cite-cite)
;;; History:
;; ChangeLog available.
;;; TODO:
;; Try and refill overly long lines? --- done? see
;; `cite-rewrap-long-lines'.
;;
;; Add better installation instructions.
;;
;; Add a cite-before-run-hook and cite-after-run-hook?
;;
;; Remove need for Gnus specific functions. --- done?
;;; Code:
;;; Stuff we need
(eval-when-compile
(autoload 'timezone-make-date-arpa-standard "timezone"))
;;; User variables.
(defvar cite-prefix ">"
"*Prefix used for citing lines.
If a line is not already cited, a SPC is also added. The default
value, \">\", is the one recommended by Son-of-RFC1036, you may
provoke people by using a non-standard option here.")
(defvar cite-prefix-regexp "[>:}]" ; These seem to be the most
; common.
"*Regexp matching a cite prefix.")
(defvar cite-re-regexp "Re\\|Aw"
"*Regexp matching a \"Reply prefix\".")
(defvar cite-sig-sep-regexp "^-- ?$"
"*Regular expression matching a sig-dash.
You definately want to anchor this to the beginning and end of the
line, so that you wouldn't match something elsewhere -- like that.")
(defvar cite-remove-sig t
"*If non-nil `cite-cite' should remove the signature.
This is the recommended setting since it is generally considered bad
form to quote the signature. Even if you have this set to t, you can
easily reinsert the sig, by calling `cite-reinsert-sig'.")
(defvar cite-remove-trailing-lines t
"*If non-nil, cite will remove trailing blank lines.
A line is considered to be blank if it matches \"^[ \\t]*$\".")
(defvar cite-quote-empty-lines nil
"*If non-nil, cite will add `cite-prefix' to empty lines.
An empty line is checked for with `cite-line-empty-p' (q.v.).")
(defvar cite-rewrap-long-lines nil
"*If non-nil, cite will attempt to rewrap long lines.
The function, `cite-rewrap-long-lines', which does the rewrapping, is
a long way from being perfect, so you may not want to call this from
`cite-cite', but rather interactively if you find a particularly bad
paragraph that needs reformatting.")
(defvar cite-make-attribution t
"*If non-nil `cite-cite' will add an attribution line.
The attribution is added above the cited text. See also
`cite-make-attribution-function'.")
(defvar cite-make-attribution-function 'cite-simple-attribution
"*Function to call to make an attribution line.
This is a function called with no arguments, it can access the values
of various headers parsed by `cite-parse-headers', and stored in
`cite-parsed-headers' (q.v.).")
(defvar cite-headers-to-parse
(list "from" "newsgroups" "subject" "date" "message-id")
"*List of (downcased) header names to parse from a message.
A function of the form cite-parse-NAME is constructed from these, and
is passed one argument. the header's contents.
See `cite-parse-headers', and, as an example `cite-parse-from'.")
(defvar cite-from-massagers
'(((string= name "[email protected]")
. (setq name "Graham"))
((and name (string-match "Kai Gro.johann" name))
. (setq name "Kai Grossjohann")))
"*Alist of massaging functions for the From: header.
The alist should be of the form.
((MATCH-FORM . REPLACEMENT-FORM)
(MATCH-FORM . REPLACEMENT-FORM)
...)
If MATCH-FORM is non-nil, REPLACEMENT-FORM will be evaluated.
You can refer to the name and address in the From header using the
variables `name' and `addr' respectively.
See also `cite-parse-from'.")
;;;; Version information.
(defconst cite-version
"1.34"
"Cite's version number.")
(defconst cite-maintainer "Lawrence Mitchell <[email protected]>"
"Email address of Cite's maintainer.")
;;; Internal variables.
(defvar cite-removed-sig nil
"The signature we have just removed.
Sometimes we might want to comment on the signature, by storing it in
a variable, it is easy to restore it.")
(defvar cite-removed-sig-pos nil
"Where in the buffer the string contained in `cite-removed-sig' was.
The position is saved as two `point-markers' in a cons cell.")
(defvar cite-parsed-headers nil
"Alist of parsed headers and their associated values.")
(defconst cite-rfc2822-address-regexp
(concat "\\([!#-'*+/-9=?A-Z^-~-]+\\(\\.[!#-'*+/-9=?A-Z^-~-]+\\)*\\|"
"\"\\([]\001-\010\013\014\016-\037!#-[^-\177]\\|"
"\\\\[\001-\011\013\014\016-\177]\\)*\""
"\\)@[!#-'*+/-9=?A-Z^-~-]+\\(\\.[!#-'*+/-9=?A-Z^-~-]+\\)*")
"Regexp to match a RFC2822 compliant email address.
This does not match some valid addresses, notably: obs-* patterns,
domain-literal or internal whitespace. Yes, this does also match
Message-IDs, but that's because Message-IDs have the same syntax,
almost.
This was concocted by Paul Jarc <[email protected]> in gnu.emacs.gnus.")
;;; User functions.
;;;; Main entry point to cite.
(defun cite-cite ()
"Cite: this function is the one called to cite an article.
Yet another citing engine for Gnus, even more minimalist than trivial
cite.
If you add extra functions to the citing engine and call them from
here, be careful that you preserve the order, and, if you're going to
change the position of point, wrap them in a
(save-excursion
(save-restriction
...))."
(save-excursion
(save-restriction
;; narrow to the newly yanked region (i.e. the article we want
;; to quote). Message puts (point) at the top of the region and
;; sets a "hidden" mark (i.e. the mark is not active) at the
;; end, which we access with (mark t).
(narrow-to-region (point) (mark t))
(cite-parse-headers)
(cite-clean-up-cites (point-min) (point-max))
;; Find the signature. We don't remove it yet, since we want
;; the removal of the signature to be first on the undo list.
(cite-find-sig)
;; Remove trailing lines.
(if cite-remove-trailing-lines
(cite-remove-trailing-lines))
(cite-remove-cite-if-line-empty (point-min) (point-max))
(cite-cite-region (point-min) (point-max))
(if cite-rewrap-long-lines
(cite-rewrap-long-lines (point-min) (point-max)))
;; This might have to be played with if using format=flowed, but
;; Emacs can't handle composing it yet, so it's not a problem.
(cite-remove-trailing-blanks)
;; insert the attribution.
(if cite-make-attribution
(insert (funcall cite-make-attribution-function)))
;; Remove the signature.
(undo-boundary)
(if cite-remove-sig
(cite-remove-sig)))))
;;;; Version information.
;;;###autoload
(defun cite-version (&optional arg)
"Echo cite's version in the minibuffer.
If optional ARG is non-nil, insert at point."
(interactive "P")
(if arg
(insert "\n" cite-version "\n")
(message "%s" cite-version)))
;;;; Attribution creation.
(defsubst cite-get-header (key)
"Return the value of the fieldname KEY from `cite-parsed-headers'.
The test is done with `assoc'."
(cadr (assoc key cite-parsed-headers)))
(defsubst cite-add-parsed-header (field value)
"Add a list cell of (FIELD VALUE) to `cite-parsed-headers'.
FIELD should be a unique identifier string (e.g. \"mid\" for
message-id). Note, we don't do error checking to see if the
identifier string already exists, so it's up to you to make sure it
doesn't."
(add-to-list 'cite-parsed-headers `(,field ,value)))
(defun cite-simple-attribution ()
"Produce a very small attribution string.
Substitute \"An unnamed person wrote:\\n\" if no email/name is
available."
(let ((email (cite-get-header "email"))
(name (cite-get-header "name")))
(if (and (null name) (null email))
"An unnamed person wrote:\n"
(concat (or name email) " wrote:\n"))))
(defun cite-mail-or-news-attribution ()
"Produce a different attribution for mail and news.
The test for whether this is a news article is done using the function
`cite-news-p'."
(let* ((email (cite-get-header "email"))
(name (cite-get-header "name"))
(date (cite-get-header "date"))
(news (cite-news-p))
(attrib (if (and (null name) (null email))
"an unnamed person wrote:\n\n"
(concat (or name email) " wrote:\n\n"))))
(if news
attrib
(concat "On " date ", " attrib))))
;;; Internal functions.
;;;; Header parsing.
;; basically stolen from tc.el but modified slightly and cleaned up.
(defun cite-parse-headers ()
"Parse the headers of the current article for useful information.
Since we narrow to the headers, we also delete them, as we don't want
them included in the followup.
See the `cite-parse-...' functions for examples of how to extract
information from headers. The functions you write should take one
argument, the header contents, mess about with it as you wish, and
then add the manipulated data to the variable `cite-parsed-headers'.
To add new functions to be called, modify the variable
`cite-headers-to-parse'."
;; make sure we're starting with a fresh set of headers.
(setq cite-parsed-headers nil)
(save-excursion
(save-restriction
(let ((point (point)))
(search-forward "\n\n" nil t) ; find the end of the header
; section
(narrow-to-region point (point))
(goto-char (point-min))
(cite-unfold-fws) ; unfold headers
(while (not (eobp))
;; Header fields take the form: TITLE: CONTENTS We strip out
;; TITLE and CONTENTS into two variables, and then pass them
;; off to different functions to parse them.
(if (looking-at "^\\([^:]+\\):[ \t]*\\([^ \t]?.*\\)$")
;; We use (downcase ...) in case the header field has
;; "non-standard" capitalisation.
(let ((name (downcase (buffer-substring-no-properties
(match-beginning 1)
(match-end 1))))
(contents (buffer-substring-no-properties
(match-beginning 2) (match-end 2))))
;; Add match conditions here if you want to parse
;; extra headers, `name' will be all lower-case. The
;; functions you write to extract information should
;; take one argument, the contents of the header
;; field.
;; Header parsing, could be made more efficient by
;; splicing in a `cond' form.
(dolist (header cite-headers-to-parse)
(when (string= header name)
(funcall (intern-soft (format "cite-parse-%s"
header)) contents)))))
(forward-line 1))
;; Delete the current (narrowed) buffer. This removes headers
;; from the followup.
(delete-region (point-min) (point-max))))))
(defun cite-parse-from (string)
"Extract the real name and/or email address from STRING.
To massage any of the extracted data, you can use
`cite-from-massagers'."
(setq string (cite-extract-address-components string))
(let ((name (car string))
(addr (cadr string)))
;; deal with b0rked TMDA addresses
;; TMDA addresses have one of the following forms (where the "-"
;; might be replaced by "+" if the MTA is sendmail):
;; `dated':
;; `sender':
;; `keyword':
(and (string-match (concat
"[-+]\\(:?dated\\|keyword\\|sender\\)[-+]"
"[0-9a-zA-Z]+\\(:?\\.[0-9a-zA-Z]+\\)?@")
addr)
(setq addr (replace-match "@" nil t addr)))
;; Massaging.
(dolist (expr cite-from-massagers)
(let ((match-fn (car expr))
(replacement (cdr expr)))
(when (funcall `(lambda () ,match-fn))
(funcall `(lambda () ,replacement)))))
(cite-add-parsed-header "name" name)
(cite-add-parsed-header "email" addr)))
(defun cite-parse-message-id (string)
"Extract the message-id from STRING.
Return it in the form <news:message-id>."
(and (string-match "^<" string)
(setq string (replace-match "<news:" nil nil string)))
(cite-add-parsed-header "mid" string))
(defun cite-parse-date (string)
"Extract the date in YYYY-MM-DD form from STRING."
(cite-add-parsed-header "date" (cite-format-date string)))
(defun cite-parse-subject (string)
"Extract the subject from STRING.
Remove \"Re:\" strings first if they occur at the beginning."
(let ((case-fold-search t))
(if (string-match (concat "^\\(?:" cite-re-regexp "\\):[ \t]*")
string)
(setq string (replace-match "" nil nil string)))
(cite-add-parsed-header "subject" string)))
(defun cite-parse-newsgroups (string)
"Extract the newsgroups from STRING."
(while (string-match ",\\([^ \t]\\)" string) ; ensure space between
; group names
(setq string (replace-match ", \\1" nil nil string)))
(cite-add-parsed-header "newsgroups" string))
;;;; Article cleanup.
;;;###autoload
(defun cite-clean-up-cites (start end)
"Make cite marks in region between START and END uniform.
e.g.
Before: \"}|> : foo.\"
After: \">>>> foo.\""
(interactive "r")
(save-excursion
(save-restriction
(narrow-to-region start end)
(goto-char (point-min))
;; cache regexp, this saves calling `concat' for every line.
(let ((regexp (concat " \\{0,2\\}" cite-prefix-regexp)))
(while (not (eobp))
(cond (;; Eat spaces (up to a maximum of two) if they are
;; followed by a cite mark. Replace the cite mark
;; matched with `cite-prefix'.
(looking-at regexp)
(delete-region (match-beginning 0) (match-end 0))
(insert cite-prefix))
(;; We've now normalised all the cite marks, if we're
;; looking at a non-space, followed by another
;; non-space (ie, not an end-of-line), insert a
;; space.
(and (not (or (= (preceding-char) ?\ )
(looking-at "^")
(looking-at "$")))
(looking-at "[^ \t]"))
(insert " "))
(t
(forward-line 1))))))))
(defun cite-remove-trailing-blanks ()
"Remove whitespace from the end of lines."
(save-excursion
(goto-char (point-min))
(while (not (eobp))
(end-of-line)
(skip-chars-backward " \t")
(delete-region (point) (point-at-eol))
(forward-line 1))))
;;;###autoload
(defun cite-remove-trailing-lines ()
"Remove trailing lines from the current buffer.
Trailling lines are searched for with the regexp \"[\\n\\t ]*\\\\'\"."
(interactive)
(save-excursion
(goto-char (point-min))
(and (re-search-forward "[\n\t ]*\\'" nil t)
(delete-region (match-beginning 0)
(match-end 0)))))
(defun cite-line-empty-p ()
"Return t if a line is \"empty\".
An empty line is one matching:
\(concat \"^\" cite-prefix \"+[ \\t]*$\")."
(looking-at (concat "^" cite-prefix "+[ \t]*$")))
(defun cite-line-really-empty-p ()
"Return t if a line doesn't match the regexp \"[^ \t\n]\"."
(not (string-match "[^ \t\n]" (buffer-substring-no-properties
(point-at-bol)
(point-at-eol)))))
(defun cite-remove-cite-if-line-empty (start end)
"Remove cite marks from a line in the region between START and END.
A cite mark is only removed if the current line is an empty.
\"Emptiness\" is checked for via the function `cite-line-empty-p' (q.v.)."
(save-excursion
(save-restriction
(narrow-to-region start end)
(goto-char (point-min))
(while (not (eobp))
(if (cite-line-empty-p)
(delete-region (point-at-bol) (point-at-eol)))
(forward-line 1)))))
;;;###autoload
(defun cite-rewrap-long-lines (start end)
"Try to reformat long lines between START and END.
This only really works with text, as code is not rewrapped well, we
try to preserve whitespace (other than line breaks) but this is far
from perfect."
(interactive "r")
(save-excursion
(save-restriction
(narrow-to-region start end)
(goto-char (point-min))
(while (not (eobp))
(let ((cite-depth (cite-count-cite-marks))
(point (point))
paragraph-cite-prefix)
(if (= cite-depth 0)
(forward-line 1)
(while (and (not (eobp))
(= cite-depth (cite-count-cite-marks)))
(forward-line 1))
(setq paragraph-cite-prefix
(make-string cite-depth
(string-to-char cite-prefix)))
(let ((fill-prefix (concat paragraph-cite-prefix " ")))
(fill-region-as-paragraph point (point) nil t))))))))
;;;; Signature removal.
;;;###autoload
(defun cite-reinsert-sig ()
"Reinsert the signature removed by function `cite-remove-sig'.
It will already be quoted, since we remove the signature after quoting
the article."
(interactive)
(if cite-removed-sig
(let ((start (marker-position (car cite-removed-sig-pos))))
(save-excursion
(goto-char start)
(set-marker-insertion-type (cdr cite-removed-sig-pos) t)
(insert cite-removed-sig)
(set-marker-insertion-type (cdr cite-removed-sig-pos) nil)))
(message "No signature to be reinserted.")))
(defun cite-find-sig ()
"Find the signature and save its postion as two markers.
The signature is defined as everything from the first occurance of
`cite-sig-sep-regexp' until the end of the buffer."
(save-excursion
(setq cite-removed-sig-pos nil)
(goto-char (point-min))
(when (re-search-forward cite-sig-sep-regexp nil t)
(let ((start (and (forward-line 0) (point-marker)))
(end (and (goto-char (point-max)) (point-marker))))
(setq cite-removed-sig-pos (cons start end))))))
;;;###autoload
(defun cite-remove-sig ()
"Remove a signature.
This removes everything from the first occurance of
`cite-sig-sep-regexp' to the end of the buffer. This function doesn't
actually search for the signature, we have already done that with
`cite-find-sig'."
(interactive)
(save-excursion
(setq cite-removed-sig nil)
(when cite-removed-sig-pos
(let ((start (marker-position (car cite-removed-sig-pos)))
(end (marker-position (cdr cite-removed-sig-pos))))
(setq cite-removed-sig
(buffer-substring-no-properties start end))
(delete-region start end)))))
;;;; Article citing.
;;;###autoload
(defun cite-cite-region (start end &optional arg prefix)
"Prefix the region between START and END with PREFIX.
With optional numeric prefix ARG, insert that many cite marks.
If PREFIX is nil, `cite-prefix' is used.
A space character is added if the current line is not already
cited."
(interactive "r\np")
(or prefix
(setq prefix cite-prefix))
(save-excursion
(save-restriction
(narrow-to-region start end)
(goto-char (point-min))
(while (not (eobp))
(loop repeat (or arg 1) do
(cond ((cite-line-really-empty-p)
(when cite-quote-empty-lines
(insert prefix)))
((looking-at cite-prefix-regexp)
(insert prefix))
(t
(insert prefix " "))))
(forward-line 1)))))
;;;###autoload
(defun cite-quote-region (start end)
"Prefix the region between START and END with a \"|\"."
(interactive "r")
(cite-cite-region start end nil "|"))
;;;###autoload
(defun cite-unquote-region (start end)
"Remove a quote prefix \"|\" from the region between START and END."
(interactive "r")
(cite-uncite-region start end nil "|"))
;;;###autoload
(defun cite-uncite-region (start end &optional arg prefix-regexp)
"Remove cites from the region between START and END.
With optional numeric prefix ARG, remove that many cite marks.
If PREFIX-REGEXP is non-nil, use that to decide what a citation prefix
is, otherwise use `cite-prefix-regexp'."
(interactive "r\np")
(save-excursion
(save-restriction
(narrow-to-region start end)
(goto-char (point-min))
(let ((arg (or arg 1))
(regexp (concat "[ \t]*" (or prefix-regexp
cite-prefix-regexp)
"[ \t]?")))
(while (not (eobp))
(let ((arg arg))
(while (> (setq arg (1- arg)) -1)
(when (looking-at regexp)
(delete-region (match-beginning 0) (match-end 0)))))
(forward-line 1))))))
;;;; Support functions.
;; Some of these functions are replicated in gnus, but we don't want
;; to force non-gnus users to load gnus files if not needed.
(defun cite-unfold-fws ()
"Unfold folding whitespace in the current buffer.
From ietf-drums.el"
(save-excursion
(while (re-search-forward "[ \t]*\n[ \t]+" nil t)
(replace-match " " t t))))
(defun cite-format-date (date)
"Format DATE as a YYYY-MM-DD string.
Adapted from gnus-util.el"
(format-time-string
"%Y-%m-%d"
(condition-case nil
(apply 'encode-time
(parse-time-string
(timezone-make-date-arpa-standard date)))
(error '(0 0)))))
(defun cite-extract-address-components (string)
"Extract address components of an email in STRING.
Adapted slightly from gnus-util.el"
(let (name address)
(when (string-match cite-rfc2822-address-regexp string)
(setq address (substring string
(match-beginning 0)
(match-end 0))))
(and address
(string-match (concat "[ \t]*<" (regexp-quote address) ">")
string)
(and (setq name (substring string 0 (match-beginning 0)))
(string-match "^\"\\(.*\\)\"" name)
(setq name (replace-match "\\1" nil nil name))))
(or name
(and (string-match "(.+)" string)
(setq name (substring string (1+ (match-beginning 0))
(1- (match-end 0)))))
(and (string-match "()" string)
(setq name address)))
(list (if (string= "" name) nil name) (or address string))))
(defun cite-news-p ()
"Say whether the current buffer contains a news message.
From message.el"
(save-excursion
(save-restriction
(goto-char (point-min))
(narrow-to-region
(point-min)
(save-excursion
(or (re-search-forward (concat
"^"
(regexp-quote mail-header-separator)
"$")
nil t)
(point-max))))
(let ((case-fold-search t))
(and (save-excursion (re-search-forward "^newsgroups:" nil t))
(not (re-search-forward "^posted-to:" nil t)))))))
(defun cite-count-cite-marks ()
"Count the number of cite marks at the beginning of a line.
Call this after calling `cite-clean-up-cites', as we search for
`cite-prefix', rather than `cite-prefix-regexp'."
(save-excursion
(forward-line 0)
(let ((count 0))
(while (looking-at cite-prefix)
(setq count (1+ count))
(forward-char 1))
count)))
(provide 'cite)
;;; cite.el ends here