copyright.el 13.9 KB
Newer Older
1
;;; copyright.el --- update the copyright notice in current buffer
Eric S. Raymond's avatar
Eric S. Raymond committed
2

3
;; Copyright (C) 1991-1995, 1998, 2001-2012  Free Software Foundation, Inc.
Eric S. Raymond's avatar
Eric S. Raymond committed
4

Karl Heuer's avatar
Karl Heuer committed
5
;; Author: Daniel Pfeiffer <occitan@esperanto.org>
6 7 8 9
;; Keywords: maint, tools

;; This file is part of GNU Emacs.

10
;; GNU Emacs is free software: you can redistribute it and/or modify
11
;; it under the terms of the GNU General Public License as published by
12 13
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
14 15 16 17 18 19 20

;; GNU Emacs is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
21
;; along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.
22 23 24 25

;;; Commentary:

;; Allows updating the copyright year and above mentioned GPL version manually
26
;; or when saving a file.
Kenichi Handa's avatar
Kenichi Handa committed
27 28
;; Do (add-hook 'before-save-hook 'copyright-update), or use
;; M-x customize-variable RET before-save-hook RET.
Roland McGrath's avatar
Roland McGrath committed
29

Eric S. Raymond's avatar
Eric S. Raymond committed
30 31
;;; Code:

Andreas Schwab's avatar
Andreas Schwab committed
32 33 34 35 36
(defgroup copyright nil
  "Update the copyright notice in current buffer."
  :group 'tools)

(defcustom copyright-limit 2000
37
  "Don't try to update copyright beyond this position unless interactive.
38
A value of nil means to search whole buffer."
Andreas Schwab's avatar
Andreas Schwab committed
39 40 41
  :group 'copyright
  :type '(choice (integer :tag "Limit")
		 (const :tag "No limit")))
42

43 44 45 46 47 48
(defcustom copyright-at-end-flag nil
  "Non-nil means to search backwards from the end of the buffer for copyright.
This is useful for ChangeLogs."
  :group 'copyright
  :type 'boolean
  :version "23.1")
Glenn Morris's avatar
Glenn Morris committed
49
;;;###autoload(put 'copyright-at-end-flag 'safe-local-variable 'booleanp)
50

Andreas Schwab's avatar
Andreas Schwab committed
51
(defcustom copyright-regexp
Miles Bader's avatar
Miles Bader committed
52
 "\\(©\\|@copyright{}\\|[Cc]opyright\\s *:?\\s *\\(?:(C)\\)?\
53
\\|[Cc]opyright\\s *:?\\s *©\\)\
Miles Bader's avatar
Miles Bader committed
54 55
\\s *\\(?:[^0-9\n]*\\s *\\)?\
\\([1-9]\\([-0-9, ';/*%#\n\t]\\|\\s<\\|\\s>\\)*[0-9]+\\)"
56
  "What your copyright notice looks like.
Andreas Schwab's avatar
Andreas Schwab committed
57 58 59
The second \\( \\) construct must match the years."
  :group 'copyright
  :type 'regexp)
60

61 62 63
(defcustom copyright-names-regexp ""
  "Regexp matching the names which correspond to the user.
Only copyright lines where the name matches this regexp will be updated.
64
This allows you to avoid adding years to a copyright notice belonging to
65
someone else or to a group for which you do not work."
66
  :group 'copyright
67 68
  :type 'regexp)

Glenn Morris's avatar
Glenn Morris committed
69 70 71 72 73
;; The worst that can happen is a malicious regexp that overflows in
;; the regexp matcher, a minor nuisance.  It's a pain to be always
;; prompted if you want to put this in a dir-locals.el.
;;;###autoload(put 'copyright-names-regexp 'safe-local-variable 'stringp)

74 75
(defcustom copyright-years-regexp
 "\\(\\s *\\)\\([1-9]\\([-0-9, ';/*%#\n\t]\\|\\s<\\|\\s>\\)*[0-9]+\\)"
76
  "Match additional copyright notice years.
77 78 79 80
The second \\( \\) construct must match the years."
  :group 'copyright
  :type 'regexp)

Glenn Morris's avatar
Glenn Morris committed
81 82 83 84 85 86 87
;; See "Copyright Notices" in maintain.info.
;; TODO? 'end only for ranges at the end, other for all ranges.
;; Minimum limit on the size of a range?
(defcustom copyright-year-ranges nil
  "Non-nil if individual consecutive years should be replaced with a range.
For example: 2005, 2006, 2007, 2008 might be replaced with 2005-2008.
If you use ranges, you should add an explanatory note in a README file.
88
The function `copyright-fix-years' respects this variable."
Glenn Morris's avatar
Glenn Morris committed
89 90 91 92 93
  :group 'copyright
  :type 'boolean
  :version "24.1")

;;;###autoload(put 'copyright-year-ranges 'safe-local-variable 'booleanp)
94

Andreas Schwab's avatar
Andreas Schwab committed
95
(defcustom copyright-query 'function
96
  "If non-nil, ask user before changing copyright.
Andreas Schwab's avatar
Andreas Schwab committed
97 98 99
When this is `function', only ask when called non-interactively."
  :group 'copyright
  :type '(choice (const :tag "Do not ask")
100 101
		 (const :tag "Ask unless interactive" function)
		 (other :tag "Ask" t)))
102 103


Karl Heuer's avatar
Karl Heuer committed
104
;; when modifying this, also modify the comment generated by autoinsert.el
105
(defconst copyright-current-gpl-version "3"
106
  "String representing the current version of the GPL or nil.")
Roland McGrath's avatar
Roland McGrath committed
107

108 109
(defvar copyright-update t
  "The function `copyright-update' sets this to nil after updating a buffer.")
Roland McGrath's avatar
Roland McGrath committed
110

111 112
;; This is a defvar rather than a defconst, because the year can
;; change during the Emacs session.
Paul Eggert's avatar
Paul Eggert committed
113
(defvar copyright-current-year (format-time-string "%Y")
114 115
  "String representing the current year.")

116
(defsubst copyright-limit ()            ; re-search-forward BOUND
117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139
  (and copyright-limit
       (if copyright-at-end-flag
	   (- (point) copyright-limit)
	 (+ (point) copyright-limit))))

(defun copyright-re-search (regexp &optional bound noerror count)
  "Re-search forward or backward depending on `copyright-at-end-flag'."
  (if copyright-at-end-flag
      (re-search-backward regexp bound noerror count)
    (re-search-forward regexp bound noerror count)))

(defun copyright-start-point ()
  "Return point-min or point-max, depending on `copyright-at-end-flag'."
  (if copyright-at-end-flag
      (point-max)
    (point-min)))

(defun copyright-offset-too-large-p ()
  "Return non-nil if point is too far from the edge of the buffer."
  (when copyright-limit
    (if copyright-at-end-flag
	(< (point) (- (point-max) copyright-limit))
      (> (point) (+ (point-min) copyright-limit)))))
140

Glenn Morris's avatar
Glenn Morris committed
141 142 143 144
(defun copyright-find-copyright ()
  "Return non-nil if a copyright header suitable for updating is found.
The header must match `copyright-regexp' and `copyright-names-regexp', if set.
This function sets the match-data that `copyright-update-year' uses."
145 146
  (widen)
  (goto-char (copyright-start-point))
Glenn Morris's avatar
Glenn Morris committed
147 148 149 150 151 152 153 154 155 156 157 158 159
  (condition-case err
      ;; (1) Need the extra \\( \\) around copyright-regexp because we
      ;; goto (match-end 1) below. See note (2) below.
      (copyright-re-search (concat "\\(" copyright-regexp
				   "\\)\\([ \t]*\n\\)?.*\\(?:"
				   copyright-names-regexp "\\)")
			   (copyright-limit)
			   t)
    ;; In case the regexp is rejected.  This is useful because
    ;; copyright-update is typically called from before-save-hook where
    ;; such an error is very inconvenient for the user.
    (error (message "Can't update copyright: %s" err) nil)))

Glenn Morris's avatar
Glenn Morris committed
160 161 162 163
(defun copyright-find-end ()
  "Possibly adjust the search performed by `copyright-find-copyright'.
If the years continue onto multiple lines that are marked as comments,
skips to the end of all the years."
Glenn Morris's avatar
Glenn Morris committed
164 165 166 167 168 169 170 171 172 173 174 175 176 177
  (while (save-excursion
	   (and (eq (following-char) ?,)
		(progn (forward-char 1) t)
		(progn (skip-chars-forward " \t") (eolp))
		comment-start-skip
		(save-match-data
		  (forward-line 1)
		  (and (looking-at comment-start-skip)
		       (goto-char (match-end 0))))
		(looking-at-p copyright-years-regexp)))
    (forward-line 1)
    (re-search-forward comment-start-skip)
    ;; (2) Need the extra \\( \\) so that the years are subexp 3, as
    ;; they are at note (1) above.
Glenn Morris's avatar
Glenn Morris committed
178
    (re-search-forward (format "\\(%s\\)" copyright-years-regexp))))
Glenn Morris's avatar
Glenn Morris committed
179

Glenn Morris's avatar
Glenn Morris committed
180 181 182 183
(defun copyright-update-year (replace noquery)
  ;; This uses the match-data from copyright-find-copyright/end.
  (goto-char (match-end 1))
  (copyright-find-end)
Paul Eggert's avatar
Paul Eggert committed
184
  (setq copyright-current-year (format-time-string "%Y"))
Glenn Morris's avatar
Glenn Morris committed
185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221
  (unless (string= (buffer-substring (- (match-end 3) 2) (match-end 3))
		   (substring copyright-current-year -2))
    (if (or noquery
	    (save-window-excursion
	      (switch-to-buffer (current-buffer))
	      ;; Fixes some point-moving oddness (bug#2209).
	      (save-excursion
		(y-or-n-p (if replace
			      (concat "Replace copyright year(s) by "
				      copyright-current-year "? ")
			    (concat "Add " copyright-current-year
				    " to copyright? "))))))
	(if replace
	    (replace-match copyright-current-year t t nil 3)
	  (let ((size (save-excursion (skip-chars-backward "0-9"))))
	    (if (and (eq (% (- (string-to-number copyright-current-year)
			       (string-to-number (buffer-substring
						  (+ (point) size)
						  (point))))
			    100)
			 1)
		     (or (eq (char-after (+ (point) size -1)) ?-)
			 (eq (char-after (+ (point) size -2)) ?-)))
		;; This is a range so just replace the end part.
		(delete-char size)
	      ;; Insert a comma with the preferred number of spaces.
	      (insert
	       (save-excursion
		 (if (re-search-backward "[0-9]\\( *, *\\)[0-9]"
					 (line-beginning-position) t)
		     (match-string 1)
		   ", ")))
	      ;; If people use the '91 '92 '93 scheme, do that as well.
	      (if (eq (char-after (+ (point) size -3)) ?')
		  (insert ?')))
	    ;; Finally insert the new year.
	    (insert (substring copyright-current-year size)))))))
222

Roland McGrath's avatar
Roland McGrath committed
223
;;;###autoload
224
(defun copyright-update (&optional arg interactivep)
Glenn Morris's avatar
Glenn Morris committed
225
  "Update copyright notice to indicate the current year.
226 227 228
With prefix ARG, replace the years in the notice rather than adding
the current year after them.  If necessary, and
`copyright-current-gpl-version' is set, any copying permissions
229 230 231 232 233 234 235
following the copyright are updated as well.
If non-nil, INTERACTIVEP tells the function to behave as when it's called
interactively."
  (interactive "*P\nd")
  (when (or copyright-update interactivep)
    (let ((noquery (or (not copyright-query)
		       (and (eq copyright-query 'function) interactivep))))
236 237
      (save-excursion
	(save-restriction
Glenn Morris's avatar
Glenn Morris committed
238 239
	  ;; If names-regexp doesn't match, we should not mess with
	  ;; the years _or_ the GPL version.
Glenn Morris's avatar
Glenn Morris committed
240
	  ;; TODO there may be multiple copyrights we should update.
Glenn Morris's avatar
Glenn Morris committed
241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264
	  (when (copyright-find-copyright)
	    (copyright-update-year arg noquery)
	    (goto-char (copyright-start-point))
	    (and copyright-current-gpl-version
		 ;; Match the GPL version comment in .el files.
		 ;; This is sensitive to line-breaks. :(
		 (copyright-re-search
		  "the Free Software Foundation[,;\n].*either version \
\\([0-9]+\\)\\(?: of the License\\)?, or[ \n].*any later version"
		  (copyright-limit) t)
		 ;; Don't update if the file is already using a more recent
		 ;; version than the "current" one.
		 (< (string-to-number (match-string 1))
		    (string-to-number copyright-current-gpl-version))
		 (or noquery
		     (save-match-data
		       (goto-char (match-end 1))
		       (save-window-excursion
			 (switch-to-buffer (current-buffer))
			 (y-or-n-p
			  (format "Replace GPL version %s with version %s? "
				  (match-string-no-properties 1)
				  copyright-current-gpl-version)))))
		 (replace-match copyright-current-gpl-version t t nil 1))))
265
	(set (make-local-variable 'copyright-update) nil)))
266 267
    ;; If a write-file-hook returns non-nil, the file is presumed to be written.
    nil))
Roland McGrath's avatar
Roland McGrath committed
268

Eric S. Raymond's avatar
Eric S. Raymond committed
269

Glenn Morris's avatar
Glenn Morris committed
270
;; FIXME heuristic should be within 50 years of present (cf calendar).
271 272 273
;;;###autoload
(defun copyright-fix-years ()
  "Convert 2 digit years to 4 digit years.
Glenn Morris's avatar
Glenn Morris committed
274 275 276
Uses heuristic: year >= 50 means 19xx, < 50 means 20xx.
If `copyright-year-ranges' (which see) is non-nil, also
independently replaces consecutive years with a range."
277
  (interactive)
Glenn Morris's avatar
Glenn Morris committed
278
  ;; TODO there may be multiple copyrights we should fix.
279
  (if (copyright-find-copyright)
Glenn Morris's avatar
Glenn Morris committed
280
      (let ((s (match-beginning 3))
281
	    (p (make-marker))
Glenn Morris's avatar
Glenn Morris committed
282 283 284 285 286 287 288
	    ;; Not line-beg-pos, so we don't mess up leading whitespace.
	    (copystart (match-beginning 0))
	    e last sep year prev-year first-year range-start range-end)
	;; In case years are continued over multiple, commented lines.
	(goto-char (match-end 1))
	(copyright-find-end)
	(setq e (copy-marker (1+ (match-end 3))))
289
	(goto-char s)
290 291 292
	(while (re-search-forward "[0-9]+" e t)
	  (set-marker p (point))
	  (goto-char (match-beginning 0))
Glenn Morris's avatar
Glenn Morris committed
293 294 295 296 297 298 299 300
	  (setq year (string-to-number (match-string 0)))
	  (and (setq sep (char-before))
	       (/= (char-syntax sep) ?\s)
	       (/= sep ?-)
	       (insert " "))
	  (when (< year 100)
	    (insert (if (>= year 50) "19" "20"))
	    (setq year (+ year (if (>= year 50) 1900 2000))))
301
	  (goto-char p)
Glenn Morris's avatar
Glenn Morris committed
302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320
	  (when copyright-year-ranges
	    ;; If the previous thing was a range, don't try to tack more on.
	    ;; Ie not 2000-2005 -> 2000-2005-2007
	    ;; TODO should merge into existing range if possible.
	    (if (eq sep ?-)
		(setq prev-year nil
		      year nil)
	      (if (and prev-year (= year (1+ prev-year)))
		  (setq range-end (point))
		(when (and first-year prev-year
			   (> prev-year first-year))
		  (goto-char range-end)
		  (delete-region range-start range-end)
		  (insert (format "-%d" prev-year))
		  (goto-char p))
		(setq first-year year
		      range-start (point)))))
	  (setq prev-year year
		last p))
321
	(when last
Glenn Morris's avatar
Glenn Morris committed
322 323 324 325 326 327
	  (when (and copyright-year-ranges
		     first-year prev-year
		     (> prev-year first-year))
	    (goto-char range-end)
	    (delete-region range-start range-end)
	    (insert (format "-%d" prev-year)))
328
	  (goto-char last)
329 330
	  ;; Don't mess up whitespace after the years.
	  (skip-chars-backward " \t")
Glenn Morris's avatar
Glenn Morris committed
331 332 333 334 335 336
	   (save-restriction
	     (narrow-to-region copystart (point))
	     ;; This is clearly wrong, eg what about comment markers?
 ;;;	    (let ((fill-prefix "     "))
	     ;; TODO do not break copyright owner over lines.
	     (fill-region (point-min) (point-max))))
337
	(set-marker e nil)
Glenn Morris's avatar
Glenn Morris committed
338 339 340 341
	(set-marker p nil))
    ;; Simply reformatting the years is not copyrightable, so it does
    ;; not seem right to call this.  Also it messes with ranges.
;;;	(copyright-update nil t))
342
    (message "No copyright message")))
343

344 345 346 347 348
;;;###autoload
(define-skeleton copyright
  "Insert a copyright by $ORGANIZATION notice at cursor."
  "Company: "
  comment-start
Paul Eggert's avatar
Paul Eggert committed
349
  "Copyright (C) " `(format-time-string "%Y") " by "
350 351
  (or (getenv "ORGANIZATION")
      str)
352
  '(if (copyright-offset-too-large-p)
353
       (message "Copyright extends beyond `copyright-limit' and won't be updated automatically."))
Stefan Monnier's avatar
Stefan Monnier committed
354
  comment-end \n)
355

Glenn Morris's avatar
Glenn Morris committed
356
;; TODO: recurse, exclude COPYING etc.
357
;;;###autoload
Glenn Morris's avatar
Glenn Morris committed
358 359 360
(defun copyright-update-directory (directory match &optional fix)
  "Update copyright notice for all files in DIRECTORY matching MATCH.
If FIX is non-nil, run `copyright-fix-years' instead."
361
  (interactive "DDirectory: \nMFilenames matching (regexp): ")
362
  (dolist (file (directory-files directory t match nil))
Glenn Morris's avatar
Glenn Morris committed
363 364
    (unless (file-directory-p file)
      (message "Updating file `%s'" file)
365
      (find-file file)
Glenn Morris's avatar
Glenn Morris committed
366 367 368 369 370 371 372 373
      (let ((inhibit-read-only t)
	    (enable-local-variables :safe)
	    copyright-query)
	(if fix
	    (copyright-fix-years)
	  (copyright-update)))
      (save-buffer)
      (kill-buffer (current-buffer)))))
374

Richard M. Stallman's avatar
Richard M. Stallman committed
375 376
(provide 'copyright)

Dave Love's avatar
Dave Love committed
377 378
;; For the copyright sign:
;; Local Variables:
379
;; coding: utf-8
Dave Love's avatar
Dave Love committed
380 381
;; End:

382
;;; copyright.el ends here