1
|
|
|
# -*- coding: utf-8 -*- |
2
|
|
|
|
3
|
1 |
|
import six |
4
|
|
|
|
5
|
1 |
|
from abc import ABCMeta, abstractmethod |
6
|
|
|
|
7
|
|
|
|
8
|
1 |
|
class SectionParser(six.with_metaclass(ABCMeta)): |
9
|
|
|
""" |
10
|
|
|
Abstract base parser of a single section in a mission file. |
11
|
|
|
|
12
|
|
|
A common approach to parse a section can be described in the following way: |
13
|
|
|
|
14
|
|
|
#. Pass a section name (e.g. 'MAIN') to :meth:`start` method. If parser can |
15
|
|
|
process a section with such name, it will return `True` and then you can |
16
|
|
|
proceed. |
17
|
|
|
#. Pass section lines one-by-one to :meth:`parse_line`. |
18
|
|
|
#. When you are done, get your parsed data by calling :meth:`stop`. This |
19
|
|
|
will tell the parser that no more data will be given and the parsing can |
20
|
|
|
be finished. |
21
|
|
|
|
22
|
|
|
| |
23
|
|
|
**Example**: |
24
|
|
|
|
25
|
|
|
.. code-block:: python |
26
|
|
|
|
27
|
|
|
section_name = "Test section" |
28
|
|
|
lines = ["foo", "bar", "baz", "qux", ] |
29
|
|
|
parser = SomeParser() |
30
|
|
|
|
31
|
|
|
if parser.start(section_name): |
32
|
|
|
for line in lines: |
33
|
|
|
parser.parse_line(line) |
34
|
|
|
result = parser.stop() |
35
|
|
|
|
36
|
|
|
""" |
37
|
|
|
#: Tells whether a parser was started. |
38
|
1 |
|
running = False |
39
|
|
|
|
40
|
|
|
#: An internal buffer which can be redefined. |
41
|
1 |
|
data = None |
42
|
|
|
|
43
|
1 |
|
def start(self, section_name): |
44
|
|
|
""" |
45
|
|
|
Try to start a parser. If a section with given name can be parsed, the |
46
|
|
|
parser will initialize it's internal data structures and set |
47
|
|
|
:attr:`running` to `True`. |
48
|
|
|
|
49
|
|
|
:param str section_name: a name of section which is going to be parsed |
50
|
|
|
|
51
|
|
|
:returns: `True` if section with a given name can be parsed by parser, |
52
|
|
|
`False` otherwise |
53
|
|
|
:rtype: :class:`bool` |
54
|
|
|
""" |
55
|
1 |
|
result = self.check_section_name(section_name) |
56
|
1 |
|
if result: |
57
|
1 |
|
self.running = True |
58
|
1 |
|
self.init_parser(section_name) |
59
|
1 |
|
return result |
60
|
|
|
|
61
|
1 |
|
@abstractmethod |
62
|
|
|
def check_section_name(self, section_name): |
63
|
|
|
""" |
64
|
|
|
Check whether a section with a given name can be parsed. |
65
|
|
|
|
66
|
|
|
:param str section_name: a name of section which is going to be parsed |
67
|
|
|
|
68
|
|
|
:returns: `True` if section with a given name can be parsed by parser, |
69
|
|
|
`False` otherwise |
70
|
|
|
:rtype: :class:`bool` |
71
|
|
|
""" |
72
|
|
|
|
73
|
1 |
|
@abstractmethod |
74
|
|
|
def init_parser(self, section_name): |
75
|
|
|
""" |
76
|
|
|
Abstract method which is called by :meth:`start` to initialize |
77
|
|
|
internal data structures. |
78
|
|
|
|
79
|
|
|
:param str section_name: a name of section which is going to be parsed |
80
|
|
|
|
81
|
|
|
:returns: ``None`` |
82
|
|
|
""" |
83
|
|
|
|
84
|
1 |
|
@abstractmethod |
85
|
|
|
def parse_line(self, line): |
86
|
|
|
""" |
87
|
|
|
Abstract method which is called manually to parse a line from mission |
88
|
|
|
section. |
89
|
|
|
|
90
|
|
|
:param str line: a single line to parse |
91
|
|
|
|
92
|
|
|
:returns: ``None`` |
93
|
|
|
""" |
94
|
|
|
|
95
|
1 |
|
def stop(self): |
96
|
|
|
""" |
97
|
|
|
Stops parser and returns fully processed data. |
98
|
|
|
|
99
|
|
|
:returns: a data structure returned by :meth:`clean` method |
100
|
|
|
|
101
|
|
|
:raises RuntimeError: if parser was not started |
102
|
|
|
""" |
103
|
1 |
|
if not self.running: |
104
|
1 |
|
raise RuntimeError("Cannot stop parser which is not running") |
105
|
|
|
|
106
|
1 |
|
self.running = False |
107
|
1 |
|
return self.clean() |
108
|
|
|
|
109
|
1 |
|
def clean(self): |
110
|
|
|
""" |
111
|
|
|
Returns fully parsed data. Is called by :meth:`stop` method. |
112
|
|
|
|
113
|
|
|
:returns: a data structure which is specific for every subclass |
114
|
|
|
""" |
115
|
1 |
|
return self.data |
116
|
|
|
|
117
|
|
|
|
118
|
1 |
|
class ValuesParser(six.with_metaclass(ABCMeta, SectionParser)): |
119
|
|
|
""" |
120
|
|
|
This is a base class for parsers which assume that a section, which is |
121
|
|
|
going to be parsed, consists of key-value pairs with unique keys, one pair |
122
|
|
|
per line. |
123
|
|
|
|
124
|
|
|
**Section definition example**:: |
125
|
|
|
|
126
|
|
|
[section name] |
127
|
|
|
key1 value1 |
128
|
|
|
key2 value2 |
129
|
|
|
key3 value3 |
130
|
|
|
""" |
131
|
|
|
|
132
|
1 |
|
def init_parser(self, section_name): |
133
|
|
|
""" |
134
|
|
|
Implements abstract method. See :meth:`SectionParser.init_parser` for |
135
|
|
|
semantics. |
136
|
|
|
|
137
|
|
|
Initializes a dictionary to store raw keys and their values. |
138
|
|
|
""" |
139
|
1 |
|
self.data = {} |
140
|
|
|
|
141
|
1 |
|
def parse_line(self, line): |
142
|
|
|
""" |
143
|
|
|
Implements abstract method. See :meth:`SectionParser.parse_line` for |
144
|
|
|
semantics. |
145
|
|
|
|
146
|
|
|
Splits line into key-value pair and puts it into internal dictionary. |
147
|
|
|
""" |
148
|
1 |
|
key, value = line.strip().split() |
149
|
1 |
|
self.data.update({key: value}) |
150
|
|
|
|
151
|
|
|
|
152
|
1 |
|
class CollectingParser(six.with_metaclass(ABCMeta, SectionParser)): |
153
|
|
|
""" |
154
|
|
|
This is a base class for parsers which assume that a section, which is |
155
|
|
|
going to be parsed, consists of homogeneous lines which describe different |
156
|
|
|
objects with one set of attributes. |
157
|
|
|
|
158
|
|
|
**Section definition example**:: |
159
|
|
|
|
160
|
|
|
[section name] |
161
|
|
|
object1_attr1 object1_attr2 object1_attr3 object1_attr4 |
162
|
|
|
object2_attr1 object2_attr2 object2_attr3 object2_attr4 |
163
|
|
|
object3_attr1 object3_attr2 object3_attr3 object3_attr4 |
164
|
|
|
""" |
165
|
|
|
|
166
|
1 |
|
def init_parser(self, section_name): |
167
|
|
|
""" |
168
|
|
|
Implements abstract method. See :meth:`SectionParser.init_parser` for |
169
|
|
|
semantics. |
170
|
|
|
|
171
|
|
|
Initializes a list for storing collection of objects. |
172
|
|
|
""" |
173
|
1 |
|
self.data = [] |
174
|
|
|
|
175
|
1 |
|
def parse_line(self, line): |
176
|
|
|
""" |
177
|
|
|
Implements abstract method. See :meth:`SectionParser.parse_line` for |
178
|
|
|
semantics. |
179
|
|
|
|
180
|
|
|
Just puts entire line to internal buffer. You probably will want to |
181
|
|
|
redefine this method to do some extra job on each line. |
182
|
|
|
""" |
183
|
|
|
self.data.append(line.strip()) |
184
|
|
|
|