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