Add 2012 to FSF copyright years for Emacs files
[bpt/emacs.git] / lisp / emacs-lisp / lisp-mnt.el
CommitLineData
c7dff41d 1;;; lisp-mnt.el --- utility functions for Emacs Lisp maintainers
0fc37e7d 2
acaf905b 3;; Copyright (C) 1992, 1994, 1997, 2000-2012 Free Software Foundation, Inc.
0fc37e7d
ER
4
5;; Author: Eric S. Raymond <esr@snark.thyrsus.com>
877d5b39 6;; Maintainer: FSF
0fc37e7d 7;; Created: 14 Jul 1992
0fc37e7d 8;; Keywords: docs
1894df21 9;; X-Bogus-Bureaucratic-Cruft: Gruad will get you if you don't watch out!
0fc37e7d
ER
10
11;; This file is part of GNU Emacs.
12
d6cba7ae 13;; GNU Emacs is free software: you can redistribute it and/or modify
0fc37e7d 14;; it under the terms of the GNU General Public License as published by
d6cba7ae
GM
15;; the Free Software Foundation, either version 3 of the License, or
16;; (at your option) any later version.
0fc37e7d
ER
17
18;; GNU Emacs is distributed in the hope that it will be useful,
19;; but WITHOUT ANY WARRANTY; without even the implied warranty of
20;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21;; GNU General Public License for more details.
22
23;; You should have received a copy of the GNU General Public License
d6cba7ae 24;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
0fc37e7d
ER
25
26;;; Commentary:
27
c7dff41d 28;; This library adds some services to Emacs-Lisp editing mode.
0fc37e7d
ER
29;;
30;; First, it knows about the header conventions for library packages.
31;; One entry point supports generating synopses from a library directory.
32;; Another can be used to check for missing headers in library files.
a1506d29 33;;
0fc37e7d
ER
34;; Another entry point automatically addresses bug mail to a package's
35;; maintainer or author.
36
d9dd743e
JB
37;; This file can be loaded by your emacs-lisp-mode-hook. Have it
38;; (require 'lisp-mnt)
0fc37e7d
ER
39
40;; This file is an example of the header conventions. Note the following
41;; features:
a1506d29 42;;
0fc37e7d
ER
43;; * Header line --- makes it possible to extract a one-line summary of
44;; the package's uses automatically for use in library synopses, KWIC
45;; indexes and the like.
a1506d29 46;;
0fc37e7d
ER
47;; Format is three semicolons, followed by the filename, followed by
48;; three dashes, followed by the summary. All fields space-separated.
179e152a
RS
49;;
50;; * A blank line
51;;
52;; * Copyright line, which looks more or less like this:
53;;
54;; ;; Copyright (C) 1999, 2000, 2001 Free Software Foundation, Inc.
55;;
56;; * A blank line
a1506d29 57;;
0fc37e7d
ER
58;; * Author line --- contains the name and net address of at least
59;; the principal author.
a1506d29 60;;
68756f1f 61;; If there are multiple authors, they should be listed on continuation
0fc37e7d 62;; lines led by ;;<TAB>, like this:
a1506d29 63;;
0fc37e7d
ER
64;; ;; Author: Ashwin Ram <Ram-Ashwin@cs.yale.edu>
65;; ;; Dave Sill <de5@ornl.gov>
66;; ;; David Lawrence <tale@pawl.rpi.edu>
67;; ;; Noah Friedman <friedman@ai.mit.edu>
68;; ;; Joe Wells <jbw@maverick.uswest.com>
69;; ;; Dave Brennan <brennan@hal.com>
70;; ;; Eric Raymond <esr@snark.thyrsus.com>
a1506d29 71;;
0fc37e7d
ER
72;; This field may have some special values; notably "FSF", meaning
73;; "Free Software Foundation".
a1506d29 74;;
0fc37e7d
ER
75;; * Maintainer line --- should be a single name/address as in the Author
76;; line, or an address only, or the string "FSF". If there is no maintainer
531fdf02 77;; line, the person(s) in the Author field are presumed to be it.
ca4bd734 78;; The idea behind these two fields is to be able to write a Lisp function
0fc37e7d 79;; that does "send mail to the author" without having to mine the name out by
c7dff41d 80;; hand. Please be careful about surrounding the network address with <> if
0fc37e7d 81;; there's also a name in the field.
a1506d29 82;;
0fc37e7d
ER
83;; * Created line --- optional, gives the original creation date of the
84;; file. For historical interest, basically.
a1506d29 85;;
0fc37e7d 86;; * Version line --- intended to give the reader a clue if they're looking
68756f1f
ER
87;; at a different version of the file than the one they're accustomed to. This
88;; may be an RCS or SCCS header.
a1506d29 89;;
0fc37e7d
ER
90;; * Adapted-By line --- this is for FSF's internal use. The person named
91;; in this field was the one responsible for installing and adapting the
92;; package for the distribution. (This file doesn't have one because the
93;; author *is* one of the maintainers.)
a1506d29 94;;
531fdf02
GM
95;; * Keywords line --- used by the finder code for finding Emacs
96;; Lisp code related to a topic.
0fc37e7d 97;;
1894df21
NF
98;; * X-Bogus-Bureaucratic-Cruft line --- this is a joke and an example
99;; of a comment header. Headers starting with `X-' should never be used
100;; for any real purpose; this is the way to safely add random headers
101;; without invoking the wrath of any program.
0fc37e7d 102;;
ca4bd734 103;; * Commentary line --- enables Lisp code to find the developer's and
0fc37e7d 104;; maintainers' explanations of the package internals.
a1506d29 105;;
0fc37e7d
ER
106;; * Change log line --- optional, exists to terminate the commentary
107;; section and start a change-log part, if one exists.
a1506d29 108;;
c0d79871 109;; * Code line --- exists so Lisp can know where commentary and/or
0fc37e7d 110;; change-log sections end.
a1506d29 111;;
0fc37e7d
ER
112;; * Footer line --- marks end-of-file so it can be distinguished from
113;; an expanded formfeed or the results of truncation.
114
115;;; Change Log:
116
117;; Tue Jul 14 23:44:17 1992 ESR
118;; * Created.
119
120;;; Code:
121
d5eead69
RS
122;;; Variables:
123
666b9413 124(defgroup lisp-mnt nil
c7dff41d 125 "Utility functions for Emacs Lisp maintainers."
666b9413
SE
126 :prefix "lm-"
127 :group 'maint)
128
af95e0d1
DL
129;; At least some of these defcustoms should probably be defconsts,
130;; since they define, or are defined by, the header format. -- fx
131
438cdcde 132(defcustom lm-header-prefix "^;+[ \t]+\\(@(#)\\)?[ \t]*\\$?"
d5eead69 133 "Prefix that is ignored before the tag.
ca4bd734
RS
134For example, you can write the 1st line synopsis string and headers like this
135in your Lisp package:
d5eead69 136
7a3ee510 137 ;; @(#) package.el -- package description
d5eead69
RS
138 ;;
139 ;; @(#) $Maintainer: Person Foo Bar $
140
141The @(#) construct is used by unix what(1) and
666b9413
SE
142then $identifier: doc string $ is used by GNU ident(1)"
143 :type 'regexp
144 :group 'lisp-mnt)
145
af95e0d1
DL
146(defcustom lm-copyright-prefix "^\\(;+[ \t]\\)+Copyright (C) "
147 "Prefix that is ignored before the dates in a copyright.
148Leading comment characters and whitespace should be in regexp group 1."
13c40e2f
GM
149 :type 'regexp
150 :group 'lisp-mnt)
151
666b9413
SE
152(defcustom lm-comment-column 16
153 "Column used for placing formatted output."
154 :type 'integer
155 :group 'lisp-mnt)
156
c7dff41d
LK
157(defcustom lm-any-header ".*"
158 "Regexp which matches start of any section."
159 :type 'regexp
160 :group 'lisp-mnt)
161
666b9413
SE
162(defcustom lm-commentary-header "Commentary\\|Documentation"
163 "Regexp which matches start of documentation section."
164 :type 'regexp
165 :group 'lisp-mnt)
166
3d1b88f6 167(defcustom lm-history-header "Change ?Log\\|History"
666b9413
SE
168 "Regexp which matches the start of code log section."
169 :type 'regexp
170 :group 'lisp-mnt)
d5eead69
RS
171
172;;; Functions:
173
0fc37e7d
ER
174;; These functions all parse the headers of the current buffer
175
2505742b 176(defun lm-get-header-re (header &optional mode)
6e24ad22 177 "Return regexp for matching HEADER.
ca4bd734
RS
178If called with optional MODE and with value `section',
179return section regexp instead."
438cdcde 180 (if (eq mode 'section)
a94d391a
RS
181 (concat "^;;;;* \\(" header "\\):[ \t]*$")
182 (concat lm-header-prefix "\\(" header "\\)[ \t]*:[ \t]*")))
d5eead69 183
2505742b 184(defun lm-get-package-name ()
6e24ad22 185 "Return package name by looking at the first line."
d5eead69
RS
186 (save-excursion
187 (goto-char (point-min))
188 (if (and (looking-at (concat lm-header-prefix))
189 (progn (goto-char (match-end 0))
190 (looking-at "\\([^\t ]+\\)")
191 (match-end 1)))
2505742b 192 (match-string-no-properties 1))))
d5eead69 193
c7dff41d 194(defun lm-section-start (header &optional after)
d5eead69 195 "Return the buffer location of a given section start marker.
ca4bd734 196The HEADER is the section mark string to search for.
c7dff41d
LK
197If AFTER is non-nil, return the location of the next line.
198If the given section does not exist, return nil."
0fc37e7d
ER
199 (save-excursion
200 (let ((case-fold-search t))
201 (goto-char (point-min))
d5eead69 202 (if (re-search-forward (lm-get-header-re header 'section) nil t)
c7dff41d
LK
203 (line-beginning-position (if after 2))))))
204(defalias 'lm-section-mark 'lm-section-start)
205
206(defun lm-section-end (header)
207 "Return the buffer location of the end of a given section.
208The HEADER is the section string marking the beginning of the
209section. If the given section does not exist, return nil.
210
211The end of the section is defined as the beginning of the next
212section of the same level or lower. The function
213`lisp-outline-level' is used to compute the level of a section.
214If no such section exists, return the end of the buffer."
38068db2 215 (require 'outline) ;; for outline-regexp.
c7dff41d
LK
216 (let ((start (lm-section-start header)))
217 (when start
218 (save-excursion
219 (goto-char start)
220 (let ((level (lisp-outline-level))
221 (case-fold-search t)
222 next-section-found)
223 (beginning-of-line 2)
224 (while (and (setq next-section-found
225 (re-search-forward
226 (lm-get-header-re lm-any-header 'section)
227 nil t))
228 (> (save-excursion
229 (beginning-of-line)
230 (lisp-outline-level))
231 level)))
232 (if next-section-found
233 (line-beginning-position)
234 (point-max)))))))
235
236(defsubst lm-code-start ()
ca4bd734 237 "Return the buffer location of the `Code' start marker."
c7dff41d
LK
238 (lm-section-start "Code"))
239(defalias 'lm-code-mark 'lm-code-start)
0fc37e7d 240
c7dff41d 241(defsubst lm-commentary-start ()
ca4bd734 242 "Return the buffer location of the `Commentary' start marker."
c7dff41d
LK
243 (lm-section-start lm-commentary-header))
244(defalias 'lm-commentary-mark 'lm-commentary-start)
245
246(defsubst lm-commentary-end ()
247 "Return the buffer location of the `Commentary' section end."
248 (lm-section-end lm-commentary-header))
d5eead69 249
c7dff41d 250(defsubst lm-history-start ()
ca4bd734 251 "Return the buffer location of the `History' start marker."
c7dff41d
LK
252 (lm-section-start lm-history-header))
253(defalias 'lm-history-mark 'lm-history-start)
d5eead69 254
13c40e2f
GM
255(defsubst lm-copyright-mark ()
256 "Return the buffer location of the `Copyright' line."
257 (save-excursion
258 (let ((case-fold-search t))
259 (goto-char (point-min))
260 (if (re-search-forward lm-copyright-prefix nil t)
c7dff41d 261 (point)))))
13c40e2f 262
d5eead69 263(defun lm-header (header)
ca4bd734
RS
264 "Return the contents of the header named HEADER."
265 (goto-char (point-min))
266 (let ((case-fold-search t))
438cdcde
SM
267 (when (and (re-search-forward (lm-get-header-re header) (lm-code-mark) t)
268 ;; RCS ident likes format "$identifier: data$"
269 (looking-at
270 (if (save-excursion
271 (skip-chars-backward "^$" (match-beginning 0))
272 (= (point) (match-beginning 0)))
273 "[^\n]+" "[^$\n]+")))
274 (match-string-no-properties 0))))
0fc37e7d 275
d5eead69 276(defun lm-header-multiline (header)
ca4bd734 277 "Return the contents of the header named HEADER, with continuation lines.
d5eead69 278The returned value is a list of strings, one per line."
0fc37e7d
ER
279 (save-excursion
280 (goto-char (point-min))
d5eead69 281 (let ((res (lm-header header)))
be961cd5 282 (when res
d5eead69
RS
283 (setq res (list res))
284 (forward-line 1)
438cdcde
SM
285 (while (and (or (looking-at (concat lm-header-prefix "[\t ]+"))
286 (and (not (looking-at
287 (lm-get-header-re "\\sw\\(\\sw\\|\\s_\\)*")))
288 (looking-at lm-header-prefix)))
289 (goto-char (match-end 0))
290 (looking-at ".+"))
291 (setq res (cons (match-string-no-properties 0) res))
2505742b 292 (forward-line 1)))
438cdcde 293 (nreverse res))))
0fc37e7d
ER
294
295;; These give us smart access to the header fields and commentary
296
be961cd5 297(defmacro lm-with-file (file &rest body)
a94d391a 298 "Execute BODY in a buffer containing the contents of FILE.
9e1b128c 299If FILE is nil, execute BODY in the current buffer."
f291fe60 300 (declare (indent 1) (debug t))
be961cd5 301 (let ((filesym (make-symbol "file")))
a94d391a 302 `(let ((,filesym ,file))
9e1b128c
RS
303 (if ,filesym
304 (with-temp-buffer
305 (insert-file-contents ,filesym)
d9dd743e 306 (emacs-lisp-mode)
9e1b128c 307 ,@body)
3731a850 308 (save-excursion
d4b6c2e3 309 ;; Switching major modes is too drastic, so just switch
d9dd743e
JB
310 ;; temporarily to the Emacs Lisp mode syntax table.
311 (with-syntax-table emacs-lisp-mode-syntax-table
d4b6c2e3
RS
312 ,@body))))))
313
af95e0d1
DL
314;; Fixme: Probably this should be amalgamated with copyright.el; also
315;; we need a check for ranges in copyright years.
316
13c40e2f
GM
317(defun lm-crack-copyright (&optional file)
318 "Return the copyright holder, and a list of copyright years.
319Use the current buffer if FILE is nil.
320Return argument is of the form (\"HOLDER\" \"YEAR1\" ... \"YEARN\")"
321 (lm-with-file file
322 (goto-char (lm-copyright-mark))
323 (let ((holder nil)
324 (years nil)
af95e0d1 325 (start (point))
13c40e2f 326 (end (line-end-position)))
af95e0d1
DL
327 ;; Cope with multi-line copyright `lines'. Assume the second
328 ;; line is indented (with the same commenting style).
329 (save-excursion
330 (beginning-of-line 2)
331 (let ((str (concat (match-string-no-properties 1) "[ \t]+")))
332 (beginning-of-line)
333 (while (looking-at str)
334 (setq end (line-end-position))
335 (beginning-of-line 2))))
336 ;; Make a single line and parse that.
337 (let ((buff (current-buffer)))
338 (with-temp-buffer
339 (insert-buffer-substring buff start end)
340 (goto-char (point-min))
341 (while (re-search-forward "^;+[ \t]+" nil t)
342 (replace-match ""))
343 (goto-char (point-min))
344 (while (re-search-forward " *\n" nil t)
345 (replace-match " "))
346 (goto-char (point-min))
347 (while (re-search-forward "\\([0-9]+\\),? +" nil t)
348 (setq years (cons (match-string-no-properties 1) years)))
349 (if (looking-at ".*$")
350 (setq holder (match-string-no-properties 0)))))
351 (cons holder (nreverse years)))))
13c40e2f 352
0fc37e7d 353(defun lm-summary (&optional file)
ca4bd734 354 "Return the one-line summary of file FILE, or current buffer if FILE is nil."
be961cd5 355 (lm-with-file file
0fc37e7d 356 (goto-char (point-min))
2505742b
DL
357 (if (and (looking-at lm-header-prefix)
358 (progn (goto-char (match-end 0))
359 (looking-at "[^ ]+[ \t]+--+[ \t]+\\(.*\\)")))
360 (let ((summary (match-string-no-properties 1)))
be961cd5
SM
361 ;; Strip off -*- specifications.
362 (if (string-match "[ \t]*-\\*-.*-\\*-" summary)
363 (substring summary 0 (match-beginning 0))
364 summary)))))
0fc37e7d 365
68756f1f 366(defun lm-crack-address (x)
6e24ad22 367 "Split up an email address X into full name and real email address.
ca4bd734 368The value is a cons of the form (FULLNAME . ADDRESS)."
68756f1f 369 (cond ((string-match "\\(.+\\) [(<]\\(\\S-+@\\S-+\\)[>)]" x)
2505742b
DL
370 (cons (match-string 1 x)
371 (match-string 2 x)))
68756f1f 372 ((string-match "\\(\\S-+@\\S-+\\) [(<]\\(.*\\)[>)]" x)
2505742b
DL
373 (cons (match-string 2 x)
374 (match-string 1 x)))
68756f1f
ER
375 ((string-match "\\S-+@\\S-+" x)
376 (cons nil x))
377 (t
378 (cons x nil))))
379
0fc37e7d 380(defun lm-authors (&optional file)
ca4bd734
RS
381 "Return the author list of file FILE, or current buffer if FILE is nil.
382Each element of the list is a cons; the car is the full name,
383the cdr is an email address."
be961cd5 384 (lm-with-file file
68756f1f 385 (let ((authorlist (lm-header-multiline "author")))
be961cd5 386 (mapcar 'lm-crack-address authorlist))))
0fc37e7d
ER
387
388(defun lm-maintainer (&optional file)
ca4bd734
RS
389 "Return the maintainer of file FILE, or current buffer if FILE is nil.
390The return value has the form (NAME . ADDRESS)."
be961cd5
SM
391 (lm-with-file file
392 (let ((maint (lm-header "maintainer")))
393 (if maint
394 (lm-crack-address maint)
395 (car (lm-authors))))))
0fc37e7d
ER
396
397(defun lm-creation-date (&optional file)
ca4bd734 398 "Return the created date given in file FILE, or current buffer if FILE is nil."
be961cd5
SM
399 (lm-with-file file
400 (lm-header "created")))
0fc37e7d 401
07d505c1
GM
402(defun lm-last-modified-date (&optional file iso-date)
403 "Return the modify-date given in file FILE, or current buffer if FILE is nil.
404ISO-DATE non-nil means return the date in ISO 8601 format."
be961cd5 405 (lm-with-file file
07d505c1
GM
406 (when (progn (goto-char (point-min))
407 (re-search-forward
408 "\\$[I]d: [^ ]+ [^ ]+ \\([^/]+\\)/\\([^/]+\\)/\\([^ ]+\\) "
409 (lm-code-mark) t))
410 (let ((dd (match-string 3))
411 (mm (match-string 2))
412 (yyyy (match-string 1)))
413 (if iso-date
414 (format "%s-%s-%s" yyyy mm dd)
415 (format "%s %s %s"
416 dd
027a4b6b 417 (nth (string-to-number mm)
07d505c1
GM
418 '("" "Jan" "Feb" "Mar" "Apr" "May" "Jun"
419 "Jul" "Aug" "Sep" "Oct" "Nov" "Dec"))
420 yyyy))))))
0fc37e7d
ER
421
422(defun lm-version (&optional file)
ca4bd734 423 "Return the version listed in file FILE, or current buffer if FILE is nil.
2505742b 424This can be found in an RCS or SCCS header."
be961cd5 425 (lm-with-file file
2505742b
DL
426 (or (lm-header "version")
427 (let ((header-max (lm-code-mark)))
428 (goto-char (point-min))
429 (cond
430 ;; Look for an RCS header
431 ((re-search-forward "\\$[I]d: [^ ]+ \\([^ ]+\\) " header-max t)
432 (match-string-no-properties 1))
433 ((re-search-forward "\\$Revision: +\\([^ ]+\\) " header-max t)
434 (match-string-no-properties 1))
435 ;; Look for an SCCS header
436 ((re-search-forward
437 (concat
438 (regexp-quote "@(#)")
439 (regexp-quote (file-name-nondirectory (buffer-file-name)))
440 "\t\\([012345679.]*\\)")
441 header-max t)
442 (match-string-no-properties 1)))))))
0fc37e7d
ER
443
444(defun lm-keywords (&optional file)
497de631
KR
445 "Return the keywords given in file FILE, or current buffer if FILE is nil.
446The return is a `downcase'-ed string, or nil if no keywords
447header. Multi-line keywords are joined up with a space between
448each line."
be961cd5 449 (lm-with-file file
497de631
KR
450 (let ((keywords (lm-header-multiline "keywords")))
451 (and keywords
452 (mapconcat 'downcase keywords " ")))))
0fc37e7d 453
5efa6032
GM
454(defun lm-keywords-list (&optional file)
455 "Return list of keywords given in file FILE."
456 (let ((keywords (lm-keywords file)))
457 (if keywords
88f4758e
JL
458 (if (string-match-p "," keywords)
459 (split-string keywords ",[ \t\n]*" t)
460 (split-string keywords "[ \t\n]+" t)))))
5efa6032 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
32226619 589 (if (called-interactively-p 'interactive)
ccb78fa3
LK
590 (message "%s" (lm-summary))
591 (lm-summary))
592 (when must-kill (kill-buffer (current-buffer))))))))
2505742b 593
531fdf02 594(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
618;;; lisp-mnt.el ends here