134 lines
4.1 KiB
Python
134 lines
4.1 KiB
Python
# This Source Code Form is subject to the terms of the Mozilla Public
|
|
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
|
|
"Mozilla l10n compare locales tool"
|
|
|
|
from compare_locales import paths
|
|
|
|
|
|
class Tree:
|
|
def __init__(self, valuetype):
|
|
self.branches = dict()
|
|
self.valuetype = valuetype
|
|
self.value = None
|
|
|
|
def __getitem__(self, leaf):
|
|
parts = []
|
|
if isinstance(leaf, paths.File):
|
|
parts = []
|
|
if leaf.module:
|
|
parts += [leaf.locale] + leaf.module.split("/")
|
|
parts += leaf.file.split("/")
|
|
else:
|
|
parts = leaf.split("/")
|
|
return self.__get(parts)
|
|
|
|
def __get(self, parts):
|
|
common = None
|
|
old = None
|
|
new = tuple(parts)
|
|
t = self
|
|
for k, v in self.branches.items():
|
|
for i, part in enumerate(zip(k, parts)):
|
|
if part[0] != part[1]:
|
|
i -= 1
|
|
break
|
|
if i < 0:
|
|
continue
|
|
i += 1
|
|
common = tuple(k[:i])
|
|
old = tuple(k[i:])
|
|
new = tuple(parts[i:])
|
|
break
|
|
if old:
|
|
self.branches.pop(k)
|
|
t = Tree(self.valuetype)
|
|
t.branches[old] = v
|
|
self.branches[common] = t
|
|
elif common:
|
|
t = self.branches[common]
|
|
if new:
|
|
if common:
|
|
return t.__get(new)
|
|
t2 = t
|
|
t = Tree(self.valuetype)
|
|
t2.branches[new] = t
|
|
if t.value is None:
|
|
t.value = t.valuetype()
|
|
return t.value
|
|
|
|
indent = " "
|
|
|
|
def getContent(self, depth=0):
|
|
"""
|
|
Returns iterator of (depth, flag, key_or_value) tuples.
|
|
If flag is 'value', key_or_value is a value object, otherwise
|
|
(flag is 'key') it's a key string.
|
|
"""
|
|
keys = sorted(self.branches.keys())
|
|
if self.value is not None:
|
|
yield (depth, "value", self.value)
|
|
for key in keys:
|
|
yield (depth, "key", key)
|
|
yield from self.branches[key].getContent(depth + 1)
|
|
|
|
def toJSON(self):
|
|
"""
|
|
Returns this Tree as a JSON-able tree of hashes.
|
|
Only the values need to take care that they're JSON-able.
|
|
"""
|
|
if self.value is not None:
|
|
return self.value
|
|
return {
|
|
"/".join(key): self.branches[key].toJSON() for key in self.branches.keys()
|
|
}
|
|
|
|
def getStrRows(self):
|
|
def tostr(t):
|
|
if t[1] == "key":
|
|
return self.indent * t[0] + "/".join(t[2])
|
|
return self.indent * (t[0] + 1) + str(t[2])
|
|
|
|
return [tostr(c) for c in self.getContent()]
|
|
|
|
def __str__(self):
|
|
return "\n".join(self.getStrRows())
|
|
|
|
|
|
class AddRemove:
|
|
def __init__(self):
|
|
self.left = self.right = None
|
|
|
|
def set_left(self, left):
|
|
if not isinstance(left, list):
|
|
left = list(li for li in left)
|
|
self.left = left
|
|
|
|
def set_right(self, right):
|
|
if not isinstance(right, list):
|
|
right = list(li for li in right)
|
|
self.right = right
|
|
|
|
def __iter__(self):
|
|
# order_map stores index in left and then index in right
|
|
order_map = {item: (i, -1) for i, item in enumerate(self.left)}
|
|
left_items = set(order_map)
|
|
# as we go through the right side, keep track of which left
|
|
# item we had in right last, and for items not in left,
|
|
# set the sortmap to (left_offset, right_index)
|
|
left_offset = -1
|
|
right_items = set()
|
|
for i, item in enumerate(self.right):
|
|
right_items.add(item)
|
|
if item in order_map:
|
|
left_offset = order_map[item][0]
|
|
else:
|
|
order_map[item] = (left_offset, i)
|
|
for item in sorted(order_map, key=lambda item: order_map[item]):
|
|
if item in left_items and item in right_items:
|
|
yield ("equal", item)
|
|
elif item in left_items:
|
|
yield ("delete", item)
|
|
else:
|
|
yield ("add", item)
|