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