Asterisk - The Open Source Telephony Project GIT-master-2de1a68
astconfigparser.py
Go to the documentation of this file.
1"""
2Copyright (C) 2016, Digium, Inc.
3
4This program is free software, distributed under the terms of
5the GNU General Public License Version 2.
6"""
7
8import re
9import glob
10import itertools
11
12from astdicts import OrderedDict
13from astdicts import MultiOrderedDict
14
15
16def merge_values(left, right, key):
17 """Merges values from right into left."""
18 if isinstance(left, list):
19 vals0 = left
20 else: # assume dictionary
21 vals0 = left[key] if key in left else []
22 vals1 = right[key] if key in right else []
23
24 return vals0 + [i for i in vals1 if i not in vals0]
25
26###############################################################################
27
28
30 """
31 A Section is a MultiOrderedDict itself that maintains a list of
32 key/value options. However, in the case of an Asterisk config
33 file a section may have other defaults sections that is can pull
34 data from (i.e. templates). So when an option is looked up by key
35 it first checks the base section and if not found looks in the
36 added default sections. If not found at that point then a 'KeyError'
37 exception is raised.
38 """
39 count = 0
40
41 def __init__(self, defaults=None, templates=None):
42 MultiOrderedDict.__init__(self)
43 # track an ordered id of sections
44 Section.count += 1
45 self.id = Section.count
46 self._defaults = [] if defaults is None else defaults
47 self._templates = [] if templates is None else templates
48
49 def __cmp__(self, other):
50 """
51 Use self.id as means of determining equality
52 """
53 return (self.id > other.id) - (self.id < other.id)
54
55 def __eq__(self, other):
56 """
57 Use self.id as means of determining equality
58 """
59 return self.id == other.id
60
61 def __lt__(self, other):
62 """
63 Use self.id as means of determining equality
64 """
65 return self.id < other.id
66
67 def __gt__(self, other):
68 """
69 Use self.id as means of determining equality
70 """
71 return self.id > other.id
72
73 def __le__(self, other):
74 """
75 Use self.id as means of determining equality
76 """
77 return self.id <= other.id
78
79 def __ge__(self, other):
80 """
81 Use self.id as means of determining equality
82 """
83 return self.id >= other.id
84
85 def get(self, key, from_self=True, from_templates=True,
86 from_defaults=True):
87 """
88 Get the values corresponding to a given key. The parameters to this
89 function form a hierarchy that determines priority of the search.
90 from_self takes priority over from_templates, and from_templates takes
91 priority over from_defaults.
92
93 Parameters:
94 from_self - If True, search within the given section.
95 from_templates - If True, search in this section's templates.
96 from_defaults - If True, search within this section's defaults.
97 """
98 if from_self and key in self:
99 return MultiOrderedDict.__getitem__(self, key)
100
101 if from_templates:
102 if self in self._templates:
103 return []
104 for t in self._templates:
105 try:
106 # fail if not found on the search - doing it this way
107 # allows template's templates to be searched.
108 return t.get(key, True, from_templates, from_defaults)
109 except KeyError:
110 pass
111
112 if from_defaults:
113 for d in self._defaults:
114 try:
115 return d.get(key, True, from_templates, from_defaults)
116 except KeyError:
117 pass
118
119 raise KeyError(key)
120
121 def __getitem__(self, key):
122 """
123 Get the value for the given key. If it is not found in the 'self'
124 then check inside templates and defaults before declaring raising
125 a KeyError exception.
126 """
127 return self.get(key)
128
129 def keys(self, self_only=False):
130 """
131 Get the keys from this section. If self_only is True, then
132 keys from this section's defaults and templates are not
133 included in the returned value
134 """
135 res = MultiOrderedDict.keys(self)
136 if self_only:
137 return res
138
139 for d in self._templates:
140 for key in d.keys():
141 if key not in res:
142 res.append(key)
143
144 for d in self._defaults:
145 for key in d.keys():
146 if key not in res:
147 res.append(key)
148 return res
149
150 def add_defaults(self, defaults):
151 """
152 Add a list of defaults to the section. Defaults are
153 sections such as 'general'
154 """
155 defaults.sort()
156 for i in defaults:
157 self._defaults.insert(0, i)
158
159 def add_templates(self, templates):
160 """
161 Add a list of templates to the section.
162 """
163 templates.sort()
164 for i in templates:
165 self._templates.insert(0, i)
166
167 def get_merged(self, key):
168 """Return a list of values for a given key merged from default(s)"""
169 # first merge key/values from defaults together
170 merged = []
171 for i in reversed(self._defaults):
172 if not merged:
173 merged = i
174 continue
175 merged = merge_values(merged, i, key)
176
177 for i in reversed(self._templates):
178 if not merged:
179 merged = i
180 continue
181 merged = merge_values(merged, i, key)
182
183 # then merge self in
184 return merge_values(merged, self, key)
185
186###############################################################################
187
188COMMENT = ';'
189COMMENT_START = ';--'
190COMMENT_END = '--;'
191
192DEFAULTSECT = 'general'
193
194
195def remove_comment(line, is_comment):
196 """Remove any commented elements from the line."""
197 if not line:
198 return line, is_comment
199
200 if is_comment:
201 part = line.partition(COMMENT_END)
202 if part[1]:
203 # found multi-line comment end check string after it
204 return remove_comment(part[2], False)
205 return "", True
206
207 part = line.partition(COMMENT_START)
208 if part[1] and not part[2].startswith('-'):
209 # found multi-line comment start check string before
210 # it to make sure there wasn't an eol comment in it
211 has_comment = part[0].partition(COMMENT)
212 if has_comment[1]:
213 # eol comment found return anything before it
214 return has_comment[0], False
215
216 # check string after it to see if the comment ends
217 line, is_comment = remove_comment(part[2], True)
218 if is_comment:
219 # return possible string data before comment
220 return part[0].strip(), True
221
222 # otherwise it was an embedded comment so combine
223 return ''.join([part[0].strip(), ' ', line]).rstrip(), False
224
225 # find the first occurence of a comment that is not escaped
226 match = re.match(r'.*?([^\\];)', line)
227
228 if match:
229 # the end of where the real string is is where the comment starts
230 line = line[0:(match.end()-1)]
231 if line.startswith(";"):
232 # if the line is actually a comment just ignore it all
233 line = ""
234
235 return line.replace("\\", "").strip(), False
236
237def try_include(line):
238 """
239 Checks to see if the given line is an include. If so return the
240 included filename, otherwise None.
241 """
242
243 match = re.match('^#include\s*([^;]+).*$', line)
244 if match:
245 trimmed = match.group(1).rstrip()
246 quoted = re.match('^"([^"]+)"$', trimmed)
247 if quoted:
248 return quoted.group(1)
249 bracketed = re.match('^<([^>]+)>$', trimmed)
250 if bracketed:
251 return bracketed.group(1)
252 return trimmed
253 return None
254
255
256def try_section(line):
257 """
258 Checks to see if the given line is a section. If so return the section
259 name, otherwise return 'None'.
260 """
261 # leading spaces were stripped when checking for comments
262 if not line.startswith('['):
263 return None, False, []
264
265 section, delim, templates = line.partition(']')
266 if not templates:
267 return section[1:], False, []
268
269 # strip out the parens and parse into an array
270 templates = templates.replace('(', "").replace(')', "").split(',')
271 # go ahead and remove extra whitespace
272 templates = [i.strip() for i in templates]
273 try:
274 templates.remove('!')
275 return section[1:], True, templates
276 except:
277 return section[1:], False, templates
278
279
280def try_option(line):
281 """Parses the line as an option, returning the key/value pair."""
282 data = re.split('=>?', line, 1)
283 # should split in two (key/val), but either way use first two elements
284 return data[0].rstrip(), data[1].lstrip()
285
286###############################################################################
287
288
289def find_dict(mdicts, key, val):
290 """
291 Given a list of mult-dicts, return the multi-dict that contains
292 the given key/value pair.
293 """
294
295 def found(d):
296 return key in d and val in d[key]
297
298 try:
299 return [d for d in mdicts if found(d)][0]
300 except IndexError:
301 raise LookupError("Dictionary not located for key = %s, value = %s"
302 % (key, val))
303
304
305def write_dicts(config_file, mdicts):
306 """Write the contents of the mdicts to the specified config file"""
307 for section, sect_list in mdicts.iteritems():
308 # every section contains a list of dictionaries
309 for sect in sect_list:
310 config_file.write("[%s]\n" % section)
311 for key, val_list in sect.iteritems():
312 # every value is also a list
313 for v in val_list:
314 key_val = key
315 if v is not None:
316 key_val += " = " + str(v)
317 config_file.write("%s\n" % (key_val))
318 config_file.write("\n")
319
320###############################################################################
321
322
324 def __init__(self, parent=None):
325 self._parent = parent
328 self._includes = OrderedDict()
329
330 def find_value(self, sections, key):
331 """Given a list of sections, try to find value(s) for the given key."""
332 # always start looking in the last one added
333 sections.sort(reverse=True)
334 for s in sections:
335 try:
336 # try to find in section and section's templates
337 return s.get(key, from_defaults=False)
338 except KeyError:
339 pass
340
341 # wasn't found in sections or a section's templates so check in
342 # defaults
343 for s in sections:
344 try:
345 # try to find in section's defaultsects
346 return s.get(key, from_self=False, from_templates=False)
347 except KeyError:
348 pass
349
350 raise KeyError(key)
351
352 def defaults(self):
353 return self._defaults
354
355 def default(self, key):
356 """Retrieves a list of dictionaries for a default section."""
357 return self.get_defaults(key)
358
359 def add_default(self, key, template_keys=None):
360 """
361 Adds a default section to defaults, returning the
362 default Section object.
363 """
364 if template_keys is None:
365 template_keys = []
366 return self.add_section(key, template_keys, self._defaults)
367
368 def sections(self):
369 return self._sections
370
371 def section(self, key):
372 """Retrieves a list of dictionaries for a section."""
373 return self.get_sections(key)
374
375 def get_sections(self, key, attr='_sections', searched=None):
376 """
377 Retrieve a list of sections that have values for the given key.
378 The attr parameter can be used to control what part of the parser
379 to retrieve values from.
380 """
381 if searched is None:
382 searched = []
383 if self in searched:
384 return []
385
386 sections = getattr(self, attr)
387 res = sections[key] if key in sections else []
388 searched.append(self)
389 if self._includes:
390 res.extend(list(itertools.chain(*[
391 incl.get_sections(key, attr, searched)
392 for incl in self._includes.itervalues()])))
393 if self._parent:
394 res += self._parent.get_sections(key, attr, searched)
395 return res
396
397 def get_defaults(self, key):
398 """
399 Retrieve a list of defaults that have values for the given key.
400 """
401 return self.get_sections(key, '_defaults')
402
403 def add_section(self, key, template_keys=None, mdicts=None):
404 """
405 Create a new section in the configuration. The name of the
406 new section is the 'key' parameter.
407 """
408 if template_keys is None:
409 template_keys = []
410 if mdicts is None:
411 mdicts = self._sections
412 res = Section()
413 for t in template_keys:
414 res.add_templates(self.get_defaults(t))
415 res.add_defaults(self.get_defaults(DEFAULTSECT))
416 mdicts.insert(0, key, res)
417 return res
418
419 def includes(self):
420 return self._includes
421
422 def add_include(self, filename, parser=None):
423 """
424 Add a new #include file to the configuration.
425 """
426 if filename in self._includes:
427 return self._includes[filename]
428
429 self._includes[filename] = res = \
430 MultiOrderedConfigParser(self) if parser is None else parser
431 return res
432
433 def get(self, section, key):
434 """Retrieves the list of values from a section for a key."""
435 try:
436 # search for the value in the list of sections
437 return self.find_value(self.section(section), key)
438 except KeyError:
439 pass
440
441 try:
442 # section may be a default section so, search
443 # for the value in the list of defaults
444 return self.find_value(self.default(section), key)
445 except KeyError:
446 raise LookupError("key %r not found for section %r"
447 % (key, section))
448
449 def multi_get(self, section, key_list):
450 """
451 Retrieves the list of values from a section for a list of keys.
452 This method is intended to be used for equivalent keys. Thus, as soon
453 as any match is found for any key in the key_list, the match is
454 returned. This does not concatenate the lookups of all of the keys
455 together.
456 """
457 for i in key_list:
458 try:
459 return self.get(section, i)
460 except LookupError:
461 pass
462
463 # Making it here means all lookups failed.
464 raise LookupError("keys %r not found for section %r" %
465 (key_list, section))
466
467 def set(self, section, key, val):
468 """Sets an option in the given section."""
469 # TODO - set in multiple sections? (for now set in first)
470 # TODO - set in both sections and defaults?
471 if section in self._sections:
472 self.section(section)[0][key] = val
473 else:
474 self.defaults(section)[0][key] = val
475
476 def read(self, filename, sect=None):
477 """Parse configuration information from a file"""
478 try:
479 with open(filename, 'rt') as config_file:
480 self._read(config_file, sect)
481 except IOError:
482 print("Could not open file " + filename + " for reading")
483
484 def _read(self, config_file, sect):
485 """Parse configuration information from the config_file"""
486 is_comment = False # used for multi-lined comments
487 for line in config_file:
488 line, is_comment = remove_comment(line, is_comment)
489 if not line:
490 # line was empty or was a comment
491 continue
492
493 include_name = try_include(line)
494 if include_name:
495 for incl in sorted(glob.iglob(include_name)):
496 parser = self.add_include(incl)
497 parser.read(incl, sect)
498 continue
499
500 section, is_template, templates = try_section(line)
501 if section:
502 if section == DEFAULTSECT or is_template:
503 sect = self.add_default(section, templates)
504 else:
505 sect = self.add_section(section, templates)
506 continue
507
508 key, val = try_option(line)
509 if sect is None:
510 raise Exception("Section not defined before assignment")
511 sect[key] = val
512
513 def write(self, config_file):
514 """Write configuration information out to a file"""
515 try:
516 for key, val in self._includes.iteritems():
517 val.write(key)
518 config_file.write('#include "%s"\n' % key)
519
520 config_file.write('\n')
521 write_dicts(config_file, self._defaults)
522 write_dicts(config_file, self._sections)
523 except:
524 try:
525 with open(config_file, 'wt') as fp:
526 self.write(fp)
527 except IOError:
528 print("Could not open file " + config_file + " for writing")
const char * str
Definition: app_jack.c:147
def add_include(self, filename, parser=None)
def add_default(self, key, template_keys=None)
def read(self, filename, sect=None)
def get_sections(self, key, attr='_sections', searched=None)
def multi_get(self, section, key_list)
def _read(self, config_file, sect)
def add_section(self, key, template_keys=None, mdicts=None)
def set(self, section, key, val)
def __init__(self, defaults=None, templates=None)
def __cmp__(self, other)
def keys(self, self_only=False)
def add_defaults(self, defaults)
def __lt__(self, other)
def __le__(self, other)
def __getitem__(self, key)
def add_templates(self, templates)
def __ge__(self, other)
def __eq__(self, other)
def __gt__(self, other)
def get(self, key, from_self=True, from_templates=True, from_defaults=True)
def insert(self, i, key, val)
Definition: astdicts.py:290
static int replace(struct ast_channel *chan, const char *cmd, char *data, struct ast_str **buf, ssize_t len)
Definition: func_strings.c:888
def write_dicts(config_file, mdicts)
def remove_comment(line, is_comment)
def find_dict(mdicts, key, val)
def try_include(line)
def merge_values(left, right, key)
def try_option(line)
def try_section(line)