Update the conversion script from sip.conf to pjsip.conf
[asterisk/asterisk.git] / contrib / scripts / sip_to_pjsip / astconfigparser.py
@@ -3,11 +3,12 @@ import re
 from astdicts import OrderedDict
 from astdicts import MultiOrderedDict
 
+
 def merge_values(left, right, key):
     """Merges values from right into left."""
     if isinstance(left, list):
         vals0 = left
-    else: # assume dictionary
+    else:  # assume dictionary
         vals0 = left[key] if key in left else []
     vals1 = right[key] if key in right else []
 
@@ -15,14 +16,16 @@ def merge_values(left, right, key):
 
 ###############################################################################
 
+
 class Section(MultiOrderedDict):
-    """A Section is a MultiOrderedDict itself that maintains a list of
-       key/value options.  However, in the case of an Asterisk config
-       file a section may have other defaults sections that is can pull
-       data from (i.e. templates).  So when an option is looked up by key
-       it first checks the base section and if not found looks in the
-       added default sections. If not found at that point then a 'KeyError'
-       exception is raised.
+    """
+    A Section is a MultiOrderedDict itself that maintains a list of
+    key/value options.  However, in the case of an Asterisk config
+    file a section may have other defaults sections that is can pull
+    data from (i.e. templates).  So when an option is looked up by key
+    it first checks the base section and if not found looks in the
+    added default sections. If not found at that point then a 'KeyError'
+    exception is raised.
     """
     count = 0
 
@@ -35,9 +38,24 @@ class Section(MultiOrderedDict):
         self._templates = [] if templates is None else templates
 
     def __cmp__(self, other):
+        """
+        Use self.id as means of determining equality
+        """
         return cmp(self.id, other.id)
 
-    def get(self, key, from_self=True, from_templates=True, from_defaults=True):
+    def get(self, key, from_self=True, from_templates=True,
+            from_defaults=True):
+        """
+        Get the values corresponding to a given key. The parameters to this
+        function form a hierarchy that determines priority of the search.
+        from_self takes priority over from_templates, and from_templates takes
+        priority over from_defaults.
+
+        Parameters:
+        from_self - If True, search within the given section.
+        from_templates - If True, search in this section's templates.
+        from_defaults - If True, search within this section's defaults.
+        """
         if from_self and key in self:
             return MultiOrderedDict.__getitem__(self, key)
 
@@ -62,13 +80,19 @@ class Section(MultiOrderedDict):
         raise KeyError(key)
 
     def __getitem__(self, key):
-        """Get the value for the given key. If it is not found in the 'self'
-           then check inside templates and defaults before declaring raising
-           a KeyError exception.
+        """
+        Get the value for the given key. If it is not found in the 'self'
+        then check inside templates and defaults before declaring raising
+        a KeyError exception.
         """
         return self.get(key)
 
     def keys(self, self_only=False):
+        """
+        Get the keys from this section. If self_only is True, then
+        keys from this section's defaults and templates are not
+        included in the returned value
+        """
         res = MultiOrderedDict.keys(self)
         if self_only:
             return res
@@ -85,13 +109,21 @@ class Section(MultiOrderedDict):
         return res
 
     def add_defaults(self, defaults):
+        """
+        Add a list of defaults to the section. Defaults are
+        sections such as 'general'
+        """
         defaults.sort()
         for i in defaults:
             self._defaults.insert(0, i)
 
     def add_templates(self, templates):
-        templates.sort(reverse=True);
-        self._templates.extend(templates)
+        """
+        Add a list of templates to the section.
+        """
+        templates.sort()
+        for i in templates:
+            self._templates.insert(0, i)
 
     def get_merged(self, key):
         """Return a list of values for a given key merged from default(s)"""
@@ -120,9 +152,11 @@ COMMENT_END = '--;'
 
 DEFAULTSECT = 'general'
 
+
 def remove_comment(line, is_comment):
     """Remove any commented elements from the line."""
-    if not line: return line, is_comment
+    if not line:
+        return line, is_comment
 
     if is_comment:
         part = line.partition(COMMENT_END)
@@ -152,23 +186,21 @@ def remove_comment(line, is_comment):
     # check for eol comment
     return line.partition(COMMENT)[0].strip(), False
 
+
 def try_include(line):
-    """Checks to see if the given line is an include.  If so return the
-       included filename, otherwise None.
     """
-    if not line.startswith('#'):
-        return None
+    Checks to see if the given line is an include.  If so return the
+    included filename, otherwise None.
+    """
+
+    match = re.match('^#include\s*[<"]?(.*)[>"]?$', line)
+    return match.group(1) if match else None
 
-    # it is an include - get file name
-    try:
-        return line[line.index('"') + 1:line.rindex('"')]
-    except ValueError:
-        print "Invalid include - could not parse filename."
-        return None
 
 def try_section(line):
-    """Checks to see if the given line is a section. If so return the section
-       name, otherwise return 'None'.
+    """
+    Checks to see if the given line is a section. If so return the section
+    name, otherwise return 'None'.
     """
     # leading spaces were stripped when checking for comments
     if not line.startswith('['):
@@ -188,6 +220,7 @@ def try_section(line):
     except:
         return section[1:], False, templates
 
+
 def try_option(line):
     """Parses the line as an option, returning the key/value pair."""
     data = re.split('=>?', line)
@@ -196,30 +229,12 @@ def try_option(line):
 
 ###############################################################################
 
-def find_value(sections, key):
-    """Given a list of sections, try to find value(s) for the given key."""
-    # always start looking in the last one added
-    sections.sort(reverse=True);
-    for s in sections:
-        try:
-            # try to find in section and section's templates
-            return s.get(key, from_defaults=False)
-        except KeyError:
-            pass
-
-    # wasn't found in sections or a section's templates so check in defaults
-    for s in sections:
-        try:
-            # try to find in section's defaultsects
-            return s.get(key, from_self=False, from_templates=False)
-        except KeyError:
-            pass
-
-    raise KeyError(key)
 
 def find_dict(mdicts, key, val):
-    """Given a list of mult-dicts, return the multi-dict that contains
-       the given key/value pair."""
+    """
+    Given a list of mult-dicts, return the multi-dict that contains
+    the given key/value pair.
+    """
 
     def found(d):
         return key in d and val in d[key]
@@ -230,44 +245,25 @@ def find_dict(mdicts, key, val):
         raise LookupError("Dictionary not located for key = %s, value = %s"
                           % (key, val))
 
-def get_sections(parser, key, attr='_sections', searched=None):
-    if searched is None:
-        searched = []
-    if parser is None or parser in searched:
-        return []
 
-    try:
-        sections = getattr(parser, attr)
-        res = sections[key] if key in sections else []
-        searched.append(parser)
-        return res + get_sections(parser._includes, key, attr, searched) \
-            + get_sections(parser._parent, key, attr, searched)
-    except:
-        # assume ordereddict of parsers
-        res = []
-        for p in parser.itervalues():
-            res.extend(get_sections(p, key, attr, searched))
-        return res
-
-def get_defaults(parser, key):
-    return get_sections(parser, key, '_defaults')
-
-def write_dicts(file, mdicts):
+def write_dicts(config_file, mdicts):
+    """Write the contents of the mdicts to the specified config file"""
     for section, sect_list in mdicts.iteritems():
         # every section contains a list of dictionaries
         for sect in sect_list:
-            file.write("[%s]\n" % section)
+            config_file.write("[%s]\n" % section)
             for key, val_list in sect.iteritems():
                 # every value is also a list
                 for v in val_list:
                     key_val = key
                     if v is not None:
                         key_val += " = " + str(v)
-                        file.write("%s\n" % (key_val))
-            file.write("\n")
+                        config_file.write("%s\n" % (key_val))
+            config_file.write("\n")
 
 ###############################################################################
 
+
 class MultiOrderedConfigParser:
     def __init__(self, parent=None):
         self._parent = parent
@@ -275,16 +271,39 @@ class MultiOrderedConfigParser:
         self._sections = MultiOrderedDict()
         self._includes = OrderedDict()
 
+    def find_value(self, sections, key):
+        """Given a list of sections, try to find value(s) for the given key."""
+        # always start looking in the last one added
+        sections.sort(reverse=True)
+        for s in sections:
+            try:
+                # try to find in section and section's templates
+                return s.get(key, from_defaults=False)
+            except KeyError:
+                pass
+
+        # wasn't found in sections or a section's templates so check in
+        # defaults
+        for s in sections:
+            try:
+                # try to find in section's defaultsects
+                return s.get(key, from_self=False, from_templates=False)
+            except KeyError:
+                pass
+
+        raise KeyError(key)
+
     def defaults(self):
         return self._defaults
 
     def default(self, key):
         """Retrieves a list of dictionaries for a default section."""
-        return get_defaults(self, key)
+        return self.get_defaults(key)
 
     def add_default(self, key, template_keys=None):
-        """Adds a default section to defaults, returning the
-           default Section object.
+        """
+        Adds a default section to defaults, returning the
+        default Section object.
         """
         if template_keys is None:
             template_keys = []
@@ -295,17 +314,47 @@ class MultiOrderedConfigParser:
 
     def section(self, key):
         """Retrieves a list of dictionaries for a section."""
-        return get_sections(self, key)
+        return self.get_sections(key)
+
+    def get_sections(self, key, attr='_sections', searched=None):
+        """
+        Retrieve a list of sections that have values for the given key.
+        The attr parameter can be used to control what part of the parser
+        to retrieve values from.
+        """
+        if searched is None:
+            searched = []
+        if self in searched:
+            return []
+
+        sections = getattr(self, attr)
+        res = sections[key] if key in sections else []
+        searched.append(self)
+        if self._includes:
+            res += self._includes.get_sections(key, attr, searched)
+        if self._parent:
+            res += self._parent.get_sections(key, attr, searched)
+        return res
+
+    def get_defaults(self, key):
+        """
+        Retrieve a list of defaults that have values for the given key.
+        """
+        return self.get_sections(key, '_defaults')
 
     def add_section(self, key, template_keys=None, mdicts=None):
+        """
+        Create a new section in the configuration. The name of the
+        new section is the 'key' parameter.
+        """
         if template_keys is None:
             template_keys = []
         if mdicts is None:
             mdicts = self._sections
         res = Section()
         for t in template_keys:
-            res.add_templates(get_defaults(self, t))
-        res.add_defaults(get_defaults(self, DEFAULTSECT))
+            res.add_templates(self.get_defaults(t))
+        res.add_defaults(self.get_defaults(DEFAULTSECT))
         mdicts.insert(0, key, res)
         return res
 
@@ -313,29 +362,50 @@ class MultiOrderedConfigParser:
         return self._includes
 
     def add_include(self, filename, parser=None):
+        """
+        Add a new #include file to the configuration.
+        """
         if filename in self._includes:
             return self._includes[filename]
 
         self._includes[filename] = res = \
-             MultiOrderedConfigParser(self) if parser is None else parser
-        return res;
+            MultiOrderedConfigParser(self) if parser is None else parser
+        return res
 
     def get(self, section, key):
         """Retrieves the list of values from a section for a key."""
         try:
             # search for the value in the list of sections
-            return find_value(self.section(section), key)
+            return self.find_value(self.section(section), key)
         except KeyError:
             pass
 
         try:
             # section may be a default section so, search
             # for the value in the list of defaults
-            return find_value(self.default(section), key)
+            return self.find_value(self.default(section), key)
         except KeyError:
             raise LookupError("key %r not found for section %r"
                               % (key, section))
 
+    def multi_get(self, section, key_list):
+        """
+        Retrieves the list of values from a section for a list of keys.
+        This method is intended to be used for equivalent keys. Thus, as soon
+        as any match is found for any key in the key_list, the match is
+        returned. This does not concatenate the lookups of all of the keys
+        together.
+        """
+        for i in key_list:
+            try:
+                return self.get(section, i)
+            except LookupError:
+                pass
+
+        # Making it here means all lookups failed.
+        raise LookupError("keys %r not found for section %r" %
+                          (key_list, section))
+
     def set(self, section, key, val):
         """Sets an option in the given section."""
         # TODO - set in multiple sections? (for now set in first)
@@ -346,15 +416,17 @@ class MultiOrderedConfigParser:
             self.defaults(section)[0][key] = val
 
     def read(self, filename):
+        """Parse configuration information from a file"""
         try:
-            with open(filename, 'rt') as file:
-                self._read(file, filename)
+            with open(filename, 'rt') as config_file:
+                self._read(config_file)
         except IOError:
             print "Could not open file ", filename, " for reading"
 
-    def _read(self, file, filename):
-        is_comment = False # used for multi-lined comments
-        for line in file:
+    def _read(self, config_file):
+        """Parse configuration information from the config_file"""
+        is_comment = False  # used for multi-lined comments
+        for line in config_file:
             line, is_comment = remove_comment(line, is_comment)
             if not line:
                 # line was empty or was a comment
@@ -377,18 +449,19 @@ class MultiOrderedConfigParser:
             key, val = try_option(line)
             sect[key] = val
 
-    def write(self, f):
+    def write(self, config_file):
+        """Write configuration information out to a file"""
         try:
             for key, val in self._includes.iteritems():
                 val.write(key)
-                f.write('#include "%s"\n' % key)
+                config_file.write('#include "%s"\n' % key)
 
-            f.write('\n')
-            write_dicts(f, self._defaults)
-            write_dicts(f, self._sections)
+            config_file.write('\n')
+            write_dicts(config_file, self._defaults)
+            write_dicts(config_file, self._sections)
         except:
             try:
-                with open(f, 'wt') as fp:
+                with open(config_file, 'wt') as fp:
                     self.write(fp)
             except IOError:
-                print "Could not open file ", f, " for writing"
+                print "Could not open file ", config_file, " for writing"