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