* admin.el (manual-pdf, manual-ps): Work in the directory with the texi file,
[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
196(defun make-manuals (root)
197 "Generate the web manuals for the Emacs webpage."
198 (interactive "DEmacs root directory: ")
199 (let* ((dest (expand-file-name "manual" root))
200 (html-node-dir (expand-file-name "html_node" dest))
201 (html-mono-dir (expand-file-name "html_mono" dest))
8d9101d8
CY
202 (ps-dir (expand-file-name "ps" dest)))
203 (when (file-directory-p dest)
204 (if (y-or-n-p (format "Directory %s exists, delete it first?" dest))
205 (delete-directory dest t)
206 (error "Aborted")))
207 (make-directory dest)
208 (make-directory html-node-dir)
209 (make-directory html-mono-dir)
8d9101d8
CY
210 (make-directory ps-dir)
211 ;; Emacs manual
212 (let ((texi (expand-file-name "doc/emacs/emacs.texi" root)))
213 (manual-html-node texi (expand-file-name "emacs" html-node-dir))
214 (manual-html-mono texi (expand-file-name "emacs.html" html-mono-dir))
8d9101d8 215 (manual-pdf texi (expand-file-name "emacs.pdf" dest))
0cb70db7 216 (manual-ps texi (expand-file-name "emacs.ps" ps-dir)))
8d9101d8
CY
217 ;; Lisp manual
218 (let ((texi (expand-file-name "doc/lispref/elisp.texi" root)))
219 (manual-html-node texi (expand-file-name "elisp" html-node-dir))
220 (manual-html-mono texi (expand-file-name "elisp.html" html-mono-dir))
8d9101d8 221 (manual-pdf texi (expand-file-name "elisp.pdf" dest))
0cb70db7 222 (manual-ps texi (expand-file-name "elisp.ps" ps-dir)))
c69f4673
GM
223 (let ((texi (expand-file-name "doc/lispintro/emacs-lisp-intro.texi" root))
224 (dest (expand-file-name "emacs-lisp-intro" dest))
0cb70db7 225 dest2)
c69f4673
GM
226 ;; Mimic the atypical directory layout used for emacs-lisp-intro.
227 (make-directory dest)
228 (make-directory (setq dest2 (expand-file-name "html_node" dest)))
229 (manual-html-node texi dest2)
230 (make-directory (setq dest2 (expand-file-name "html_mono" dest)))
231 (manual-html-mono texi (expand-file-name "emacs-lisp-intro.html" dest2))
c69f4673 232 (manual-pdf texi (expand-file-name "emacs-lisp-intro.pdf" dest))
0cb70db7
GM
233 (make-directory (setq dest2 (expand-file-name "ps" dest)))
234 (manual-ps texi (expand-file-name "emacs-lisp-intro.ps" dest2)))
f08b09fc 235 ;; Misc manuals
c69f4673 236 (let ((manuals '("ada-mode" "auth" "autotype" "bovine" "calc" "cc-mode"
f08b09fc 237 "cl" "dbus" "dired-x" "ebrowse" "ede" "ediff"
c69f4673 238 "edt" "eieio" "emacs-gnutls" "emacs-mime" "epa" "erc" "ert"
f08b09fc 239 "eshell" "eudc" "faq" "flymake" "forms"
c69f4673 240 "gnus" "htmlfontify" "idlwave" "info"
f08b09fc
CY
241 "mairix-el" "message" "mh-e" "newsticker"
242 "nxml-mode" "org" "pcl-cvs" "pgg" "rcirc"
c69f4673
GM
243 "reftex" "remember" "sasl" "sc" "semantic"
244 "ses" "sieve" "smtpmail" "speedbar" "srecode" "tramp"
245 "url" "vip" "viper" "widget" "wisent" "woman")))
f08b09fc
CY
246 (dolist (manual manuals)
247 (manual-misc-html manual root html-node-dir html-mono-dir)))
8d9101d8
CY
248 (message "Manuals created in %s" dest)))
249
250(defconst manual-doctype-string
251 "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\"
252\"http://www.w3.org/TR/html4/loose.dtd\">\n\n")
253
254(defconst manual-meta-string
255 "<meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\">
256<link rev=\"made\" href=\"mailto:webmasters@gnu.org\">
257<link rel=\"icon\" type=\"image/png\" href=\"/graphics/gnu-head-mini.png\">
258<meta name=\"ICBM\" content=\"42.256233,-71.006581\">
259<meta name=\"DC.title\" content=\"gnu.org\">\n\n")
260
261(defconst manual-style-string "<style type=\"text/css\">
0605ec8e 262@import url('/s/emacs/manual.css');\n</style>\n")
8d9101d8 263
f08b09fc
CY
264(defun manual-misc-html (name root html-node-dir html-mono-dir)
265 (let ((texi (expand-file-name (format "doc/misc/%s.texi" name) root)))
266 (manual-html-node texi (expand-file-name name html-node-dir))
267 (manual-html-mono texi (expand-file-name (concat name ".html")
268 html-mono-dir))))
269
8d9101d8
CY
270(defun manual-html-mono (texi-file dest)
271 "Run Makeinfo on TEXI-FILE, emitting mono HTML output to DEST.
272This function also edits the HTML files so that they validate as
273HTML 4.01 Transitional, and pulls in the gnu.org stylesheet using
274the @import directive."
275 (call-process "makeinfo" nil nil nil
c0765905 276 "-D" "WWW_GNU_ORG"
c69f4673
GM
277 "-I" (expand-file-name "../emacs"
278 (file-name-directory texi-file))
279 "-I" (expand-file-name "../misc"
280 (file-name-directory texi-file))
8d9101d8
CY
281 "--html" "--no-split" texi-file "-o" dest)
282 (with-temp-buffer
283 (insert-file-contents dest)
284 (setq buffer-file-name dest)
285 (manual-html-fix-headers)
286 (manual-html-fix-index-1)
287 (manual-html-fix-index-2 t)
288 (manual-html-fix-node-div)
289 (goto-char (point-max))
290 (re-search-backward "</body>[\n \t]*</html>")
291 (insert "</div>\n\n")
292 (save-buffer)))
293
294(defun manual-html-node (texi-file dir)
295 "Run Makeinfo on TEXI-FILE, emitting per-node HTML output to DIR.
296This function also edits the HTML files so that they validate as
297HTML 4.01 Transitional, and pulls in the gnu.org stylesheet using
298the @import directive."
299 (unless (file-exists-p texi-file)
300 (error "Manual file %s not found" texi-file))
301 (call-process "makeinfo" nil nil nil
c0765905 302 "-D" "WWW_GNU_ORG"
c69f4673
GM
303 "-I" (expand-file-name "../emacs"
304 (file-name-directory texi-file))
305 "-I" (expand-file-name "../misc"
306 (file-name-directory texi-file))
8d9101d8
CY
307 "--html" texi-file "-o" dir)
308 ;; Loop through the node files, fixing them up.
309 (dolist (f (directory-files dir nil "\\.html\\'"))
310 (let (opoint)
311 (with-temp-buffer
312 (insert-file-contents (expand-file-name f dir))
313 (setq buffer-file-name (expand-file-name f dir))
314 (if (looking-at "<meta http-equiv")
315 ;; Ignore those HTML files that are just redirects.
316 (set-buffer-modified-p nil)
317 (manual-html-fix-headers)
318 (if (equal f "index.html")
319 (let (copyright-text)
320 (manual-html-fix-index-1)
321 ;; Move copyright notice to the end.
f08b09fc
CY
322 (when (re-search-forward "[ \t]*<p>Copyright &copy;" nil t)
323 (setq opoint (match-beginning 0))
324 (re-search-forward "</blockquote>")
325 (setq copyright-text (buffer-substring opoint (point)))
326 (delete-region opoint (point)))
8d9101d8 327 (manual-html-fix-index-2)
f08b09fc
CY
328 (if copyright-text
329 (insert copyright-text))
330 (insert "\n</div>\n"))
8d9101d8
CY
331 ;; For normal nodes, give the header div a blue bg.
332 (manual-html-fix-node-div))
333 (save-buffer))))))
334
8d9101d8 335(defun manual-pdf (texi-file dest)
0cb70db7 336 "Run texi2pdf on TEXI-FILE, emitting pdf output to DEST."
b9a54f5e
GM
337 (let ((default-directory (file-name-directory texi-file)))
338 (call-process "texi2pdf" nil nil nil
339 "-I" "../emacs" "-I" "../misc"
340 texi-file "-o" dest)))
8d9101d8 341
0cb70db7
GM
342(defun manual-ps (texi-file dest)
343 "Generate a PostScript version of TEXI-FILE as DEST."
b9a54f5e
GM
344 (let ((dvi-dest (concat (file-name-sans-extension dest) ".dvi"))
345 (default-directory (file-name-directory texi-file)))
0cb70db7 346 (call-process "texi2dvi" nil nil nil
b9a54f5e 347 "-I" "../emacs" "-I" "../misc"
0cb70db7
GM
348 texi-file "-o" dvi-dest)
349 (call-process "dvips" nil nil nil dvi-dest "-o" dest)
350 (delete-file dvi-dest)
351 (call-process "gzip" nil nil nil dest)))
8d9101d8
CY
352
353(defun manual-html-fix-headers ()
354 "Fix up HTML headers for the Emacs manual in the current buffer."
355 (let (opoint)
356 (insert manual-doctype-string)
357 (search-forward "<head>\n")
358 (insert manual-meta-string)
359 (search-forward "<meta")
360 (setq opoint (match-beginning 0))
361 (re-search-forward "<!--")
362 (goto-char (match-beginning 0))
363 (delete-region opoint (point))
364 (insert manual-style-string)
365 (search-forward "<meta http-equiv=\"Content-Style")
366 (setq opoint (match-beginning 0))
367 (search-forward "</head>")
368 (delete-region opoint (match-beginning 0))))
369
370(defun manual-html-fix-node-div ()
371 "Fix up HTML \"node\" divs in the current buffer."
372 (let (opoint div-end)
373 (while (search-forward "<div class=\"node\">" nil t)
374 (replace-match
375 "<div class=\"node\" style=\"background-color:#DDDDFF\">"
376 t t)
377 (setq opoint (point))
378 (re-search-forward "</div>")
379 (setq div-end (match-beginning 0))
380 (goto-char opoint)
381 (if (search-forward "<hr>" div-end 'move)
382 (replace-match "" t t)))))
383
384(defun manual-html-fix-index-1 ()
385 (let (opoint)
f08b09fc
CY
386 (re-search-forward "<body>\n")
387 (setq opoint (match-end 0))
388 (search-forward "<h2 class=\"")
8d9101d8
CY
389 (goto-char (match-beginning 0))
390 (delete-region opoint (point))
391 (insert "<div id=\"content\" class=\"inner\">\n\n")))
392
393(defun manual-html-fix-index-2 (&optional table-workaround)
394 "Replace the index list in the current buffer with a HTML table."
395 (let (done open-td tag desc)
396 ;; Convert the list that Makeinfo made into a table.
f08b09fc
CY
397 (or (search-forward "<ul class=\"menu\">" nil t)
398 (search-forward "<ul>"))
8d9101d8
CY
399 (replace-match "<table style=\"float:left\" width=\"100%\">")
400 (forward-line 1)
401 (while (not done)
402 (cond
403 ((or (looking-at "<li>\\(<a.+</a>\\):[ \t]+\\(.*\\)$")
404 (looking-at "<li>\\(<a.+</a>\\)$"))
405 (setq tag (match-string 1))
406 (setq desc (match-string 2))
407 (replace-match "" t t)
408 (when open-td
409 (save-excursion
410 (forward-char -1)
411 (skip-chars-backward " ")
412 (delete-region (point) (line-end-position))
413 (insert "</td>\n </tr>")))
414 (insert " <tr>\n ")
415 (if table-workaround
416 ;; This works around a Firefox bug in the mono file.
417 (insert "<td bgcolor=\"white\">")
418 (insert "<td>"))
419 (insert tag "</td>\n <td>" (or desc ""))
420 (setq open-td t))
421 ((eq (char-after) ?\n)
422 (delete-char 1)
423 ;; Negate the following `forward-line'.
424 (forward-line -1))
425 ((looking-at "<!-- ")
426 (search-forward "-->"))
427 ((looking-at "<p>[- ]*The Detailed Node Listing[- \n]*")
428 (replace-match " </td></tr></table>\n
429<h3>Detailed Node Listing</h3>\n\n" t t)
430 (search-forward "<p>")
f08b09fc 431 (search-forward "<p>" nil t)
8d9101d8
CY
432 (goto-char (match-beginning 0))
433 (skip-chars-backward "\n ")
434 (setq open-td nil)
435 (insert "</p>\n\n<table style=\"float:left\" width=\"100%\">"))
436 ((looking-at "</li></ul>")
437 (replace-match "" t t))
438 ((looking-at "<p>")
439 (replace-match "" t t)
440 (when open-td
441 (insert " </td></tr>")
442 (setq open-td nil))
443 (insert " <tr>
444 <th colspan=\"2\" align=\"left\" style=\"text-align:left\">")
f08b09fc
CY
445 (if (re-search-forward "</p>[ \t\n]*<ul class=\"menu\">" nil t)
446 (replace-match " </th></tr>")))
8d9101d8
CY
447 ((looking-at "[ \t]*</ul>[ \t]*$")
448 (replace-match
449 (if open-td
450 " </td></tr>\n</table>"
451 "</table>") t t)
452 (setq done t))
453 (t
454 (if (eobp)
dcddaabb 455 (error "Parse error in %s" f)) ; f is bound in manual-html-node
8d9101d8
CY
456 (unless open-td
457 (setq done t))))
458 (forward-line 1))))
459
58474503
GM
460\f
461;; Stuff to check new defcustoms got :version tags.
462;; Adapted from check-declare.el.
463
464(defun cusver-find-files (root &optional old)
465 "Find .el files beneath directory ROOT that contain defcustoms.
466If optional OLD is non-nil, also include defvars."
467 (process-lines find-program root
468 "-name" "*.el"
469 "-exec" grep-program
470 "-l" "-E" (format "^[ \\t]*\\(def%s"
471 (if old "(custom|var)"
472 "custom"
473 ))
474 "{}" "+"))
475
5407f8d2
GM
476(defvar cusver-new-version (format "%s.%s" emacs-major-version
477 (1+ emacs-minor-version))
1a316a53
GM
478 "Version number that new defcustoms should have.")
479
58474503
GM
480(defun cusver-scan (file &optional old)
481 "Scan FILE for `defcustom' calls.
482Return a list with elements of the form (VAR . VER),
483This means that FILE contains a defcustom for variable VAR, with
484a :version tag having value VER (may be nil).
485If optional argument OLD is non-nil, also scan for defvars."
486 (let ((m (format "Scanning %s..." file))
487 (re (format "^[ \t]*\\((def%s\\)[ \t\n]"
1a316a53
GM
488 (if old "\\(custom\\|var\\)" "\\(custom\\|group\\)")))
489 alist var ver form glist grp)
58474503
GM
490 (message "%s" m)
491 (with-temp-buffer
492 (insert-file-contents file)
493 ;; FIXME we could theoretically be inside a string.
494 (while (re-search-forward re nil t)
495 (goto-char (match-beginning 1))
496 (if (and (setq form (ignore-errors (read (current-buffer))))
1a316a53 497 (setq var (car-safe (cdr-safe form)))
58474503
GM
498 ;; Exclude macros, eg (defcustom ,varname ...).
499 (symbolp var))
1a316a53
GM
500 (progn
501 (setq ver (car (cdr-safe (memq :version form))))
502 (if (equal "group" (match-string 2))
503 ;; Group :version could be old.
504 (if (equal ver cusver-new-version)
505 (setq glist (cons (cons var ver) glist)))
506 ;; If it specifies a group and the whole group has a
507 ;; version. use that.
508 (unless ver
509 (setq grp (car (cdr-safe (memq :group form))))
510 (and grp
511 (setq grp (car (cdr-safe grp))) ; (quote foo) -> foo
512 (setq ver (assq grp glist))))
513 (setq alist (cons (cons var ver) alist))))
58474503
GM
514 (if form (message "Malformed defcustom: `%s'" form)))))
515 (message "%sdone" m)
516 alist))
517
2c35bdbb
GM
518(defun cusver-scan-cus-start (file)
519 "Scan cus-start.el and return an alist with elements (VAR . VER)."
520 (if (file-readable-p file)
521 (with-temp-buffer
522 (insert-file-contents file)
523 (when (search-forward "(let ((all '(" nil t)
524 (backward-char 1)
525 (let (var ver alist)
526 (dolist (elem (ignore-errors (read (current-buffer))))
527 (when (symbolp (setq var (car-safe elem)))
528 (or (stringp (setq ver (nth 3 elem)))
529 (setq ver nil))
530 (setq alist (cons (cons var ver) alist))))
531 alist)))))
532
58474503
GM
533(define-button-type 'cusver-xref 'action #'cusver-goto-xref)
534
535(defun cusver-goto-xref (button)
536 "Jump to a lisp file for the BUTTON at point."
537 (let ((file (button-get button 'file))
538 (var (button-get button 'var)))
539 (if (not (file-readable-p file))
540 (message "Cannot read `%s'" file)
541 (with-current-buffer (find-file-noselect file)
542 (goto-char (point-min))
543 (or (re-search-forward (format "^[ \t]*(defcustom[ \t]*%s" var) nil t)
544 (message "Unable to locate defcustom"))
545 (pop-to-buffer (current-buffer))))))
546
547;; You should probably at least do a grep over the old directory
2c35bdbb
GM
548;; to check the results of this look sensible.
549;; TODO Check cus-start if something moved from C to Lisp.
550;; TODO Handle renamed things with aliases to the old names.
1a316a53 551(defun cusver-check (newdir olddir version)
58474503
GM
552 "Check that defcustoms have :version tags where needed.
553NEWDIR is the current lisp/ directory, OLDDIR is that from the previous
554release. A defcustom that is only in NEWDIR should have a :version
555tag. We exclude cases where a defvar exists in OLDDIR, since
556just converting a defvar to a defcustom does not require a :version bump.
557
558Note that a :version tag should also be added if the value of a defcustom
559changes (in a non-trivial way). This function does not check for that."
5407f8d2
GM
560 (interactive (list (read-directory-name "New Lisp directory: ")
561 (read-directory-name "Old Lisp directory: ")
562 (number-to-string
563 (read-number "New version number: "
564 (string-to-number cusver-new-version)))))
58474503
GM
565 (or (file-directory-p (setq newdir (expand-file-name newdir)))
566 (error "Directory `%s' not found" newdir))
567 (or (file-directory-p (setq olddir (expand-file-name olddir)))
568 (error "Directory `%s' not found" olddir))
1a316a53 569 (setq cusver-new-version version)
58474503
GM
570 (let* ((newfiles (progn (message "Finding new files with defcustoms...")
571 (cusver-find-files newdir)))
572 (oldfiles (progn (message "Finding old files with defcustoms...")
573 (cusver-find-files olddir t)))
574 (newcus (progn (message "Reading new defcustoms...")
575 (mapcar
576 (lambda (file)
577 (cons file (cusver-scan file))) newfiles)))
dcf8834b 578 oldcus result thisfile file)
58474503
GM
579 (message "Reading old defcustoms...")
580 (dolist (file oldfiles)
581 (setq oldcus (append oldcus (cusver-scan file t))))
2c35bdbb
GM
582 (setq oldcus (append oldcus (cusver-scan-cus-start
583 (expand-file-name "cus-start.el" olddir))))
58474503
GM
584 ;; newcus has elements (FILE (VAR VER) ... ).
585 ;; oldcus just (VAR . VER).
586 (message "Checking for version tags...")
587 (dolist (new newcus)
588 (setq file (car new)
589 thisfile
590 (let (missing var)
591 (dolist (cons (cdr new))
592 (or (cdr cons)
593 (assq (setq var (car cons)) oldcus)
594 (push var missing)))
595 (if missing
596 (cons file missing))))
597 (if thisfile
598 (setq result (cons thisfile result))))
599 (message "Checking for version tags... done")
600 (if (not result)
601 (message "No missing :version tags")
602 (pop-to-buffer "*cusver*")
603 (erase-buffer)
604 (insert "These defcustoms might be missing :version tags:\n\n")
605 (dolist (elem result)
606 (let* ((str (file-relative-name (car elem) newdir))
607 (strlen (length str)))
608 (dolist (var (cdr elem))
609 (insert (format "%s: %s\n" str var))
610 (make-text-button (+ (line-beginning-position 0) strlen 2)
611 (line-end-position 0)
612 'file (car elem)
613 'var var
614 'help-echo "Mouse-2: visit this definition"
615 :type 'cusver-xref)))))))
616
69c52df1
GM
617(provide 'admin)
618
d3841127 619;;; admin.el ends here