#!/usr/bin/ruby #-- #------------------------------------------------------------------------------- #++ # #= Introduction # #== Name: checktype.rb # #== Author: Tony Smith # #== Description # # Example trigger to enforce a rule "files of suffix .x need # to be added as type Z". (For example, ".jpg" files must # always be "binary" and ".sh" files should always be "text".) # # This script is mostly a reimplentation of Jeff Bowles # and Wed Peters' Perl binary.pl using P4Ruby but it uses # a more generalised trigger execution framework to make # it easier to concentrate on the nuts and bolts of what # your trigger does. # #== Requires # Ruby # P4Ruby # P4Triggers module # #== Example 'triggers' section: # # Note: since this is applicable to only filenames with certain suffixes, # you might want to restrict the trigger to run when those files are # submitted. So, if this is looking at .cpp/.h/.txt/.html lines, you might # want to have it run only on those files.) # # Triggers: # exampleB //.../*.jpg "ruby whatever/checktype.rb %changelist%" # exampleB //.../*.bmp "ruby whatever/checktype.rb %changelist%" # exampleB //.../*.sh "ruby whatever/checktype.rb %changelist%" # #== Note # # For triggers I recommend you use a P4CONFIG file rather than hard coding # username/password in the script itself. This script assumes you've taken # that advice. # #-- #------------------------------------------------------------------------------- #++ $:.unshift( File.dirname( __FILE__ ) ) require "P4" require "P4Triggers" # # These are the file extensions and the Perforce types we want to # enforce. For each one we specify a regular expression for matching # the valid filetypes for the extension and a corresponding textual # description of the match (so we can tell the user what we're looking # for. # TYPEMAP = Hash.new TYPEMAP[ "bmp" ] = FileType.new( /.*binary/, "binary/ubinary" ) TYPEMAP[ "doc" ] = FileType.new( /.*binary/, "binary/ubinary" ) TYPEMAP[ "exe" ] = FileType.new( /.*binary/, "binary/ubinary" ) TYPEMAP[ "gif" ] = FileType.new( /.*binary/, "binary/ubinary" ) TYPEMAP[ "html"] = FileType.new( /ktext/, "ktext" ) TYPEMAP[ "jpg" ] = FileType.new( /.*binary/, "binary/ubinary" ) TYPEMAP[ "ppt" ] = FileType.new( /.*binary/, "binary/ubinary" ) TYPEMAP[ "sh" ] = FileType.new( /k?xtext/, "xtext/kxtext" ) # # The trigger class itself. The main method in here is validate() which # is invoked from the super-class' parse_change() method. # class TypeTrigger < P4Trigger def initialize( max_errors ) @max_errors = max_errors super() end # # The error message we give to the user # @@USER_MESSAGE = "\n\n" + "Your submission has been rejected because the following files\n" + "had incorrect types\n\n" # The printf() style format string used for each of the bad file # entries @@BADFILE_FORMAT= " %s: was %s, should be %s\n" # Method to return the expected filetype given a filename. Returns # nil if no specific filetype has been mandated. def filetype( depot_file ) ext = depot_file.sub( /.*\.(.*)/, '\1' ) ext = ext.downcase TYPEMAP[ ext ] end # Enforce type checking. Basic algorithm is that we iterate over # the typemap and match each extension against the depot file. If # they match, we check the type. def validate() # First gather the list of bad files badlist = Array.new change.each_file do |file| # Ignore files not open for add next unless ( file.revisions[ 0 ].action == "add" ) type = filetype( file.depot_file ) next unless type basetype = file.revisions[ 0 ].type.sub( /\+.*/, "" ) if ( ! type.match( basetype ) ) badlist.push( file ) end end # Now report any problems to the user report( badlist ) if ( ! badlist.empty? ) return badlist.empty? end # Method to report the error to the user. Just formats the error # message and sends it. We only report the first @max_errors # bad files. On a large changelist they'll be grateful for that. def report( badfiles ) errors = 0 msg = @@USER_MESSAGE badfiles.each do |file| type = filetype( file.depot_file ) msg += sprintf( @@BADFILE_FORMAT, file.depot_file, file.revisions[ 0 ].type, type.msg ) errors += 1 break if ( errors >= @max_errors ) end message( msg ) end end #-- #------------------------------------------------------------------------------- # Start of main script execution #------------------------------------------------------------------------------- #++ # By this stage it's pretty simple. Even argument validation is handled by # the p4Trigger class so we don't even need to check that we were passed # a changelist number. tt = TypeTrigger.new( 10 ) exit( tt.parse_change( ARGV.shift ) )