1 1.1 christos #!/bin/sh 2 1.1 christos #! -*-perl-*- 3 1.1 christos 4 1.1 christos # Update an FSF copyright year list to include the current year. 5 1.1 christos 6 1.1.1.2 christos # Copyright (C) 2009-2022 Free Software Foundation, Inc. 7 1.1 christos # 8 1.1 christos # This program is free software: you can redistribute it and/or modify 9 1.1 christos # it under the terms of the GNU General Public License as published by 10 1.1.1.2 christos # the Free Software Foundation, either version 3, or (at your option) 11 1.1 christos # any later version. 12 1.1 christos # 13 1.1 christos # This program is distributed in the hope that it will be useful, 14 1.1 christos # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 1.1 christos # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 1.1 christos # GNU General Public License for more details. 17 1.1 christos # 18 1.1 christos # You should have received a copy of the GNU General Public License 19 1.1 christos # along with this program. If not, see <https://www.gnu.org/licenses/>. 20 1.1 christos # 21 1.1 christos # Written by Jim Meyering and Joel E. Denny 22 1.1 christos 23 1.1 christos # This script updates an FSF copyright year list to include the current year. 24 1.1 christos # Usage: update-copyright [FILE...] 25 1.1 christos # 26 1.1 christos # The arguments to this script should be names of files that contain 27 1.1 christos # copyright statements to be updated. The copyright holder's name 28 1.1 christos # defaults to "Free Software Foundation, Inc." but may be changed to 29 1.1 christos # any other name by using the "UPDATE_COPYRIGHT_HOLDER" environment 30 1.1 christos # variable. 31 1.1 christos # 32 1.1 christos # For example, you might wish to use the update-copyright target rule 33 1.1 christos # in maint.mk from gnulib's maintainer-makefile module. 34 1.1 christos # 35 1.1 christos # Iff a copyright statement is recognized in a file and the final 36 1.1 christos # year is not the current year, then the statement is updated for the 37 1.1 christos # new year and it is reformatted to: 38 1.1 christos # 39 1.1 christos # 1. Fit within 72 columns. 40 1.1 christos # 2. Convert 2-digit years to 4-digit years by prepending "19". 41 1.1 christos # 3. Expand copyright year intervals. (See "Environment variables" 42 1.1 christos # below.) 43 1.1 christos # 44 1.1 christos # A warning is printed for every file for which no copyright 45 1.1 christos # statement is recognized. 46 1.1 christos # 47 1.1 christos # Each file's copyright statement must be formatted correctly in 48 1.1 christos # order to be recognized. For example, each of these is fine: 49 1.1 christos # 50 1.1 christos # Copyright @copyright{} 1990-2005, 2007-2009 Free Software 51 1.1 christos # Foundation, Inc. 52 1.1 christos # 53 1.1 christos # # Copyright (C) 1990-2005, 2007-2009 Free Software 54 1.1 christos # # Foundation, Inc. 55 1.1 christos # 56 1.1 christos # /* 57 1.1 christos # * Copyright © 90,2005,2007-2009 58 1.1 christos # * Free Software Foundation, Inc. 59 1.1 christos # */ 60 1.1 christos # 61 1.1 christos # However, the following format is not recognized because the line 62 1.1 christos # prefix changes after the first line: 63 1.1 christos # 64 1.1 christos # ## Copyright (C) 1990-2005, 2007-2009 Free Software 65 1.1 christos # # Foundation, Inc. 66 1.1 christos # 67 1.1 christos # However, any correctly formatted copyright statement following 68 1.1 christos # a non-matching copyright statements would be recognized. 69 1.1 christos # 70 1.1 christos # The exact conditions that a file's copyright statement must meet 71 1.1 christos # to be recognized are: 72 1.1 christos # 73 1.1 christos # 1. It is the first copyright statement that meets all of the 74 1.1 christos # following conditions. Subsequent copyright statements are 75 1.1 christos # ignored. 76 1.1 christos # 2. Its format is "Copyright (C)", then a list of copyright years, 77 1.1 christos # and then the name of the copyright holder. 78 1.1 christos # 3. The "(C)" takes one of the following forms or is omitted 79 1.1 christos # entirely: 80 1.1 christos # 81 1.1 christos # A. (C) 82 1.1 christos # B. (c) 83 1.1 christos # C. @copyright{} 84 1.1 christos # D. © 85 1.1 christos # E. 86 1.1 christos # 87 1.1 christos # 4. The "Copyright" appears at the beginning of a line, except that it 88 1.1 christos # may be prefixed by any sequence (e.g., a comment) of no more than 89 1.1 christos # 5 characters -- including white space. 90 1.1 christos # 5. Iff such a prefix is present, the same prefix appears at the 91 1.1 christos # beginning of each remaining line within the FSF copyright 92 1.1 christos # statement. There is one exception in order to support C-style 93 1.1 christos # comments: if the first line's prefix contains nothing but 94 1.1 christos # whitespace surrounding a "/*", then the prefix for all subsequent 95 1.1 christos # lines is the same as the first line's prefix except with each of 96 1.1 christos # "/" and possibly "*" replaced by a " ". The replacement of "*" 97 1.1 christos # by " " is consistent throughout all subsequent lines. 98 1.1 christos # 6. Blank lines, even if preceded by the prefix, do not appear 99 1.1 christos # within the FSF copyright statement. 100 1.1 christos # 7. Each copyright year is 2 or 4 digits, and years are separated by 101 1.1 christos # commas, "-", or "--". Whitespace may appear after commas. 102 1.1 christos # 103 1.1 christos # Environment variables: 104 1.1 christos # 105 1.1 christos # 1. If UPDATE_COPYRIGHT_FORCE=1, a recognized FSF copyright statement 106 1.1 christos # is reformatted even if it does not need updating for the new 107 1.1 christos # year. If unset or set to 0, only updated FSF copyright 108 1.1 christos # statements are reformatted. 109 1.1 christos # 2. If UPDATE_COPYRIGHT_USE_INTERVALS=1, every series of consecutive 110 1.1 christos # copyright years (such as 90, 1991, 1992-2007, 2008) in a 111 1.1 christos # reformatted FSF copyright statement is collapsed to a single 112 1.1 christos # interval (such as 1990-2008). If unset or set to 0, all existing 113 1.1 christos # copyright year intervals in a reformatted FSF copyright statement 114 1.1 christos # are expanded instead. 115 1.1 christos # If UPDATE_COPYRIGHT_USE_INTERVALS=2, convert a sequence with gaps 116 1.1 christos # to the minimal containing range. For example, convert 117 1.1 christos # 2000, 2004-2007, 2009 to 2000-2009. 118 1.1 christos # 3. For testing purposes, you can set the assumed current year in 119 1.1 christos # UPDATE_COPYRIGHT_YEAR. 120 1.1 christos # 4. The default maximum line length for a copyright line is 72. 121 1.1 christos # Set UPDATE_COPYRIGHT_MAX_LINE_LENGTH to use a different length. 122 1.1 christos # 5. Set UPDATE_COPYRIGHT_HOLDER if the copyright holder is other 123 1.1 christos # than "Free Software Foundation, Inc.". 124 1.1 christos 125 1.1 christos # This is a prologue that allows to run a perl script as an executable 126 1.1 christos # on systems that are compliant to a POSIX version before POSIX:2017. 127 1.1 christos # On such systems, the usual invocation of an executable through execlp() 128 1.1 christos # or execvp() fails with ENOEXEC if it is a script that does not start 129 1.1 christos # with a #! line. The script interpreter mentioned in the #! line has 130 1.1 christos # to be /bin/sh, because on GuixSD systems that is the only program that 131 1.1 christos # has a fixed file name. The second line is essential for perl and is 132 1.1 christos # also useful for editing this file in Emacs. The next two lines below 133 1.1 christos # are valid code in both sh and perl. When executed by sh, they re-execute 134 1.1 christos # the script through the perl program found in $PATH. The '-x' option 135 1.1 christos # is essential as well; without it, perl would re-execute the script 136 1.1 christos # through /bin/sh. When executed by perl, the next two lines are a no-op. 137 1.1 christos eval 'exec perl -wSx -0777 -pi "$0" "$@"' 138 1.1 christos if 0; 139 1.1 christos 140 1.1 christos my $VERSION = '2020-04-04.15:07'; # UTC 141 1.1 christos # The definition above must lie within the first 8 lines in order 142 1.1 christos # for the Emacs time-stamp write hook (at end) to update it. 143 1.1 christos # If you change this file with Emacs, please let the write hook 144 1.1 christos # do its job. Otherwise, update this string manually. 145 1.1 christos 146 1.1 christos use strict; 147 1.1 christos use warnings; 148 1.1 christos 149 1.1 christos my $copyright_re = 'Copyright'; 150 1.1 christos my $circle_c_re = '(?:\([cC]\)|@copyright\{}|\\\\\(co|©|)'; 151 1.1 christos my $holder = $ENV{UPDATE_COPYRIGHT_HOLDER}; 152 1.1 christos $holder ||= 'Free Software Foundation, Inc.'; 153 1.1 christos my $prefix_max = 5; 154 1.1 christos my $margin = $ENV{UPDATE_COPYRIGHT_MAX_LINE_LENGTH}; 155 1.1 christos !$margin || $margin !~ m/^\d+$/ 156 1.1 christos and $margin = 72; 157 1.1 christos 158 1.1 christos my $tab_width = 8; 159 1.1 christos 160 1.1 christos my $this_year = $ENV{UPDATE_COPYRIGHT_YEAR}; 161 1.1 christos if (!$this_year || $this_year !~ m/^\d{4}$/) 162 1.1 christos { 163 1.1 christos my ($sec, $min, $hour, $mday, $month, $year) = localtime (time ()); 164 1.1 christos $this_year = $year + 1900; 165 1.1 christos } 166 1.1 christos 167 1.1 christos # Unless the file consistently uses "\r\n" as the EOL, use "\n" instead. 168 1.1 christos my $eol = /(?:^|[^\r])\n/ ? "\n" : "\r\n"; 169 1.1 christos 170 1.1 christos my $leading; 171 1.1 christos my $prefix; 172 1.1 christos my $ws_re; 173 1.1 christos my $stmt_re; 174 1.1 christos while (/(^|\n)(.{0,$prefix_max})$copyright_re/g) 175 1.1 christos { 176 1.1 christos $leading = "$1$2"; 177 1.1 christos $prefix = $2; 178 1.1 christos if ($prefix =~ /^(\s*\/)\*(\s*)$/) 179 1.1 christos { 180 1.1 christos $prefix =~ s,/, ,; 181 1.1 christos my $prefix_ws = $prefix; 182 1.1 christos $prefix_ws =~ s/\*/ /; # Only whitespace. 183 1.1 christos if (/\G(?:[^*\n]|\*[^\/\n])*\*?\n$prefix_ws/) 184 1.1 christos { 185 1.1 christos $prefix = $prefix_ws; 186 1.1 christos } 187 1.1 christos } 188 1.1 christos $ws_re = '[ \t\r\f]'; # \s without \n 189 1.1 christos $ws_re = 190 1.1 christos "(?:$ws_re*(?:$ws_re|\\n" . quotemeta($prefix) . ")$ws_re*)"; 191 1.1 christos my $holder_re = $holder; 192 1.1 christos $holder_re =~ s/\s/$ws_re/g; 193 1.1 christos my $stmt_remainder_re = 194 1.1 christos "(?:$ws_re$circle_c_re)?" 195 1.1 christos . "$ws_re(?:(?:\\d\\d)?\\d\\d(?:,$ws_re?|--?))*" 196 1.1 christos . "((?:\\d\\d)?\\d\\d)$ws_re$holder_re"; 197 1.1 christos if (/\G$stmt_remainder_re/) 198 1.1 christos { 199 1.1 christos $stmt_re = 200 1.1 christos quotemeta($leading) . "($copyright_re$stmt_remainder_re)"; 201 1.1 christos last; 202 1.1 christos } 203 1.1 christos } 204 1.1 christos if (defined $stmt_re) 205 1.1 christos { 206 1.1 christos /$stmt_re/ or die; # Should never die. 207 1.1 christos my $stmt = $1; 208 1.1 christos my $final_year_orig = $2; 209 1.1 christos 210 1.1 christos # Handle two-digit year numbers like "98" and "99". 211 1.1 christos my $final_year = $final_year_orig; 212 1.1 christos $final_year <= 99 213 1.1 christos and $final_year += 1900; 214 1.1 christos 215 1.1 christos if ($final_year != $this_year) 216 1.1 christos { 217 1.1 christos # Update the year. 218 1.1 christos $stmt =~ s/\b$final_year_orig\b/$final_year, $this_year/; 219 1.1 christos } 220 1.1 christos if ($final_year != $this_year || $ENV{'UPDATE_COPYRIGHT_FORCE'}) 221 1.1 christos { 222 1.1 christos # Normalize all whitespace including newline-prefix sequences. 223 1.1 christos $stmt =~ s/$ws_re/ /g; 224 1.1 christos 225 1.1 christos # Put spaces after commas. 226 1.1 christos $stmt =~ s/, ?/, /g; 227 1.1 christos 228 1.1 christos # Convert 2-digit to 4-digit years. 229 1.1 christos $stmt =~ s/(\b\d\d\b)/19$1/g; 230 1.1 christos 231 1.1 christos # Make the use of intervals consistent. 232 1.1 christos if (!$ENV{UPDATE_COPYRIGHT_USE_INTERVALS}) 233 1.1 christos { 234 1.1 christos $stmt =~ s/(\d{4})--?(\d{4})/join(', ', $1..$2)/eg; 235 1.1 christos } 236 1.1 christos else 237 1.1 christos { 238 1.1 christos my $ndash = $ARGV =~ /\.tex(i(nfo)?)?$/ ? "--" : "-"; 239 1.1 christos 240 1.1 christos $stmt =~ 241 1.1 christos s/ 242 1.1 christos (\d{4}) 243 1.1 christos (?: 244 1.1 christos (,\ |--?) 245 1.1 christos ((??{ 246 1.1 christos if ($2 ne ', ') { '\d{4}'; } 247 1.1 christos elsif (!$3) { $1 + 1; } 248 1.1 christos else { $3 + 1; } 249 1.1 christos })) 250 1.1 christos )+ 251 1.1 christos /$1$ndash$3/gx; 252 1.1 christos 253 1.1 christos # When it's 2, emit a single range encompassing all year numbers. 254 1.1 christos $ENV{UPDATE_COPYRIGHT_USE_INTERVALS} == 2 255 1.1 christos and $stmt =~ s/\b(\d{4})\b.*\b(\d{4})\b/$1$ndash$2/; 256 1.1 christos } 257 1.1 christos 258 1.1 christos # Format within margin. 259 1.1 christos my $stmt_wrapped; 260 1.1 christos my $text_margin = $margin - length($prefix); 261 1.1 christos if ($prefix =~ /^(\t+)/) 262 1.1 christos { 263 1.1 christos $text_margin -= length($1) * ($tab_width - 1); 264 1.1 christos } 265 1.1 christos while (length $stmt) 266 1.1 christos { 267 1.1 christos if (($stmt =~ s/^(.{1,$text_margin})(?: |$)//) 268 1.1 christos || ($stmt =~ s/^([\S]+)(?: |$)//)) 269 1.1 christos { 270 1.1 christos my $line = $1; 271 1.1 christos $stmt_wrapped .= $stmt_wrapped ? "$eol$prefix" : $leading; 272 1.1 christos $stmt_wrapped .= $line; 273 1.1 christos } 274 1.1 christos else 275 1.1 christos { 276 1.1 christos # Should be unreachable, but we don't want an infinite 277 1.1 christos # loop if it can be reached. 278 1.1 christos die; 279 1.1 christos } 280 1.1 christos } 281 1.1 christos 282 1.1 christos # Replace the old copyright statement. 283 1.1 christos s/$stmt_re/$stmt_wrapped/; 284 1.1 christos } 285 1.1 christos } 286 1.1 christos else 287 1.1 christos { 288 1.1 christos print STDERR "$ARGV: warning: copyright statement not found\n"; 289 1.1 christos } 290 1.1 christos 291 1.1 christos # Hey Emacs! 292 1.1 christos # Local variables: 293 1.1 christos # coding: utf-8 294 1.1 christos # mode: perl 295 1.1 christos # indent-tabs-mode: nil 296 1.1 christos # eval: (add-hook 'before-save-hook 'time-stamp) 297 1.1 christos # time-stamp-line-limit: 200 298 1.1 christos # time-stamp-start: "my $VERSION = '" 299 1.1 christos # time-stamp-format: "%:y-%02m-%02d.%02H:%02M" 300 1.1 christos # time-stamp-time-zone: "UTC0" 301 1.1 christos # time-stamp-end: "'; # UTC" 302 1.1 christos # End: 303