1# This Source Code Form is subject to the terms of the Mozilla Public 2# License, v. 2.0. If a copy of the MPL was not distributed with this 3# file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 5'Mozilla l10n compare locales tool' 6 7from __future__ import absolute_import 8from __future__ import print_function 9 10import six 11from six.moves import zip 12 13from compare_locales import paths 14 15 16class Tree(object): 17 def __init__(self, valuetype): 18 self.branches = dict() 19 self.valuetype = valuetype 20 self.value = None 21 22 def __getitem__(self, leaf): 23 parts = [] 24 if isinstance(leaf, paths.File): 25 parts = [] 26 if leaf.module: 27 parts += [leaf.locale] + leaf.module.split('/') 28 parts += leaf.file.split('/') 29 else: 30 parts = leaf.split('/') 31 return self.__get(parts) 32 33 def __get(self, parts): 34 common = None 35 old = None 36 new = tuple(parts) 37 t = self 38 for k, v in six.iteritems(self.branches): 39 for i, part in enumerate(zip(k, parts)): 40 if part[0] != part[1]: 41 i -= 1 42 break 43 if i < 0: 44 continue 45 i += 1 46 common = tuple(k[:i]) 47 old = tuple(k[i:]) 48 new = tuple(parts[i:]) 49 break 50 if old: 51 self.branches.pop(k) 52 t = Tree(self.valuetype) 53 t.branches[old] = v 54 self.branches[common] = t 55 elif common: 56 t = self.branches[common] 57 if new: 58 if common: 59 return t.__get(new) 60 t2 = t 61 t = Tree(self.valuetype) 62 t2.branches[new] = t 63 if t.value is None: 64 t.value = t.valuetype() 65 return t.value 66 67 indent = ' ' 68 69 def getContent(self, depth=0): 70 ''' 71 Returns iterator of (depth, flag, key_or_value) tuples. 72 If flag is 'value', key_or_value is a value object, otherwise 73 (flag is 'key') it's a key string. 74 ''' 75 keys = sorted(self.branches.keys()) 76 if self.value is not None: 77 yield (depth, 'value', self.value) 78 for key in keys: 79 yield (depth, 'key', key) 80 for child in self.branches[key].getContent(depth + 1): 81 yield child 82 83 def toJSON(self): 84 ''' 85 Returns this Tree as a JSON-able tree of hashes. 86 Only the values need to take care that they're JSON-able. 87 ''' 88 if self.value is not None: 89 return self.value 90 return dict(('/'.join(key), self.branches[key].toJSON()) 91 for key in self.branches.keys()) 92 93 def getStrRows(self): 94 def tostr(t): 95 if t[1] == 'key': 96 return self.indent * t[0] + '/'.join(t[2]) 97 return self.indent * (t[0] + 1) + str(t[2]) 98 99 return [tostr(c) for c in self.getContent()] 100 101 def __str__(self): 102 return '\n'.join(self.getStrRows()) 103 104 105class AddRemove(object): 106 def __init__(self): 107 self.left = self.right = None 108 109 def set_left(self, left): 110 if not isinstance(left, list): 111 left = list(l for l in left) 112 self.left = left 113 114 def set_right(self, right): 115 if not isinstance(right, list): 116 right = list(l for l in right) 117 self.right = right 118 119 def __iter__(self): 120 # order_map stores index in left and then index in right 121 order_map = dict((item, (i, -1)) for i, item in enumerate(self.left)) 122 left_items = set(order_map) 123 # as we go through the right side, keep track of which left 124 # item we had in right last, and for items not in left, 125 # set the sortmap to (left_offset, right_index) 126 left_offset = -1 127 right_items = set() 128 for i, item in enumerate(self.right): 129 right_items.add(item) 130 if item in order_map: 131 left_offset = order_map[item][0] 132 else: 133 order_map[item] = (left_offset, i) 134 for item in sorted(order_map, key=lambda item: order_map[item]): 135 if item in left_items and item in right_items: 136 yield ('equal', item) 137 elif item in left_items: 138 yield ('delete', item) 139 else: 140 yield ('add', item) 141