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