Home | History | Annotate | Line # | Download | only in extra
      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 &copy; 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. &copy;
     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|&copy;|)';
    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