text: # Copyright 2012 OpenStack Foundation
  #
  #    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, line_no, line):
          self.msg = message
          self.line = line
          self.line_no = line_no
  
      def __str__(self):
          return 'at line %d, %s: %r' % (self.line_no, self.msg, self.line)
  
  
  class BaseParser(object):
  
      PARSE_EXC = ParseError
  
      def __init__(self):
          super(BaseParser, self).__init__()
          self.line_no = 0
  
      def _assignment(self, key, value):
          self.assignment(key, value)
          return None, []
  
      def _get_section(self, line):
          if not line.endswith(']'):
              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 (0 <= 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.startswith(("\"", "'")):
              value = value[1:-1]
          return key.strip(), [value]
  
      def _single_line_parse(self, line, key, value):
          self.line_no += 1
  
          if line.startswith(('#', ';')):
              self.comment(line[1:].strip())
              return key, value
  
          active, _, comment = line.partition(';')
          self.comment(comment.strip())
  
          if not active:
              # Blank line, ends multi-line values
              if key:
                  key, value = self._assignment(key, value)
              return key, value
  
          if active.startswith((' ', '\t')):
              # Continuation of previous assignment
              if key is None:
                  return self.error_unexpected_continuation(line)
  
              value.append(active.lstrip())
              return key, value
  
          if key:
              # Flush previous assignment, if any
              key, value = self._assignment(key, value)
  
          if active.startswith('['):
              # Section start
              section = self._get_section(active)
              if section:
                  self.new_section(section)
  
          else:
              key, value = self._split_key_value(active)
              if not key:
                  return self.error_empty_key(line)
  
          return key, value
  
      def parse(self, line_iter=None):
          if line_iter is None:
              return
  
          key = None
          value = []
  
          for line in line_iter:
              key, value = self._single_line_parse(line, key, value)
  
          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 make_parser_error(self, template, line):
          raise self.PARSE_EXC(template, self.line_no, line)
  
      def error_invalid_assignment(self, line):
          self.make_parser_error("No ':' or '=' found in assignment", line)
  
      def error_empty_key(self, line):
          self.make_parser_error('Key cannot be empty', line)
  
      def error_unexpected_continuation(self, line):
          self.make_parser_error('Unexpected continuation line', line)
  
      def error_no_section_end_bracket(self, line):
          self.make_parser_error('Invalid section (must end with ])', line)
  
      def error_no_section_name(self, line):
          self.make_parser_error('Empty section name', line)
  
  
  class ConfigParser(BaseParser):
      """Parses a single config file, populating 'sections' to look like:
  
          {'DEFAULT': {'key': [value, ...], ...},
           ...}
      """
  
      def __init__(self, filename, sections):
          super(ConfigParser, self).__init__()
          self.filename = filename
          self.sections = sections
          self.section = None
  
      def parse(self, line_iter=None):
          with open(self.filename) as f:
              return super(ConfigParser, self).parse(f)
  
      def new_section(self, section):
          self.section = section
          self.sections.setdefault(self.section, [])
  
      def assignment(self, key, value):
          if not self.section:
              raise self.error_no_section()
  
          value = '\n'.join(value)
          self.sections[self.section].append([key, value])
  
      def error_no_section(self):
          self.make_parser_error('Section must be started before assignment', '')
  