# Copyright 2014 DreamHost, LLC # # Author: DreamHost, LLC # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack LLC. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. class ParseError(Exception): def __init__(self, message, lineno, line): self.msg = message self.line = line self.lineno = lineno def __str__(self): return 'at line %d, %s: %r' % (self.lineno, self.msg, self.line) class BaseParser(object): lineno = 0 parse_exc = ParseError def _assignment(self, key, value): self.assignment(key, value) return None, [] def _get_section(self, line): if line[-1] != ']': return self.error_no_section_end_bracket(line) if len(line) <= 2: return self.error_no_section_name(line) return line[1:-1] def _split_key_value(self, line): colon = line.find(':') equal = line.find('=') if colon < 0 and equal < 0: return self.error_invalid_assignment(line) if colon < 0 or (equal >= 0 and equal < colon): key, value = line[:equal], line[equal + 1:] else: key, value = line[:colon], line[colon + 1:] value = value.strip() if ((value and value[0] == value[-1]) and (value[0] == "\"" or value[0] == "'")): value = value[1:-1] return key.strip(), [value] def parse(self, lineiter): key = None value = [] for line in lineiter: self.lineno += 1 line = line.rstrip() if not line: # Blank line, ends multi-line values if key: key, value = self._assignment(key, value) continue elif line[0] in (' ', '\t'): # Continuation of previous assignment if key is None: self.error_unexpected_continuation(line) else: value.append(line.lstrip()) continue if key: # Flush previous assignment, if any key, value = self._assignment(key, value) if line[0] == '[': # Section start section = self._get_section(line) if section: self.new_section(section) elif line[0] in '#;': self.comment(line[1:].lstrip()) else: key, value = self._split_key_value(line) if not key: return self.error_empty_key(line) if key: # Flush previous assignment, if any self._assignment(key, value) def assignment(self, key, value): """Called when a full assignment is parsed""" raise NotImplementedError() def new_section(self, section): """Called when a new section is started""" raise NotImplementedError() def comment(self, comment): """Called when a comment is parsed""" pass def error_invalid_assignment(self, line): raise self.parse_exc("No ':' or '=' found in assignment", self.lineno, line) def error_empty_key(self, line): raise self.parse_exc('Key cannot be empty', self.lineno, line) def error_unexpected_continuation(self, line): raise self.parse_exc('Unexpected continuation line', self.lineno, line) def error_no_section_end_bracket(self, line): raise self.parse_exc('Invalid section (must end with ])', self.lineno, line) def error_no_section_name(self, line): raise self.parse_exc('Empty section name', self.lineno, line)