in python/qpid_dispatch_internal/management/config.py [0:0]
def _parse(self, lines):
"""
Parse config file format into a section list
The config file format is a text file in JSON-ish syntax. It allows
the user to define a set of Entities which contain Attributes.
Attributes may be either a single item or a map of nested attributes.
Entities and map Attributes start with a single open brace on a line by
itself (no non-comment text after the opening brace!)
Entities and map Attributes are terminated by a single closing brace
that appears on a line by itself (no trailing comma and no non-comment
trailing text!)
Entity names and Attribute names and items are NOT enclosed in quotes
nor are they terminated with commas, however some select Attributes
have values which are expected to be valid JSON (double quoted
strings, etc)
Unlike JSON the config file also allows comments. A comment begins
with the '#' character and is terminated at the end of line.
"""
# note: these regexes expect that trailing comment and leading and
# trailing whitespace has been removed
#
entity = re.compile(r'([\w-]+)[ \t]*{[ \t]*$') # WORD {
attr_map = re.compile(r'([\$]*[\w-]+)[ \t]*:[ \t]*{[ \t]*$') # WORD: {
json_map = re.compile(r'("[\$]*[\w-]+)"[ \t]*:[ \t]*{[ \t]*$') # "WORD": {
attr_item = re.compile(r'([\w-]+)[ \t]*:[ \t]*([^ \t{]+.*)$') # WORD1: VALUE
end = re.compile(r'^}$') # } (only)
json_end = re.compile(r'}$') # } (at eol)
# The 'pattern:' and 'bindingKey:' attributes in the schema are special
# snowflakes. They allow '#' characters in their value, so they cannot
# be treated as comment delimiters
special_snowflakes = ['pattern', 'bindingKey', 'hostname']
hash_ok = re.compile(r'([\w-]+)[ \t]*:[ \t]*([\S]+).*')
# the 'openProperties' and 'groups' attributes are also special
# snowflakes in that their value is expected to be valid JSON. These
# values do allow single line comments which are stripped out, but the
# remaining content is expected to be valid JSON.
json_snowflakes = ['openProperties', 'groups']
self._line_num = 1
self._child_level = 0
self._in_json = False
def sub(line):
"""Do substitutions to make line json-friendly"""
line = line.strip()
# ignore empty and comment lines
if not line or line.startswith("#"):
self._line_num += 1
return ""
# watch JSON for embedded maps and map terminations
# always pass JSON as-is except appending a comma at the end
if self._in_json:
if json_map.search(line):
self._child_level += 1
if json_end.search(line):
self._child_level -= 1
if self._child_level == 0:
self._in_json = False
line = re.sub(json_end, r'},', line)
self._line_num += 1
return line
# filter off pattern items before stripping comments
if attr_item.search(line):
if re.sub(attr_item, r'\1', line) in special_snowflakes:
self._line_num += 1
return re.sub(hash_ok, r'"\1": "\2",', line)
# now trim trailing comment
line = line.split('#')[0].strip()
if entity.search(line):
# WORD { --> ["WORD", {
line = re.sub(entity, r'["\1", {', line)
elif attr_map.search(line):
# WORD: { --> ["WORD": {
key = re.sub(attr_map, r'\1', line)
line = re.sub(attr_map, r'"\1": {', line)
self._child_level += 1
if key in json_snowflakes:
self._in_json = True
elif attr_item.search(line):
# WORD: VALUE --> "WORD": "VALUE"
line = re.sub(attr_item, r'"\1": "\2",', line)
elif end.search(line):
# } --> "}," or "}]," depending on nesting level
if self._child_level > 0:
line = re.sub(end, r'},', line)
self._child_level -= 1
else:
# end top level entity list item
line = re.sub(end, r'}],', line)
else:
# unexpected syntax, let json parser figure it out
self._log(LOG_WARNING,
"Invalid config file syntax (line %d):\n"
">>> %s"
% (self._line_num, line))
self._line_num += 1
return line
js_text = "[%s]" % ("\n".join([sub(l) for l in lines]))
if self._in_json or self._child_level != 0:
self._log(LOG_WARNING,
"Configuration file: invalid entity nesting detected.")
spare_comma = re.compile(r',\s*([]}])') # Strip spare commas
js_text = re.sub(spare_comma, r'\1', js_text)
# Convert dictionary keys to camelCase
try:
sections = json.loads(js_text)
except Exception as e:
self.dump_json("Contents of failed config file", js_text)
raise
Config.transform_sections(sections)
return sections