#!/usr/bin/env python
# vim: set expandtab:
"""
.. module:: config
   :synopsis: Parse Asterisk configuration files.

This module provides parsing functionality for asterisk config files.

Example
----------

.. code-block:: python

   import asterisk.config
   import sys
   
   # load and parse the config file
   try:
      config = asterisk.config.Config('/etc/asterisk/extensions.conf')
   except asterisk.config.ParseError as e:
      print "Parse Error line: %s: %s" % (e.line, e.strerror)
      sys.exit(1)
   except IOError as e:
      print "Error opening file: %s" % e.strerror
      sys.exit(1)
   
   # print our parsed output
   for category in config.categories:
      print '[%s]' % category.name   # print the current category

      for item in category.items:
         print '   %s = %s' % (item.name, item.value)


Specification
-------------
"""

import sys


class ParseError(Exception):
    pass


class Line(object):
    def __init__(self, line, number):
        self.line = ''
        self.comment = ''
        line = line.strip()    # I guess we don't preserve indentation
        self.number = number
        parts = line.split(';')
        if len(parts) >= 2:
            self.line = parts[0].strip()
            self.comment = ';'.join(
                parts[1:])  # Just in case the comment contained ';'
        else:
            self.line = line

    def __str__(self):
        return self.get_line()

    def get_line(self):
        if self.comment and self.line:
            return '%s\t;%s' % (self.line, self.comment)
        elif self.comment and not self.line:
            return ';%s' % self.comment
        return self.line


class Category(Line):
    def __init__(self, line='', num=-1, name=None):
        Line.__init__(self, line, num)
        if self.line:
            if (self.line[0] != '[' or self.line[-1] != ']'):
                raise ParseError(
                    self.number, "Missing '[' or ']' in category definition")
            self.name = self.line[1:-1]
        elif name:
            self.name = name
        else:
            raise Exception(
                "Must provide name or line representing a category")

        self.items = []
        self.comments = []

    def get_line(self):
        if self.comment:
            return '[%s]\t;%s' % (self.name, self.comment)
        return '[%s]' % self.name

    def append(self, item):
        self.items.append(item)

    def insert(self, index, item):
        self.items.insert(index, item)

    def pop(self, index=-1):
        self.items.pop(index)

    def remove(self, item):
        self.items.remove(item)


class Item(Line):
    def __init__(self, line='', num=-1, name=None, value=None):
        Line.__init__(self, line, num)
        self.style = ''
        if self.line:
            self.parse()
        elif (name and value):
            self.name = name
            self.value = value
        else:
            raise Exception("Must provide name or value representing an item")

    def parse(self):
        try:
            name, value = self.line.split('=', 1)
        except ValueError:
            if self.line.strip()[-1] == ']':
                raise ParseError(self.number, "Category name missing '['")
            else:
                raise ParseError(
                    self.number, "Item must be in name = value pairs")

        if value and value[0] == '>':
            self.style = '>'  # preserve the style of the original
            value = value[1:].strip()
        self.name = name.strip()
        self.value = value

    def get_line(self):
        if self.comment:
            return '%s =%s %s\t;%s' % (self.name, self.style, self.value, self.comment)
        return '%s =%s %s' % (self.name, self.style, self.value)


class Config(object):
    def __init__(self, filename):
        self.filename = filename
        self.raw_lines = []     # Holds the raw strings
        self.lines = []         # Holds things in order
        self.categories = []

        # load and parse the file
        self.load()
        self.parse()

    def load(self):
        self.raw_lines = open(self.filename).readlines()
        #try:
            #self.raw_lines = open(self.filename).readlines()
        #except IOError:
            #sys.stderr.write('WARNING: error opening filename: %s  No data read. Starting new file?' % self.filename)
            #self.raw_lines = []

    def parse(self):
        cat = None
        num = 0
        for line in self.raw_lines:
            num += 1
            line = line.strip()
            if not line or line[0] == ';':
                item = Line(line or '', num)
                self.lines.append(item)
                if cat:
                    cat.comments.append(item)
                continue
            elif line[0] == '[':
                cat = Category(line, num)
                self.lines.append(cat)
                self.categories.append(cat)
                continue
            else:
                item = Item(line, num)
                self.lines.append(item)
                if cat:
                    cat.append(item)
                continue
