mikael
May 28, 2020 - 22:10
I have this middle-sized data structure that I want to maintain in a format that does not contain any extra parentheses or curly braces. Here is a small sample:
_rules = """
left:
type: leading
target:
attribute: x
leading: value
source:
regular: view.x
container: view.bounds.x
right:
type: trailing
target:
attribute: x
and_so_on: ...
"""
This is nice to edit as Pythonista editor helps with the indentation.
Then I of course want to turn it into the corresponding Python data structure for usage, i.e. something like this:
{
"left": {
"type": "leading",
"target": {
"attribute": "x",
"leading": "value",
"trailing": "value + gap",
"flex": etc...
I did not want to bring in yaml as a dependency, so instead wrote the very fancy parser below, shared here in case someone else has a similar need, or knows of a smarter way to go about this type of thing.
import re
def parse_rules(rules):
rule_dict = dict()
dicts = [rule_dict]
spaces = re.compile(' *')
for i, line in enumerate(rules.splitlines()):
i += 11 # Used to match error line number to my file
if line.strip() == '': continue
indent = len(spaces.match(line).group())
if indent % 4 != 0:
raise RuntimeError(f'Broken indent on line {i}')
indent = indent // 4 + 1
if indent > len(dicts):
raise RuntimeError(f'Extra indentation on line {i}')
dicts = dicts[:indent]
line = line.strip()
if line.endswith(':'):
key = line[:-1].strip()
new_dict = dict()
dicts[-1][key] = new_dict
dicts.append(new_dict)
else:
try:
key, content = line.split(':')
dicts[-1][key.strip()] = content.strip()
except Exception as error:
raise RuntimeError(f'Cannot parse line {i}', error)
return rule_dict
I only need the contents as strings, but for other use cases you might throw in an eval around the content to support numbers, strings, lists and whatnot.