#!/usr/bin/perl
#
# PERFORCE BUNDLE WRAPPER v0.2
# Rachel 'Sparks' Blackman <sparks@noderunner.net>
#
# NO GUARANTEES OF OPERATION WHATSOEVER ARE MADE.  You're welcome to use
# this file however you want (though I'd love credit to be left in it),
# but I can't be held responsible for the operation of it.  It works on
# my machines, I can't promise more.  I'd welcome changes being submitted
# back, this is an ugly hack and I'm sure there are better ways to do it. :)
#
# This wrapper will watch for Perforce operations performed on 
# files matching certain rulesets, and execute those rules before
# actually passing the operations on to the real p4 client.
#
# This is intended to allow MacOS X .nib files and other 'bundles'
# to be tarballed on p4 operations so they can be treated as a single
# file, for purposes of making Xcode work with Perforce.
# 
# It could, however, be presumably used for other files easily enough,
# as it should work under any UNIX p4 client situation as well.  Right
# now, however, it's just written to do .nib files.
#
# THE ONE SIGNIFICANT GOTCHA IS THAT YOU MUST MANUALLY CHECK OUT NIB FILES.
# Interface Builder will whine if you open a nib file which is not 
# opened for edit (I managed that much), but Interface Builder does not
# know to run an SCM edit command.  However, you can use the SCM 'Edit'
# command from the project window, and it will properly check out the
# NIB file and set it up for edit.  Yay!
#
# Occasionally, Xcode seems to get brain-damaged about the 'add' command
# and lose track of what it's trying to do with .nib files, and lose the
# 'SCM Edit' option in the UI. You can bring it back in sync from the 
# command-line, but if anyone knows a good way to fix this, let me know.
# 
# Also, at present, any p4 command which requires stdin to the user doesn't
# work with this wrapper, i.e., 'p4 submit' doesn't work if you don't
# provide the changelist description on the command line.  If someone can
# think of a good way around that, I'd love to hear it. :)
#
# To install, just rename your existing p4 to p4.orig, and copy this
# script (set executable) into the directory as p4.
#
# If you are modifying this wrapper, you have my deepest sympathies. ;)
#
####

use Getopt::Std;

my $p4 = "";

# First we find the actual local Perforce executable
# choosing from a list of possibilities.  We prefer '.orig'
# because most likely /we/ are installed as 'p4', after all!
#
for $p4chk ("/usr/local/bin/p4.orig","/usr/bin/p4.orig", 
            "/usr/local/bin/p4","/usr/bin/p4") {

   if (!$p4 && -e $p4chk) {
      $p4 = $p4chk;
   }
}

# A little bit of sanity checking, just in case we didn't find
# what we wanted..
#
if (!$p4) {
   print (STDERR "Perforce wrapper unable to find Perforce executable.\n");
}

# Parse all of the command line parameters
getopts('GVsc:C:d:H:L:p:P:u:x:', \%opts);

# Get our command...
#
$command = $ARGV[0];

# And all the parameters to it.  There's probably a prettier way to
# do this, but let's see you write one at 3am! ;)
#
@params = @ARGV;
@params = reverse(@params);
pop(@params);
@params = reverse(@params);

# Okay.  We now have all our data, so we can check for specific types
# of things.  Really, what we most care about are 'add', 'submit', 
# 'fstat' and 'sync' calls.
# 
# All of these require changes to the call we make to the p4 client
# command, but sync will also require some post-processing when
# the command is done.  Ugh!
#
if (($command eq 'add') || ($command eq 'submit')) {
   # Add and submit are similar because both will need to actually
   # tarball any files matching our patterns.
   #
   for ($count = 0; $count < @params; $count++) {
      $basefile = $params[$count];
      $revision = "";
      $file = $basefile;

      if ($basefile =~ /(.*)#([0123456789]+)/) {
          $file = $1;
          $revision = $2;
      }

      if (($file =~ /(.*)\.nib/i) || ($file =~ /(.*)\.framework/i)) {
         # We have a bundle.  Let's build a tarball.
         #
         $dir = $opts{'d'};
         if ($dir eq "") {
            $dir = '.';
         }
         $tarball = "${file}.tar";
         $cmd = "cd $dir ; tar -cf $tarball $file";

         system("$cmd");

         if ($revision ne "") {
             $tarball .= "#${revision}";
         }

         $params[$count] = $tarball;
      }
   }
}

if (($command eq 'fstat') || ($command eq 'sync') || ($command eq 'filelog')
    || ($command eq 'revert') || ($command eq 'print') || ($command eq 'edit')) {
   # These are all similar because we need to modify 
   # the filename parameters
   #
   for ($count = 0; $count < @params; $count++) {
      $basefile = $params[$count];
      $revision = '';
      $file = $basefile;

      if ($basefile =~ /(.*)#([0123456789]+)/) {
          $file = $1;
          $revision = $2;
      }

      if (($file =~ /(.*)\.nib/i) || ($file =~ /(.*).framework/i)) {
         $tarball = "${file}.tar";

         if ($revision ne '') {
             $tarball .= "#${revision}";
         }
         $params[$count] = $tarball;
      }
   }
}

# Assemble the command again with all of our options,
# and any modifications we've made to the parameters.
# This command will then be passed on to the real p4 command,
# transparently.  By this point, we have all our tarball conversions
# which we need going INTO p4.
# 
$p4cmd = $p4;

foreach $opt (sort(keys %opts)) {
   $p4cmd .= " -$opt";
   if ($opts{$opt} != 1) {
      $p4cmd .= " $opts{$opt}";
   }
}

$p4cmd .= " $command";
$p4cmd .= " @params";

# FOR DEBUG PURPOSES FOR NOW:
# open (P4LOG, ">> /tmp/p4wrap.log");
# print (P4LOG "$p4cmd\n");
# close (P4LOG);

# Now comes the fun part; we have to run the p4 command, and
# /if/ it's a sync, we have to watch for our .nib.tar files,
# in order to unpack them /and/ toss the proper .nib filename
# out for Xcode to parse.  Fun stuff.
#
open (P4PIPE, "$p4cmd |") || die ("Couldn't execute Perforce command!");
while ($line = <P4PIPE>) {

   chomp ($line);  

   if ($command eq "sync") {
      # Watch for .nib.tar files in the sync, untar, and output 
      # the line with just a .nib instead.
      #
      if ($line =~ /(.*)#([0123456789]+) - ([^ ]*)(.*)/) {
         $depotfile = $1;
         $revision = $2;
         $action = $3;
         $clientfile = $4;

         if ($clientfile =~ /(.*)\.nib\.tar/) {
            # It's a nib.  We'll map the filename back, and we 
            # want to untar it as well.
            #
            $dir = $opts{'d'};
            if ($dir eq '') {
                $dir = '.';
            }

            $tarcmd = "cd $dir ; tar xf $clientfile";
            system($tarcmd);
            $clientfile = "${1}.nib";
            $modcmd = "cd $dir ; chmod -R a-w $clientfile";
            system($modcmd);
         }
         elsif ($clientfile =~ /(.*)\.framework\.tar/) {
            # It's a framework.  We'll map the filename back, and we 
            # want to untar it as well.
            #
            $dir = $opts{'d'};
            if ($dir eq '') {
                $dir = '.';
            }

            $tarcmd = "cd $dir ; tar xf $clientfile";
            system($tarcmd);
            $clientfile = "${1}.framework";
            $modcmd = "cd $dir ; chmod -R a-w $clientfile";
            system($modcmd);
         }

         $line = "${depotfile}#${revision} - ${action} ${clientfile}";
      }
   }
   elsif ($command eq "edit") {
      # Watch for .nib.tar files in the edit, untar, and output 
      # the line with just a .nib instead.
      #
      if ($line =~ /(.*)#([0123456789]+) - (.*)/) {
         $depotfile = $1;
         $revision = $2;
         $action = $3;

         if ($depotfile =~ /(.*)\.nib\.tar/) {
            # It's a nib.  We'll map the filename back, and we 
            # want to untar it as well.
            #
            $dir = $opts{'d'};
            if ($dir eq '') {
                $dir = '.';
            }

            $depotfile = "${1}.nib";

            $clientfile = "";
	    if ($depotfile =~ /\/\/(.*)\/([^\/]*)/) {
                $clientfile = $2;
            }

            $tarcmd = "cd $dir ; chmod -R u+w $clientfile ; tar xf ${clientfile}.tar";

            system($tarcmd);
         }
         elsif ($depotfile =~ /(.*)\.framework\.tar/) {
            # It's a nib.  We'll map the filename back, and we 
            # want to untar it as well.
            #
            $dir = $opts{'d'};
            if ($dir eq '') {
                $dir = '.';
            }

            $depotfile = "${1}.framework";

            $clientfile = "";
            if ($depotfile =~ /\/\/(.*)\/([^\/]+)/) {	
                $clientfile = $2;
            }

            $tarcmd = "cd $dir ; chmod -R u+w $clientfile ; tar xf ${clientfile}.tar";

            system($tarcmd);
         }

         $line = "${depotfile}#${revision} - ${action}";
      }
   }
   elsif ($command eq "fstat") {
      # Watch for .nib.tar files in the fstat output, and change
      # the line to just have a .nib file for the name.
      # 
      if ($line =~ /^\.\.\. clientFile (.*)/) {
         $file = $1;

         # It's a nib.  We map the filename back to what Xcode expects
         #
         if ($file =~ /(.*)\.nib\.tar/) {
             $file = "${1}.nib";
         }
         elsif ($file =~ /(.*)\.framework\.tar/) {
             $file = "${1}.framework";
         }

         $line = "... clientFile $file";
      } 
      elsif ($line =~ /^\.\.\. depotFile (.*)/) {
         $file = $1;

         # It's a nib.  We map the filename back to what Xcode expects
         #
         if ($file =~ /(.*)\.nib\.tar/) {
             $file = "${1}.nib";
         }
         elsif ($file =~ /(.*)\.framework\.tar/) {
             $file = "${1}.framework";
         }

         $line = "... depotFile $file";
      } 
   }
   elsif (($command eq "filelog") || ($command eq "print")) {
      if ($line =~ /^\/\/(.*)/) {
         $file = $1;
         if ($file =~ /(.*).nib.tar/) {
             $file = "${1}.nib";
         }
         elsif ($file =~ /(.*).framework.tar/) {
             $file = "${1}.framework";
         }

         $line = "//$file";
      }
   }
   elsif ($command eq "revert") {
      if ($line =~ /^\/\/(.*)#([0123456789]+) - (.*)/) {
         $file = $1;
         $revision = $2;
         $action = $3;

         if ($file =~ /(.*).nib.tar/) {
             $file = "${1}.nib";

             $clientfile = "";
             if ($file =~ /(.*)\/([^\/]*)/) {	
                 $clientfile = $2;
             }

             $dir = $opts{'d'};
             if ($dir eq '') {
                 $dir = '.';
             }

             # Make our .nib write-only so that Xcode will properly
             # attempt to check it out... we hope?

             $modcmd = "cd $dir ; chmod -R a-w $clientfile ; chmod u+w ${clientfile}.tar";
             system($modcmd);
         }
         elsif ($file =~ /(.*).framework.tar/) {
             $file = "${1}.framework";

             $clientfile = "";
             if ($file =~ /(.*)\/([^\/]*)/) {	
                 $clientfile = $2;
             }

             $dir = $opts{'d'};
             if ($dir eq '') {
                 $dir = '.';
             }

             # Make our .nib write-only so that Xcode will properly
             # attempt to check it out... we hope?

             $modcmd = "cd $dir ; chmod -R a-w $clientfile ; chmod u+w ${clientfile}.tar";
             system($modcmd);
         }

         $line = "//${file}#${revision} - ${action}";
      }
   }
   elsif ($command eq "add") {
      if ($line =~ /^\/\/(.*)#([0123456789]+) - (.*)/) {
         $file = $1;
         $revision = $2;
         $action = $3;

         if ($file =~ /(.*).nib.tar/) {
             $file = "${1}.nib";
         }
         elsif ($file =~ /(.*).framework.tar/) {
             $file = "${1}.framework";
         }

         $line = "//${file}#${revision} - ${action}";
      }
   }


   # Print our finalized line.
   #
   print "$line\n";

}

# ...all done!  Xcode should be happy.