Completed
Push — master ( a15d44...9e402b )
by P.R.
05:06
created

Node.modify_label_list()   B

Complexity

Conditions 4

Size

Total Lines 31

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 4.026

Importance

Changes 4
Bugs 2 Features 0
Metric Value
c 4
b 2
f 0
dl 0
loc 31
ccs 15
cts 17
cp 0.8824
rs 8.5806
cc 4
crap 4.026
1
"""
2
SDoc
3
4
Copyright 2016 Set Based IT Consultancy
5
6
Licence MIT
7
"""
8
# ----------------------------------------------------------------------------------------------------------------------
9 1
import abc
10
11 1
from sdoc.sdoc2 import in_scope, out_scope, node_store
12
13
14 1
class Node(metaclass=abc.ABCMeta):
15
    """
16
    Abstract class for SDoc2 nodes.
17
    """
18
19
    # ------------------------------------------------------------------------------------------------------------------
20 1
    def __init__(self, io, name, options=None, argument=''):
21
        """
22
        Object constructor.
23
24
        :param None|cleo.styles.output_style.OutputStyle io: The IO object.
25
        :param str name: The (command) name of this node.
26
        :param dict[str,str] options: The options of this node.
27
        :param str argument: The argument of this node (inline commands only).
28
        """
29 1
        self._io = io
30
        """
31
        The IO object.
32
33
        :type None|cleo.styles.output_style.OutputStyle:
34
        """
35
36 1
        self.id = 0
0 ignored issues
show
Coding Style Naming introduced by
The name id does not conform to the attribute naming conventions ([a-z_][a-z0-9_]{2,30}$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
37
        """
38
        The ID of this SDoc2 node.
39
40
        :type: int
41
        """
42
43 1
        self.name = name
44
        """
45
        The (command) name of this node.
46
47
        :type: str
48
        """
49
50 1
        self._argument = argument
51
        """
52
        The argument of this node (inline commands only).
53
54
        :type: str
55
        """
56
57 1
        self._options = options if options else {}
58
        """
59
        The options of this node.
60
61
        :type: dict[str,mixed]
62
        """
63
64 1
        self.child_nodes = []
65
        """
66
        The ID's of the SDoc2 child nodes of this SDoc2 node.
67
68
        :type: list[int]
69
        """
70
71 1
        self.position = None
72
        """
73
        The position where this node is defined.
74
75
        :type: None|sdoc.sdoc2.Position.Position
76
        """
77
78 1
        self.labels = []
79 1
        """
80
        The list of labels in the node.
81
82
        :type:
83
        """
84
85
    # ------------------------------------------------------------------------------------------------------------------
86 1
    @property
87
    def io(self):
0 ignored issues
show
Coding Style Naming introduced by
The name io does not conform to the attribute naming conventions ([a-z_][a-z0-9_]{2,30}$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
88
        """
89
        Getter for io.
90
91
        :rtype: cleo.styles.output_style.OutputStyle
92
        """
93 1
        return self._io
94
95
    # ------------------------------------------------------------------------------------------------------------------
96 1
    @property
97
    def argument(self):
98
        """
99
        Getter for argument.
100
101
        :rtype: str
102
        """
103 1
        return self._argument
104
105
    # ------------------------------------------------------------------------------------------------------------------
106 1
    @argument.setter
107
    def argument(self, new_argument):
108
        """
109
        Setter for argument.
110
111
        :param str new_argument: The new argument.
112
        """
113
        self._argument = new_argument
114
115
    # ------------------------------------------------------------------------------------------------------------------
116 1
    def print_info(self, level):
117
        """
118
        Temp function for development.
119
120
        :param int level: the level of block commands.
121
        """
122
        self.io.writeln("{0!s}{1:4d} {2!s}".format(' ' * 4 * level, self.id, self.name))
123
124
        for node_id in self.child_nodes:
125
            node = in_scope(node_id)
126
127
            node.print_info(level + 1)
128
129
            out_scope(node)
130
131
    # ------------------------------------------------------------------------------------------------------------------
132 1
    def _remove_child_nodes(self, node_list):
133
        """
134
        Removes child nodes from list of child nodes of this node.
135
136
        :param list[int] node_list: The child nodes the be removed.
137
        """
138 1
        for node in node_list:
139 1
            self.child_nodes.remove(node)
140
141
    # ------------------------------------------------------------------------------------------------------------------
142 1
    def get_hierarchy_name(self):
143
        """
144
        Returns the hierarchy name if this node is a part of a hierarchy. Otherwise returns False.
145
146
        :rtype: str|bool
147
        """
148 1
        return False
149
150
    # ------------------------------------------------------------------------------------------------------------------
151 1
    @abc.abstractmethod
152
    def get_command(self):
153
        """
154
        Returns command of this node.
155
156
        :rtype: str
157
        """
158
        raise NotImplementedError()
159
160
    # ------------------------------------------------------------------------------------------------------------------
161 1
    def get_hierarchy_level(self, parent_hierarchy_level=-1):
162
        """
163
        Returns the hierarchy level if this node is a part of a hierarchy.
164
165
        :param int parent_hierarchy_level: The hierarchy level of the parent node in the same hierarchy.
166
167
        :rtype: int
168
        """
169
        raise RuntimeError("This method MUST only be called when a node is a part of an hierarchy.")
170
171
    # ------------------------------------------------------------------------------------------------------------------
172 1
    def get_option_value(self, option_name):
173
        """
174
        Returns the value of an option. Returns None if the option is not set.
175
176
        :param str option_name: The name of the option.
177
178
        :rtype: str
179
        """
180 1
        return self._options[option_name] if option_name in self._options else None
181
182
    # ------------------------------------------------------------------------------------------------------------------
183 1
    def set_option_value(self, option, value):
184
        """
185
        Sets value for option.
186
187
        :param str option: The name of an option
188
        :param mixed value: The value of an option
189
        """
190 1
        self._options[option] = value
191
192
    # ------------------------------------------------------------------------------------------------------------------
193 1
    @abc.abstractmethod
194
    def is_block_command(self):
195
        """
196
        Returns True if this node is created by a block command. Otherwise returns False.
197
198
        :rtype: bool
199
        """
200
        raise NotImplementedError()
201
202
    # ------------------------------------------------------------------------------------------------------------------
203 1
    def is_document_root(self):
204
        """
205
        Returns True if this node is a document root node. Otherwise returns False.
206
207
        :rtype: bool
208
        """
209 1
        return False
210
211
    # ------------------------------------------------------------------------------------------------------------------
212 1
    def is_hierarchy_root(self):
213
        """
214
        Returns True if this node can be the root of a hierarchy. Otherwise returns False.
215
216
        :rtype: bool
217
        """
218
        return False
219
220
    # ------------------------------------------------------------------------------------------------------------------
221 1
    @abc.abstractmethod
222
    def is_inline_command(self):
223
        """
224
        Returns True if this node is created by a inline command. Otherwise returns False.
225
226
        :rtype: bool
227
        """
228
        raise NotImplementedError()
229
230
    # ------------------------------------------------------------------------------------------------------------------
231 1
    def is_phrasing(self):
232
        """
233
        Returns True if this node is a phrasing node, i.e. is a part of a paragraph. Otherwise returns False.
234
235
        :rtype: bool
236
        """
237 1
        return False
238
239
    # ------------------------------------------------------------------------------------------------------------------
240 1
    def is_list_element(self):
241
        """
242
        Returns True if this node is a list element, e.g. an item in itemize. Otherwise returns False.
243
244
        :rtype: bool
245
        """
246 1
        return False
247
248
    # ------------------------------------------------------------------------------------------------------------------
249 1
    def append_child_node(self, child_node):
250
        """
251
        Appends a child node to the list of child nodes of the node.
252
253
        :param sdoc.sdoc2.node.Node.Node child_node: The new child node
254
        """
255 1
        self.child_nodes.append(child_node.id)
256
257
    # ------------------------------------------------------------------------------------------------------------------
258 1
    def prepare_content_tree(self):
259
        """
260
        Prepares this node for further processing.
261
        """
262 1
        for node_id in self.child_nodes:
263 1
            node = in_scope(node_id)
264
265 1
            node.prepare_content_tree()
266
267 1
            out_scope(node)
268
269
    # ------------------------------------------------------------------------------------------------------------------
270 1
    def number(self, numbers):
271
        """
272
        Numbers all numerable nodes such as chapters, sections, figures, and, items.
273
274
        :param numbers: The current numbers.
275
        """
276 1
        for node_id in self.child_nodes:
277 1
            node = in_scope(node_id)
278
279 1
            node.number(numbers)
280
281 1
            out_scope(node)
282
283
    # ------------------------------------------------------------------------------------------------------------------
284 1
    def get_enumerated_items(self):
285
        """
286
        Returns a list with a tuple with command and number of enumerated child nodes.
287
288
        Thi method is intended for unit test only.
289
290
        :rtype: list[(str,str)]
291
        """
292 1
        items = list()
293
294
        # First append the enumeration of this node (if any).
295 1
        if 'number' in self._options:
296 1
            items.append((self.get_command(), self._options['number'], self._argument))
297
298
        # Second append the enumeration of child nodes (if any).
299 1
        for node_id in self.child_nodes:
300 1
            node = in_scope(node_id)
301
302 1
            tmp = node.get_enumerated_items()
303 1
            if tmp:
304 1
                items.append(tmp)
305
306 1
            out_scope(node)
307
308 1
        return items
309
310
    # ------------------------------------------------------------------------------------------------------------------
311 1
    def parse_labels(self):
312
        """
313
        Parses all labels and call methods to collect labels.
314
        """
315 1
        self.modify_label_list()
316
317 1
        if self.labels:
318 1
            self.set_id_heading_node()
319
320
    # ------------------------------------------------------------------------------------------------------------------
321 1
    def modify_label_list(self):
322
        """
323
        Creates label list for each heading node, and for node_store. Removes label nodes from child list.
324
        """
325 1
        obsolete_node_ids = []
326 1
        for node_id in self.child_nodes:
327 1
            node = in_scope(node_id)
328
329 1
            if node.get_command() == 'label':
330
                # Appending in Node labels list.
331 1
                self.labels.append(node.id)
332
333 1
                self.append_label_list_in_node_store(node)
334 1
                if self.get_option_value('number'):
335 1
                    label_arg = self.get_option_value('number')
336 1
                    title_attribute = self.argument
337
                else:
338
                    label_arg = self.argument
339
                    title_attribute = None
340
341 1
                node_store.labels[node.argument] = {'argument': label_arg,
342
                                                    'title':    title_attribute}
343
344
                # Removing node from child nodes.
345 1
                obsolete_node_ids.append(node.id)
346
347 1
            node.parse_labels()
348
349 1
            out_scope(node)
350
351 1
        self._remove_child_nodes(obsolete_node_ids)
352
353
    # ------------------------------------------------------------------------------------------------------------------
354 1
    def append_label_list_in_node_store(self, node):
355
        """
356
        Appending in NodeStore labels list.
357
358
        :param sdoc.sdoc2.node.Node.Node node: The current node.
359
        """
360 1
        if node.argument not in node_store.labels:
361 1
            if self.argument:
362 1
                node_store.labels[node.argument] = self.argument
363
364
            else:
365
                if 'number' in self._options:
366
                    node_store.labels[node.argument] = self._options['number']
367
                else:
368
                    node_store.labels[node.argument] = node.argument
369
370
        else:
371
            # @todo log definitions of both labels
372
            raise NameError('Duplicate label', node.argument)
373
374
    # ------------------------------------------------------------------------------------------------------------------
375 1
    def set_id_heading_node(self):
376
        """
377
        Sets id to heading node. (Argument of first label)
378
        """
379 1
        node = in_scope(self.labels[0])
380 1
        self._options['id'] = node.argument
381 1
        out_scope(node)
382
383
    # ------------------------------------------------------------------------------------------------------------------
384 1
    def change_ref_argument(self):
385
        """
386
        Changes reference argument on number of depending heading node.
387
        """
388 1
        for node_id in self.child_nodes:
389 1
            node = in_scope(node_id)
390
391 1
            if node.get_command() == 'ref':
392
393 1
                if node.argument in node_store.labels:
394 1
                    node.set_option_value('href', '#{0}'.format(node.argument))
395
396 1
                    if node_store.labels[node.argument]['title']:
397 1
                        node.set_option_value('title', node_store.labels[node.argument]['title'])
398
399 1
                    node.text = node_store.labels[node.argument]['argument']
400
401
                else:
402 1
                    node_store.error("Label '{}' not found".format(node.argument), node)
403
404 1
            node.change_ref_argument()
405
406 1
            out_scope(node)
407
408
# ----------------------------------------------------------------------------------------------------------------------
409