Commit | Line | Data |
---|---|---|
3f61a2e7 | 1 | ;;; copyright.el --- update the copyright notice in current buffer |
d501f516 | 2 | |
73b0cd50 | 3 | ;; Copyright (C) 1991-1995, 1998, 2001-2011 |
0412a5a4 | 4 | ;; Free Software Foundation, 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") | |
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 | ||
69311816 RS |
69 | (defcustom copyright-years-regexp |
70 | "\\(\\s *\\)\\([1-9]\\([-0-9, ';/*%#\n\t]\\|\\s<\\|\\s>\\)*[0-9]+\\)" | |
b649d2e4 | 71 | "Match additional copyright notice years. |
69311816 RS |
72 | The second \\( \\) construct must match the years." |
73 | :group 'copyright | |
74 | :type 'regexp) | |
75 | ||
3f61a2e7 | 76 | |
3f251fcd | 77 | (defcustom copyright-query 'function |
b649d2e4 | 78 | "If non-nil, ask user before changing copyright. |
3f251fcd AS |
79 | When this is `function', only ask when called non-interactively." |
80 | :group 'copyright | |
81 | :type '(choice (const :tag "Do not ask") | |
e4f0bdfa AS |
82 | (const :tag "Ask unless interactive" function) |
83 | (other :tag "Ask" t))) | |
3f61a2e7 KH |
84 | |
85 | ||
a7acbbe4 | 86 | ;; when modifying this, also modify the comment generated by autoinsert.el |
948d9b97 | 87 | (defconst copyright-current-gpl-version "3" |
9c05459c | 88 | "String representing the current version of the GPL or nil.") |
0343b087 | 89 | |
fe177a62 GM |
90 | (defvar copyright-update t |
91 | "The function `copyright-update' sets this to nil after updating a buffer.") | |
9cfd2eeb | 92 | |
b7812d30 EZ |
93 | ;; This is a defvar rather than a defconst, because the year can |
94 | ;; change during the Emacs session. | |
9c05459c | 95 | (defvar copyright-current-year (substring (current-time-string) -4) |
b7812d30 EZ |
96 | "String representing the current year.") |
97 | ||
4168d2c7 | 98 | (defsubst copyright-limit () ; re-search-forward BOUND |
b2c7c56d GM |
99 | (and copyright-limit |
100 | (if copyright-at-end-flag | |
101 | (- (point) copyright-limit) | |
102 | (+ (point) copyright-limit)))) | |
103 | ||
104 | (defun copyright-re-search (regexp &optional bound noerror count) | |
105 | "Re-search forward or backward depending on `copyright-at-end-flag'." | |
106 | (if copyright-at-end-flag | |
107 | (re-search-backward regexp bound noerror count) | |
108 | (re-search-forward regexp bound noerror count))) | |
109 | ||
110 | (defun copyright-start-point () | |
111 | "Return point-min or point-max, depending on `copyright-at-end-flag'." | |
112 | (if copyright-at-end-flag | |
113 | (point-max) | |
114 | (point-min))) | |
115 | ||
116 | (defun copyright-offset-too-large-p () | |
117 | "Return non-nil if point is too far from the edge of the buffer." | |
118 | (when copyright-limit | |
119 | (if copyright-at-end-flag | |
120 | (< (point) (- (point-max) copyright-limit)) | |
121 | (> (point) (+ (point-min) copyright-limit))))) | |
4168d2c7 | 122 | |
0412a5a4 GM |
123 | (defun copyright-find-copyright () |
124 | "Return non-nil if a copyright header suitable for updating is found. | |
125 | The header must match `copyright-regexp' and `copyright-names-regexp', if set. | |
126 | This function sets the match-data that `copyright-update-year' uses." | |
572bf6f2 GM |
127 | (widen) |
128 | (goto-char (copyright-start-point)) | |
0412a5a4 GM |
129 | (condition-case err |
130 | ;; (1) Need the extra \\( \\) around copyright-regexp because we | |
131 | ;; goto (match-end 1) below. See note (2) below. | |
132 | (copyright-re-search (concat "\\(" copyright-regexp | |
133 | "\\)\\([ \t]*\n\\)?.*\\(?:" | |
134 | copyright-names-regexp "\\)") | |
135 | (copyright-limit) | |
136 | t) | |
137 | ;; In case the regexp is rejected. This is useful because | |
138 | ;; copyright-update is typically called from before-save-hook where | |
139 | ;; such an error is very inconvenient for the user. | |
140 | (error (message "Can't update copyright: %s" err) nil))) | |
141 | ||
f47f5302 | 142 | (defun copyright-update-year (replace noquery) |
0412a5a4 GM |
143 | ;; This uses the match-data from copyright-find-copyright. |
144 | (goto-char (match-end 1)) | |
145 | ;; If the years are continued onto multiple lines | |
146 | ;; that are marked as comments, skip to the end of the years anyway. | |
147 | (while (save-excursion | |
148 | (and (eq (following-char) ?,) | |
149 | (progn (forward-char 1) t) | |
150 | (progn (skip-chars-forward " \t") (eolp)) | |
151 | comment-start-skip | |
152 | (save-match-data | |
153 | (forward-line 1) | |
154 | (and (looking-at comment-start-skip) | |
155 | (goto-char (match-end 0)))) | |
156 | (looking-at-p copyright-years-regexp))) | |
157 | (forward-line 1) | |
158 | (re-search-forward comment-start-skip) | |
159 | ;; (2) Need the extra \\( \\) so that the years are subexp 3, as | |
160 | ;; they are at note (1) above. | |
161 | (re-search-forward (format "\\(%s\\)" copyright-years-regexp))) | |
162 | ||
163 | ;; Note that `current-time-string' isn't locale-sensitive. | |
164 | (setq copyright-current-year (substring (current-time-string) -4)) | |
165 | (unless (string= (buffer-substring (- (match-end 3) 2) (match-end 3)) | |
166 | (substring copyright-current-year -2)) | |
167 | (if (or noquery | |
168 | (save-window-excursion | |
169 | (switch-to-buffer (current-buffer)) | |
170 | ;; Fixes some point-moving oddness (bug#2209). | |
171 | (save-excursion | |
172 | (y-or-n-p (if replace | |
173 | (concat "Replace copyright year(s) by " | |
174 | copyright-current-year "? ") | |
175 | (concat "Add " copyright-current-year | |
176 | " to copyright? ")))))) | |
177 | (if replace | |
178 | (replace-match copyright-current-year t t nil 3) | |
179 | (let ((size (save-excursion (skip-chars-backward "0-9")))) | |
180 | (if (and (eq (% (- (string-to-number copyright-current-year) | |
181 | (string-to-number (buffer-substring | |
182 | (+ (point) size) | |
183 | (point)))) | |
184 | 100) | |
185 | 1) | |
186 | (or (eq (char-after (+ (point) size -1)) ?-) | |
187 | (eq (char-after (+ (point) size -2)) ?-))) | |
188 | ;; This is a range so just replace the end part. | |
189 | (delete-char size) | |
190 | ;; Insert a comma with the preferred number of spaces. | |
191 | (insert | |
192 | (save-excursion | |
193 | (if (re-search-backward "[0-9]\\( *, *\\)[0-9]" | |
194 | (line-beginning-position) t) | |
195 | (match-string 1) | |
196 | ", "))) | |
197 | ;; If people use the '91 '92 '93 scheme, do that as well. | |
198 | (if (eq (char-after (+ (point) size -3)) ?') | |
199 | (insert ?'))) | |
200 | ;; Finally insert the new year. | |
201 | (insert (substring copyright-current-year size))))))) | |
b7812d30 | 202 | |
0343b087 | 203 | ;;;###autoload |
f47f5302 | 204 | (defun copyright-update (&optional arg interactivep) |
dbc76957 | 205 | "Update copyright notice to indicate the current year. |
9c05459c RS |
206 | With prefix ARG, replace the years in the notice rather than adding |
207 | the current year after them. If necessary, and | |
208 | `copyright-current-gpl-version' is set, any copying permissions | |
f47f5302 SM |
209 | following the copyright are updated as well. |
210 | If non-nil, INTERACTIVEP tells the function to behave as when it's called | |
211 | interactively." | |
212 | (interactive "*P\nd") | |
213 | (when (or copyright-update interactivep) | |
214 | (let ((noquery (or (not copyright-query) | |
215 | (and (eq copyright-query 'function) interactivep)))) | |
3f61a2e7 KH |
216 | (save-excursion |
217 | (save-restriction | |
0412a5a4 GM |
218 | ;; If names-regexp doesn't match, we should not mess with |
219 | ;; the years _or_ the GPL version. | |
220 | (when (copyright-find-copyright) | |
221 | (copyright-update-year arg noquery) | |
222 | (goto-char (copyright-start-point)) | |
223 | (and copyright-current-gpl-version | |
224 | ;; Match the GPL version comment in .el files. | |
225 | ;; This is sensitive to line-breaks. :( | |
226 | (copyright-re-search | |
227 | "the Free Software Foundation[,;\n].*either version \ | |
228 | \\([0-9]+\\)\\(?: of the License\\)?, or[ \n].*any later version" | |
229 | (copyright-limit) t) | |
230 | ;; Don't update if the file is already using a more recent | |
231 | ;; version than the "current" one. | |
232 | (< (string-to-number (match-string 1)) | |
233 | (string-to-number copyright-current-gpl-version)) | |
234 | (or noquery | |
235 | (save-match-data | |
236 | (goto-char (match-end 1)) | |
237 | (save-window-excursion | |
238 | (switch-to-buffer (current-buffer)) | |
239 | (y-or-n-p | |
240 | (format "Replace GPL version %s with version %s? " | |
241 | (match-string-no-properties 1) | |
242 | copyright-current-gpl-version))))) | |
243 | (replace-match copyright-current-gpl-version t t nil 1)))) | |
3f61a2e7 | 244 | (set (make-local-variable 'copyright-update) nil))) |
f47f5302 SM |
245 | ;; If a write-file-hook returns non-nil, the file is presumed to be written. |
246 | nil)) | |
0343b087 | 247 | |
d501f516 | 248 | |
fe177a62 | 249 | ;; FIXME should be within 50 years of present (cf calendar). |
422032f0 KS |
250 | ;;;###autoload |
251 | (defun copyright-fix-years () | |
252 | "Convert 2 digit years to 4 digit years. | |
253 | Uses heuristic: year >= 50 means 19xx, < 50 means 20xx." | |
254 | (interactive) | |
d226ec23 | 255 | (if (copyright-find-copyright) |
07b41c42 LK |
256 | (let ((s (match-beginning 2)) |
257 | (e (copy-marker (1+ (match-end 2)))) | |
258 | (p (make-marker)) | |
422032f0 | 259 | last) |
422032f0 | 260 | (goto-char s) |
07b41c42 LK |
261 | (while (re-search-forward "[0-9]+" e t) |
262 | (set-marker p (point)) | |
263 | (goto-char (match-beginning 0)) | |
264 | (let ((sep (char-before)) | |
265 | (year (string-to-number (match-string 0)))) | |
266 | (when (and sep | |
267 | (/= (char-syntax sep) ?\s) | |
268 | (/= sep ?-)) | |
422032f0 | 269 | (insert " ")) |
07b41c42 LK |
270 | (when (< year 100) |
271 | (insert (if (>= year 50) "19" "20")))) | |
272 | (goto-char p) | |
273 | (setq last p)) | |
422032f0 KS |
274 | (when last |
275 | (goto-char last) | |
02d9d682 RS |
276 | ;; Don't mess up whitespace after the years. |
277 | (skip-chars-backward " \t") | |
278 | (save-restriction | |
b2c7c56d | 279 | (narrow-to-region (copyright-start-point) (point)) |
02d9d682 | 280 | (let ((fill-prefix " ")) |
07b41c42 | 281 | (fill-region s last)))) |
422032f0 | 282 | (set-marker e nil) |
07b41c42 | 283 | (set-marker p nil) |
422032f0 | 284 | (copyright-update nil t)) |
07b41c42 | 285 | (message "No copyright message"))) |
422032f0 | 286 | |
3f61a2e7 KH |
287 | ;;;###autoload |
288 | (define-skeleton copyright | |
289 | "Insert a copyright by $ORGANIZATION notice at cursor." | |
290 | "Company: " | |
291 | comment-start | |
b7812d30 | 292 | "Copyright (C) " `(substring (current-time-string) -4) " by " |
3f61a2e7 KH |
293 | (or (getenv "ORGANIZATION") |
294 | str) | |
b2c7c56d | 295 | '(if (copyright-offset-too-large-p) |
3f61a2e7 | 296 | (message "Copyright extends beyond `copyright-limit' and won't be updated automatically.")) |
48a96f51 | 297 | comment-end \n) |
3f61a2e7 | 298 | |
4182531c | 299 | ;;;###autoload |
470fc354 RS |
300 | (defun copyright-update-directory (directory match) |
301 | "Update copyright notice for all files in DIRECTORY matching MATCH." | |
4182531c | 302 | (interactive "DDirectory: \nMFilenames matching (regexp): ") |
470fc354 | 303 | (dolist (file (directory-files directory t match nil)) |
4182531c | 304 | (message "Updating file `%s'" file) |
470fc354 RS |
305 | (find-file file) |
306 | (let ((copyright-query nil)) | |
307 | (copyright-update)) | |
308 | (save-buffer) | |
309 | (kill-buffer (current-buffer)))) | |
310 | ||
896546cd RS |
311 | (provide 'copyright) |
312 | ||
bf5f1abd DL |
313 | ;; For the copyright sign: |
314 | ;; Local Variables: | |
d260b218 | 315 | ;; coding: utf-8 |
bf5f1abd DL |
316 | ;; End: |
317 | ||
e8af40ee | 318 | ;;; copyright.el ends here |