(LIB_STANDARD): Add -lmach.
[bpt/emacs.git] / lisp / emacs-lisp / lisp-mnt.el
CommitLineData
0fc37e7d
ER
1;;; lisp-mnt.el --- minor mode for Emacs Lisp maintainers
2
8f1204db 3;; Copyright (C) 1992, 1994 Free Software Foundation, Inc.
0fc37e7d
ER
4
5;; Author: Eric S. Raymond <esr@snark.thyrsus.com>
6;; Maintainer: Eric S. Raymond <esr@snark.thyrsus.com>
7;; Created: 14 Jul 1992
d5eead69 8;; Version: $Id: lisp-mnt.el,v 1.14 1996/02/04 21:30:40 rms Exp rms $
0fc37e7d 9;; Keywords: docs
1894df21 10;; X-Bogus-Bureaucratic-Cruft: Gruad will get you if you don't watch out!
0fc37e7d
ER
11
12;; This file is part of GNU Emacs.
13
14;; GNU Emacs is free software; you can redistribute it and/or modify
15;; it under the terms of the GNU General Public License as published by
7c938215 16;; the Free Software Foundation; either version 2, or (at your option)
0fc37e7d
ER
17;; any later version.
18
19;; GNU Emacs is distributed in the hope that it will be useful,
20;; but WITHOUT ANY WARRANTY; without even the implied warranty of
21;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22;; GNU General Public License for more details.
23
24;; You should have received a copy of the GNU General Public License
d5eead69 25;; along with GNU Emacs; see the file COPYING. If not, write to
470c5afe
EN
26;; the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
27;; Boston, MA 02111-1307, USA.
0fc37e7d
ER
28
29;;; Commentary:
30
31;; This minor mode adds some services to Emacs-Lisp editing mode.
32;;
33;; First, it knows about the header conventions for library packages.
34;; One entry point supports generating synopses from a library directory.
35;; Another can be used to check for missing headers in library files.
36;;
37;; Another entry point automatically addresses bug mail to a package's
38;; maintainer or author.
39
40;; This file can be loaded by your lisp-mode-hook. Have it (require 'lisp-mnt)
41
42;; This file is an example of the header conventions. Note the following
43;; features:
44;;
45;; * Header line --- makes it possible to extract a one-line summary of
46;; the package's uses automatically for use in library synopses, KWIC
47;; indexes and the like.
48;;
49;; Format is three semicolons, followed by the filename, followed by
50;; three dashes, followed by the summary. All fields space-separated.
51;;
52;; * Author line --- contains the name and net address of at least
53;; the principal author.
54;;
68756f1f 55;; If there are multiple authors, they should be listed on continuation
0fc37e7d
ER
56;; lines led by ;;<TAB>, like this:
57;;
58;; ;; Author: Ashwin Ram <Ram-Ashwin@cs.yale.edu>
59;; ;; Dave Sill <de5@ornl.gov>
60;; ;; David Lawrence <tale@pawl.rpi.edu>
61;; ;; Noah Friedman <friedman@ai.mit.edu>
62;; ;; Joe Wells <jbw@maverick.uswest.com>
63;; ;; Dave Brennan <brennan@hal.com>
64;; ;; Eric Raymond <esr@snark.thyrsus.com>
65;;
66;; This field may have some special values; notably "FSF", meaning
67;; "Free Software Foundation".
68;;
69;; * Maintainer line --- should be a single name/address as in the Author
70;; line, or an address only, or the string "FSF". If there is no maintainer
71;; line, the person(s) in the Author field are presumed to be it. The example
72;; in this file is mildly bogus because the maintainer line is redundant.
73;; The idea behind these two fields is to be able to write a lisp function
74;; that does "send mail to the author" without having to mine the name out by
75;; hand. Please be careful about surrounding the network address with <> if
76;; there's also a name in the field.
77;;
78;; * Created line --- optional, gives the original creation date of the
79;; file. For historical interest, basically.
80;;
81;; * Version line --- intended to give the reader a clue if they're looking
68756f1f
ER
82;; at a different version of the file than the one they're accustomed to. This
83;; may be an RCS or SCCS header.
0fc37e7d
ER
84;;
85;; * Adapted-By line --- this is for FSF's internal use. The person named
86;; in this field was the one responsible for installing and adapting the
87;; package for the distribution. (This file doesn't have one because the
88;; author *is* one of the maintainers.)
89;;
90;; * Keywords line --- used by the finder code (now under construction)
c0d79871 91;; for finding Emacs Lisp code related to a topic.
0fc37e7d 92;;
1894df21
NF
93;; * X-Bogus-Bureaucratic-Cruft line --- this is a joke and an example
94;; of a comment header. Headers starting with `X-' should never be used
95;; for any real purpose; this is the way to safely add random headers
96;; without invoking the wrath of any program.
0fc37e7d
ER
97;;
98;; * Commentary line --- enables lisp code to find the developer's and
99;; maintainers' explanations of the package internals.
100;;
101;; * Change log line --- optional, exists to terminate the commentary
102;; section and start a change-log part, if one exists.
103;;
c0d79871 104;; * Code line --- exists so Lisp can know where commentary and/or
0fc37e7d
ER
105;; change-log sections end.
106;;
107;; * Footer line --- marks end-of-file so it can be distinguished from
108;; an expanded formfeed or the results of truncation.
109
110;;; Change Log:
111
112;; Tue Jul 14 23:44:17 1992 ESR
113;; * Created.
114
115;;; Code:
116
117(require 'picture) ; provides move-to-column-force
68756f1f 118(require 'emacsbug)
0fc37e7d 119
d5eead69
RS
120;;; Variables:
121
122(defconst lm-header-prefix "^;;*[ \t]+\\(@\(#\)\\)?[ \t]*\\([\$]\\)?"
123 "Prefix that is ignored before the tag.
124Eg. you can write the 1st line synopsis string and headers like this
125in your lisp package:
126
127 ;; @(#) package.el -- pacakge description
128 ;;
129 ;; @(#) $Maintainer: Person Foo Bar $
130
131The @(#) construct is used by unix what(1) and
132then $identifier: doc string $ is used by GNU ident(1)")
133
134(defconst lm-comment-column 16
135 "Column used for placing formatted output.")
136
137(defconst lm-commentary-header "Commentary\\|Documentation"
138 "Regexp which matches start of documentation section.")
139
140(defconst lm-history-header "Change Log\\|History"
141 "Regexp which matches the start of code log section.")
142
143;;; Functions:
144
0fc37e7d
ER
145;; These functions all parse the headers of the current buffer
146
d5eead69
RS
147(defsubst lm-get-header-re (header &optional mode)
148 "Returns regexp for matching HEADER. If called with optional MODE and
149with value 'section, return section regexp instead."
150 (cond
151 ((eq mode 'section)
152 (concat "^;;;;* " header ":[ \t]*$"))
153 (t
154 (concat lm-header-prefix header ":[ \t]*"))))
155
156(defsubst lm-get-package-name ()
157 "Returns package name by looking at the first line."
158 (save-excursion
159 (goto-char (point-min))
160 (if (and (looking-at (concat lm-header-prefix))
161 (progn (goto-char (match-end 0))
162 (looking-at "\\([^\t ]+\\)")
163 (match-end 1)))
164 (buffer-substring (match-beginning 1) (match-end 1))
165 )))
166
167(defun lm-section-mark (header &optional after)
168 "Return the buffer location of a given section start marker.
169The HEADER is section mark string to find and AFTER is non-nil
170returns location of next line."
0fc37e7d
ER
171 (save-excursion
172 (let ((case-fold-search t))
173 (goto-char (point-min))
d5eead69 174 (if (re-search-forward (lm-get-header-re header 'section) nil t)
0fc37e7d
ER
175 (progn
176 (beginning-of-line)
64e07c7b 177 (if after (forward-line 1))
0fc37e7d
ER
178 (point))
179 nil))))
180
d5eead69
RS
181(defsubst lm-code-mark ()
182 "Return the buffer location of the 'Code' start marker."
0fc37e7d
ER
183 (lm-section-mark "Code"))
184
d5eead69
RS
185(defsubst lm-commentary-mark ()
186 "Return the buffer location of the 'Commentary' start marker."
187 (lm-section-mark lm-commentary-header))
188
189(defsubst lm-history-mark ()
190 "Return the buffer location of the 'history' start marker."
191 (lm-section-mark lm-history-header))
192
193(defun lm-header (header)
194 "Return the contents of a named HEADER."
0fc37e7d
ER
195 (goto-char (point-min))
196 (let ((case-fold-search t))
d5eead69
RS
197 (if (and (re-search-forward (lm-get-header-re header) (lm-code-mark) t)
198 ;; RCS ident likes format "$identifier: data$"
199 (looking-at "\\([^$\n]+\\)")
200 (match-end 1))
0fc37e7d
ER
201 (buffer-substring (match-beginning 1) (match-end 1))
202 nil)))
203
d5eead69
RS
204(defun lm-header-multiline (header)
205 "Return the contents of a named HEADER, with possible continuation lines.
206The returned value is a list of strings, one per line."
0fc37e7d
ER
207 (save-excursion
208 (goto-char (point-min))
d5eead69
RS
209 (let ((res (lm-header header)))
210 (cond
211 (res
212 (setq res (list res))
213 (forward-line 1)
214
215 (while (and (looking-at (concat lm-header-prefix "[\t ]+"))
216 (progn
217 (goto-char (match-end 0))
218 (looking-at "\\(.*\\)"))
219 (match-end 1))
220 (setq res (cons (buffer-substring
221 (match-beginning 1)
222 (match-end 1))
223 res))
224 (forward-line 1))
225 ))
226 res
227 )))
0fc37e7d
ER
228
229;; These give us smart access to the header fields and commentary
230
231(defun lm-summary (&optional file)
d5eead69 232 "Return the buffer's or optional FILE's one-line summary."
0fc37e7d
ER
233 (save-excursion
234 (if file
235 (find-file file))
236 (goto-char (point-min))
237 (prog1
d5eead69
RS
238 (if (and
239 (looking-at lm-header-prefix)
240 (progn (goto-char (match-end 0))
241 (looking-at "[^ ]+[ \t]+--+[ \t]+\\(.*\\)")))
0fc37e7d
ER
242 (buffer-substring (match-beginning 1) (match-end 1)))
243 (if file
244 (kill-buffer (current-buffer)))
245 )))
246
68756f1f 247(defun lm-crack-address (x)
d5eead69
RS
248 "Cracks email address from string.
249Given a string 'x' containing a human and email address, parse it
250into a cons pair (NAME . ADDRESS)."
68756f1f
ER
251 (cond ((string-match "\\(.+\\) [(<]\\(\\S-+@\\S-+\\)[>)]" x)
252 (cons (substring x (match-beginning 1) (match-end 1))
253 (substring x (match-beginning 2) (match-end 2))))
254 ((string-match "\\(\\S-+@\\S-+\\) [(<]\\(.*\\)[>)]" x)
255 (cons (substring x (match-beginning 2) (match-end 2))
256 (substring x (match-beginning 1) (match-end 1))))
257 ((string-match "\\S-+@\\S-+" x)
258 (cons nil x))
259 (t
260 (cons x nil))))
261
0fc37e7d 262(defun lm-authors (&optional file)
d5eead69
RS
263 "Return the buffer's or optional FILE's author list. Each element of the
264list is a cons; the car is a name-aming-humans, the cdr an email
265address."
0fc37e7d
ER
266 (save-excursion
267 (if file
268 (find-file file))
68756f1f
ER
269 (let ((authorlist (lm-header-multiline "author")))
270 (prog1
271 (mapcar 'lm-crack-address authorlist)
272 (if file
273 (kill-buffer (current-buffer)))
274 ))))
0fc37e7d
ER
275
276(defun lm-maintainer (&optional file)
d5eead69
RS
277 "Seearch for 'maintainer'. Get a package's bug-report & maintenance address.
278Parse it out of FILE, or the current buffer if FILE is nil.
279The return value is a (NAME . ADDRESS) cons."
0fc37e7d
ER
280 (save-excursion
281 (if file
282 (find-file file))
283 (prog1
68756f1f
ER
284 (let ((maint (lm-header "maintainer")))
285 (if maint
286 (lm-crack-address maint)
287 (car (lm-authors))))
0fc37e7d
ER
288 (if file
289 (kill-buffer (current-buffer)))
290 )))
291
292(defun lm-creation-date (&optional file)
d5eead69
RS
293 "Seearch for 'created'. Return a package's creation date, if any.
294Parse it out of FILE, or the current buffer if FILE is nil."
0fc37e7d
ER
295 (save-excursion
296 (if file
297 (find-file file))
298 (prog1
299 (lm-header "created")
300 (if file
301 (kill-buffer (current-buffer)))
302 )))
303
304
305(defun lm-last-modified-date (&optional file)
d5eead69 306 "Return a package's last-modified date, if it has one."
0fc37e7d
ER
307 (save-excursion
308 (if file
309 (find-file file))
310 (prog1
68756f1f
ER
311 (if (progn
312 (goto-char (point-min))
313 (re-search-forward
314 "\\$Id: [^ ]+ [^ ]+ \\([^/]+\\)/\\([^/]+\\)/\\([^ ]+\\) "
315 (lm-code-mark) t))
316 (format "%s %s %s"
317 (buffer-substring (match-beginning 3) (match-end 3))
318 (nth (string-to-int
319 (buffer-substring (match-beginning 2) (match-end 2)))
320 '("" "Jan" "Feb" "Mar" "Apr" "May" "Jun"
321 "Jul" "Aug" "Sep" "Oct" "Nov" "Dec"))
322 (buffer-substring (match-beginning 1) (match-end 1))
323 ))
0fc37e7d
ER
324 (if file
325 (kill-buffer (current-buffer)))
68756f1f 326 )))
0fc37e7d
ER
327
328(defun lm-version (&optional file)
d5eead69
RS
329 "Search for RCS identifier '$Id'. Return the package's version field.
330If none, look for an RCS or SCCS header to crack it out of."
0fc37e7d
ER
331 (save-excursion
332 (if file
333 (find-file file))
334 (prog1
335 (or
336 (lm-header "version")
337 (let ((header-max (lm-code-mark)))
338 (goto-char (point-min))
339 (cond
340 ;; Look for an RCS header
341 ((re-search-forward "\\$Id: [^ ]+ \\([^ ]+\\) " header-max t)
342 (buffer-substring (match-beginning 1) (match-end 1)))
343
344 ;; Look for an SCCS header
345 ((re-search-forward
346 (concat
347 (regexp-quote "@(#)")
348 (regexp-quote (file-name-nondirectory (buffer-file-name)))
349 "\t\\([012345679.]*\\)")
350 header-max t)
351 (buffer-substring (match-beginning 1) (match-end 1)))
352
353 (t nil))))
354 (if file
355 (kill-buffer (current-buffer)))
356 )))
357
358(defun lm-keywords (&optional file)
d5eead69
RS
359 "Search for 'keywords'. Return the header containing the package's
360topic keywords. Parse them out of FILE, or the current buffer if FILE is nil."
0fc37e7d
ER
361 (save-excursion
362 (if file
363 (find-file file))
364 (prog1
365 (let ((keywords (lm-header "keywords")))
366 (and keywords (downcase keywords)))
367 (if file
368 (kill-buffer (current-buffer)))
369 )))
370
371(defun lm-adapted-by (&optional file)
d5eead69
RS
372 "Search for 'adapted-by'. Return the name or code of the person who
373cleaned up this package for distribution. Parse it out of FILE, or
374the current buffer if FILE is nil."
0fc37e7d
ER
375 (save-excursion
376 (if file
377 (find-file file))
378 (prog1
379 (lm-header "adapted-by")
380 (if file
381 (kill-buffer (current-buffer)))
382 )))
383
64e07c7b 384(defun lm-commentary (&optional file)
d5eead69
RS
385 "Return the commentary region of a file, as a string.
386The area is started with tag 'Commentary' and eded with tag
387'Change Log' or 'History'."
0fc37e7d
ER
388 (save-excursion
389 (if file
390 (find-file file))
391 (prog1
d5eead69
RS
392 (let ((commentary (lm-commentary-mark))
393 (change-log (lm-history-mark))
394 (code (lm-code-mark))
395 )
396 (cond
397 ((and commentary change-log)
398 (buffer-substring commentary change-log))
399 ((and commentary code)
400 (buffer-substring commentary code))
401 (t
402 nil)))
0fc37e7d
ER
403 (if file
404 (kill-buffer (current-buffer)))
405 )))
406
407;;; Verification and synopses
408
d5eead69
RS
409(defun lm-insert-at-column (col &rest strings)
410 "Insert list of STRINGS, at column COL."
411 (if (> (current-column) col) (insert "\n"))
412 (move-to-column-force col)
413 (apply 'insert strings))
0fc37e7d 414
d5eead69 415(defun lm-verify (&optional file showok &optional verb)
0fc37e7d
ER
416 "Check that the current buffer (or FILE if given) is in proper format.
417If FILE is a directory, recurse on its files and generate a report into
418a temporary buffer."
d5eead69
RS
419 (interactive)
420 (let* ((verb (or verb (interactive-p)))
421 ret
422 name
423 )
424 (if verb
425 (setq ret "Ok.")) ;init value
426
427 (if (and file (file-directory-p file))
428 (setq
429 ret
430 (progn
431 (switch-to-buffer (get-buffer-create "*lm-verify*"))
432 (erase-buffer)
433 (mapcar
434 '(lambda (f)
435 (if (string-match ".*\\.el$" f)
436 (let ((status (lm-verify f)))
437 (if status
0fc37e7d
ER
438 (progn
439 (insert f ":")
d5eead69
RS
440 (lm-insert-at-column lm-comment-column status "\n"))
441 (and showok
442 (progn
443 (insert f ":")
444 (lm-insert-at-column lm-comment-column "OK\n")))))))
445 (directory-files file))
446 ))
447 (save-excursion
448 (if file
449 (find-file file))
450 (setq name (lm-get-package-name))
451
452 (setq
453 ret
454 (prog1
455 (cond
456 ((null name)
457 "Can't find a package NAME")
458
459 ((not (lm-authors))
460 "Author: tag missing.")
461
462 ((not (lm-maintainer))
463 "Maintainer: tag missing.")
464
465 ((not (lm-summary))
466 "Can't find a one-line 'Summary' description")
467
468 ((not (lm-keywords))
469 "Keywords: tag missing.")
470
471 ((not (lm-commentary-mark))
472 "Can't find a 'Commentary' section marker.")
473
474 ((not (lm-history-mark))
475 "Can't find a 'History' section marker.")
476
477 ((not (lm-code-mark))
478 "Can't find a 'Code' section marker")
479
480 ((progn
481 (goto-char (point-max))
482 (not
483 (re-search-backward
484 (concat "^;;;[ \t]+" name "[ \t]+ends here[ \t]*$"
485 "\\|^;;;[ \t]+ End of file[ \t]+" name)
486 nil t
487 )))
488 (format "Can't find a footer line for [%s]" name))
489 (t
490 ret))
491 (if file
492 (kill-buffer (current-buffer)))
493 ))))
494 (if verb
495 (message ret))
496 ret
497 ))
0fc37e7d
ER
498
499(defun lm-synopsis (&optional file showall)
500 "Generate a synopsis listing for the buffer or the given FILE if given.
501If FILE is a directory, recurse on its files and generate a report into
502a temporary buffer. If SHOWALL is on, also generate a line for files
503which do not include a recognizable synopsis."
d5eead69
RS
504 (interactive
505 (list
506 (read-file-name "Synopsis for (file or dir): ")))
507
0fc37e7d
ER
508 (if (and file (file-directory-p file))
509 (progn
510 (switch-to-buffer (get-buffer-create "*lm-verify*"))
511 (erase-buffer)
512 (mapcar
513 '(lambda (f)
514 (if (string-match ".*\\.el$" f)
515 (let ((syn (lm-synopsis f)))
516 (if syn
517 (progn
518 (insert f ":")
f9636fb2 519 (lm-insert-at-column lm-comment-column syn "\n"))
0fc37e7d
ER
520 (and showall
521 (progn
522 (insert f ":")
f9636fb2 523 (lm-insert-at-column lm-comment-column "NA\n")))))))
0fc37e7d
ER
524 (directory-files file))
525 )
526 (save-excursion
527 (if file
528 (find-file file))
529 (prog1
530 (lm-summary)
531 (if file
532 (kill-buffer (current-buffer)))
533 ))))
534
535(defun lm-report-bug (topic)
536 "Report a bug in the package currently being visited to its maintainer.
537Prompts for bug subject. Leaves you in a mail buffer."
68756f1f 538 (interactive "sBug Subject: ")
d5eead69
RS
539 (let ((package (lm-get-package-name))
540 (addr (lm-maintainer))
541 (version (lm-version)))
3082623d
RS
542 (mail nil
543 (if addr
544 (concat (car addr) " <" (cdr addr) ">")
545 bug-gnu-emacs)
546 topic)
0fc37e7d
ER
547 (goto-char (point-max))
548 (insert "\nIn "
549 package
ef92f83b 550 (if version (concat " version " version) "")
0fc37e7d 551 "\n\n")
d5eead69 552 (message
0fc37e7d
ER
553 (substitute-command-keys "Type \\[mail-send] to send bug report."))))
554
555(provide 'lisp-mnt)
556
557;;; lisp-mnt.el ends here
d5eead69 558