#!/usr/bin/env python # vim:sw=4:et: """Report differences in two Gaphor models. This can be called as: python compare.py model1.gaphor model2.gaphor This file is part of Gaphor. """ __all__ = ['Compare'] from __future__ import generators try: import env except ImportError: pass import gaphor.parser import gaphor.storage import gaphor.UML class Compare(object): """This class makes it possible to compare two files. By default reports are printed to stdout in a diff-like syntax. """ def __init__(self, filename1, filename2): self.filename1 = filename1 self.filename2 = filename2 self.elements1, self.factory1 = self.load(self.filename1) self.elements2, self.factory2 = self.load(self.filename2) self.show_id = True def out(self, msg): """Print a message generated by report(). """ print msg def report(self, factory, element, name=None, value=None, isref=False): """Report an element that has differences. The attribute show_id can be set to False to suppress element ids. A fancy diff message is send to method out(msg). """ if factory is self.factory1: msg = '-' else: msg = '+' if isinstance(element, gaphor.parser.canvas): msg += ' :' else: if self.show_id: msg += ' %s' % element.id n = element.get('name') if n: msg += ' (%s)' % n if self.show_id or n: msg += ':' msg += ' %s' % (isinstance(element, gaphor.parser.canvas) and 'Canvas' or element.type) if name: msg += '.%s' % name if value: if isref: if self.show_id: msg += ' = %s' % value obj = factory.lookup(value) if hasattr(obj, 'name'): msg += ' (%s)' % obj.name else: msg += ' = %s' % value self.out(msg) def load(self, filename): """Load the model file and create a factory. A tuple (elements, factory) is returned. """ elements = gaphor.parser.parse(filename) factory = gaphor.UML.ElementFactory() try: gaphor.storage.load_elements(elements, factory) except Exception, e: self.out('! File %s could not be loaded completely.' % filename) self.out('! Trying to diff on parsed elements only.') self.out(e) return elements, factory def elements_in_both_files(self): """Generator function that returns tuples (element1, element2) of elements that exist in both files (they have the same id). """ vals = [] for key1, val1 in self.elements1.iteritems(): val2 = self.elements2.get(key1) if val2: yield (val1, val2) def check_missing_elements(self): """Report elements that exist in one factory, but not in the other. """ keys1 = self.elements1.keys() keys2 = self.elements2.keys() for key in keys1: if key not in keys2: self.report(self.factory1, self.elements1[key]) for key in keys2: if key not in keys1: self.report(self.factory2, self.elements2[key]) def check_missing_references(self, element1, element2): """Report references to other elements that are present in one element and not in the other one. """ keys1 = element1.references.keys() keys2 = element2.references.keys() for key in keys1: if key not in keys2: self.report(self.factory1, element1, key) for key in keys2: if key not in keys1: self.report(self.factory2, element2, key) def check_differences_references(self, element1, element2): keys1 = element1.references.keys() keys2 = element2.references.keys() for key in keys1: if key in keys2: val1 = element1.references.get(key) val2 = element2.references.get(key) try: for val in val1: if val not in val2: self.report(self.factory1, element1, key, val, True) for val in val2: if val not in val1: self.report(self.factory2, element2, key, val, True) except TypeError: if val1 != val2: self.report(self.factory1, element1, key, val1, True) self.report(self.factory2, element2, key, val2, True) def check_missing_values(self, element1, element2): keys1 = element1.values.keys() keys2 = element2.values.keys() for key in keys1: if key not in keys2: self.report(self.factory1, element1, key) for key in keys2: if key not in keys1: self.report(self.factory2, element2, key) def check_differences_values(self, element1, element2): keys1 = element1.values.keys() keys2 = element2.values.keys() for key in keys1: if key in keys2: val1 = element1.values.get(key) val2 = element2.values.get(key) if val1 != val2: self.report(self.factory1, element1, key, val1) self.report(self.factory2, element2, key, val2) def compare(self): """Start the comparison of the files provided to the constructor. """ self.check_missing_elements() for element1, element2 in self.elements_in_both_files(): self.check_missing_references(element1, element2) self.check_differences_references(element1, element2) self.check_missing_values(element1, element2) self.check_differences_values(element1, element2) if __name__ == '__main__': import sys usage = "usage: %s [-v][-h|--help] old_model new_model" % sys.argv[0] files = [] show_id = False # Parse command line arguments: for arg in sys.argv[1:]: if arg.startswith('-'): if arg == '-v': show_id = True elif arg in ('-h', '--help'): print usage sys.exit(0) else: print '%s: invalid option "%s".' % (sys.argv[0], arg) print usage sys.exit(1) else: files.append(arg) if len(files) != 2: print usage sys.exit(1) c = Compare(files[0], files[1]) c.show_id = show_id c.compare()