* lisp/emacs-lisp/cl-macs.el (cl-symbol-macrolet): Use macroexp-progn.
[bpt/emacs.git] / lisp / emacs-lisp / copyright.el
CommitLineData
3f61a2e7 1;;; copyright.el --- update the copyright notice in current buffer
d501f516 2
30213927 3;; Copyright (C) 1991-1995, 1998, 2001-2013 Free Software Foundation, Inc.
58142744 4
3e910376 5;; Author: Daniel Pfeiffer <occitan@esperanto.org>
3f61a2e7
KH
6;; Keywords: maint, tools
7
8;; This file is part of GNU Emacs.
9
d6cba7ae 10;; GNU Emacs is free software: you can redistribute it and/or modify
3f61a2e7 11;; it under the terms of the GNU General Public License as published by
d6cba7ae
GM
12;; the Free Software Foundation, either version 3 of the License, or
13;; (at your option) any later version.
3f61a2e7
KH
14
15;; GNU Emacs is distributed in the hope that it will be useful,
16;; but WITHOUT ANY WARRANTY; without even the implied warranty of
17;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18;; GNU General Public License for more details.
19
20;; You should have received a copy of the GNU General Public License
d6cba7ae 21;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
3f61a2e7
KH
22
23;;; Commentary:
24
25;; Allows updating the copyright year and above mentioned GPL version manually
f47f5302 26;; or when saving a file.
6b61353c
KH
27;; Do (add-hook 'before-save-hook 'copyright-update), or use
28;; M-x customize-variable RET before-save-hook RET.
0343b087 29
d46bac56
ER
30;;; Code:
31
3f251fcd
AS
32(defgroup copyright nil
33 "Update the copyright notice in current buffer."
34 :group 'tools)
35
36(defcustom copyright-limit 2000
b649d2e4 37 "Don't try to update copyright beyond this position unless interactive.
9c05459c 38A value of nil means to search whole buffer."
3f251fcd
AS
39 :group 'copyright
40 :type '(choice (integer :tag "Limit")
41 (const :tag "No limit")))
3f61a2e7 42
b2c7c56d
GM
43(defcustom copyright-at-end-flag nil
44 "Non-nil means to search backwards from the end of the buffer for copyright.
45This is useful for ChangeLogs."
46 :group 'copyright
47 :type 'boolean
48 :version "23.1")
0fe719e6 49;;;###autoload(put 'copyright-at-end-flag 'safe-local-variable 'booleanp)
b2c7c56d 50
3f251fcd 51(defcustom copyright-regexp
e145a7fe 52 "\\(©\\|@copyright{}\\|[Cc]opyright\\s *:?\\s *\\(?:(C)\\)?\
d260b218 53\\|[Cc]opyright\\s *:?\\s *©\\)\
24a517fc
MB
54\\s *\\(?:[^0-9\n]*\\s *\\)?\
55\\([1-9]\\([-0-9, ';/*%#\n\t]\\|\\s<\\|\\s>\\)*[0-9]+\\)"
b649d2e4 56 "What your copyright notice looks like.
3f251fcd
AS
57The second \\( \\) construct must match the years."
58 :group 'copyright
59 :type 'regexp)
3f61a2e7 60
b649d2e4
SM
61(defcustom copyright-names-regexp ""
62 "Regexp matching the names which correspond to the user.
63Only copyright lines where the name matches this regexp will be updated.
8a1dd108 64This allows you to avoid adding years to a copyright notice belonging to
b649d2e4 65someone else or to a group for which you do not work."
44168837 66 :group 'copyright
b649d2e4
SM
67 :type 'regexp)
68
0fe719e6
GM
69;; The worst that can happen is a malicious regexp that overflows in
70;; the regexp matcher, a minor nuisance. It's a pain to be always
71;; prompted if you want to put this in a dir-locals.el.
72;;;###autoload(put 'copyright-names-regexp 'safe-local-variable 'stringp)
73
69311816
RS
74(defcustom copyright-years-regexp
75 "\\(\\s *\\)\\([1-9]\\([-0-9, ';/*%#\n\t]\\|\\s<\\|\\s>\\)*[0-9]+\\)"
b649d2e4 76 "Match additional copyright notice years.
69311816
RS
77The second \\( \\) construct must match the years."
78 :group 'copyright
79 :type 'regexp)
80
0fe719e6
GM
81;; See "Copyright Notices" in maintain.info.
82;; TODO? 'end only for ranges at the end, other for all ranges.
83;; Minimum limit on the size of a range?
84(defcustom copyright-year-ranges nil
85 "Non-nil if individual consecutive years should be replaced with a range.
86For example: 2005, 2006, 2007, 2008 might be replaced with 2005-2008.
87If you use ranges, you should add an explanatory note in a README file.
e2b5bdd7 88The function `copyright-fix-years' respects this variable."
0fe719e6
GM
89 :group 'copyright
90 :type 'boolean
91 :version "24.1")
92
93;;;###autoload(put 'copyright-year-ranges 'safe-local-variable 'booleanp)
3f61a2e7 94
3f251fcd 95(defcustom copyright-query 'function
b649d2e4 96 "If non-nil, ask user before changing copyright.
3f251fcd
AS
97When this is `function', only ask when called non-interactively."
98 :group 'copyright
99 :type '(choice (const :tag "Do not ask")
e4f0bdfa
AS
100 (const :tag "Ask unless interactive" function)
101 (other :tag "Ask" t)))
3f61a2e7
KH
102
103
a7acbbe4 104;; when modifying this, also modify the comment generated by autoinsert.el
948d9b97 105(defconst copyright-current-gpl-version "3"
9c05459c 106 "String representing the current version of the GPL or nil.")
0343b087 107
fe177a62
GM
108(defvar copyright-update t
109 "The function `copyright-update' sets this to nil after updating a buffer.")
9cfd2eeb 110
b7812d30
EZ
111;; This is a defvar rather than a defconst, because the year can
112;; change during the Emacs session.
0bfcf5c5 113(defvar copyright-current-year (format-time-string "%Y")
b7812d30
EZ
114 "String representing the current year.")
115
4168d2c7 116(defsubst copyright-limit () ; re-search-forward BOUND
b2c7c56d
GM
117 (and copyright-limit
118 (if copyright-at-end-flag
119 (- (point) copyright-limit)
120 (+ (point) copyright-limit))))
121
122(defun copyright-re-search (regexp &optional bound noerror count)
123 "Re-search forward or backward depending on `copyright-at-end-flag'."
124 (if copyright-at-end-flag
125 (re-search-backward regexp bound noerror count)
126 (re-search-forward regexp bound noerror count)))
127
128(defun copyright-start-point ()
129 "Return point-min or point-max, depending on `copyright-at-end-flag'."
130 (if copyright-at-end-flag
131 (point-max)
132 (point-min)))
133
134(defun copyright-offset-too-large-p ()
135 "Return non-nil if point is too far from the edge of the buffer."
136 (when copyright-limit
137 (if copyright-at-end-flag
138 (< (point) (- (point-max) copyright-limit))
139 (> (point) (+ (point-min) copyright-limit)))))
4168d2c7 140
0412a5a4
GM
141(defun copyright-find-copyright ()
142 "Return non-nil if a copyright header suitable for updating is found.
143The header must match `copyright-regexp' and `copyright-names-regexp', if set.
144This function sets the match-data that `copyright-update-year' uses."
572bf6f2
GM
145 (widen)
146 (goto-char (copyright-start-point))
30213927
GM
147 ;; In case the regexp is rejected. This is useful because
148 ;; copyright-update is typically called from before-save-hook where
149 ;; such an error is very inconvenient for the user.
150 (with-demoted-errors "Can't update copyright: %s"
151 ;; (1) Need the extra \\( \\) around copyright-regexp because we
152 ;; goto (match-end 1) below. See note (2) below.
153 (copyright-re-search (concat "\\(" copyright-regexp
154 "\\)\\([ \t]*\n\\)?.*\\(?:"
155 copyright-names-regexp "\\)")
156 (copyright-limit)
157 t)))
0412a5a4 158
0fe719e6
GM
159(defun copyright-find-end ()
160 "Possibly adjust the search performed by `copyright-find-copyright'.
161If the years continue onto multiple lines that are marked as comments,
162skips to the end of all the years."
0412a5a4
GM
163 (while (save-excursion
164 (and (eq (following-char) ?,)
165 (progn (forward-char 1) t)
166 (progn (skip-chars-forward " \t") (eolp))
167 comment-start-skip
168 (save-match-data
169 (forward-line 1)
170 (and (looking-at comment-start-skip)
171 (goto-char (match-end 0))))
172 (looking-at-p copyright-years-regexp)))
173 (forward-line 1)
174 (re-search-forward comment-start-skip)
175 ;; (2) Need the extra \\( \\) so that the years are subexp 3, as
176 ;; they are at note (1) above.
0fe719e6 177 (re-search-forward (format "\\(%s\\)" copyright-years-regexp))))
0412a5a4 178
0fe719e6
GM
179(defun copyright-update-year (replace noquery)
180 ;; This uses the match-data from copyright-find-copyright/end.
181 (goto-char (match-end 1))
182 (copyright-find-end)
0bfcf5c5 183 (setq copyright-current-year (format-time-string "%Y"))
0412a5a4
GM
184 (unless (string= (buffer-substring (- (match-end 3) 2) (match-end 3))
185 (substring copyright-current-year -2))
186 (if (or noquery
187 (save-window-excursion
188 (switch-to-buffer (current-buffer))
189 ;; Fixes some point-moving oddness (bug#2209).
190 (save-excursion
191 (y-or-n-p (if replace
192 (concat "Replace copyright year(s) by "
193 copyright-current-year "? ")
194 (concat "Add " copyright-current-year
195 " to copyright? "))))))
196 (if replace
197 (replace-match copyright-current-year t t nil 3)
198 (let ((size (save-excursion (skip-chars-backward "0-9"))))
199 (if (and (eq (% (- (string-to-number copyright-current-year)
200 (string-to-number (buffer-substring
201 (+ (point) size)
202 (point))))
203 100)
204 1)
205 (or (eq (char-after (+ (point) size -1)) ?-)
206 (eq (char-after (+ (point) size -2)) ?-)))
207 ;; This is a range so just replace the end part.
208 (delete-char size)
209 ;; Insert a comma with the preferred number of spaces.
210 (insert
211 (save-excursion
212 (if (re-search-backward "[0-9]\\( *, *\\)[0-9]"
213 (line-beginning-position) t)
214 (match-string 1)
215 ", ")))
216 ;; If people use the '91 '92 '93 scheme, do that as well.
217 (if (eq (char-after (+ (point) size -3)) ?')
218 (insert ?')))
219 ;; Finally insert the new year.
220 (insert (substring copyright-current-year size)))))))
b7812d30 221
0343b087 222;;;###autoload
f47f5302 223(defun copyright-update (&optional arg interactivep)
dbc76957 224 "Update copyright notice to indicate the current year.
9c05459c
RS
225With prefix ARG, replace the years in the notice rather than adding
226the current year after them. If necessary, and
227`copyright-current-gpl-version' is set, any copying permissions
f47f5302
SM
228following the copyright are updated as well.
229If non-nil, INTERACTIVEP tells the function to behave as when it's called
230interactively."
231 (interactive "*P\nd")
232 (when (or copyright-update interactivep)
233 (let ((noquery (or (not copyright-query)
234 (and (eq copyright-query 'function) interactivep))))
3f61a2e7
KH
235 (save-excursion
236 (save-restriction
0412a5a4
GM
237 ;; If names-regexp doesn't match, we should not mess with
238 ;; the years _or_ the GPL version.
0fe719e6 239 ;; TODO there may be multiple copyrights we should update.
0412a5a4
GM
240 (when (copyright-find-copyright)
241 (copyright-update-year arg noquery)
242 (goto-char (copyright-start-point))
243 (and copyright-current-gpl-version
244 ;; Match the GPL version comment in .el files.
245 ;; This is sensitive to line-breaks. :(
246 (copyright-re-search
247 "the Free Software Foundation[,;\n].*either version \
248\\([0-9]+\\)\\(?: of the License\\)?, or[ \n].*any later version"
249 (copyright-limit) t)
250 ;; Don't update if the file is already using a more recent
251 ;; version than the "current" one.
252 (< (string-to-number (match-string 1))
253 (string-to-number copyright-current-gpl-version))
254 (or noquery
255 (save-match-data
256 (goto-char (match-end 1))
257 (save-window-excursion
258 (switch-to-buffer (current-buffer))
259 (y-or-n-p
260 (format "Replace GPL version %s with version %s? "
261 (match-string-no-properties 1)
262 copyright-current-gpl-version)))))
263 (replace-match copyright-current-gpl-version t t nil 1))))
3f61a2e7 264 (set (make-local-variable 'copyright-update) nil)))
f47f5302
SM
265 ;; If a write-file-hook returns non-nil, the file is presumed to be written.
266 nil))
0343b087 267
d501f516 268
0fe719e6 269;; FIXME heuristic should be within 50 years of present (cf calendar).
422032f0
KS
270;;;###autoload
271(defun copyright-fix-years ()
272 "Convert 2 digit years to 4 digit years.
0fe719e6
GM
273Uses heuristic: year >= 50 means 19xx, < 50 means 20xx.
274If `copyright-year-ranges' (which see) is non-nil, also
275independently replaces consecutive years with a range."
422032f0 276 (interactive)
0fe719e6 277 ;; TODO there may be multiple copyrights we should fix.
d226ec23 278 (if (copyright-find-copyright)
0fe719e6 279 (let ((s (match-beginning 3))
07b41c42 280 (p (make-marker))
0fe719e6
GM
281 ;; Not line-beg-pos, so we don't mess up leading whitespace.
282 (copystart (match-beginning 0))
283 e last sep year prev-year first-year range-start range-end)
284 ;; In case years are continued over multiple, commented lines.
285 (goto-char (match-end 1))
286 (copyright-find-end)
287 (setq e (copy-marker (1+ (match-end 3))))
422032f0 288 (goto-char s)
07b41c42
LK
289 (while (re-search-forward "[0-9]+" e t)
290 (set-marker p (point))
291 (goto-char (match-beginning 0))
0fe719e6
GM
292 (setq year (string-to-number (match-string 0)))
293 (and (setq sep (char-before))
294 (/= (char-syntax sep) ?\s)
295 (/= sep ?-)
296 (insert " "))
297 (when (< year 100)
298 (insert (if (>= year 50) "19" "20"))
299 (setq year (+ year (if (>= year 50) 1900 2000))))
07b41c42 300 (goto-char p)
0fe719e6
GM
301 (when copyright-year-ranges
302 ;; If the previous thing was a range, don't try to tack more on.
303 ;; Ie not 2000-2005 -> 2000-2005-2007
304 ;; TODO should merge into existing range if possible.
305 (if (eq sep ?-)
306 (setq prev-year nil
307 year nil)
308 (if (and prev-year (= year (1+ prev-year)))
309 (setq range-end (point))
310 (when (and first-year prev-year
311 (> prev-year first-year))
312 (goto-char range-end)
313 (delete-region range-start range-end)
314 (insert (format "-%d" prev-year))
315 (goto-char p))
316 (setq first-year year
317 range-start (point)))))
318 (setq prev-year year
319 last p))
422032f0 320 (when last
0fe719e6
GM
321 (when (and copyright-year-ranges
322 first-year prev-year
323 (> prev-year first-year))
324 (goto-char range-end)
325 (delete-region range-start range-end)
326 (insert (format "-%d" prev-year)))
422032f0 327 (goto-char last)
02d9d682
RS
328 ;; Don't mess up whitespace after the years.
329 (skip-chars-backward " \t")
0fe719e6
GM
330 (save-restriction
331 (narrow-to-region copystart (point))
332 ;; This is clearly wrong, eg what about comment markers?
333 ;;; (let ((fill-prefix " "))
334 ;; TODO do not break copyright owner over lines.
335 (fill-region (point-min) (point-max))))
422032f0 336 (set-marker e nil)
0fe719e6
GM
337 (set-marker p nil))
338 ;; Simply reformatting the years is not copyrightable, so it does
339 ;; not seem right to call this. Also it messes with ranges.
340;;; (copyright-update nil t))
07b41c42 341 (message "No copyright message")))
422032f0 342
3f61a2e7
KH
343;;;###autoload
344(define-skeleton copyright
345 "Insert a copyright by $ORGANIZATION notice at cursor."
346 "Company: "
347 comment-start
0bfcf5c5 348 "Copyright (C) " `(format-time-string "%Y") " by "
3f61a2e7
KH
349 (or (getenv "ORGANIZATION")
350 str)
b2c7c56d 351 '(if (copyright-offset-too-large-p)
3f61a2e7 352 (message "Copyright extends beyond `copyright-limit' and won't be updated automatically."))
48a96f51 353 comment-end \n)
3f61a2e7 354
0fe719e6 355;; TODO: recurse, exclude COPYING etc.
4182531c 356;;;###autoload
0fe719e6
GM
357(defun copyright-update-directory (directory match &optional fix)
358 "Update copyright notice for all files in DIRECTORY matching MATCH.
359If FIX is non-nil, run `copyright-fix-years' instead."
4182531c 360 (interactive "DDirectory: \nMFilenames matching (regexp): ")
470fc354 361 (dolist (file (directory-files directory t match nil))
0fe719e6
GM
362 (unless (file-directory-p file)
363 (message "Updating file `%s'" file)
fbb5e336
GM
364 ;; FIXME we should not use find-file+save+kill.
365 (let ((enable-local-variables :safe)
366 (enable-local-eval nil))
367 (find-file file))
368 (let ((inhibit-read-only t))
0fe719e6
GM
369 (if fix
370 (copyright-fix-years)
371 (copyright-update)))
372 (save-buffer)
373 (kill-buffer (current-buffer)))))
470fc354 374
896546cd
RS
375(provide 'copyright)
376
bf5f1abd
DL
377;; For the copyright sign:
378;; Local Variables:
d260b218 379;; coding: utf-8
bf5f1abd
DL
380;; End:
381
e8af40ee 382;;; copyright.el ends here