Commit | Line | Data |
---|---|---|
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 | 39 | A 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. | |
46 | This 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 |
58 | The 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. | |
64 | Only copyright lines where the name matches this regexp will be updated. | |
8a1dd108 | 65 | This allows you to avoid adding years to a copyright notice belonging to |
b649d2e4 | 66 | someone 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 |
78 | The 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. | |
87 | For example: 2005, 2006, 2007, 2008 might be replaced with 2005-2008. | |
88 | If you use ranges, you should add an explanatory note in a README file. | |
e2b5bdd7 | 89 | The 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 |
98 | When 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. | |
144 | The header must match `copyright-regexp' and `copyright-names-regexp', if set. | |
145 | This 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'. | |
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) | |
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 |
226 | With prefix ARG, replace the years in the notice rather than adding |
227 | the current year after them. If necessary, and | |
228 | `copyright-current-gpl-version' is set, any copying permissions | |
f47f5302 SM |
229 | following the copyright are updated as well. |
230 | If non-nil, INTERACTIVEP tells the function to behave as when it's called | |
231 | interactively." | |
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 |
274 | Uses heuristic: year >= 50 means 19xx, < 50 means 20xx. |
275 | If `copyright-year-ranges' (which see) is non-nil, also | |
276 | independently 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. | |
360 | If 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 |