#!/usr/bin/python -t
# -*- mode: Python; indent-tabs-mode: nil; coding: utf-8 -*-
#
# Copyright (c) 2005-2008 Red Hat Inc.
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

import errno, os, sys
import re
import time
from optparse import OptionParser

__version__ = "1.0"

class BumpSpecError(Exception):
    pass

class SpecFile:
    def __init__(self, filename, verbose=False):
        self.verbose = verbose

        self.filename = filename
        f = open(filename,"r")
        self.lines = f.readlines()
        f.close()
        
    def bumpRelease(self):
        bump_patterns = [(re.compile(r"^Release\s*:\s*(\d+.*)", re.I), self.increase), 
                       (re.compile(r"^%define\s+rel\s+(\d+.*)"), self.increase), 
                       (re.compile(r"^%define\s+release\s+(\d+.*)", re.I), self.increase), 
                       (re.compile(r"^Release\s*:\s+%release_func\s+(\d+.*)"), self.increase),
                       (re.compile(r"^%define\s+baserelease\s+(\d+.*)"), self.increase),
                       ]
        skip_pattern = re.compile(r"\$Revision:")
        for i in range(len(self.lines)):
            if skip_pattern.search(self.lines[i]):
                continue
            for bumpit, bumpit_func in bump_patterns:
                (self.lines[i], n) = bumpit.subn(bumpit_func, self.lines[i], 1)
                if n:  # bumped
                    return
        
        # Here, no line matched at all.
        # Happens with macro-overloaded spec files e.g.
        # Bump ^Release: ... line least-insignificant.
        for i in range(len(self.lines)):
            if self.lines[i].startswith('Release:'):
                old = self.lines[i][len('Release:'):].rstrip()
                new = self.increaseFallback(old)
                if self.verbose:
                    self.debugdiff(old, new)
                self.lines[i] = self.lines[i].replace(old, new)
                return

        if self.verbose:
            print >> sys.stderr, 'ERROR: No release value matched:', self.filename
            sys.exit(1)

    def addChangelogEntry(self, evr, entry, email):
        if len(evr):
            evrstring = ' - %s' % evr
        else:
            evrstring = ''
        changematch = re.compile(r"^%changelog")
        date = time.strftime("%a %b %d %Y",   time.localtime(time.time()))
        newchangelogentry = "%changelog\n* "+date+" "+email+evrstring+"\n"+entry+"\n\n"
        for i in range(len(self.lines)):
            if(changematch.match(self.lines[i])):
                self.lines[i] = newchangelogentry
                break

    def increaseMain(self, release):
        if release.startswith('0.'):
            relre = re.compile(r'^0\.(?P<rel>\d+)(?P<post>.*)')
            pre = True
        else:
            relre = re.compile(r'^(?P<rel>\d+)(?P<post>.*)')
            pre = False
        relmatch = relre.search(release)
        if not relmatch:  # pattern match failed
            raise BumpSpecError
        value = int(relmatch.group('rel'))
        post = relmatch.group('post')

        if not pre:
            if post.find('rc')>=0:
                print 'WARNING: Bad pre-release versioning scheme:', self.filename
                raise BumpSpecError
            new = `value+1`+post
        else:
            new = '0.'+`value+1`+post
        return new

    def increaseJPP(self, release):
        """Fedora jpackage release versioning scheme"""

        relre = re.compile(r'(?P<prefix>.*)(?P<rel>\d+)(?P<jpp>jpp\.)(?P<post>.*)')
        relmatch = relre.search(release)
        if not relmatch:  # pattern match failed
            raise BumpSpecError
        
        prefix = relmatch.group('prefix')
        value = int(relmatch.group('rel'))
        jpp = relmatch.group('jpp')
        post = relmatch.group('post')

        newpost = self.increaseMain(post)
        new = prefix+str(value)+jpp+newpost
        return new

    def increaseFallback(self, release):
        """bump at the very-right or add .1 as a last resort"""
        relre = re.compile(r'(?P<prefix>.+\.)(?P<post>\d+$)')
        relmatch = relre.search(release)
        if relmatch:
            prefix = relmatch.group('prefix')
            post = relmatch.group('post')
            new = prefix+self.increaseMain(post)
        else:
            new = release.rstrip()+'.1'
        return new

    def increase(self, match):
        old = match.group(1)  # only the release value
        try:
            if old.find('jpp')>0:
                new = self.increaseJPP(old)
            else:
                new = self.increaseMain(old)
        except BumpSpecError:
            new = self.increaseFallback(old)
        if self.verbose:
            self.debugdiff(old, new)
        # group 0 is the full line that defines the release
        return match.group(0).replace(old, new)

    def writeFile(self, filename):
        f = open(filename, "w")
        f.writelines(self.lines)
        f.close()

    def debugdiff(self, old, new):
        print self.filename
        print '-%s' % old
        print '+%s\n' % new

if __name__ == "__main__":
    usage = '''Usage: %prog [OPTION]... SPECFILE...

rpmdev-bumpspec bumps release tags in specfiles.'''

    version = '''rpmdev-bumpspec version %s

Copyright (c) 2005-2008 Red Hat Inc.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.''' % __version__

    parser = OptionParser(usage=usage)
    parser.add_option("-c", "--comment", default='- rebuilt',
                      help="changelog comment (default:\"- rebuilt\")")
    parser.add_option("-u", "--userstring", default=None,
                      help="user name+email string")
    parser.add_option("-V", "--verbose", default=False, action='store_true',
                      help="more output")
    parser.add_option("-v", "--version", default=False, action='store_true',
                      help="output version number and exit")
    (opts, args) = parser.parse_args()

    if opts.version:
        print version
        sys.exit(0)

    userstring = os.getenv('RPM_PACKAGER') or os.getenv('MAILTO')
    if not userstring and not opts.userstring:
        print 'ERROR: Set $RPM_PACKAGER environment variable or use option -u!'
        sys.exit(errno.EINVAL)
    if opts.userstring:
        userstring = opts.userstring

    for aspec in args:
        s = SpecFile(aspec, opts.verbose)
        s.bumpRelease()
        s.writeFile(aspec)

        # Get EVR for changelog entry.
        (epoch,ver,rel) = os.popen("LC_ALL=C rpm --specfile -q --qf '%%{epoch} %%{version} %%{release}\n' --define 'dist %%{nil}' %s | head -1" % aspec).read().strip().split(' ')
        if epoch != '(none)':
            evr = str(epoch)+':'
        else:
            evr = ''
        evr += ver+'-'+rel

        s.addChangelogEntry(evr, opts.comment, userstring)
        s.writeFile(aspec)

sys.exit(0)
