summaryrefslogtreecommitdiff
path: root/contrib/scripts/sip_to_pjsip/astconfigparser.py
diff options
context:
space:
mode:
authorMark Michelson <mmichelson@digium.com>2013-10-31 22:09:47 +0000
committerMark Michelson <mmichelson@digium.com>2013-10-31 22:09:47 +0000
commitdd221c74c53113b0109f1ca1b392d43dd768cef5 (patch)
tree2f59a5e44d9f828c52c21d09183bb23bb60a56b9 /contrib/scripts/sip_to_pjsip/astconfigparser.py
parente9fc32105353a65b89f546008ca98ffadf359704 (diff)
Update the conversion script from sip.conf to pjsip.conf
(closes issue ASTERISK-22374) Reported by Matt Jordan Review: https://reviewboard.asterisk.org/r/2846 ........ Merged revisions 402327 from http://svn.asterisk.org/svn/asterisk/branches/12 git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@402328 65c4cc65-6c06-0410-ace0-fbb531ad65f3
Diffstat (limited to 'contrib/scripts/sip_to_pjsip/astconfigparser.py')
-rw-r--r--contrib/scripts/sip_to_pjsip/astconfigparser.py467
1 files changed, 467 insertions, 0 deletions
diff --git a/contrib/scripts/sip_to_pjsip/astconfigparser.py b/contrib/scripts/sip_to_pjsip/astconfigparser.py
new file mode 100644
index 000000000..c93173dee
--- /dev/null
+++ b/contrib/scripts/sip_to_pjsip/astconfigparser.py
@@ -0,0 +1,467 @@
+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
+ vals0 = left[key] if key in left else []
+ vals1 = right[key] if key in right else []
+
+ return vals0 + [i for i in vals1 if i not in vals0]
+
+###############################################################################
+
+
+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.
+ """
+ count = 0
+
+ def __init__(self, defaults=None, templates=None):
+ MultiOrderedDict.__init__(self)
+ # track an ordered id of sections
+ Section.count += 1
+ self.id = Section.count
+ self._defaults = [] if defaults is None else defaults
+ 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):
+ """
+ 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)
+
+ if from_templates:
+ if self in self._templates:
+ return []
+ for t in self._templates:
+ try:
+ # fail if not found on the search - doing it this way
+ # allows template's templates to be searched.
+ return t.get(key, True, from_templates, from_defaults)
+ except KeyError:
+ pass
+
+ if from_defaults:
+ for d in self._defaults:
+ try:
+ return d.get(key, True, from_templates, from_defaults)
+ except KeyError:
+ pass
+
+ 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.
+ """
+ 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
+
+ for d in self._templates:
+ for key in d.keys():
+ if key not in res:
+ res.append(key)
+
+ for d in self._defaults:
+ for key in d.keys():
+ if key not in res:
+ res.append(key)
+ 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):
+ """
+ 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)"""
+ # first merge key/values from defaults together
+ merged = []
+ for i in reversed(self._defaults):
+ if not merged:
+ merged = i
+ continue
+ merged = merge_values(merged, i, key)
+
+ for i in reversed(self._templates):
+ if not merged:
+ merged = i
+ continue
+ merged = merge_values(merged, i, key)
+
+ # then merge self in
+ return merge_values(merged, self, key)
+
+###############################################################################
+
+COMMENT = ';'
+COMMENT_START = ';--'
+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 is_comment:
+ part = line.partition(COMMENT_END)
+ if part[1]:
+ # found multi-line comment end check string after it
+ return remove_comment(part[2], False)
+ return "", True
+
+ part = line.partition(COMMENT_START)
+ if part[1]:
+ # found multi-line comment start check string before
+ # it to make sure there wasn't an eol comment in it
+ has_comment = part[0].partition(COMMENT)
+ if has_comment[1]:
+ # eol comment found return anything before it
+ return has_comment[0], False
+
+ # check string after it to see if the comment ends
+ line, is_comment = remove_comment(part[2], True)
+ if is_comment:
+ # return possible string data before comment
+ return part[0].strip(), True
+
+ # otherwise it was an embedded comment so combine
+ return ''.join([part[0].strip(), ' ', line]).rstrip(), False
+
+ # 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.
+ """
+
+ match = re.match('^#include\s*[<"]?(.*)[>"]?$', line)
+ return match.group(1) if match else None
+
+
+def try_section(line):
+ """
+ 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('['):
+ return None, False, []
+
+ section, delim, templates = line.partition(']')
+ if not templates:
+ return section[1:], False, []
+
+ # strip out the parens and parse into an array
+ templates = templates.replace('(', "").replace(')', "").split(',')
+ # go ahead and remove extra whitespace
+ templates = [i.strip() for i in templates]
+ try:
+ templates.remove('!')
+ return section[1:], True, templates
+ 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)
+ # should split in two (key/val), but either way use first two elements
+ return data[0].rstrip(), data[1].lstrip()
+
+###############################################################################
+
+
+def find_dict(mdicts, key, val):
+ """
+ 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]
+
+ try:
+ return [d for d in mdicts if found(d)][0]
+ except IndexError:
+ raise LookupError("Dictionary not located for key = %s, value = %s"
+ % (key, val))
+
+
+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:
+ 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)
+ config_file.write("%s\n" % (key_val))
+ config_file.write("\n")
+
+###############################################################################
+
+
+class MultiOrderedConfigParser:
+ def __init__(self, parent=None):
+ self._parent = parent
+ self._defaults = MultiOrderedDict()
+ 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 self.get_defaults(key)
+
+ def add_default(self, key, template_keys=None):
+ """
+ Adds a default section to defaults, returning the
+ default Section object.
+ """
+ if template_keys is None:
+ template_keys = []
+ return self.add_section(key, template_keys, self._defaults)
+
+ def sections(self):
+ return self._sections
+
+ def section(self, key):
+ """Retrieves a list of dictionaries for a section."""
+ 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(self.get_defaults(t))
+ res.add_defaults(self.get_defaults(DEFAULTSECT))
+ mdicts.insert(0, key, res)
+ return res
+
+ def includes(self):
+ 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
+
+ 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 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 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)
+ # TODO - set in both sections and defaults?
+ if section in self._sections:
+ self.section(section)[0][key] = val
+ else:
+ self.defaults(section)[0][key] = val
+
+ def read(self, filename):
+ """Parse configuration information from a file"""
+ try:
+ with open(filename, 'rt') as config_file:
+ self._read(config_file)
+ except IOError:
+ print "Could not open file ", filename, " for reading"
+
+ 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
+ continue
+
+ include_name = try_include(line)
+ if include_name:
+ parser = self.add_include(include_name)
+ parser.read(include_name)
+ continue
+
+ section, is_template, templates = try_section(line)
+ if section:
+ if section == DEFAULTSECT or is_template:
+ sect = self.add_default(section, templates)
+ else:
+ sect = self.add_section(section, templates)
+ continue
+
+ key, val = try_option(line)
+ sect[key] = val
+
+ def write(self, config_file):
+ """Write configuration information out to a file"""
+ try:
+ for key, val in self._includes.iteritems():
+ val.write(key)
+ config_file.write('#include "%s"\n' % key)
+
+ config_file.write('\n')
+ write_dicts(config_file, self._defaults)
+ write_dicts(config_file, self._sections)
+ except:
+ try:
+ with open(config_file, 'wt') as fp:
+ self.write(fp)
+ except IOError:
+ print "Could not open file ", config_file, " for writing"