* admin/admin.el (make-manuals): Avoid hard-coding list of misc manuals.
[bpt/emacs.git] / admin / admin.el
CommitLineData
74499542
GM
1;;; admin.el --- utilities for Emacs administration
2
ab422c4d 3;; Copyright (C) 2001-2013 Free Software Foundation, Inc.
74499542
GM
4
5;; This file is part of GNU Emacs.
6
9ad5de0c 7;; GNU Emacs is free software: you can redistribute it and/or modify
74499542 8;; it under the terms of the GNU General Public License as published by
9ad5de0c
GM
9;; the Free Software Foundation, either version 3 of the License, or
10;; (at your option) any later version.
74499542
GM
11
12;; GNU Emacs is distributed in the hope that it will be useful,
13;; but WITHOUT ANY WARRANTY; without even the implied warranty of
14;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15;; GNU General Public License for more details.
16
17;; You should have received a copy of the GNU General Public License
9ad5de0c 18;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
74499542
GM
19
20;;; Commentary:
21
54381691
LK
22;; add-release-logs Add ``Version X released'' change log entries.
23;; set-version Change Emacs version number in source tree.
a3045b7e
GM
24;; set-copyright Change emacs short copyright string (eg as
25;; printed by --version) in source tree.
74499542
GM
26
27;;; Code:
28
dcddaabb
GM
29(defvar add-log-time-format) ; in add-log
30
78cd48e7
GM
31;; Does this information need to be in every ChangeLog, as opposed to
32;; just the top-level one? Only if you allow changes the same
33;; day as the release.
34;; http://lists.gnu.org/archive/html/emacs-devel/2013-03/msg00161.html
8c39e821 35(defun add-release-logs (root version &optional date)
74499542 36 "Add \"Version VERSION released.\" change log entries in ROOT.
8c39e821
GM
37Root must be the root of an Emacs source tree.
38Optional argument DATE is the release date, default today."
39 (interactive (list (read-directory-name "Emacs root directory: ")
40 (read-string "Version number: "
41 (format "%s.%s" emacs-major-version
42 emacs-minor-version))
43 (read-string "Release date: "
44 (progn (require 'add-log)
1a7bceef
GM
45 (let ((add-log-time-zone-rule t))
46 (funcall add-log-time-format))))))
3f4a4bdf 47 (setq root (expand-file-name root))
74499542
GM
48 (unless (file-exists-p (expand-file-name "src/emacs.c" root))
49 (error "%s doesn't seem to be the root of an Emacs source tree" root))
54381691 50 (require 'add-log)
1a7bceef
GM
51 (or date (setq date (let ((add-log-time-zone-rule t))
52 (funcall add-log-time-format))))
74499542
GM
53 (let* ((logs (process-lines "find" root "-name" "ChangeLog"))
54 (entry (format "%s %s <%s>\n\n\t* Version %s released.\n\n"
8c39e821 55 date
3f4a4bdf
FP
56 (or add-log-full-name (user-full-name))
57 (or add-log-mailing-address user-mail-address)
58 version)))
74499542 59 (dolist (log logs)
8c39e821
GM
60 (find-file log)
61 (goto-char (point-min))
62 (insert entry))))
74499542 63
74499542
GM
64(defun set-version-in-file (root file version rx)
65 (find-file (expand-file-name file root))
66 (goto-char (point-min))
67 (unless (re-search-forward rx nil t)
68 (error "Version not found in %s" file))
69 (replace-match (format "%s" version) nil nil nil 1))
70
74499542
GM
71(defun set-version (root version)
72 "Set Emacs version to VERSION in relevant files under ROOT.
73Root must be the root of an Emacs source tree."
91ebb8c9 74 (interactive "DEmacs root directory: \nsVersion number: ")
74499542
GM
75 (unless (file-exists-p (expand-file-name "src/emacs.c" root))
76 (error "%s doesn't seem to be the root of an Emacs source tree" root))
74499542
GM
77 (set-version-in-file root "README" version
78 (rx (and "version" (1+ space)
79 (submatch (1+ (in "0-9."))))))
c4444d16 80 (set-version-in-file root "configure.ac" version
24bbe01e
GM
81 (rx (and "AC_INIT" (1+ (not (in ?,)))
82 ?, (0+ space)
83 (submatch (1+ (in "0-9."))))))
f4f358f1
GM
84 (set-version-in-file root "doc/emacs/emacsver.texi" version
85 (rx (and "EMACSVER" (1+ space)
86 (submatch (1+ (in "0-9."))))))
adf94aa6
GM
87 (set-version-in-file root "doc/man/emacs.1" version
88 (rx (and ".TH EMACS" (1+ not-newline)
89 "GNU Emacs" (1+ space)
90 (submatch (1+ (in "0-9."))))))
70b0d280
EZ
91 (set-version-in-file root "nt/config.nt" version
92 (rx (and bol "#" (0+ blank) "define" (1+ blank)
a0c64452 93 "VERSION" (1+ blank) "\""
1fe1ef05 94 (submatch (1+ (in "0-9."))))))
9d9d12cd
EZ
95 (set-version-in-file root "msdos/sed2v2.inp" version
96 (rx (and bol "/^#undef " (1+ not-newline)
a0c64452 97 "define VERSION" (1+ space) "\""
9d9d12cd 98 (submatch (1+ (in "0-9."))))))
e3aef5c6
CS
99 (set-version-in-file root "nt/makefile.w32-in" version
100 (rx (and "VERSION" (0+ space) "=" (0+ space)
101 (submatch (1+ (in "0-9."))))))
95f76284
JR
102 ;; nt/emacs.rc also contains the version number, but in an awkward
103 ;; format. It must contain four components, separated by commas, and
104 ;; in two places those commas are followed by space, in two other
105 ;; places they are not.
106 (let* ((version-components (append (split-string version "\\.")
107 '("0" "0")))
108 (comma-version
109 (concat (car version-components) ","
110 (cadr version-components) ","
d0834a5c 111 (cadr (cdr version-components)) ","
95f76284
JR
112 (cadr (cdr (cdr version-components)))))
113 (comma-space-version
114 (concat (car version-components) ", "
115 (cadr version-components) ", "
d0834a5c 116 (cadr (cdr version-components)) ", "
95f76284
JR
117 (cadr (cdr (cdr version-components))))))
118 (set-version-in-file root "nt/emacs.rc" comma-version
119 (rx (and "FILEVERSION" (1+ space)
120 (submatch (1+ (in "0-9,"))))))
121 (set-version-in-file root "nt/emacs.rc" comma-version
122 (rx (and "PRODUCTVERSION" (1+ space)
123 (submatch (1+ (in "0-9,"))))))
124 (set-version-in-file root "nt/emacs.rc" comma-space-version
125 (rx (and "\"FileVersion\"" (0+ space) ?, (0+ space)
126 ?\" (submatch (1+ (in "0-9, "))) "\\0\"")))
127 (set-version-in-file root "nt/emacs.rc" comma-space-version
128 (rx (and "\"ProductVersion\"" (0+ space) ?,
129 (0+ space) ?\" (submatch (1+ (in "0-9, ")))
b7063e7e
JR
130 "\\0\"")))
131 ;; Likewise for emacsclient.rc
132 (set-version-in-file root "nt/emacsclient.rc" comma-version
133 (rx (and "FILEVERSION" (1+ space)
134 (submatch (1+ (in "0-9,"))))))
135 (set-version-in-file root "nt/emacsclient.rc" comma-version
136 (rx (and "PRODUCTVERSION" (1+ space)
137 (submatch (1+ (in "0-9,"))))))
138 (set-version-in-file root "nt/emacsclient.rc" comma-space-version
139 (rx (and "\"FileVersion\"" (0+ space) ?, (0+ space)
140 ?\" (submatch (1+ (in "0-9, "))) "\\0\"")))
141 (set-version-in-file root "nt/emacsclient.rc" comma-space-version
95f76284
JR
142 (rx (and "\"ProductVersion\"" (0+ space) ?,
143 (0+ space) ?\" (submatch (1+ (in "0-9, ")))
dcf8834b 144 "\\0\"")))
eec5fae2 145 ;; Major version only.
dcf8834b
GM
146 (when (string-match "\\([0-9]\\{2,\\}\\)" version)
147 (setq version (match-string 1 version))
eec5fae2
GM
148 (set-version-in-file root "src/msdos.c" version
149 (rx (and "Vwindow_system_version" (1+ not-newline)
150 ?\( (submatch (1+ (in "0-9"))) ?\))))
dcf8834b
GM
151 (set-version-in-file root "etc/refcards/ru-refcard.tex" version
152 "\\\\newcommand{\\\\versionemacs}\\[0\\]\
153{\\([0-9]\\{2,\\}\\)}.+%.+version of Emacs")
154 (set-version-in-file root "etc/refcards/emacsver.tex" version
155 "\\\\def\\\\versionemacs\
156{\\([0-9]\\{2,\\}\\)}.+%.+version of Emacs"))))
157
3f4a4bdf 158
a3045b7e
GM
159;; Note this makes some assumptions about form of short copyright.
160(defun set-copyright (root copyright)
161 "Set Emacs short copyright to COPYRIGHT in relevant files under ROOT.
162Root must be the root of an Emacs source tree."
163 (interactive (list
164 (read-directory-name "Emacs root directory: " nil nil t)
165 (read-string
166 "Short copyright string: "
167 (format "Copyright (C) %s Free Software Foundation, Inc."
168 (format-time-string "%Y")))))
169 (unless (file-exists-p (expand-file-name "src/emacs.c" root))
170 (error "%s doesn't seem to be the root of an Emacs source tree" root))
78f83752
GM
171 (set-version-in-file root "configure.ac" copyright
172 (rx (and bol "copyright" (0+ (not (in ?\")))
287d4c2c 173 ?\" (submatch (1+ (not (in ?\")))) ?\")))
5ccd466c
GM
174 (set-version-in-file root "msdos/sed2v2.inp" copyright
175 (rx (and bol "/^#undef " (1+ not-newline)
176 "define COPYRIGHT" (1+ space)
177 ?\" (submatch (1+ (not (in ?\")))) ?\")))
78f83752
GM
178 (set-version-in-file root "nt/config.nt" copyright
179 (rx (and bol "#" (0+ blank) "define" (1+ blank)
180 "COPYRIGHT" (1+ blank)
181 ?\" (submatch (1+ (not (in ?\")))) ?\")))
a3045b7e 182 (set-version-in-file root "lib-src/rcs2log" copyright
287d4c2c
GM
183 (rx (and "Copyright" (0+ space) ?= (0+ space)
184 ?\' (submatch (1+ nonl)))))
287d4c2c
GM
185 (when (string-match "\\([0-9]\\{4\\}\\)" copyright)
186 (setq copyright (match-string 1 copyright))
dcf8834b
GM
187 (set-version-in-file root "etc/refcards/ru-refcard.tex" copyright
188 "\\\\newcommand{\\\\cyear}\\[0\\]\
189{\\([0-9]\\{4\\}\\)}.+%.+copyright year")
190 (set-version-in-file root "etc/refcards/emacsver.tex" copyright
191 "\\\\def\\\\year\
192{\\([0-9]\\{4\\}\\)}.+%.+copyright year")))
a3045b7e 193
8d9101d8
CY
194;;; Various bits of magic for generating the web manuals
195
0773c610
GM
196(defun manual-misc-manuals (root)
197 "Return doc/misc manuals as list of strings."
198 ;; Like `make -C doc/misc echo-info', but works if unconfigured.
199 (with-temp-buffer
200 (insert-file-contents (expand-file-name "doc/misc/Makefile.in" root))
201 (search-forward "INFO_TARGETS = ")
202 (let ((start (point))
203 res)
204 (end-of-line)
205 (while (and (looking-back "\\\\")
206 (zerop (forward-line 1)))
207 (end-of-line))
208 (split-string (replace-regexp-in-string
209 "\\(\\\\\\|\\.info\\)" ""
210 (buffer-substring start (point)))))))
211
8d9101d8
CY
212(defun make-manuals (root)
213 "Generate the web manuals for the Emacs webpage."
214 (interactive "DEmacs root directory: ")
215 (let* ((dest (expand-file-name "manual" root))
216 (html-node-dir (expand-file-name "html_node" dest))
217 (html-mono-dir (expand-file-name "html_mono" dest))
f114e6c3
GM
218 (ps-dir (expand-file-name "ps" dest))
219 (pdf-dir (expand-file-name "pdf" dest)))
8d9101d8
CY
220 (when (file-directory-p dest)
221 (if (y-or-n-p (format "Directory %s exists, delete it first?" dest))
222 (delete-directory dest t)
223 (error "Aborted")))
224 (make-directory dest)
225 (make-directory html-node-dir)
226 (make-directory html-mono-dir)
8d9101d8 227 (make-directory ps-dir)
f114e6c3 228 (make-directory pdf-dir)
8d9101d8
CY
229 ;; Emacs manual
230 (let ((texi (expand-file-name "doc/emacs/emacs.texi" root)))
231 (manual-html-node texi (expand-file-name "emacs" html-node-dir))
232 (manual-html-mono texi (expand-file-name "emacs.html" html-mono-dir))
f114e6c3 233 (manual-pdf texi (expand-file-name "emacs.pdf" pdf-dif))
0cb70db7 234 (manual-ps texi (expand-file-name "emacs.ps" ps-dir)))
8d9101d8
CY
235 ;; Lisp manual
236 (let ((texi (expand-file-name "doc/lispref/elisp.texi" root)))
237 (manual-html-node texi (expand-file-name "elisp" html-node-dir))
238 (manual-html-mono texi (expand-file-name "elisp.html" html-mono-dir))
f114e6c3 239 (manual-pdf texi (expand-file-name "elisp.pdf" pdf-dir))
0cb70db7 240 (manual-ps texi (expand-file-name "elisp.ps" ps-dir)))
9f1d94c0
GM
241 ;; Lisp intro.
242 (let ((texi (expand-file-name "doc/lispintro/emacs-lisp-intro.texi" root)))
243 (manual-html-node texi (expand-file-name "eintr" html-node-dir))
244 (manual-html-mono texi (expand-file-name "eintr.html" html-mono-dir))
f114e6c3 245 (manual-pdf texi (expand-file-name "eintr.pdf" pdf-dir))
9f1d94c0 246 (manual-ps texi (expand-file-name "eintr.ps" ps-dir)))
f08b09fc 247 ;; Misc manuals
0773c610
GM
248 (dolist (manual (manual-misc-manuals root))
249 (manual-misc-html manual root html-node-dir html-mono-dir))
8d9101d8
CY
250 (message "Manuals created in %s" dest)))
251
252(defconst manual-doctype-string
253 "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\"
254\"http://www.w3.org/TR/html4/loose.dtd\">\n\n")
255
256(defconst manual-meta-string
257 "<meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\">
258<link rev=\"made\" href=\"mailto:webmasters@gnu.org\">
259<link rel=\"icon\" type=\"image/png\" href=\"/graphics/gnu-head-mini.png\">
260<meta name=\"ICBM\" content=\"42.256233,-71.006581\">
261<meta name=\"DC.title\" content=\"gnu.org\">\n\n")
262
263(defconst manual-style-string "<style type=\"text/css\">
0605ec8e 264@import url('/s/emacs/manual.css');\n</style>\n")
8d9101d8 265
f08b09fc
CY
266(defun manual-misc-html (name root html-node-dir html-mono-dir)
267 (let ((texi (expand-file-name (format "doc/misc/%s.texi" name) root)))
268 (manual-html-node texi (expand-file-name name html-node-dir))
269 (manual-html-mono texi (expand-file-name (concat name ".html")
270 html-mono-dir))))
271
8d9101d8
CY
272(defun manual-html-mono (texi-file dest)
273 "Run Makeinfo on TEXI-FILE, emitting mono HTML output to DEST.
274This function also edits the HTML files so that they validate as
275HTML 4.01 Transitional, and pulls in the gnu.org stylesheet using
276the @import directive."
277 (call-process "makeinfo" nil nil nil
c0765905 278 "-D" "WWW_GNU_ORG"
c69f4673
GM
279 "-I" (expand-file-name "../emacs"
280 (file-name-directory texi-file))
281 "-I" (expand-file-name "../misc"
282 (file-name-directory texi-file))
8d9101d8
CY
283 "--html" "--no-split" texi-file "-o" dest)
284 (with-temp-buffer
285 (insert-file-contents dest)
286 (setq buffer-file-name dest)
287 (manual-html-fix-headers)
288 (manual-html-fix-index-1)
289 (manual-html-fix-index-2 t)
290 (manual-html-fix-node-div)
291 (goto-char (point-max))
292 (re-search-backward "</body>[\n \t]*</html>")
293 (insert "</div>\n\n")
294 (save-buffer)))
295
296(defun manual-html-node (texi-file dir)
297 "Run Makeinfo on TEXI-FILE, emitting per-node HTML output to DIR.
298This function also edits the HTML files so that they validate as
299HTML 4.01 Transitional, and pulls in the gnu.org stylesheet using
300the @import directive."
301 (unless (file-exists-p texi-file)
302 (error "Manual file %s not found" texi-file))
303 (call-process "makeinfo" nil nil nil
c0765905 304 "-D" "WWW_GNU_ORG"
c69f4673
GM
305 "-I" (expand-file-name "../emacs"
306 (file-name-directory texi-file))
307 "-I" (expand-file-name "../misc"
308 (file-name-directory texi-file))
8d9101d8
CY
309 "--html" texi-file "-o" dir)
310 ;; Loop through the node files, fixing them up.
311 (dolist (f (directory-files dir nil "\\.html\\'"))
312 (let (opoint)
313 (with-temp-buffer
314 (insert-file-contents (expand-file-name f dir))
315 (setq buffer-file-name (expand-file-name f dir))
316 (if (looking-at "<meta http-equiv")
317 ;; Ignore those HTML files that are just redirects.
318 (set-buffer-modified-p nil)
319 (manual-html-fix-headers)
320 (if (equal f "index.html")
321 (let (copyright-text)
322 (manual-html-fix-index-1)
323 ;; Move copyright notice to the end.
f08b09fc
CY
324 (when (re-search-forward "[ \t]*<p>Copyright &copy;" nil t)
325 (setq opoint (match-beginning 0))
326 (re-search-forward "</blockquote>")
327 (setq copyright-text (buffer-substring opoint (point)))
328 (delete-region opoint (point)))
8d9101d8 329 (manual-html-fix-index-2)
f08b09fc
CY
330 (if copyright-text
331 (insert copyright-text))
332 (insert "\n</div>\n"))
8d9101d8
CY
333 ;; For normal nodes, give the header div a blue bg.
334 (manual-html-fix-node-div))
335 (save-buffer))))))
336
8d9101d8 337(defun manual-pdf (texi-file dest)
0cb70db7 338 "Run texi2pdf on TEXI-FILE, emitting pdf output to DEST."
b9a54f5e
GM
339 (let ((default-directory (file-name-directory texi-file)))
340 (call-process "texi2pdf" nil nil nil
341 "-I" "../emacs" "-I" "../misc"
342 texi-file "-o" dest)))
8d9101d8 343
0cb70db7
GM
344(defun manual-ps (texi-file dest)
345 "Generate a PostScript version of TEXI-FILE as DEST."
b9a54f5e
GM
346 (let ((dvi-dest (concat (file-name-sans-extension dest) ".dvi"))
347 (default-directory (file-name-directory texi-file)))
0cb70db7 348 (call-process "texi2dvi" nil nil nil
b9a54f5e 349 "-I" "../emacs" "-I" "../misc"
0cb70db7
GM
350 texi-file "-o" dvi-dest)
351 (call-process "dvips" nil nil nil dvi-dest "-o" dest)
352 (delete-file dvi-dest)
353 (call-process "gzip" nil nil nil dest)))
8d9101d8
CY
354
355(defun manual-html-fix-headers ()
356 "Fix up HTML headers for the Emacs manual in the current buffer."
357 (let (opoint)
358 (insert manual-doctype-string)
359 (search-forward "<head>\n")
360 (insert manual-meta-string)
361 (search-forward "<meta")
362 (setq opoint (match-beginning 0))
363 (re-search-forward "<!--")
364 (goto-char (match-beginning 0))
365 (delete-region opoint (point))
366 (insert manual-style-string)
367 (search-forward "<meta http-equiv=\"Content-Style")
368 (setq opoint (match-beginning 0))
369 (search-forward "</head>")
370 (delete-region opoint (match-beginning 0))))
371
372(defun manual-html-fix-node-div ()
373 "Fix up HTML \"node\" divs in the current buffer."
374 (let (opoint div-end)
375 (while (search-forward "<div class=\"node\">" nil t)
376 (replace-match
377 "<div class=\"node\" style=\"background-color:#DDDDFF\">"
378 t t)
379 (setq opoint (point))
380 (re-search-forward "</div>")
381 (setq div-end (match-beginning 0))
382 (goto-char opoint)
383 (if (search-forward "<hr>" div-end 'move)
384 (replace-match "" t t)))))
385
386(defun manual-html-fix-index-1 ()
387 (let (opoint)
f08b09fc
CY
388 (re-search-forward "<body>\n")
389 (setq opoint (match-end 0))
390 (search-forward "<h2 class=\"")
8d9101d8
CY
391 (goto-char (match-beginning 0))
392 (delete-region opoint (point))
393 (insert "<div id=\"content\" class=\"inner\">\n\n")))
394
395(defun manual-html-fix-index-2 (&optional table-workaround)
396 "Replace the index list in the current buffer with a HTML table."
397 (let (done open-td tag desc)
398 ;; Convert the list that Makeinfo made into a table.
f08b09fc
CY
399 (or (search-forward "<ul class=\"menu\">" nil t)
400 (search-forward "<ul>"))
8d9101d8
CY
401 (replace-match "<table style=\"float:left\" width=\"100%\">")
402 (forward-line 1)
403 (while (not done)
404 (cond
405 ((or (looking-at "<li>\\(<a.+</a>\\):[ \t]+\\(.*\\)$")
406 (looking-at "<li>\\(<a.+</a>\\)$"))
407 (setq tag (match-string 1))
408 (setq desc (match-string 2))
409 (replace-match "" t t)
410 (when open-td
411 (save-excursion
412 (forward-char -1)
413 (skip-chars-backward " ")
414 (delete-region (point) (line-end-position))
415 (insert "</td>\n </tr>")))
416 (insert " <tr>\n ")
417 (if table-workaround
418 ;; This works around a Firefox bug in the mono file.
419 (insert "<td bgcolor=\"white\">")
420 (insert "<td>"))
421 (insert tag "</td>\n <td>" (or desc ""))
422 (setq open-td t))
423 ((eq (char-after) ?\n)
424 (delete-char 1)
425 ;; Negate the following `forward-line'.
426 (forward-line -1))
427 ((looking-at "<!-- ")
428 (search-forward "-->"))
429 ((looking-at "<p>[- ]*The Detailed Node Listing[- \n]*")
430 (replace-match " </td></tr></table>\n
431<h3>Detailed Node Listing</h3>\n\n" t t)
432 (search-forward "<p>")
f08b09fc 433 (search-forward "<p>" nil t)
8d9101d8
CY
434 (goto-char (match-beginning 0))
435 (skip-chars-backward "\n ")
436 (setq open-td nil)
437 (insert "</p>\n\n<table style=\"float:left\" width=\"100%\">"))
438 ((looking-at "</li></ul>")
439 (replace-match "" t t))
440 ((looking-at "<p>")
441 (replace-match "" t t)
442 (when open-td
443 (insert " </td></tr>")
444 (setq open-td nil))
445 (insert " <tr>
446 <th colspan=\"2\" align=\"left\" style=\"text-align:left\">")
f08b09fc
CY
447 (if (re-search-forward "</p>[ \t\n]*<ul class=\"menu\">" nil t)
448 (replace-match " </th></tr>")))
8d9101d8
CY
449 ((looking-at "[ \t]*</ul>[ \t]*$")
450 (replace-match
451 (if open-td
452 " </td></tr>\n</table>"
453 "</table>") t t)
454 (setq done t))
455 (t
456 (if (eobp)
dcddaabb 457 (error "Parse error in %s" f)) ; f is bound in manual-html-node
8d9101d8
CY
458 (unless open-td
459 (setq done t))))
460 (forward-line 1))))
461
58474503
GM
462\f
463;; Stuff to check new defcustoms got :version tags.
464;; Adapted from check-declare.el.
465
466(defun cusver-find-files (root &optional old)
467 "Find .el files beneath directory ROOT that contain defcustoms.
468If optional OLD is non-nil, also include defvars."
469 (process-lines find-program root
470 "-name" "*.el"
471 "-exec" grep-program
472 "-l" "-E" (format "^[ \\t]*\\(def%s"
473 (if old "(custom|var)"
474 "custom"
475 ))
476 "{}" "+"))
477
5407f8d2
GM
478(defvar cusver-new-version (format "%s.%s" emacs-major-version
479 (1+ emacs-minor-version))
1a316a53
GM
480 "Version number that new defcustoms should have.")
481
58474503
GM
482(defun cusver-scan (file &optional old)
483 "Scan FILE for `defcustom' calls.
484Return a list with elements of the form (VAR . VER),
485This means that FILE contains a defcustom for variable VAR, with
486a :version tag having value VER (may be nil).
487If optional argument OLD is non-nil, also scan for defvars."
488 (let ((m (format "Scanning %s..." file))
489 (re (format "^[ \t]*\\((def%s\\)[ \t\n]"
1a316a53
GM
490 (if old "\\(custom\\|var\\)" "\\(custom\\|group\\)")))
491 alist var ver form glist grp)
58474503
GM
492 (message "%s" m)
493 (with-temp-buffer
494 (insert-file-contents file)
495 ;; FIXME we could theoretically be inside a string.
496 (while (re-search-forward re nil t)
497 (goto-char (match-beginning 1))
498 (if (and (setq form (ignore-errors (read (current-buffer))))
1a316a53 499 (setq var (car-safe (cdr-safe form)))
58474503
GM
500 ;; Exclude macros, eg (defcustom ,varname ...).
501 (symbolp var))
1a316a53
GM
502 (progn
503 (setq ver (car (cdr-safe (memq :version form))))
504 (if (equal "group" (match-string 2))
505 ;; Group :version could be old.
506 (if (equal ver cusver-new-version)
507 (setq glist (cons (cons var ver) glist)))
508 ;; If it specifies a group and the whole group has a
509 ;; version. use that.
510 (unless ver
511 (setq grp (car (cdr-safe (memq :group form))))
512 (and grp
513 (setq grp (car (cdr-safe grp))) ; (quote foo) -> foo
514 (setq ver (assq grp glist))))
515 (setq alist (cons (cons var ver) alist))))
58474503
GM
516 (if form (message "Malformed defcustom: `%s'" form)))))
517 (message "%sdone" m)
518 alist))
519
2c35bdbb
GM
520(defun cusver-scan-cus-start (file)
521 "Scan cus-start.el and return an alist with elements (VAR . VER)."
522 (if (file-readable-p file)
523 (with-temp-buffer
524 (insert-file-contents file)
525 (when (search-forward "(let ((all '(" nil t)
526 (backward-char 1)
527 (let (var ver alist)
528 (dolist (elem (ignore-errors (read (current-buffer))))
529 (when (symbolp (setq var (car-safe elem)))
530 (or (stringp (setq ver (nth 3 elem)))
531 (setq ver nil))
532 (setq alist (cons (cons var ver) alist))))
533 alist)))))
534
58474503
GM
535(define-button-type 'cusver-xref 'action #'cusver-goto-xref)
536
537(defun cusver-goto-xref (button)
538 "Jump to a lisp file for the BUTTON at point."
539 (let ((file (button-get button 'file))
540 (var (button-get button 'var)))
541 (if (not (file-readable-p file))
542 (message "Cannot read `%s'" file)
543 (with-current-buffer (find-file-noselect file)
544 (goto-char (point-min))
545 (or (re-search-forward (format "^[ \t]*(defcustom[ \t]*%s" var) nil t)
546 (message "Unable to locate defcustom"))
547 (pop-to-buffer (current-buffer))))))
548
549;; You should probably at least do a grep over the old directory
2c35bdbb
GM
550;; to check the results of this look sensible.
551;; TODO Check cus-start if something moved from C to Lisp.
552;; TODO Handle renamed things with aliases to the old names.
1a316a53 553(defun cusver-check (newdir olddir version)
58474503
GM
554 "Check that defcustoms have :version tags where needed.
555NEWDIR is the current lisp/ directory, OLDDIR is that from the previous
556release. A defcustom that is only in NEWDIR should have a :version
557tag. We exclude cases where a defvar exists in OLDDIR, since
558just converting a defvar to a defcustom does not require a :version bump.
559
560Note that a :version tag should also be added if the value of a defcustom
561changes (in a non-trivial way). This function does not check for that."
5407f8d2
GM
562 (interactive (list (read-directory-name "New Lisp directory: ")
563 (read-directory-name "Old Lisp directory: ")
564 (number-to-string
565 (read-number "New version number: "
566 (string-to-number cusver-new-version)))))
58474503
GM
567 (or (file-directory-p (setq newdir (expand-file-name newdir)))
568 (error "Directory `%s' not found" newdir))
569 (or (file-directory-p (setq olddir (expand-file-name olddir)))
570 (error "Directory `%s' not found" olddir))
1a316a53 571 (setq cusver-new-version version)
58474503
GM
572 (let* ((newfiles (progn (message "Finding new files with defcustoms...")
573 (cusver-find-files newdir)))
574 (oldfiles (progn (message "Finding old files with defcustoms...")
575 (cusver-find-files olddir t)))
576 (newcus (progn (message "Reading new defcustoms...")
577 (mapcar
578 (lambda (file)
579 (cons file (cusver-scan file))) newfiles)))
dcf8834b 580 oldcus result thisfile file)
58474503
GM
581 (message "Reading old defcustoms...")
582 (dolist (file oldfiles)
583 (setq oldcus (append oldcus (cusver-scan file t))))
2c35bdbb
GM
584 (setq oldcus (append oldcus (cusver-scan-cus-start
585 (expand-file-name "cus-start.el" olddir))))
58474503
GM
586 ;; newcus has elements (FILE (VAR VER) ... ).
587 ;; oldcus just (VAR . VER).
588 (message "Checking for version tags...")
589 (dolist (new newcus)
590 (setq file (car new)
591 thisfile
592 (let (missing var)
593 (dolist (cons (cdr new))
594 (or (cdr cons)
595 (assq (setq var (car cons)) oldcus)
596 (push var missing)))
597 (if missing
598 (cons file missing))))
599 (if thisfile
600 (setq result (cons thisfile result))))
601 (message "Checking for version tags... done")
602 (if (not result)
603 (message "No missing :version tags")
604 (pop-to-buffer "*cusver*")
605 (erase-buffer)
606 (insert "These defcustoms might be missing :version tags:\n\n")
607 (dolist (elem result)
608 (let* ((str (file-relative-name (car elem) newdir))
609 (strlen (length str)))
610 (dolist (var (cdr elem))
611 (insert (format "%s: %s\n" str var))
612 (make-text-button (+ (line-beginning-position 0) strlen 2)
613 (line-end-position 0)
614 'file (car elem)
615 'var var
616 'help-echo "Mouse-2: visit this definition"
617 :type 'cusver-xref)))))))
618
69c52df1
GM
619(provide 'admin)
620
d3841127 621;;; admin.el ends here