# Copyright 2005, 2006 by Simon 'corecode' Schubert <corecode@fs.ei.tum.de>
#
# This program is free software under the GNU GPL (>=v2)
# Read the file COPYING coming with the software for details.

import os
import re
import time
import rcsparse
from stat import *

class _walkentry:
    def __init__(self, repo, path, rcsfile, time):
        self.repo = repo
        self.path = os.path.join(path, rcsfile)
        self.time = time

    def file(self):
        return self.repo.file(self)

class TagExpander:
    def __init__(self, cvsroot):
        self.cvsroot = os.path.abspath(cvsroot)

        self.expandkw = [f.replace("expand_", "") for f in dir(self)
                         if f.startswith("expand_")]
        self.keywords = dict(zip(self.expandkw, self.expandkw))

        for l in file(os.path.join(cvsroot, "CVSROOT", "config")).readlines():
            k = re.match("\s*LocalKeyword=(\w+)=(\w+)", l)
            if k:
                k, t = k.groups()
                self.keywords[k] = t
            k = re.match("\s*KeywordExpand=(e|i)((?:\w+,?)*)", l)
            if k:
                m, kws = k.groups()
                if m == "i":
                    self.expandkw = []
                for kw in kws.split(","):
                    if m == "e":
                        self.expandkw.remove(kw)
                    else:
                        self.expandkw.append(kw)

        self.matcher = re.compile("\\$(%s)(?::[^$\n]*)?\\$" %
                                  "|".join(self.expandkw))

    def expand(self, path, rev, data):
        odelta = 0
        retdata = data
        for r in self.matcher.finditer(data):
            tag = r.groups()[0]
            newval = getattr(self, "expand_" + self.keywords[tag])(path, rev)
            newtag = "$%s: %s $" % (tag, newval)
            retdata = (retdata[:odelta + r.start()] + newtag +
                       retdata[odelta + r.end():])
            odelta += len(newtag) - (r.end() - r.start())

        return retdata

    def expand_Author(self, path, rev):
        return rev[2]

    def expand_CVSHeader(self, path, rev):
        return "%s,v %s %s %s %s" % (path, rev[0], self.expand_Date(path, rev),
                                     rev[2], rev[3])

    def expand_Date(self, path, rev):
        return time.strftime("%Y/%m/%d %H:%M:%S", time.gmtime(rev[1]))

    def expand_Header(self, path, rev):
        return self.expand_CVSHeader(os.path.join(self.cvsroot, path), rev)

    def expand_Id(self, path, rev):
        return self.expand_CVSHeader(os.path.basename(path), rev)

    def expand_RCSfile(self, path, rev):
        return os.path.basename(path) + ",v"

    def expand_Revision(self, path, rev):
        return rev[0]

    def expand_Source(self, path, rev):
        return os.path.join(self.cvsroot, path) + ",v"

    def expand_Name(self, path, rev):
        return ""

class CVSrepo:
    def __init__(self, cvsroot):
        self.cvsroot = os.path.normpath(cvsroot)
        if not os.path.isdir(os.path.join(cvsroot, "CVSROOT")):
            raise "not a cvs repo!"
        self.tagexp = TagExpander(cvsroot)

    def _query(self, command):
        return os.popen('cvs -d %(cvsroot)s -R -r %(command)s'
                        % {'cvsroot': self.cvsroot, 'command': command})

    def walk(self, *pathlist):
        """
        -> iterator (path, name, date)

        Walks the CVS tree, returning one entry per match.
        """

        for path in pathlist:
            for dirpath, dirnames, filenames in os.walk(
                                            os.path.join(self.cvsroot, path)):
                def atticwalker(arg, dir_, files):
                    for f in files:
                        if os.path.isfile(os.path.join(dir_, f)):
                            arg.append(os.path.join('Attic', f))

                if dirpath.startswith(self.cvsroot + '/'):
                    cvsreldir = dirpath[len(self.cvsroot)+1:]
                else:
                    raise Error("path not in repo: %s" % dirpath)
                if 'Attic' in dirnames:
                    dirnames.remove('Attic')
                    os.path.walk(os.path.join(dirpath, 'Attic'),
                                 atticwalker,
                                 filenames)

                for filename in filenames:
                    if not filename.endswith(',v'):
                        continue

                    rcsfilename = filename[:-2].replace('Attic/', '')
                    yield _walkentry(self, cvsreldir, rcsfilename,
                                     os.path.getmtime(
                                         os.path.join(dirpath, filename)))

    def filex(self, path):
        """
        -> rcsparse.rcsfile
        """

        if path.__class__ == _walkentry:
            path = path.path

        try:
            v = os.path.join(self.cvsroot, path) + ',v'
            return rcsparse.rcsfile(v), os.stat(v)[ST_MODE]
        except IOError:
            cpath = os.path.split(os.path.join(self.cvsroot, path))
            v = os.path.join(cpath[0], 'Attic', cpath[1]) + ',v'
            return rcsparse.rcsfile(v), os.stat(v)[ST_MODE]

    def file(self, path):
        f, mode = self.filex(path)
        return f

    def checkout(self, path, rev=None):
        """
        -> file/rev data
        """

        f, mode = self.filex(path)
        rawdata = f.checkout(rev)
        if not (f.expand or "x") in "ob":
            rawdata = self.tagexp.expand(path, f.revs[rev], rawdata)
        return rawdata, mode

def issuccessor(lhs, rhs):
    if lhs.count('.') != rhs.count('.'):
        return False
    ls = lhs.rsplit('.', 1)
    rs = rhs.rsplit('.', 1)
    if ls[0] != rs[0]:
        return False
    if len(rs[-1]) > len(ls[-1]):
        return True
    if int(rs[-1]) > int(ls[-1]):
        return True
    return False

def getpredecessor(rev):
    s = rev.split('.')
    if len(s) % 2 == 0 and len(s) >= 4 and s[-2] == '0' and int(s[-1]) % 2 == 0:
        # cvs branchpoint
        s = s[:-2]

    if s[-1] == '1':
        if s[-2] == '1':
            return '0'
        if len(s) >= 4:
            s = s[:-2]
            return '.'.join(s)
        # huh?
        raise Error('predecessor on first revision')

    s[-1] = str(int(s[-1]) - 1)

    return '.'.join(s)

#for res in CVSwalk('/space/cvs/dragonfly', 'src/sys/net'):
#   print res
