1# coding=utf-8
2
3
4class SerialTreeManager(object):
5    """
6    Manages output tree by building it from flat test names.
7    """
8
9    def __init__(self, offset):
10        super(SerialTreeManager, self).__init__()
11        # Currently active branch as list. New nodes go to this branch
12        self.current_branch = []
13        # node unique name to its nodeId
14        self._node_ids_dict = {}
15        # Node id mast be incremented for each new branch
16        self._max_node_id = offset
17        self.offset = offset
18
19    def _calculate_relation(self, branch_as_list):
20        """
21        Get relation of branch_as_list to current branch.
22        :return: tuple. First argument could be: "same", "child", "parent" or "sibling"(need to start new tree)
23        Second argument is relative path from current branch to child if argument is child
24        """
25        if branch_as_list == self.current_branch:
26            return "same", None
27
28        hierarchy_name_len = len(branch_as_list)
29        current_branch_len = len(self.current_branch)
30
31        if hierarchy_name_len > current_branch_len and branch_as_list[0:current_branch_len] == self.current_branch:
32            return "child", branch_as_list[current_branch_len:]
33
34        if hierarchy_name_len < current_branch_len and self.current_branch[0:hierarchy_name_len] == branch_as_list:
35            return "parent", None
36
37        return "sibling", None
38
39    def _add_new_node(self, new_node_name):
40        """
41        Adds new node to branch
42        """
43        self.current_branch.append(new_node_name)
44        self._max_node_id += 1
45        self._node_ids_dict[".".join(self.current_branch)] = self._max_node_id
46
47    def level_opened(self, test_as_list, func_to_open):
48        """
49        To be called on test start.
50
51        :param test_as_list: test name splitted as list
52        :param func_to_open: func to be called if test can open new level
53        :return: None if new level opened, or tuple of command client should execute and try opening level again
54         Command is "open" (open provided level) or "close" (close it). Second item is test name as list
55        """
56        relation, relative_path = self._calculate_relation(test_as_list)
57        if relation == 'same':
58            return  # Opening same level?
59        if relation == 'child':
60            # If one level -- open new level gracefully
61            if len(relative_path) == 1:
62                self._add_new_node(relative_path[0])
63                func_to_open()
64                return None
65            else:
66                # Open previous level
67                return [("open", self.current_branch + relative_path[0:1])]
68        if relation == "sibling":
69            if self.current_branch:
70                # Different tree, close whole branch
71                return [("close", self.current_branch)]
72            else:
73                return None
74        if relation == 'parent':
75            # Opening parent? Insane
76            pass
77
78    def level_closed(self, test_as_list, func_to_close):
79        """
80        To be called on test end or failure.
81
82        See level_opened doc.
83        """
84        relation, relative_path = self._calculate_relation(test_as_list)
85        if relation == 'same':
86            # Closing current level
87            func_to_close()
88            self.current_branch.pop()
89        if relation == 'child':
90            return None
91
92        if relation == 'sibling':
93            pass
94        if relation == 'parent':
95            return [("close", self.current_branch)]
96
97    @property
98    def parent_branch(self):
99        return self.current_branch[:-1] if self.current_branch else None
100
101    def _get_node_id(self, branch):
102        return self._node_ids_dict[".".join(branch)]
103
104    # Part of contract
105    # noinspection PyUnusedLocal
106    def get_node_ids(self, test_name):
107        """
108
109        :return: (current_node_id, parent_node_id)
110        """
111        current = self._get_node_id(self.current_branch)
112        parent = self._get_node_id(self.parent_branch) if self.parent_branch else str(self.offset)
113        return str(current), str(parent)
114