Completed
Push — master ( 025da3...410210 )
by Oleksandr
01:36
created

parse_line()   A

Complexity

Conditions 1

Size

Total Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1
Metric Value
cc 1
dl 0
loc 10
ccs 1
cts 1
cp 1
crap 1
rs 9.4286
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