#!/usr/bin/python2.4
#
# Copyright (c) 2007, Google, Inc.  All rights reserved.
# 
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
# 
# - Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 
# - Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# 
# - Neither the name of the Google, Inc nor the names of their
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
# 
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
# DAMAGE.

# This utility script takes a patch intended for DSpace <=1.4.x, and updates
# it so it can be applied to DSpace post-code reorganisation.
#
# If --multi is specified, the input patch is rewritten as several individual
# patches that can be applied to individual add-ons from within Eclipse.
#
# Otherwise, the updated patch file should be applied to the parent of the
# add-ons (i.e. the directory containing dspace, dspace-api, dspace-jspui, etc.)


__author__ = 'Robert Tansley (roberttansley@google.com)'
__version__ = '0.1 (2007-07-09)'

import re

from optparse import OptionParser

# First is location of file within pre-1.5 dspace/
# Second is root of add-on in new structure
# Third is location within new add-on

MAP = [
  ('etc/oai-web.xml', 'dspace-oai', 'src/webapp/WEB-INF/web.xml'),
  ('etc/dspace-web.xml', 'dspace-jspui', 'src/webapp/WEB-INF/web.xml'),
  ('build.xml', 'dspace', 'src/main/config/build.xml'),
  ('config/', 'dspace', 'config/'),
  ('bin/', 'dspace', 'bin/'),
  ('docs/', 'dspace', 'docs/'),
  ('etc/', 'dspace', 'etc/'),
  ('jsp/', 'dspace-jspui', 'src/main/webapp/'),
  ('src/org/dspace/app/oai/', 'dspace-oai', 'src/main/java/org/dspace/app/oai/'),
  ('src/org/dspace/app/webui/', 'dspace-jspui', 'src/main/java/org/dspace/app/webui/'),
  ('src/', 'dspace-api', 'src/main/java/')
]

INDEXLINE_RE = re.compile('Index: (.+)\n')
PLUSLINE_RE = re.compile('\+\+\+\s(\S+)')


def GetNewLocation(old_location, trim):
  """Return where file in old patch should be in new structure.
  
  'trim' path components are trimmed from the start of the old location.
  A tuple (<add-on directory>, <location>) is returned.
  """
  if trim > 0:
    old_location = old_location.split('/', trim)[trim]
  for loc, new_root, new_loc in MAP:
    if loc in old_location:
      return new_root, old_location.replace(loc, new_loc)
  return None, old_location


def StartPatchForFile(input_patch_file, output_prefix, patches_out, current_out,
                      old_filename, multi, trim,
                      from_index_line=False, line_before=None):
  """Write preamble for a new file being patched, with the location updated.

  Returns the file handle that patch info should be copied to.
  """  
  directory, new_filename = GetNewLocation(old_filename, trim)
  if directory is None:
    print 'Warning: No mapping rule for %s: defaulting to dspace-api' % old_filename
    directory = 'dspace-api'

  if multi:
    # Select correct place to write to
    if directory not in patches_out:
      patches_out[directory] = open('%s-%s' % (output_prefix, directory), 'w')
    current_out = patches_out[directory]
  else:
    # Need to include add-on root in new path
    new_filename = '%s/%s' % (directory, new_filename)
  
  if from_index_line:
    current_out.write('Index: %s\n' % new_filename)
    # Write out everything until the +++
    line_in = ''
    while not line_in.startswith('+++'):
      current_out.write(line_in)
      line_in = input_patch_file.readline()
  
  if line_before:
    current_out.write(line_before)
  
  current_out.write('+++ %s\n' % new_filename)

  return current_out


def main():
  usage = 'Usage: %prog [options] patchfile'
  parser = OptionParser(usage)
  parser.add_option('-m', '--multi', action='store_true', dest='multi',
                    help='write multiple patch files (one per addon)')
  parser.add_option('-t', '--trim', type="int", dest='trim', default=0,
                    help='number of path components to trim from input patch')
  parser.add_option('-o', '--output', dest='output',
                    help='output filename (or prefix for --multi)',
                    default='patch')
  (options, args) = parser.parse_args()
  if len(args) != 1:
    parser.error('Incorrect number of arguments')
  
  patches_out = {}
  current_out = None
  if not options.multi:
    current_out = open(options.output, 'w')
  
  input_patch_file = open(args[0])
  line_in = input_patch_file.readline()

  while line_in:
    # Check to see if we're patching a new file
    if line_in.startswith('Index:'):
      match = INDEXLINE_RE.match(line_in)
      assert match
      current_out = StartPatchForFile(input_patch_file, options.output,
                                      patches_out, current_out,
                                      match.group(1), options.multi,
                                      options.trim, from_index_line=True)
    elif line_in.startswith('---'):
      # New file encountered
      plus_line = input_patch_file.readline()
      match = PLUSLINE_RE.match(plus_line)
      assert match
      current_out = StartPatchForFile(input_patch_file, options.output,
                                      patches_out, current_out,
                                      match.group(1), options.multi,
                                      options.trim, line_before=line_in)
    else:
      # Regular line; just duplicate it
      current_out.write(line_in)

    line_in = input_patch_file.readline()

  # Close files
  if options.multi:
    for f in patches_out.itervalues():
      f.close()
  else:
    current_out.close()


if __name__ == "__main__":
    main()