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