blob: 9bf399f0c7c9f6098887715b8b7e8c360e7deecd [file] [log] [blame]
Matthew Treinish481466b2012-12-20 17:16:01 -05001# vim: tabstop=4 shiftwidth=4 softtabstop=4
2
3# Copyright 2012 OpenStack LLC.
4#
5# Licensed under the Apache License, Version 2.0 (the "License"); you may
6# not use this file except in compliance with the License. You may obtain
7# a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14# License for the specific language governing permissions and limitations
15# under the License.
16
17
18class ParseError(Exception):
19 def __init__(self, message, lineno, line):
20 self.msg = message
21 self.line = line
22 self.lineno = lineno
23
24 def __str__(self):
25 return 'at line %d, %s: %r' % (self.lineno, self.msg, self.line)
26
27
28class BaseParser(object):
29 lineno = 0
30 parse_exc = ParseError
31
32 def _assignment(self, key, value):
33 self.assignment(key, value)
34 return None, []
35
36 def _get_section(self, line):
37 if line[-1] != ']':
38 return self.error_no_section_end_bracket(line)
39 if len(line) <= 2:
40 return self.error_no_section_name(line)
41
42 return line[1:-1]
43
44 def _split_key_value(self, line):
45 colon = line.find(':')
46 equal = line.find('=')
47 if colon < 0 and equal < 0:
48 return self.error_invalid_assignment(line)
49
50 if colon < 0 or (equal >= 0 and equal < colon):
51 key, value = line[:equal], line[equal + 1:]
52 else:
53 key, value = line[:colon], line[colon + 1:]
54
55 value = value.strip()
56 if ((value and value[0] == value[-1]) and
Joe Gordon2b0591d2013-02-14 23:18:39 +000057 (value[0] == "\"" or value[0] == "'")):
Matthew Treinish481466b2012-12-20 17:16:01 -050058 value = value[1:-1]
59 return key.strip(), [value]
60
61 def parse(self, lineiter):
62 key = None
63 value = []
64
65 for line in lineiter:
66 self.lineno += 1
67
68 line = line.rstrip()
69 if not line:
70 # Blank line, ends multi-line values
71 if key:
72 key, value = self._assignment(key, value)
73 continue
74 elif line[0] in (' ', '\t'):
75 # Continuation of previous assignment
76 if key is None:
77 self.error_unexpected_continuation(line)
78 else:
79 value.append(line.lstrip())
80 continue
81
82 if key:
83 # Flush previous assignment, if any
84 key, value = self._assignment(key, value)
85
86 if line[0] == '[':
87 # Section start
88 section = self._get_section(line)
89 if section:
90 self.new_section(section)
91 elif line[0] in '#;':
92 self.comment(line[1:].lstrip())
93 else:
94 key, value = self._split_key_value(line)
95 if not key:
96 return self.error_empty_key(line)
97
98 if key:
99 # Flush previous assignment, if any
100 self._assignment(key, value)
101
102 def assignment(self, key, value):
Joe Gordon2b0591d2013-02-14 23:18:39 +0000103 """Called when a full assignment is parsed"""
Matthew Treinish481466b2012-12-20 17:16:01 -0500104 raise NotImplementedError()
105
106 def new_section(self, section):
Joe Gordon2b0591d2013-02-14 23:18:39 +0000107 """Called when a new section is started"""
Matthew Treinish481466b2012-12-20 17:16:01 -0500108 raise NotImplementedError()
109
110 def comment(self, comment):
Joe Gordon2b0591d2013-02-14 23:18:39 +0000111 """Called when a comment is parsed"""
Matthew Treinish481466b2012-12-20 17:16:01 -0500112 pass
113
114 def error_invalid_assignment(self, line):
115 raise self.parse_exc("No ':' or '=' found in assignment",
116 self.lineno, line)
117
118 def error_empty_key(self, line):
119 raise self.parse_exc('Key cannot be empty', self.lineno, line)
120
121 def error_unexpected_continuation(self, line):
122 raise self.parse_exc('Unexpected continuation line',
123 self.lineno, line)
124
125 def error_no_section_end_bracket(self, line):
126 raise self.parse_exc('Invalid section (must end with ])',
127 self.lineno, line)
128
129 def error_no_section_name(self, line):
130 raise self.parse_exc('Empty section name', self.lineno, line)