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