Completed
Push — master ( 9797b3...5db226 )
by P.R.
03:05
created

Node.io()   A

Complexity

Conditions 1

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 8
ccs 2
cts 2
cp 1
rs 9.4285
c 0
b 0
f 0
cc 1
crap 1
1
"""
2
SDoc
3
4
Copyright 2016 Set Based IT Consultancy
5
6
Licence MIT
7
"""
8
# ----------------------------------------------------------------------------------------------------------------------
9 1
import abc
10 1
from sdoc.sdoc2 import in_scope, out_scope, node_store
11
12
13 1
class Node:
1 ignored issue
show
best-practice introduced by
Too many public methods (23/20)
Loading history...
14
    """
15
    Abstract class for SDoc2 nodes.
16
    """
17
18
    # ------------------------------------------------------------------------------------------------------------------
19 1
    def __init__(self, io, name, options=None, argument=''):
20
        """
21
        Object constructor.
22
23
        :param None|cleo.styles.output_style.OutputStyle io: The IO object.
24
        :param str name: The (command) name of this node.
25
        :param dict[str,str] options: The options of this node.
26
        :param str argument: The argument of this node (inline commands only).
27
        """
28 1
        self._io = io
29
        """
30
        The IO object.
31
32
        :type None|cleo.styles.output_style.OutputStyle:
33
        """
34
35 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...
36
        """
37
        The ID of this SDoc2 node.
38
39
        :type: int
40
        """
41
42 1
        self.name = name
43
        """
44
        The (command) name of this node.
45
46
        :type: str
47
        """
48
49 1
        self._argument = argument
50
        """
51
        The argument of this node (inline commands only).
52
53
        :type: str
54
        """
55
56 1
        self._options = options if options else {}
57
        """
58
        The options of this node.
59
60
        :type: dict[str,mixed]
61
        """
62
63 1
        self.child_nodes = []
64
        """
65
        The ID's of the SDoc2 child nodes of this SDoc2 node.
66
67
        :type: list[int]
68
        """
69
70 1
        self.position = None
71
        """
72
        The position where this node is defined.
73
74
        :type: None|sdoc.sdoc2.Position.Position
75
        """
76
77 1
        self.labels = []
78 1
        """
79
        The list of labels in the node.
80
81
        :type:
82
        """
83
84
    # ------------------------------------------------------------------------------------------------------------------
85 1
    @property
86
    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...
87
        """
88
        Getter for io.
89
90
        :rtype: cleo.styles.output_style.OutputStyle
91
        """
92 1
        return self._io
93
94
    # ------------------------------------------------------------------------------------------------------------------
95 1
    @property
96
    def argument(self):
97
        """
98
        Getter for argument.
99
100
        :rtype: str
101
        """
102
        return self._argument
103
104
    # ------------------------------------------------------------------------------------------------------------------
105 1
    @argument.setter
106
    def argument(self, new_argument):
107
        """
108
        Setter for argument.
109
110
        :param str new_argument: The new argument.
111
        """
112
        self._argument = new_argument
113
114
    # ------------------------------------------------------------------------------------------------------------------
115 1
    def print_info(self, level):
116
        """
117
        Temp function for development.
118
119
        :param int level: the level of block commands.
120
        """
121
        self.io.writeln("{0!s}{1:4d} {2!s}".format(' ' * 4 * level, self.id, self.name))
122
        for node_id in self.child_nodes:
123
            node = in_scope(node_id)
124
125
            node.print_info(level + 1)
126
127
            out_scope(node)
128
129
    # ------------------------------------------------------------------------------------------------------------------
130 1
    def get_hierarchy_name(self):
1 ignored issue
show
Coding Style introduced by
This method could be written as a function/class method.

If a method does not access any attributes of the class, it could also be implemented as a function or static method. This can help improve readability. For example

class Foo:
    def some_method(self, x, y):
        return x + y;

could be written as

class Foo:
    @classmethod
    def some_method(cls, x, y):
        return x + y;
Loading history...
131
        """
132
        Returns the hierarchy name if this node is a part of a hierarchy. Otherwise returns False.
133
134
        :rtype: str|bool
135
        """
136 1
        return False
137
138
    # ------------------------------------------------------------------------------------------------------------------
139 1
    @abc.abstractmethod
140
    def get_command(self):
141
        """
142
        Returns command of this node.
143
144
        :rtype: str
145
        """
146
        raise NotImplementedError()
147
148
    # ------------------------------------------------------------------------------------------------------------------
149 1
    def get_hierarchy_level(self, parent_hierarchy_level=-1):
2 ignored issues
show
Unused Code introduced by
The argument parent_hierarchy_level seems to be unused.
Loading history...
Coding Style introduced by
This method could be written as a function/class method.

If a method does not access any attributes of the class, it could also be implemented as a function or static method. This can help improve readability. For example

class Foo:
    def some_method(self, x, y):
        return x + y;

could be written as

class Foo:
    @classmethod
    def some_method(cls, x, y):
        return x + y;
Loading history...
150
        """
151
        Returns the hierarchy level if this node is a part of a hierarchy.
152
153
        :param int parent_hierarchy_level: The hierarchy level of the parent node in the same hierarchy.
154
155
        :rtype: int
156
        """
157
        raise RuntimeError("This method MUST only be called when a node is a part of an hierarchy.")
158
159
    # ------------------------------------------------------------------------------------------------------------------
160 1
    def get_option_value(self, option_name):
161
        """
162
        Returns the value of an option. Returns None if the option is not set.
163
164
        :param str option_name: The name of the option.
165
166
        :rtype: str
167
        """
168
        return self._options[option_name] if option_name in self._options else None
169
170
    # ------------------------------------------------------------------------------------------------------------------
171 1
    def set_option_value(self, option, value):
172
        """
173
        Sets value for option.
174
175
        :param str option: The name of an option
176
        :param mixed value: The value of an option
177
        """
178
        self._options[option] = value
179
180
    # ------------------------------------------------------------------------------------------------------------------
181 1
    @abc.abstractmethod
182
    def is_block_command(self):
183
        """
184
        Returns True if this node is created by a block command. Otherwise returns False.
185
186
        :rtype: bool
187
        """
188
        raise NotImplementedError()
189
190
    # ------------------------------------------------------------------------------------------------------------------
191 1
    def is_document_root(self):
1 ignored issue
show
Coding Style introduced by
This method could be written as a function/class method.

If a method does not access any attributes of the class, it could also be implemented as a function or static method. This can help improve readability. For example

class Foo:
    def some_method(self, x, y):
        return x + y;

could be written as

class Foo:
    @classmethod
    def some_method(cls, x, y):
        return x + y;
Loading history...
192
        """
193
        Returns True if this node is a document root node. Otherwise returns False.
194
195
        :rtype: bool
196
        """
197 1
        return False
198
199
    # ------------------------------------------------------------------------------------------------------------------
200 1
    def is_hierarchy_root(self):
1 ignored issue
show
Coding Style introduced by
This method could be written as a function/class method.

If a method does not access any attributes of the class, it could also be implemented as a function or static method. This can help improve readability. For example

class Foo:
    def some_method(self, x, y):
        return x + y;

could be written as

class Foo:
    @classmethod
    def some_method(cls, x, y):
        return x + y;
Loading history...
201
        """
202
        Returns True if this node can be the root of a hierarchy. Otherwise returns False.
203
204
        :rtype: bool
205
        """
206
        return False
207
208
    # ------------------------------------------------------------------------------------------------------------------
209 1
    @abc.abstractmethod
210
    def is_inline_command(self):
211
        """
212
        Returns True if this node is created by a inline command. Otherwise returns False.
213
214
        :rtype: bool
215
        """
216
        raise NotImplementedError()
217
218
    # ------------------------------------------------------------------------------------------------------------------
219 1
    def is_phrasing(self):
1 ignored issue
show
Coding Style introduced by
This method could be written as a function/class method.

If a method does not access any attributes of the class, it could also be implemented as a function or static method. This can help improve readability. For example

class Foo:
    def some_method(self, x, y):
        return x + y;

could be written as

class Foo:
    @classmethod
    def some_method(cls, x, y):
        return x + y;
Loading history...
220
        """
221
        Returns True if this node is a phrasing node, i.e. is a part of a paragraph. Otherwise returns False.
222
223
        :rtype: bool
224
        """
225 1
        return False
226
227
    # ------------------------------------------------------------------------------------------------------------------
228 1
    def is_list_element(self):
1 ignored issue
show
Coding Style introduced by
This method could be written as a function/class method.

If a method does not access any attributes of the class, it could also be implemented as a function or static method. This can help improve readability. For example

class Foo:
    def some_method(self, x, y):
        return x + y;

could be written as

class Foo:
    @classmethod
    def some_method(cls, x, y):
        return x + y;
Loading history...
229
        """
230
        Returns True if this node is a list element, e.g. an item in itemize. Otherwise returns False.
231
232
        :rtype: bool
233
        """
234 1
        return False
235
236
    # ------------------------------------------------------------------------------------------------------------------
237 1
    def append_child_node(self, child_node):
238
        """
239
        Appends a child node to the list of child nodes of the node.
240
241
        :param sdoc.sdoc2.node.Node.Node child_node: The new child node
242
        """
243 1
        self.child_nodes.append(child_node.id)
244
245
    # ------------------------------------------------------------------------------------------------------------------
246 1
    def prepare_content_tree(self):
247
        """
248
        Prepares this node for further processing.
249
        """
250 1
        for node_id in self.child_nodes:
251 1
            node = in_scope(node_id)
252
253 1
            node.prepare_content_tree()
254
255 1
            out_scope(node)
256
257
    # ------------------------------------------------------------------------------------------------------------------
258 1
    def number(self, numbers):
259
        """
260
        Numbers all numerable nodes such as chapters, sections, figures, and, items.
261
262
        :param numbers: The current numbers.
263
        """
264 1
        for node_id in self.child_nodes:
265 1
            node = in_scope(node_id)
266
267 1
            node.number(numbers)
268
269 1
            out_scope(node)
270
271
    # ------------------------------------------------------------------------------------------------------------------
272 1
    def get_enumerated_items(self):
273
        """
274
        Returns a list with a tuple with command and number of enumerated child nodes.
275
276
        Thi method is intended for unit test only.
277
278
        :rtype: list[(str,str)]
279
        """
280 1
        items = list()
281
282
        # First append the enumeration of this node (if any).
283 1
        if 'number' in self._options:
284 1
            items.append((self.get_command(), self._options['number'], self._argument))
285
286
        # Second append the enumeration of child nodes (if any).
287 1
        for node_id in self.child_nodes:
288 1
            node = in_scope(node_id)
289
290 1
            tmp = node.get_enumerated_items()
291 1
            if tmp:
292 1
                items.append(tmp)
293
294 1
            out_scope(node)
295
296 1
        return items
297
298
    # ------------------------------------------------------------------------------------------------------------------
299 1
    def parse_labels(self):
300
        """
301
        Parses all labels and call methods to collect labels.and for
302
        """
303
        self.modify_label_list()
304
305
        if self.labels:
306
            self.set_id_heading_node()
307
308
    # ------------------------------------------------------------------------------------------------------------------
309 1
    def modify_label_list(self):
310
        """
311
        Creates label list for each heading node, and for node_store. Removes label nodes from child list.
312
        """
313
        for node_id in self.child_nodes:
314
            node = in_scope(node_id)
315
316
            if node.get_command() == 'label':
317
                # Appending in Node labels list.
318
                self.labels.append(node.id)
319
320
                self.append_label_list_in_node_store(node)
321
                if self.get_option_value('number'):
322
                    label_arg = self.get_option_value('number')
323
                    title_attribute = self.argument
324
                else:
325
                    label_arg = self.argument
326
                    title_attribute = None
327
328
                node_store.labels[node.argument] = {'argument': label_arg,
329
                                                    'title': title_attribute}
330
331
                # Removing node from child nodes.
332
                self.child_nodes.remove(node.id)
333
334
            node.parse_labels()
335
336
            out_scope(node)
337
338
    # ------------------------------------------------------------------------------------------------------------------
339 1
    def append_label_list_in_node_store(self, node):
340
        """
341
        Appending in NodeStore labels list.
342
343
        :param sdoc.sdoc2.node.Node.Node node: The current node.
344
        """
345
        if node.argument not in node_store.labels:
346
            if self.argument:
347
                node_store.labels[node.argument] = self.argument
348
349
            else:
350
                if 'number' in self._options:
351
                    node_store.labels[node.argument] = self._options['number']
352
                else:
353
                    node_store.labels[node.argument] = node.argument
354
355
        else:
356
            # @todo log definitions of both labels
357
            raise NameError('Duplicate label', node.argument)
358
359
    # ------------------------------------------------------------------------------------------------------------------
360 1
    def set_id_heading_node(self):
361
        """
362
        Sets id to heading node. (Argument of first label)
363
        """
364
        node = in_scope(self.labels[0])
365
        self._options['id'] = node.argument
366
        out_scope(node)
367
368
    # ------------------------------------------------------------------------------------------------------------------
369 1
    def change_ref_argument(self):
370
        """
371
        Changes reference argument on number of depending heading node.
372
        """
373
        for node_id in self.child_nodes:
374
            node = in_scope(node_id)
375
376
            if node.argument in node_store.labels and node.get_command() == 'ref':
377
                node.set_option_value('href', '#{0}'.format(node.argument))
378
379
                if node_store.labels[node.argument]['title']:
380
                    node.set_option_value('title', node_store.labels[node.argument]['title'])
381
382
                node.argument = node_store.labels[node.argument]['argument']
383
384
            node.change_ref_argument()
385
386
            out_scope(node)
387
388
# ----------------------------------------------------------------------------------------------------------------------
389