summaryrefslogblamecommitdiff
path: root/tools/map-diff.py
blob: 1da5cdec4993ebd57a0c81e9dec0d2f37b73e058 (plain) (tree)












































































                                                                                                    




                                                        
                                           
                                                                             


























                                                                                                                                                                 





                                                                    










































































                                                                                                                                               
#!/usr/bin/env python
#-*- coding:utf-8 -*-

import sys
import os
import subprocess
import re
import tempfile

class MapDiff(object):

    @staticmethod
    def check_programs():
        """
            Checks the require programs are available
        """
        def which(program):
            import os
            def is_exe(fpath):
                return os.path.isfile(fpath) and os.access(fpath, os.X_OK)
            fpath, fname = os.path.split(program)
            if fpath:
                if is_exe(program):
                    return program
            else:
                for path in os.environ["PATH"].split(os.pathsep):
                    exe_file = os.path.join(path, program)
                    if is_exe(exe_file):
                        return exe_file
            return None

        platform_programs = MapDiff.PROGRAMS.get(sys.platform, MapDiff.PROGRAMS.get('default'))
        for program in platform_programs.values():
            if not which(program):
                raise Exception('The required "%s" program is missing from your PATH.' % program)

    MAP_RE = re.compile(r'^\d{3}-\d{1}(\.tmx)?$')
    PROGRAMS = {
        'default': {
            'tmxrasterizer': 'tmxrasterizer',
            'im_convert': 'convert',
            'im_display': 'display',
            'git': 'git',
        },
        'win32': {
            'tmxrasterizer': 'tmxrasterizer.exe',
            'im_convert': 'convert.exe',
            'im_display': 'display.exe',
            'git': 'git.exe',
        },
    }

    def __init__(self):
        self.platform_programs = MapDiff.PROGRAMS.get(sys.platform, MapDiff.PROGRAMS.get('default'))

    def _diffmaps(self, tmx1, tmx2, tmxdiffpath):
        tmxraster1 = self._rastermap(tmx1)
        tmxraster2 = self._rastermap(tmx2)
        tmxf, tmxdiff = tempfile.mkstemp(suffix='.png')
        subprocess.check_call([
            self.platform_programs.get('im_convert'), tmxraster1, tmxraster2,
            '-compose', 'Difference',
            '-auto-level',
            '-composite',
            tmxraster2,
            '-compose', 'Screen',
            '-composite',
            tmxdiffpath
        ])
        os.unlink(tmxdiff)
        os.unlink(tmxraster1)
        os.unlink(tmxraster2)
        sys.stdout.write((u'Map diff written to %s\n' % tmxdiffpath).encode('utf-8'))
        subprocess.check_call([self.platform_programs.get('im_display'), tmxdiffpath])

    def _rastermap(self, tmx):
        tmxf, tmxraster = tempfile.mkstemp(suffix='.png')
        subprocess.check_call([
            self.platform_programs.get('tmxrasterizer'),
            '--scale', '1.0',
            tmx, tmxraster
        ])
        if os.stat(tmxraster).st_size == 0:
            raise Exception('A problem was encountered when rendering a map')
        return tmxraster


class MapGitRevDiff(MapDiff):

    def __init__(self, map_name):
        super(MapGitRevDiff, self).__init__()
        self.map_name = map_name

    def diff(self):
        if not MapDiff.MAP_RE.match(self.map_name):
            sys.stderr.write(u'Invalid map name: %s.\n' % self.map_name)
            return 1
        if not self.map_name.endswith(u'.tmx'):
            self.map_name = self.map_name+u'.tmx'
        self.tmx_path = os.path.join(u'..', u'maps', self.map_name)
        self.map_number = os.path.splitext(os.path.basename(self.map_name))[0]
        p = subprocess.Popen([self.platform_programs.get('git'), '--no-pager', 'log', '-n', '2', '--oneline', '--follow', self.tmx_path], stdout=subprocess.PIPE)
        log = p.communicate()[0].splitlines()
        if not len(log) == 2:
            raise Exception('This map has only one version')
        c1 = log[0].split(' ')[0]
        c2 = log[1].split(' ')[0]

        # We have the 2 revs to compare. Let's extract the related tmx file
        p1 = self._mktmx_from_rev(c1)
        p2 = self._mktmx_from_rev(c2)
        try:
            difftmxpath = '%s_%s-%s.png' % (self.map_number, c1, c2)
            self._diffmaps(p1, p2, difftmxpath)
        finally:
            os.unlink(p1)
            os.unlink(p2)

    def _mktmx_from_rev(self, rev):
        p = subprocess.Popen([self.platform_programs.get('git'), '--no-pager', 'show', '%s:%s' % (rev, self.tmx_path)], stdout=subprocess.PIPE)
        contents = p.communicate()[0]
        revtmx = '%s-%s.tmx' % (self.map_number, rev)
        f = open(revtmx, 'w')
        f.write(contents)
        f.close()
        return revtmx


class MapFileDiff(MapDiff):

    def __init__(self, map1, map2):
        super(MapFileDiff, self).__init__()
        self.map1 = map1
        self.map2 = map2

    def diff(self):
        b1 = os.path.splitext(os.path.basename(self.map1))[0]
        b2 = os.path.splitext(os.path.basename(self.map2))[0]
        difftmxpath = '%s__%s.png' % (b1, b2)
        self._diffmaps(self.map1, self.map2, difftmxpath)


def usage():
    sys.stderr.write(u'''Usage: %s MAP_NAME
       %s CHANGED_TMX REFERENCE_TMX

    Example:
        $ ./map-diff.py 007-1
    will highlight the changes between the current 007-1 map and its previous version

        $ ./map-diff.py changes-made-by-someone-007-1.tmx ../maps-007-1.tmx
    will highlight the changes between the two tmx maps.
    Note that these 2 tmx to compare have to satisfy their dependancies, e.g tilesets.
    Hence they should be in a sibling directory of the client-data/maps folder.
    \n''' % (sys.argv[0], sys.argv[0]))

def main():
    if not len(sys.argv) > 1:
        usage()
        return 127
    if not os.path.basename(os.path.dirname(os.getcwdu())) == u'client-data':
        sys.stderr.write(u'This script must be run from client-data/tools.\n')
        return 1
    try:
        MapDiff.check_programs()
    except Exception as e:
        sys.stderr.write(u'%s\n' % e)
        return 126
    if len(sys.argv) == 2:
        map_name = sys.argv[1]
        mapdiff = MapGitRevDiff(map_name)
        try:
            mapdiff.diff()
        except Exception as e:
            sys.stderr.write(u'\x1b[31m\x1b[1mError while generating the diff for map %s: %s\x1b[0m\n' % (map_name, e))
            return 1
        else:
            return 0
    else:
        map1 = sys.argv[1]
        map2 = sys.argv[2]
        mapdiff = MapFileDiff(map1, map2)
        try:
            mapdiff.diff()
        except Exception as e:
            sys.stderr.write(u'\x1b[31m\x1b[1mError while generating the diff for %s and %s: %s\x1b[0m\n' % (map1, map2, e))
            return 1
        else:
            return 0

if __name__ == '__main__':
    sys.exit(main())