#/* # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information # regarding copyright ownership. The ASF licenses this file # to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. #*/ # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # # Insert or update (iou) the license in a given file. The license is # assumed to be located in a comment block that contains a particular # license string ($licenseString) somewhere inside it, and that the # comment contains nothing but that license. The entire comment including # that line will be removed, and a new comment consisting solely of the # new license will be inserted. # # The license is read from "iou_license.txt", either in the local # directory where this script is invoked or from the dirctory where the # script itself lives (should be an option someday). This is the text # that will go *inside* the comment. It does not contain any # comment-related characters, so that the comment could be added to # files with different comment styles. # # The license in "iou_license.txt" can contain an optional license # version line that assigns a version string to that specific license # content (eg, LicenseVersion v1.0 ). When the text changes, this version # string should change. If the script encounters a file whose # license has the same version (if versioning was used) as the current # one, the file will remain unchanged. # # Comments are understood on a per-file-type basis. # # Without arguments, this script will process all files within the # current directory and its sub-directories. Alternatively, one or # more files and/or directories can be specified as arguments. # # Usage: iou_license.pl [item ...] # # item: One or more files and/or directories to be processed # # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # # The following lines are designed to run perl on this script, no # matter which shell the script is accidentally run under. The point # here is this: The normal way to invoke perl on a script is to put # a line starting with #! at the begging of the script that invokes # perl, as in "#!/usr/bin/perl". The problem with this is that the # script then hard codes the location of perl into itself, and when # that changes the script breaks (and rather mysteriously at that). # So this script purposefully specifies *no* interpreter at the top, # and then adapts to whatever shell is running to get to perl. (I # tried putting #!/bin/sh at the top to guarantee the shell, but perl # is too clever by half -- when the shell script executes perl, perl # looks at the #! line and says "this is a shell script" and invokes # the shell.) # # Yes this is ugly, but it is an Officially Certified Hack(TM), so # no purist, anti-hacker, ivory tower kvetching. # eval '(exit $?0)' && eval 'exec perl -S $0 ${1+"$@"}' & eval 'exec perl -S $0 $argv:q' if 0; $scriptDir = $0; $scriptDir =~ s,/[^/]+$,,; push( @INC, $scriptDir ); $Verbose = 1; # someday make this turn-off-able $LicenseTextFileName = "iou_license.txt"; $LicenseTextFile = "$scriptDir/$LicenseTextFileName"; # make this an option someday @args = ( $#ARGV < 0 ? '.' : @ARGV ); # by default, use all source # # Set licenseString to a unique identifying string within the license text. # This string will be used to determine whether we've found the license comment. # It would be more desirable and less maintenance if this string were changed # to a unique tag that could remain constant (eg, license_comment_start). Till # then, however, we'll have to live with what we have. # # It may also be useful to allow this string to be set on the command-line # since source files can contain a wide variety of licenses. For example, # let's say we're importing a set of sources from The Foobar Software Depot, # and those sources contain the "Common Foobar License" (CFL), we could use the # a command line option to replace the CFL by executing a command like the # following to override the default licenseString definition: # # iou_license.pl -s "Common Foobar License" # $licenseString = "Licensed to the Apache Software Foundation"; # The ':'-separated pre, mid, and end comment strings for each file type # Optional fourth and fifth elements are regular expressions for pre and end # (otherwise a simple substring match is done). If the fourth and/or fifth # elements exist, they will override the simple pre and end elements. $java = '/*: *: */'; $shell = '#/*:#:#*/'; $bat = '@rem /*:@rem:@rem */'; $html = ':\<\!(\n|\s|\-\-):.*(\-\-\s*)?\>'; $mapping{'.bat'} = $bat; $mapping{'.c'} = $java; $mapping{'.h'} = $java; $mapping{'.java'} = $java; $mapping{'.policy'} = $java; $mapping{'.html'} = $html; $mapping{'.jsp'} = $html; $mapping{'.jspfragment'} = $html; $mapping{'.xml'} = $html; $mapping{'.nmk'} = $shell; $mapping{'.jmk'} = $shell; $mapping{'.sh'} = $shell; $mapping{'.csh'} = $shell; $mapping{'.ksh'} = $shell; $mapping{'.pl'} = $shell; $mapping{'.properties'} = $shell; $mapping{'makefile'} = $shell; $mapping{'Makefile'} = $shell; $mapping{'GNUmakefile'} = $shell; # technically all you need is "" but we use the !s to draw # a cutesy box. &createGetPats(); # # Process the arguments. If the arg is a directory, recurse looking for # known file types # foreach $arg ( @args ) { if ( -T $arg ) # text file { push( @files, $arg ); } elsif ( -d $arg ) # directory -- run find { push( @files, split(/\s+/, $files = `find $arg \\( $names \\) -print`) ); } else { print STDERR "unknown file type for $arg: ignoring\n"; } } &getCurrentLicense(); # read the current license $error = 0; # process each file foreach $file ( @files ) { $error |= &process_file( $file ); } exit( $error ); # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # process_file: # Process a single file. Figure out from its suffix what kind of # comment to use (currently only .java, .c, and .h are understood). # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ sub process_file { local ( $file ) = @_; if ( -l $file ) { print "link: $file: skipping\n" if ( $Verbose ); return; } local ( $pre, $mid, $end, $prePat, $endPat ) = &getPats( $file ); if ( $pre eq 'UNDEF' ) { print "unknown file type: $file: skipping\n" if ( $Verbose ); return; } local ( $found_license ); # did we find the license comment? local ( $comment_start ); # current comment starting position local ( $in_comment ); # boolean stating whether we're inside a comment block local ( $license_start, $license_end ); # license comment start/end positions local ( $license_version ); # license version in file open( FILE, $file ) || die( "$file: $!" ); $prePat = &pat( $pre ) unless ( defined($prePat) && $prePat ne '' ); $endPat = &pat( $end ) unless ( defined($endPat) && $endPat ne '' ); while ( ) # search for existing license { if ( $. == 1 && /^[#%]!/ ) { # skip over initial #! or %! $license_start = tell( FILE ); # overwritten if comment is... $license_end = tell( FILE ); # overwritten if comment is... next; # ...present later in file } ## ## If the prePat is closed by the endPat on the same line, a license ## is probably not there: ## if ( /$prePat.*$endPat/ ) { next; } elsif ( /$prePat/ ) { $in_comment = 1; $comment_start = tell( FILE ) - length; } elsif ( /.*$licenseString/ && $in_comment ) { $found_license++; $license_start = $comment_start; } elsif ( /LicenseVersion ([^\s]+)/ ) { $license_version = $1; } elsif ( /$endPat/ ) { $in_comment = 0; ## ## When the file is a script with a special first line (eg, #!/bin/sh), license_end will ## be set to some number of characters greater than zero (ie, the length of that first line) ## and license_start will be equal to license_end. Otherwise, license_end will have ## been set to zero, so we need to account for this special case: ## if ( $found_license && (!$license_end || ($license_start==$license_end)) ) { $license_end = tell( FILE ); } } } $found_license = 0 if ( $found_license && (!$license_end || ($license_start==$license_end)) ); if ( $Verbose ) { print "$file: "; if ( !$found_license ) { print "adding"; } else { print "replacing license "; print ( $license_version ? $license_version: "(unversioned)" ); } print "\n"; } local ( $tmp ) = "$file.cr"; # create tmp file with new stuff open( NEW, ">$tmp" ) || die( "$tmp: $!" ); seek( FILE, 0, 0 ); read( FILE, $buf, $license_start ); # copy pre-comment stuff print NEW $buf; print NEW "$pre\n"; foreach $l ( @License ) # put in license { print NEW $mid, ' ', $l; } print NEW "$end\n"; seek( FILE, $license_end, 0 ); # copy post-comment stuff while ( read(FILE, $buf,4096) ) { print NEW $buf; } close( NEW ); # rename tmp to cur close( FILE ); if ( !rename($tmp, $file) ) { print "couldn't rename files for $file: $!"; return 1; } return 0; } # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # getCurrentLicense # Read current license and optional version. # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ sub getCurrentLicense { open( LICENSE, "$LicenseTextFileName" ) || open( LICENSE, $LicenseTextFile ) || die( "$LicenseTextFile: $!" ); @License = ; close( LICENSE ); foreach $_ ( @License ) { if ( /LicenseVersion ([^\s]+)/ ) { $CurrentLicenseVersion = $1; last; } } print "License version $CurrentLicenseVersion\n" if ( $Verbose && $CurrentLicenseVersion ); } # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # createGetPats # Build up the maps from file name to pre, mid, and end comment # strings by creating the getPats method. Also define $name # for the find comment to find such things (if necessary). # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ sub createGetPats { push( @matchList, 'sub getPats {' ); push( @matchList, ' local ($_) = @_;' ); foreach $type ( keys(%mapping) ) { $map = $mapping{$type}; ( $pre, $mid, $end ) = ( $map =~ /^([^:]*)\:([^:]*)\:(.*)$/ ); # Optional args $prePat = $endPat = undef; if ( $end =~ /^([^:]*)\:([^:]*)\:(.*)$/ ) { $prePat = $2; $endPat = $3; $end = $1; } if ( $type =~ /^\./ ) { push( @findArgs, "-name '*$type'" ); $pat = "m,\\$type\$,"; } else { push( @findArgs, "-name '$type*'" ); $pat = "(m,^$type, || m,/$type,)"; } push( @matchList, " $pat && return ( '$pre', '$mid', '$end', " . (defined($prePat) ? "'$prePat', " : "undef, ") . (defined($endPat) ? "'$endPat');" : "undef);") ); } $names = join( ' -o ', @findArgs ); push( @matchList, " return ('UNDEF', 'UNDEF', 'UNDEF', undef, undef);" ); push( @matchList, "}" ); $getPats = join( "\n", @matchList, '' ); eval $getPats; } # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # pat # Turn a simple string into something usable in a pattern by # escaping the special characters # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ sub pat { local ( $_ ) = @_; s/(\W)/\\$1/g; return $_; }