f5baf13be9b1ee59eff318e5aff4b660b761ad03
[asterisk/asterisk.git] / contrib / scripts / sip_to_res_sip / astconfigparser.py
1 from astdicts import MultiOrderedDict
2
3 def merge_values(left, right, key):
4     """Merges values from right into left."""
5     if isinstance(left, list):
6         vals0 = left
7     else: # assume dictionary
8         vals0 = left[key] if key in left else []
9     vals1 = right[key] if key in right else []
10
11     return vals0 + [i for i in vals1 if i not in vals0]
12
13 ###############################################################################
14
15 class Section(MultiOrderedDict):
16     """A Section is a MultiOrderedDict itself that maintains a list of
17        key/value options.  However, in the case of an Asterisk config
18        file a section may have other defaults sections that is can pull
19        data from (i.e. templates).  So when an option is looked up by key
20        it first checks the base section and if not found looks in the
21        added default sections. If not found at that point then a 'KeyError'
22        exception is raised.
23     """
24     def __init__(self, defaults = []):
25         MultiOrderedDict.__init__(self)
26         self._defaults = defaults
27
28     def __getitem__(self, key):
29         """Get the value for the given key. If it is not found in the 'self'
30            then check inside the defaults before declaring unable to locate."""
31         if key in self:
32             return MultiOrderedDict.__getitem__(self, key)
33
34         for default in self._defaults:
35             if key in default:
36                 return default[key]
37
38         raise KeyError(key)
39
40     def keys(self):
41         res = MultiOrderedDict.keys(self)
42         for d in self._defaults:
43             for key in d.keys():
44                 if key not in res:
45                     res.append(key)
46         return res
47
48     def add_default(self, default):
49         self._defaults.append(default)
50
51     def get_merged(self, key):
52         """Return a list of values for a given key merged from default(s)"""
53         # first merge key/values from defaults together
54         merged = []
55         for i in self._defaults:
56             if not merged:
57                 merged = i
58                 continue
59             merged = merge_values(merged, i, key)
60         # then merge self in
61         return merge_values(merged, self, key)
62
63 ###############################################################################
64
65 def remove_comment(line):
66     """Remove any commented elements from the given line"""
67     line = line.partition(COMMENT)[0]
68     return line.rstrip()
69
70 def try_section(line):
71     """Checks to see if the given line is a section. If so return the section
72        name, otherwise return 'None'.
73     """
74     if not line.startswith('['):
75         return None
76
77     first, second, third = line.partition(']')
78     # TODO - third may contain template, parse to see if it is a template
79     #        or is a list of templates...return?
80     return first[1:]
81
82 def try_option(line):
83     """Parses the line as an option, returning the key/value pair."""
84     first, second, third = line.partition('=')
85     return first.strip(), third.strip()
86
87 ###############################################################################
88
89 def get_value(mdict, key, index=-1):
90     """Given a multi-dict, retrieves a value for the given key. If the key only
91        holds a single value return that value. If the key holds more than one
92        value and an index is given that is greater than or equal to zero then
93        return the value at the index. Otherwise return the list of values."""
94     vals = mdict[key]
95     if len(vals) == 1:
96         return vals[0]
97     if index >= 0:
98         return vals[index]
99     return vals
100
101 def find_value(mdicts, key, index=-1):
102     """Given a list of multi-dicts, try to find value(s) for the given key."""
103     if not isinstance(mdicts, list):
104         # given a single multi-dict
105         return get_value(mdicts, key, index)
106
107     for d in mdicts:
108         if key in d:
109             return d[key]
110     # not found, throw error
111     raise KeyError(key)
112
113 def find_dict(mdicts, key, val):
114     """Given a list of mult-dicts, return the multi-dict that contains
115        the given key/value pair."""
116
117     def found(d):
118         # just check the first value of the key
119         return key in d and d[key][0] == val
120
121     if isinstance(mdicts, list):
122         try:
123             return [d for d in mdicts if found(d)][0]
124         except IndexError:
125             pass
126     elif found(mdicts):
127         return mdicts
128
129     raise LookupError("Dictionary not located for key = %s, value = %s"
130                       % (key, val))
131
132 ###############################################################################
133
134 COMMENT = ';'
135 DEFAULTSECT = 'general'
136
137 class MultiOrderedConfigParser:
138     def __init__(self):
139         self._default = MultiOrderedDict()
140         # sections contain dictionaries of dictionaries
141         self._sections = MultiOrderedDict()
142
143     def default(self):
144         return self._default
145
146     def sections(self):
147         return self._sections
148
149     def section(self, section, index=-1):
150         """Retrieves a section dictionary for the given section. If the section
151            holds only a single section dictionary return that dictionary. If
152            the section holds more than one dictionary and an index is given
153            that is greater than or equal to zero then return the dictionary at
154            the index. Otherwise return the list of dictionaries for the given
155            section name."""
156         try:
157             return get_value(self._sections, section, index)
158         except KeyError:
159             raise LookupError("section %r not found" % section)
160
161     def add_section(self, section, defaults=[]):
162         """Adds a section with the given name and defaults."""
163         self._sections[section] = res = Section(defaults)
164         return res
165
166     def get(self, key, section=DEFAULTSECT, index=-1):
167         """Retrieves a value for the given key from the given section. If the
168            key only holds a single value return that value. If the key holds
169            more than one value and an index is given that is greater than or
170            equal to zero then return the value at the index. Otherwise return
171            the list of values."""
172         try:
173             if section == DEFAULTSECT:
174                 return get_value(self._default, key, index)
175
176             # search section(s)
177             return find_value(self.section(section), key, index)
178         except KeyError:
179             # check default section if we haven't already
180             if section != DEFAULTSECT:
181                 return self.get(key, DEFAULTSECT, index)
182             raise LookupError("key %r not found in section %r"
183                               % (key, section))
184
185     def set(self, key, val, section=DEFAULTSECT):
186         """Sets an option in the given section."""
187         if section == DEFAULTSECT:
188             self._default[key] = val
189         else:
190             # for now only set value in first section
191             self.section(section, 0)[key] = val
192
193     def read(self, filename):
194         try:
195             with open(filename, 'rt') as file:
196                 self._read(file, filename)
197         except IOError:
198             print "Could not open file ", filename, " for reading"
199
200     def _read(self, file, filename):
201         for line in file:
202             line = remove_comment(line)
203             if not line:
204                 # line was empty or was a comment
205                 continue
206
207             section = try_section(line)
208             if section:
209                 if section == DEFAULTSECT:
210                     sect = self._default
211                 else:
212                     self._sections[section] = sect = Section([self._default])
213                     # TODO - if section has templates add those
214                     #        with sect.add_default
215                 continue
216
217             key, val = try_option(line)
218             sect[key] = val
219
220     def write(self, filename):
221         try:
222             with open(filename, 'wt') as file:
223                 self._write(file)
224         except IOError:
225             print "Could not open file ", filename, " for writing"
226         pass
227
228     def _write(self, file):
229         # TODO - need to write out default section, but right now in
230         #        our case res_sip.conf has not default/general section
231         for section, sect_list in self._sections.iteritems():
232             # every section contains a list of dictionaries
233             for sect in sect_list:
234                 file.write("[%s]\n" % section)
235                 for key, val_list in sect.iteritems():
236                     # every value is also a list
237                     for v in val_list:
238                         key_val = key
239                         if (v is not None):
240                             key_val += " = " + str(v)
241                         file.write("%s\n" % (key_val))
242                 file.write("\n")