Commit | Line | Data |
---|---|---|
3f61a2e7 | 1 | ;;; copyright.el --- update the copyright notice in current buffer |
d501f516 | 2 | |
acaf905b | 3 | ;; Copyright (C) 1991-1995, 1998, 2001-2012 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 | 38 | A 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. | |
45 | This 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 |
57 | The 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. | |
63 | Only copyright lines where the name matches this regexp will be updated. | |
8a1dd108 | 64 | This allows you to avoid adding years to a copyright notice belonging to |
b649d2e4 | 65 | someone 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 |
77 | The 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. | |
86 | For example: 2005, 2006, 2007, 2008 might be replaced with 2005-2008. | |
87 | If you use ranges, you should add an explanatory note in a README file. | |
88 | The function `copyright-fix-year' respects this variable." | |
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 |
97 | When 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. | |
9c05459c | 113 | (defvar copyright-current-year (substring (current-time-string) -4) |
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. | |
143 | The header must match `copyright-regexp' and `copyright-names-regexp', if set. | |
144 | This function sets the match-data that `copyright-update-year' uses." | |
572bf6f2 GM |
145 | (widen) |
146 | (goto-char (copyright-start-point)) | |
0412a5a4 GM |
147 | (condition-case err |
148 | ;; (1) Need the extra \\( \\) around copyright-regexp because we | |
149 | ;; goto (match-end 1) below. See note (2) below. | |
150 | (copyright-re-search (concat "\\(" copyright-regexp | |
151 | "\\)\\([ \t]*\n\\)?.*\\(?:" | |
152 | copyright-names-regexp "\\)") | |
153 | (copyright-limit) | |
154 | t) | |
155 | ;; In case the regexp is rejected. This is useful because | |
156 | ;; copyright-update is typically called from before-save-hook where | |
157 | ;; such an error is very inconvenient for the user. | |
158 | (error (message "Can't update copyright: %s" err) nil))) | |
159 | ||
0fe719e6 GM |
160 | (defun copyright-find-end () |
161 | "Possibly adjust the search performed by `copyright-find-copyright'. | |
162 | If the years continue onto multiple lines that are marked as comments, | |
163 | skips 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) | |
0412a5a4 GM |
184 | ;; Note that `current-time-string' isn't locale-sensitive. |
185 | (setq copyright-current-year (substring (current-time-string) -4)) | |
186 | (unless (string= (buffer-substring (- (match-end 3) 2) (match-end 3)) | |
187 | (substring copyright-current-year -2)) | |
188 | (if (or noquery | |
189 | (save-window-excursion | |
190 | (switch-to-buffer (current-buffer)) | |
191 | ;; Fixes some point-moving oddness (bug#2209). | |
192 | (save-excursion | |
193 | (y-or-n-p (if replace | |
194 | (concat "Replace copyright year(s) by " | |
195 | copyright-current-year "? ") | |
196 | (concat "Add " copyright-current-year | |
197 | " to copyright? ")))))) | |
198 | (if replace | |
199 | (replace-match copyright-current-year t t nil 3) | |
200 | (let ((size (save-excursion (skip-chars-backward "0-9")))) | |
201 | (if (and (eq (% (- (string-to-number copyright-current-year) | |
202 | (string-to-number (buffer-substring | |
203 | (+ (point) size) | |
204 | (point)))) | |
205 | 100) | |
206 | 1) | |
207 | (or (eq (char-after (+ (point) size -1)) ?-) | |
208 | (eq (char-after (+ (point) size -2)) ?-))) | |
209 | ;; This is a range so just replace the end part. | |
210 | (delete-char size) | |
211 | ;; Insert a comma with the preferred number of spaces. | |
212 | (insert | |
213 | (save-excursion | |
214 | (if (re-search-backward "[0-9]\\( *, *\\)[0-9]" | |
215 | (line-beginning-position) t) | |
216 | (match-string 1) | |
217 | ", "))) | |
218 | ;; If people use the '91 '92 '93 scheme, do that as well. | |
219 | (if (eq (char-after (+ (point) size -3)) ?') | |
220 | (insert ?'))) | |
221 | ;; Finally insert the new year. | |
222 | (insert (substring copyright-current-year size))))))) | |
b7812d30 | 223 | |
0343b087 | 224 | ;;;###autoload |
f47f5302 | 225 | (defun copyright-update (&optional arg interactivep) |
dbc76957 | 226 | "Update copyright notice to indicate the current year. |
9c05459c RS |
227 | With prefix ARG, replace the years in the notice rather than adding |
228 | the current year after them. If necessary, and | |
229 | `copyright-current-gpl-version' is set, any copying permissions | |
f47f5302 SM |
230 | following the copyright are updated as well. |
231 | If non-nil, INTERACTIVEP tells the function to behave as when it's called | |
232 | interactively." | |
233 | (interactive "*P\nd") | |
234 | (when (or copyright-update interactivep) | |
235 | (let ((noquery (or (not copyright-query) | |
236 | (and (eq copyright-query 'function) interactivep)))) | |
3f61a2e7 KH |
237 | (save-excursion |
238 | (save-restriction | |
0412a5a4 GM |
239 | ;; If names-regexp doesn't match, we should not mess with |
240 | ;; the years _or_ the GPL version. | |
0fe719e6 | 241 | ;; TODO there may be multiple copyrights we should update. |
0412a5a4 GM |
242 | (when (copyright-find-copyright) |
243 | (copyright-update-year arg noquery) | |
244 | (goto-char (copyright-start-point)) | |
245 | (and copyright-current-gpl-version | |
246 | ;; Match the GPL version comment in .el files. | |
247 | ;; This is sensitive to line-breaks. :( | |
248 | (copyright-re-search | |
249 | "the Free Software Foundation[,;\n].*either version \ | |
250 | \\([0-9]+\\)\\(?: of the License\\)?, or[ \n].*any later version" | |
251 | (copyright-limit) t) | |
252 | ;; Don't update if the file is already using a more recent | |
253 | ;; version than the "current" one. | |
254 | (< (string-to-number (match-string 1)) | |
255 | (string-to-number copyright-current-gpl-version)) | |
256 | (or noquery | |
257 | (save-match-data | |
258 | (goto-char (match-end 1)) | |
259 | (save-window-excursion | |
260 | (switch-to-buffer (current-buffer)) | |
261 | (y-or-n-p | |
262 | (format "Replace GPL version %s with version %s? " | |
263 | (match-string-no-properties 1) | |
264 | copyright-current-gpl-version))))) | |
265 | (replace-match copyright-current-gpl-version t t nil 1)))) | |
3f61a2e7 | 266 | (set (make-local-variable 'copyright-update) nil))) |
f47f5302 SM |
267 | ;; If a write-file-hook returns non-nil, the file is presumed to be written. |
268 | nil)) | |
0343b087 | 269 | |
d501f516 | 270 | |
0fe719e6 | 271 | ;; FIXME heuristic should be within 50 years of present (cf calendar). |
422032f0 KS |
272 | ;;;###autoload |
273 | (defun copyright-fix-years () | |
274 | "Convert 2 digit years to 4 digit years. | |
0fe719e6 GM |
275 | Uses heuristic: year >= 50 means 19xx, < 50 means 20xx. |
276 | If `copyright-year-ranges' (which see) is non-nil, also | |
277 | independently replaces consecutive years with a range." | |
422032f0 | 278 | (interactive) |
0fe719e6 | 279 | ;; TODO there may be multiple copyrights we should fix. |
d226ec23 | 280 | (if (copyright-find-copyright) |
0fe719e6 | 281 | (let ((s (match-beginning 3)) |
07b41c42 | 282 | (p (make-marker)) |
0fe719e6 GM |
283 | ;; Not line-beg-pos, so we don't mess up leading whitespace. |
284 | (copystart (match-beginning 0)) | |
285 | e last sep year prev-year first-year range-start range-end) | |
286 | ;; In case years are continued over multiple, commented lines. | |
287 | (goto-char (match-end 1)) | |
288 | (copyright-find-end) | |
289 | (setq e (copy-marker (1+ (match-end 3)))) | |
422032f0 | 290 | (goto-char s) |
07b41c42 LK |
291 | (while (re-search-forward "[0-9]+" e t) |
292 | (set-marker p (point)) | |
293 | (goto-char (match-beginning 0)) | |
0fe719e6 GM |
294 | (setq year (string-to-number (match-string 0))) |
295 | (and (setq sep (char-before)) | |
296 | (/= (char-syntax sep) ?\s) | |
297 | (/= sep ?-) | |
298 | (insert " ")) | |
299 | (when (< year 100) | |
300 | (insert (if (>= year 50) "19" "20")) | |
301 | (setq year (+ year (if (>= year 50) 1900 2000)))) | |
07b41c42 | 302 | (goto-char p) |
0fe719e6 GM |
303 | (when copyright-year-ranges |
304 | ;; If the previous thing was a range, don't try to tack more on. | |
305 | ;; Ie not 2000-2005 -> 2000-2005-2007 | |
306 | ;; TODO should merge into existing range if possible. | |
307 | (if (eq sep ?-) | |
308 | (setq prev-year nil | |
309 | year nil) | |
310 | (if (and prev-year (= year (1+ prev-year))) | |
311 | (setq range-end (point)) | |
312 | (when (and first-year prev-year | |
313 | (> prev-year first-year)) | |
314 | (goto-char range-end) | |
315 | (delete-region range-start range-end) | |
316 | (insert (format "-%d" prev-year)) | |
317 | (goto-char p)) | |
318 | (setq first-year year | |
319 | range-start (point))))) | |
320 | (setq prev-year year | |
321 | last p)) | |
422032f0 | 322 | (when last |
0fe719e6 GM |
323 | (when (and copyright-year-ranges |
324 | first-year prev-year | |
325 | (> prev-year first-year)) | |
326 | (goto-char range-end) | |
327 | (delete-region range-start range-end) | |
328 | (insert (format "-%d" prev-year))) | |
422032f0 | 329 | (goto-char last) |
02d9d682 RS |
330 | ;; Don't mess up whitespace after the years. |
331 | (skip-chars-backward " \t") | |
0fe719e6 GM |
332 | (save-restriction |
333 | (narrow-to-region copystart (point)) | |
334 | ;; This is clearly wrong, eg what about comment markers? | |
335 | ;;; (let ((fill-prefix " ")) | |
336 | ;; TODO do not break copyright owner over lines. | |
337 | (fill-region (point-min) (point-max)))) | |
422032f0 | 338 | (set-marker e nil) |
0fe719e6 GM |
339 | (set-marker p nil)) |
340 | ;; Simply reformatting the years is not copyrightable, so it does | |
341 | ;; not seem right to call this. Also it messes with ranges. | |
342 | ;;; (copyright-update nil t)) | |
07b41c42 | 343 | (message "No copyright message"))) |
422032f0 | 344 | |
3f61a2e7 KH |
345 | ;;;###autoload |
346 | (define-skeleton copyright | |
347 | "Insert a copyright by $ORGANIZATION notice at cursor." | |
348 | "Company: " | |
349 | comment-start | |
b7812d30 | 350 | "Copyright (C) " `(substring (current-time-string) -4) " by " |
3f61a2e7 KH |
351 | (or (getenv "ORGANIZATION") |
352 | str) | |
b2c7c56d | 353 | '(if (copyright-offset-too-large-p) |
3f61a2e7 | 354 | (message "Copyright extends beyond `copyright-limit' and won't be updated automatically.")) |
48a96f51 | 355 | comment-end \n) |
3f61a2e7 | 356 | |
0fe719e6 | 357 | ;; TODO: recurse, exclude COPYING etc. |
4182531c | 358 | ;;;###autoload |
0fe719e6 GM |
359 | (defun copyright-update-directory (directory match &optional fix) |
360 | "Update copyright notice for all files in DIRECTORY matching MATCH. | |
361 | If FIX is non-nil, run `copyright-fix-years' instead." | |
4182531c | 362 | (interactive "DDirectory: \nMFilenames matching (regexp): ") |
470fc354 | 363 | (dolist (file (directory-files directory t match nil)) |
0fe719e6 GM |
364 | (unless (file-directory-p file) |
365 | (message "Updating file `%s'" file) | |
7e6e2304 | 366 | (find-file file) |
0fe719e6 GM |
367 | (let ((inhibit-read-only t) |
368 | (enable-local-variables :safe) | |
369 | copyright-query) | |
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 |