Switch to recommended form of GPLv3 permissions notice.
[bpt/emacs.git] / lisp / emacs-lisp / lisp-mnt.el
CommitLineData
c7dff41d 1;;; lisp-mnt.el --- utility functions for Emacs Lisp maintainers
0fc37e7d 2
3731a850 3;; Copyright (C) 1992, 1994, 1997, 2000, 2001, 2002, 2003, 2004,
8b72699e 4;; 2005, 2006, 2007, 2008 Free Software Foundation, Inc.
0fc37e7d
ER
5
6;; Author: Eric S. Raymond <esr@snark.thyrsus.com>
877d5b39 7;; Maintainer: FSF
0fc37e7d 8;; Created: 14 Jul 1992
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
e0085d62 16;; the Free Software Foundation; either version 3, 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
3a35cf56
LK
26;; the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
27;; Boston, MA 02110-1301, USA.
0fc37e7d
ER
28
29;;; Commentary:
30
c7dff41d 31;; This library adds some services to Emacs-Lisp editing mode.
0fc37e7d
ER
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.
a1506d29 36;;
0fc37e7d
ER
37;; Another entry point automatically addresses bug mail to a package's
38;; maintainer or author.
39
d9dd743e
JB
40;; This file can be loaded by your emacs-lisp-mode-hook. Have it
41;; (require 'lisp-mnt)
0fc37e7d
ER
42
43;; This file is an example of the header conventions. Note the following
44;; features:
a1506d29 45;;
0fc37e7d
ER
46;; * Header line --- makes it possible to extract a one-line summary of
47;; the package's uses automatically for use in library synopses, KWIC
48;; indexes and the like.
a1506d29 49;;
0fc37e7d
ER
50;; Format is three semicolons, followed by the filename, followed by
51;; three dashes, followed by the summary. All fields space-separated.
179e152a
RS
52;;
53;; * A blank line
54;;
55;; * Copyright line, which looks more or less like this:
56;;
57;; ;; Copyright (C) 1999, 2000, 2001 Free Software Foundation, Inc.
58;;
59;; * A blank line
a1506d29 60;;
0fc37e7d
ER
61;; * Author line --- contains the name and net address of at least
62;; the principal author.
a1506d29 63;;
68756f1f 64;; If there are multiple authors, they should be listed on continuation
0fc37e7d 65;; lines led by ;;<TAB>, like this:
a1506d29 66;;
0fc37e7d
ER
67;; ;; Author: Ashwin Ram <Ram-Ashwin@cs.yale.edu>
68;; ;; Dave Sill <de5@ornl.gov>
69;; ;; David Lawrence <tale@pawl.rpi.edu>
70;; ;; Noah Friedman <friedman@ai.mit.edu>
71;; ;; Joe Wells <jbw@maverick.uswest.com>
72;; ;; Dave Brennan <brennan@hal.com>
73;; ;; Eric Raymond <esr@snark.thyrsus.com>
a1506d29 74;;
0fc37e7d
ER
75;; This field may have some special values; notably "FSF", meaning
76;; "Free Software Foundation".
a1506d29 77;;
0fc37e7d
ER
78;; * Maintainer line --- should be a single name/address as in the Author
79;; line, or an address only, or the string "FSF". If there is no maintainer
80;; line, the person(s) in the Author field are presumed to be it. The example
81;; in this file is mildly bogus because the maintainer line is redundant.
ca4bd734 82;; The idea behind these two fields is to be able to write a Lisp function
0fc37e7d 83;; that does "send mail to the author" without having to mine the name out by
c7dff41d 84;; hand. Please be careful about surrounding the network address with <> if
0fc37e7d 85;; there's also a name in the field.
a1506d29 86;;
0fc37e7d
ER
87;; * Created line --- optional, gives the original creation date of the
88;; file. For historical interest, basically.
a1506d29 89;;
0fc37e7d 90;; * Version line --- intended to give the reader a clue if they're looking
68756f1f
ER
91;; at a different version of the file than the one they're accustomed to. This
92;; may be an RCS or SCCS header.
a1506d29 93;;
0fc37e7d
ER
94;; * Adapted-By line --- this is for FSF's internal use. The person named
95;; in this field was the one responsible for installing and adapting the
96;; package for the distribution. (This file doesn't have one because the
97;; author *is* one of the maintainers.)
a1506d29 98;;
0fc37e7d 99;; * Keywords line --- used by the finder code (now under construction)
c0d79871 100;; for finding Emacs Lisp code related to a topic.
0fc37e7d 101;;
1894df21
NF
102;; * X-Bogus-Bureaucratic-Cruft line --- this is a joke and an example
103;; of a comment header. Headers starting with `X-' should never be used
104;; for any real purpose; this is the way to safely add random headers
105;; without invoking the wrath of any program.
0fc37e7d 106;;
ca4bd734 107;; * Commentary line --- enables Lisp code to find the developer's and
0fc37e7d 108;; maintainers' explanations of the package internals.
a1506d29 109;;
0fc37e7d
ER
110;; * Change log line --- optional, exists to terminate the commentary
111;; section and start a change-log part, if one exists.
a1506d29 112;;
c0d79871 113;; * Code line --- exists so Lisp can know where commentary and/or
0fc37e7d 114;; change-log sections end.
a1506d29 115;;
0fc37e7d
ER
116;; * Footer line --- marks end-of-file so it can be distinguished from
117;; an expanded formfeed or the results of truncation.
118
119;;; Change Log:
120
121;; Tue Jul 14 23:44:17 1992 ESR
122;; * Created.
123
124;;; Code:
125
d5eead69
RS
126;;; Variables:
127
666b9413 128(defgroup lisp-mnt nil
c7dff41d 129 "Utility functions for Emacs Lisp maintainers."
666b9413
SE
130 :prefix "lm-"
131 :group 'maint)
132
af95e0d1
DL
133;; At least some of these defcustoms should probably be defconsts,
134;; since they define, or are defined by, the header format. -- fx
135
438cdcde 136(defcustom lm-header-prefix "^;+[ \t]+\\(@(#)\\)?[ \t]*\\$?"
d5eead69 137 "Prefix that is ignored before the tag.
ca4bd734
RS
138For example, you can write the 1st line synopsis string and headers like this
139in your Lisp package:
d5eead69 140
7a3ee510 141 ;; @(#) package.el -- package description
d5eead69
RS
142 ;;
143 ;; @(#) $Maintainer: Person Foo Bar $
144
145The @(#) construct is used by unix what(1) and
666b9413
SE
146then $identifier: doc string $ is used by GNU ident(1)"
147 :type 'regexp
148 :group 'lisp-mnt)
149
af95e0d1
DL
150(defcustom lm-copyright-prefix "^\\(;+[ \t]\\)+Copyright (C) "
151 "Prefix that is ignored before the dates in a copyright.
152Leading comment characters and whitespace should be in regexp group 1."
13c40e2f
GM
153 :type 'regexp
154 :group 'lisp-mnt)
155
666b9413
SE
156(defcustom lm-comment-column 16
157 "Column used for placing formatted output."
158 :type 'integer
159 :group 'lisp-mnt)
160
c7dff41d
LK
161(defcustom lm-any-header ".*"
162 "Regexp which matches start of any section."
163 :type 'regexp
164 :group 'lisp-mnt)
165
666b9413
SE
166(defcustom lm-commentary-header "Commentary\\|Documentation"
167 "Regexp which matches start of documentation section."
168 :type 'regexp
169 :group 'lisp-mnt)
170
3d1b88f6 171(defcustom lm-history-header "Change ?Log\\|History"
666b9413
SE
172 "Regexp which matches the start of code log section."
173 :type 'regexp
174 :group 'lisp-mnt)
d5eead69
RS
175
176;;; Functions:
177
0fc37e7d
ER
178;; These functions all parse the headers of the current buffer
179
2505742b 180(defun lm-get-header-re (header &optional mode)
6e24ad22 181 "Return regexp for matching HEADER.
ca4bd734
RS
182If called with optional MODE and with value `section',
183return section regexp instead."
438cdcde 184 (if (eq mode 'section)
a94d391a
RS
185 (concat "^;;;;* \\(" header "\\):[ \t]*$")
186 (concat lm-header-prefix "\\(" header "\\)[ \t]*:[ \t]*")))
d5eead69 187
2505742b 188(defun lm-get-package-name ()
6e24ad22 189 "Return package name by looking at the first line."
d5eead69
RS
190 (save-excursion
191 (goto-char (point-min))
192 (if (and (looking-at (concat lm-header-prefix))
193 (progn (goto-char (match-end 0))
194 (looking-at "\\([^\t ]+\\)")
195 (match-end 1)))
2505742b 196 (match-string-no-properties 1))))
d5eead69 197
c7dff41d 198(defun lm-section-start (header &optional after)
d5eead69 199 "Return the buffer location of a given section start marker.
ca4bd734 200The HEADER is the section mark string to search for.
c7dff41d
LK
201If AFTER is non-nil, return the location of the next line.
202If the given section does not exist, return nil."
0fc37e7d
ER
203 (save-excursion
204 (let ((case-fold-search t))
205 (goto-char (point-min))
d5eead69 206 (if (re-search-forward (lm-get-header-re header 'section) nil t)
c7dff41d
LK
207 (line-beginning-position (if after 2))))))
208(defalias 'lm-section-mark 'lm-section-start)
209
210(defun lm-section-end (header)
211 "Return the buffer location of the end of a given section.
212The HEADER is the section string marking the beginning of the
213section. If the given section does not exist, return nil.
214
215The end of the section is defined as the beginning of the next
216section of the same level or lower. The function
217`lisp-outline-level' is used to compute the level of a section.
218If no such section exists, return the end of the buffer."
38068db2 219 (require 'outline) ;; for outline-regexp.
c7dff41d
LK
220 (let ((start (lm-section-start header)))
221 (when start
222 (save-excursion
223 (goto-char start)
224 (let ((level (lisp-outline-level))
225 (case-fold-search t)
226 next-section-found)
227 (beginning-of-line 2)
228 (while (and (setq next-section-found
229 (re-search-forward
230 (lm-get-header-re lm-any-header 'section)
231 nil t))
232 (> (save-excursion
233 (beginning-of-line)
234 (lisp-outline-level))
235 level)))
236 (if next-section-found
237 (line-beginning-position)
238 (point-max)))))))
239
240(defsubst lm-code-start ()
ca4bd734 241 "Return the buffer location of the `Code' start marker."
c7dff41d
LK
242 (lm-section-start "Code"))
243(defalias 'lm-code-mark 'lm-code-start)
0fc37e7d 244
c7dff41d 245(defsubst lm-commentary-start ()
ca4bd734 246 "Return the buffer location of the `Commentary' start marker."
c7dff41d
LK
247 (lm-section-start lm-commentary-header))
248(defalias 'lm-commentary-mark 'lm-commentary-start)
249
250(defsubst lm-commentary-end ()
251 "Return the buffer location of the `Commentary' section end."
252 (lm-section-end lm-commentary-header))
d5eead69 253
c7dff41d 254(defsubst lm-history-start ()
ca4bd734 255 "Return the buffer location of the `History' start marker."
c7dff41d
LK
256 (lm-section-start lm-history-header))
257(defalias 'lm-history-mark 'lm-history-start)
d5eead69 258
13c40e2f
GM
259(defsubst lm-copyright-mark ()
260 "Return the buffer location of the `Copyright' line."
261 (save-excursion
262 (let ((case-fold-search t))
263 (goto-char (point-min))
264 (if (re-search-forward lm-copyright-prefix nil t)
c7dff41d 265 (point)))))
13c40e2f 266
d5eead69 267(defun lm-header (header)
ca4bd734
RS
268 "Return the contents of the header named HEADER."
269 (goto-char (point-min))
270 (let ((case-fold-search t))
438cdcde
SM
271 (when (and (re-search-forward (lm-get-header-re header) (lm-code-mark) t)
272 ;; RCS ident likes format "$identifier: data$"
273 (looking-at
274 (if (save-excursion
275 (skip-chars-backward "^$" (match-beginning 0))
276 (= (point) (match-beginning 0)))
277 "[^\n]+" "[^$\n]+")))
278 (match-string-no-properties 0))))
0fc37e7d 279
d5eead69 280(defun lm-header-multiline (header)
ca4bd734 281 "Return the contents of the header named HEADER, with continuation lines.
d5eead69 282The returned value is a list of strings, one per line."
0fc37e7d
ER
283 (save-excursion
284 (goto-char (point-min))
d5eead69 285 (let ((res (lm-header header)))
be961cd5 286 (when res
d5eead69
RS
287 (setq res (list res))
288 (forward-line 1)
438cdcde
SM
289 (while (and (or (looking-at (concat lm-header-prefix "[\t ]+"))
290 (and (not (looking-at
291 (lm-get-header-re "\\sw\\(\\sw\\|\\s_\\)*")))
292 (looking-at lm-header-prefix)))
293 (goto-char (match-end 0))
294 (looking-at ".+"))
295 (setq res (cons (match-string-no-properties 0) res))
2505742b 296 (forward-line 1)))
438cdcde 297 (nreverse res))))
0fc37e7d
ER
298
299;; These give us smart access to the header fields and commentary
300
be961cd5 301(defmacro lm-with-file (file &rest body)
a94d391a 302 "Execute BODY in a buffer containing the contents of FILE.
9e1b128c 303If FILE is nil, execute BODY in the current buffer."
be961cd5 304 (let ((filesym (make-symbol "file")))
a94d391a 305 `(let ((,filesym ,file))
9e1b128c
RS
306 (if ,filesym
307 (with-temp-buffer
308 (insert-file-contents ,filesym)
d9dd743e 309 (emacs-lisp-mode)
9e1b128c 310 ,@body)
3731a850 311 (save-excursion
d4b6c2e3 312 ;; Switching major modes is too drastic, so just switch
d9dd743e
JB
313 ;; temporarily to the Emacs Lisp mode syntax table.
314 (with-syntax-table emacs-lisp-mode-syntax-table
d4b6c2e3
RS
315 ,@body))))))
316
be961cd5
SM
317(put 'lm-with-file 'lisp-indent-function 1)
318(put 'lm-with-file 'edebug-form-spec t)
319
af95e0d1
DL
320;; Fixme: Probably this should be amalgamated with copyright.el; also
321;; we need a check for ranges in copyright years.
322
13c40e2f
GM
323(defun lm-crack-copyright (&optional file)
324 "Return the copyright holder, and a list of copyright years.
325Use the current buffer if FILE is nil.
326Return argument is of the form (\"HOLDER\" \"YEAR1\" ... \"YEARN\")"
327 (lm-with-file file
328 (goto-char (lm-copyright-mark))
329 (let ((holder nil)
330 (years nil)
af95e0d1 331 (start (point))
13c40e2f 332 (end (line-end-position)))
af95e0d1
DL
333 ;; Cope with multi-line copyright `lines'. Assume the second
334 ;; line is indented (with the same commenting style).
335 (save-excursion
336 (beginning-of-line 2)
337 (let ((str (concat (match-string-no-properties 1) "[ \t]+")))
338 (beginning-of-line)
339 (while (looking-at str)
340 (setq end (line-end-position))
341 (beginning-of-line 2))))
342 ;; Make a single line and parse that.
343 (let ((buff (current-buffer)))
344 (with-temp-buffer
345 (insert-buffer-substring buff start end)
346 (goto-char (point-min))
347 (while (re-search-forward "^;+[ \t]+" nil t)
348 (replace-match ""))
349 (goto-char (point-min))
350 (while (re-search-forward " *\n" nil t)
351 (replace-match " "))
352 (goto-char (point-min))
353 (while (re-search-forward "\\([0-9]+\\),? +" nil t)
354 (setq years (cons (match-string-no-properties 1) years)))
355 (if (looking-at ".*$")
356 (setq holder (match-string-no-properties 0)))))
357 (cons holder (nreverse years)))))
13c40e2f 358
0fc37e7d 359(defun lm-summary (&optional file)
ca4bd734 360 "Return the one-line summary of file FILE, or current buffer if FILE is nil."
be961cd5 361 (lm-with-file file
0fc37e7d 362 (goto-char (point-min))
2505742b
DL
363 (if (and (looking-at lm-header-prefix)
364 (progn (goto-char (match-end 0))
365 (looking-at "[^ ]+[ \t]+--+[ \t]+\\(.*\\)")))
366 (let ((summary (match-string-no-properties 1)))
be961cd5
SM
367 ;; Strip off -*- specifications.
368 (if (string-match "[ \t]*-\\*-.*-\\*-" summary)
369 (substring summary 0 (match-beginning 0))
370 summary)))))
0fc37e7d 371
68756f1f 372(defun lm-crack-address (x)
6e24ad22 373 "Split up an email address X into full name and real email address.
ca4bd734 374The value is a cons of the form (FULLNAME . ADDRESS)."
68756f1f 375 (cond ((string-match "\\(.+\\) [(<]\\(\\S-+@\\S-+\\)[>)]" x)
2505742b
DL
376 (cons (match-string 1 x)
377 (match-string 2 x)))
68756f1f 378 ((string-match "\\(\\S-+@\\S-+\\) [(<]\\(.*\\)[>)]" x)
2505742b
DL
379 (cons (match-string 2 x)
380 (match-string 1 x)))
68756f1f
ER
381 ((string-match "\\S-+@\\S-+" x)
382 (cons nil x))
383 (t
384 (cons x nil))))
385
0fc37e7d 386(defun lm-authors (&optional file)
ca4bd734
RS
387 "Return the author list of file FILE, or current buffer if FILE is nil.
388Each element of the list is a cons; the car is the full name,
389the cdr is an email address."
be961cd5 390 (lm-with-file file
68756f1f 391 (let ((authorlist (lm-header-multiline "author")))
be961cd5 392 (mapcar 'lm-crack-address authorlist))))
0fc37e7d
ER
393
394(defun lm-maintainer (&optional file)
ca4bd734
RS
395 "Return the maintainer of file FILE, or current buffer if FILE is nil.
396The return value has the form (NAME . ADDRESS)."
be961cd5
SM
397 (lm-with-file file
398 (let ((maint (lm-header "maintainer")))
399 (if maint
400 (lm-crack-address maint)
401 (car (lm-authors))))))
0fc37e7d
ER
402
403(defun lm-creation-date (&optional file)
ca4bd734 404 "Return the created date given in file FILE, or current buffer if FILE is nil."
be961cd5
SM
405 (lm-with-file file
406 (lm-header "created")))
0fc37e7d 407
07d505c1
GM
408(defun lm-last-modified-date (&optional file iso-date)
409 "Return the modify-date given in file FILE, or current buffer if FILE is nil.
410ISO-DATE non-nil means return the date in ISO 8601 format."
be961cd5 411 (lm-with-file file
07d505c1
GM
412 (when (progn (goto-char (point-min))
413 (re-search-forward
414 "\\$[I]d: [^ ]+ [^ ]+ \\([^/]+\\)/\\([^/]+\\)/\\([^ ]+\\) "
415 (lm-code-mark) t))
416 (let ((dd (match-string 3))
417 (mm (match-string 2))
418 (yyyy (match-string 1)))
419 (if iso-date
420 (format "%s-%s-%s" yyyy mm dd)
421 (format "%s %s %s"
422 dd
027a4b6b 423 (nth (string-to-number mm)
07d505c1
GM
424 '("" "Jan" "Feb" "Mar" "Apr" "May" "Jun"
425 "Jul" "Aug" "Sep" "Oct" "Nov" "Dec"))
426 yyyy))))))
0fc37e7d
ER
427
428(defun lm-version (&optional file)
ca4bd734 429 "Return the version listed in file FILE, or current buffer if FILE is nil.
2505742b 430This can be found in an RCS or SCCS header."
be961cd5 431 (lm-with-file file
2505742b
DL
432 (or (lm-header "version")
433 (let ((header-max (lm-code-mark)))
434 (goto-char (point-min))
435 (cond
436 ;; Look for an RCS header
437 ((re-search-forward "\\$[I]d: [^ ]+ \\([^ ]+\\) " header-max t)
438 (match-string-no-properties 1))
439 ((re-search-forward "\\$Revision: +\\([^ ]+\\) " header-max t)
440 (match-string-no-properties 1))
441 ;; Look for an SCCS header
442 ((re-search-forward
443 (concat
444 (regexp-quote "@(#)")
445 (regexp-quote (file-name-nondirectory (buffer-file-name)))
446 "\t\\([012345679.]*\\)")
447 header-max t)
448 (match-string-no-properties 1)))))))
0fc37e7d
ER
449
450(defun lm-keywords (&optional file)
ca4bd734 451 "Return the keywords given in file FILE, or current buffer if FILE is nil."
be961cd5
SM
452 (lm-with-file file
453 (let ((keywords (lm-header "keywords")))
454 (and keywords (downcase keywords)))))
0fc37e7d 455
5efa6032
GM
456(defun lm-keywords-list (&optional file)
457 "Return list of keywords given in file FILE."
458 (let ((keywords (lm-keywords file)))
459 (if keywords
460 (split-string keywords ",?[ \t]"))))
461
de00302b 462(defvar finder-known-keywords)
5efa6032
GM
463(defun lm-keywords-finder-p (&optional file)
464 "Return non-nil if any keywords in FILE are known to finder."
465 (require 'finder)
466 (let ((keys (lm-keywords-list file)))
467 (catch 'keyword-found
468 (while keys
de00302b 469 (if (assoc (intern (car keys)) finder-known-keywords)
5efa6032
GM
470 (throw 'keyword-found t))
471 (setq keys (cdr keys)))
472 nil)))
473
0fc37e7d 474(defun lm-adapted-by (&optional file)
ca4bd734
RS
475 "Return the adapted-by names in file FILE, or current buffer if FILE is nil.
476This is the name of the person who cleaned up this package for
477distribution."
be961cd5
SM
478 (lm-with-file file
479 (lm-header "adapted-by")))
0fc37e7d 480
64e07c7b 481(defun lm-commentary (&optional file)
ca4bd734 482 "Return the commentary in file FILE, or current buffer if FILE is nil.
c7dff41d
LK
483Return the value as a string. In the file, the commentary
484section starts with the tag `Commentary' or `Documentation' and
485ends just before the next section. If the commentary section is
486absent, return nil."
be961cd5 487 (lm-with-file file
c7dff41d
LK
488 (let ((start (lm-commentary-start)))
489 (when start
490 (buffer-substring-no-properties start (lm-commentary-end))))))
0fc37e7d
ER
491
492;;; Verification and synopses
493
d5eead69 494(defun lm-insert-at-column (col &rest strings)
6e24ad22 495 "Insert, at column COL, list of STRINGS."
d5eead69 496 (if (> (current-column) col) (insert "\n"))
b4d73c71 497 (move-to-column col t)
d5eead69 498 (apply 'insert strings))
0fc37e7d 499
179e152a 500(defun lm-verify (&optional file showok verbose non-fsf-ok)
0fc37e7d 501 "Check that the current buffer (or FILE if given) is in proper format.
13c40e2f 502If FILE is a directory, recurse on its files and generate a report in a
179e152a
RS
503temporary buffer. In that case, the optional argument SHOWOK
504says display \"OK\" in temp buffer for files that have no problems.
505
506Optional argument VERBOSE specifies verbosity level.
507Optional argument NON-FSF-OK if non-nil means a non-FSF
508copyright notice is allowed."
509 (interactive (list nil nil t))
510 (let* ((ret (and verbose "Ok"))
2505742b 511 name)
d5eead69 512 (if (and file (file-directory-p file))
2505742b
DL
513 (setq ret
514 (with-temp-buffer
0e250bf8
DK
515 (dolist (f (directory-files file nil "\\.el\\'")
516 (buffer-string))
517 (when (file-regular-p f)
518 (let ((status (lm-verify f)))
519 (insert f ":")
520 (if status
521 (lm-insert-at-column lm-comment-column status
522 "\n")
523 (if showok
524 (lm-insert-at-column lm-comment-column
525 "OK\n"))))))))
be961cd5 526 (lm-with-file file
d5eead69 527 (setq name (lm-get-package-name))
2505742b
DL
528 (setq ret
529 (cond
530 ((null name)
d71eb4d4 531 "Can't find package name")
2505742b 532 ((not (lm-authors))
179e152a 533 "`Author:' tag missing")
2505742b 534 ((not (lm-maintainer))
179e152a 535 "`Maintainer:' tag missing")
2505742b 536 ((not (lm-summary))
179e152a 537 "Can't find the one-line summary description")
2505742b 538 ((not (lm-keywords))
179e152a 539 "`Keywords:' tag missing")
5efa6032 540 ((not (lm-keywords-finder-p))
179e152a 541 "`Keywords:' has no valid finder keywords (see `finder-known-keywords')")
2505742b 542 ((not (lm-commentary-mark))
179e152a 543 "Can't find a 'Commentary' section marker")
2505742b 544 ((not (lm-history-mark))
179e152a 545 "Can't find a 'History' section marker")
2505742b
DL
546 ((not (lm-code-mark))
547 "Can't find a 'Code' section marker")
548 ((progn
549 (goto-char (point-max))
550 (not
551 (re-search-backward
552 (concat "^;;;[ \t]+" name "[ \t]+ends here[ \t]*$"
553 "\\|^;;;[ \t]+ End of file[ \t]+" name)
554 nil t)))
d71eb4d4 555 "Can't find the footer line")
13c40e2f 556 ((not (and (lm-copyright-mark) (lm-crack-copyright)))
179e152a
RS
557 "Can't find a valid copyright notice")
558 ((not (or non-fsf-ok
559 (string-match "Free Software Foundation"
560 (car (lm-crack-copyright)))))
561 "Copyright holder is not the Free Software Foundation")
2505742b
DL
562 (t
563 ret)))))
179e152a 564 (if verbose
274f1353 565 (message "%s" ret))
2505742b 566 ret))
0fc37e7d
ER
567
568(defun lm-synopsis (&optional file showall)
569 "Generate a synopsis listing for the buffer or the given FILE if given.
ca4bd734
RS
570If FILE is a directory, recurse on its files and generate a report in
571a temporary buffer. If SHOWALL is non-nil, also generate a line for files
0fc37e7d 572which do not include a recognizable synopsis."
d5eead69
RS
573 (interactive
574 (list
575 (read-file-name "Synopsis for (file or dir): ")))
576
0fc37e7d 577 (if (and file (file-directory-p file))
ccb78fa3
LK
578 (with-output-to-temp-buffer "*Synopsis*"
579 (set-buffer standard-output)
580 (dolist (f (directory-files file nil ".*\\.el\\'"))
581 (let ((syn (lm-synopsis (expand-file-name f file))))
582 (when (or syn showall)
583 (insert f ":")
584 (lm-insert-at-column lm-comment-column (or syn "NA") "\n")))))
2505742b 585 (save-excursion
ccb78fa3
LK
586 (let ((must-kill (and file (not (get-file-buffer file)))))
587 (when file (find-file file))
588 (prog1
589 (if (interactive-p)
590 (message "%s" (lm-summary))
591 (lm-summary))
592 (when must-kill (kill-buffer (current-buffer))))))))
2505742b
DL
593
594(eval-when-compile (defvar report-emacs-bug-address))
0fc37e7d
ER
595
596(defun lm-report-bug (topic)
597 "Report a bug in the package currently being visited to its maintainer.
6e24ad22 598Prompts for bug subject TOPIC. Leaves you in a mail buffer."
68756f1f 599 (interactive "sBug Subject: ")
2505742b
DL
600 (require 'emacsbug)
601 (let ((package (lm-get-package-name))
602 (addr (lm-maintainer))
603 (version (lm-version)))
604 (compose-mail (if addr
605 (concat (car addr) " <" (cdr addr) ">")
606 report-emacs-bug-address)
607 topic)
0fc37e7d 608 (goto-char (point-max))
2505742b
DL
609 (insert "\nIn " package)
610 (if version
611 (insert " version " version))
612 (newline 2)
55aed120 613 (message "%s"
0fc37e7d
ER
614 (substitute-command-keys "Type \\[mail-send] to send bug report."))))
615
616(provide 'lisp-mnt)
617
cbee283d 618;; arch-tag: fa3c5ab4-a37b-4e46-b7cf-b6d78b90e69e
0fc37e7d 619;;; lisp-mnt.el ends here