# -*- coding: utf-8 -*-

"""
Taken from Twisted package.
"""

"""
Copyright (c) 2001-2010
Allen Short
Andy Gayton
Andrew Bennetts
Antoine Pitrou
Apple Computer, Inc.
Benjamin Bruheim
Bob Ippolito
Canonical Limited
Christopher Armstrong
David Reid
Donovan Preston
Eric Mangold
Eyal Lotem
Itamar Shtull-Trauring
James Knight
Jason A. Mobarak
Jean-Paul Calderone
Jessica McKellar
Jonathan Jacobs
Jonathan Lange
Jonathan D. Simms
JÃ¼rgen Hermann
Kevin Horn
Kevin Turner
Mary Gardiner
Matthew Lefkowitz
Massachusetts Institute of Technology
Moshe Zadka
Paul Swartz
Pavel Pergamenshchik
Ralph Meijer
Sean Riley
Software Freedom Conservancy
Travis B. Hartwell
Thijs Triemstra
Thomas Herve
Timothy Allen

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""

import sys, os


class _inf(object):
    """
    An object that is bigger than all other objects.
    """
    def __cmp__(self, other):
        """
        @param other: Another object.
        @type other: any

        @return: 0 if other is inf, 1 otherwise.
        @rtype: C{int}
        """
        if other is _inf:
            return 0
        return 1

_inf = _inf()


class IncomparableVersions(TypeError):
    """
    Two versions could not be compared.
    """

class Version(object):
    """
    An object that represents a three-part version number.

    If running from an svn checkout, include the revision number in
    the version string.
    """
    def __init__(self, package, major, minor, micro, prerelease=None):
        """
        @param package: Name of the package that this is a version of.
        @type package: C{str}
        @param major: The major version number.
        @type major: C{int}
        @param minor: The minor version number.
        @type minor: C{int}
        @param micro: The micro version number.
        @type micro: C{int}
        @param prerelease: The prerelease number.
        @type prerelease: C{int}
        """
        self.package = package
        self.major = major
        self.minor = minor
        self.micro = micro
        self.prerelease = prerelease


    def short(self):
        """
        Return a string in canonical short version format,
        <major>.<minor>.<micro>[+rSVNVer].
        """
        s = self.base()
        svnver = self._getSVNVersion()
        if svnver:
            s += '+r' + str(svnver)
        return s


    def base(self):
        """
        Like L{short}, but without the +rSVNVer.
        """
        if self.prerelease is None:
            pre = ""
        else:
            pre = "pre%s" % (self.prerelease,)
        return '%d.%d.%d%s' % (self.major,
                               self.minor,
                               self.micro,
                               pre)


    def __repr__(self):
        svnver = self._formatSVNVersion()
        if svnver:
            svnver = '  #' + svnver
        if self.prerelease is None:
            prerelease = ""
        else:
            prerelease = ", prerelease=%r" % (self.prerelease,)
        return '%s(%r, %d, %d, %d%s)%s' % (
            self.__class__.__name__,
            self.package,
            self.major,
            self.minor,
            self.micro,
            prerelease,
            svnver)


    def __str__(self):
        return '[%s, version %s]' % (
            self.package,
            self.short())


    def __cmp__(self, other):
        """
        Compare two versions, considering major versions, minor versions, micro
        versions, then prereleases.

        A version with a prerelease is always less than a version without a
        prerelease. If both versions have prereleases, they will be included in
        the comparison.

        @param other: Another version.
        @type other: L{Version}

        @return: NotImplemented when the other object is not a Version, or one
            of -1, 0, or 1.

        @raise IncomparableVersions: when the package names of the versions
            differ.
        """
        if not isinstance(other, self.__class__):
            return NotImplemented
        if self.package != other.package:
            raise IncomparableVersions("%r != %r"
                                       % (self.package, other.package))

        if self.prerelease is None:
            prerelease = _inf
        else:
            prerelease = self.prerelease

        if other.prerelease is None:
            otherpre = _inf
        else:
            otherpre = other.prerelease

        x = cmp((self.major,
                    self.minor,
                    self.micro,
                    prerelease),
                   (other.major,
                    other.minor,
                    other.micro,
                    otherpre))
        return x


    def _parseSVNEntries_4(self, entriesFile):
        """
        Given a readable file object which represents a .svn/entries file in
        format version 4, return the revision as a string.  We do this by
        reading first XML element in the document that has a 'revision'
        attribute.
        """
        from xml.dom.minidom import parse
        doc = parse(entriesFile).documentElement
        for node in doc.childNodes:
            if hasattr(node, 'getAttribute'):
                rev = node.getAttribute('revision')
                if rev is not None:
                    return rev.encode('ascii')


    def _parseSVNEntries_8(self, entriesFile):
        """
        Given a readable file object which represents a .svn/entries file in
        format version 8, return the revision as a string.
        """
        entriesFile.readline()
        entriesFile.readline()
        entriesFile.readline()
        return entriesFile.readline().strip()
    
    
    # Add handlers for version 9 and 10 formats, which are the same as
    # version 8 as far as revision information is concerned.
    _parseSVNEntries_9 = _parseSVNEntries_8
    _parseSVNEntriesTenPlus = _parseSVNEntries_8


    def _getSVNVersion(self):
        """
        Figure out the SVN revision number based on the existance of
        <package>/.svn/entries, and its contents. This requires discovering the
        format version from the 'format' file and parsing the entries file
        accordingly.

        @return: None or string containing SVN Revision number.
        """
        mod = sys.modules.get(self.package)
        if mod:
            svn = os.path.join(os.path.dirname(mod.__file__), '.svn')
            if not os.path.exists(svn):
                # It's not an svn working copy
                return None

            formatFile = os.path.join(svn, 'format')
            if os.path.exists(formatFile):
                # It looks like a less-than-version-10 working copy.
                format = file(formatFile).read().strip()
                parser = getattr(self, '_parseSVNEntries_' + format, None)
            else:
                # It looks like a version-10-or-greater working copy, which
                # has version information in the entries file.
                parser = self._parseSVNEntriesTenPlus

            if parser is None:
                return 'Unknown'

            entriesFile = os.path.join(svn, 'entries')
            entries = file(entriesFile)
            try:
                try:
                    return parser(entries)
                finally:
                    entries.close()
            except:
                return 'Unknown'


    def _formatSVNVersion(self):
        ver = self._getSVNVersion()
        if ver is None:
            return ''
        return ' (SVN r%s)' % (ver,)



def getVersionString(version):
    """
    Get a friendly string for the given version object.

    @param version: A L{Version} object.
    @return: A string containing the package and short version number.
    """
    result = '%s %s' % (version.package, version.short())
    return result
