diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..329efad --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +__pycache__/ +*.pyc +config.gie diff --git a/ginie.py b/ginie.py new file mode 100755 index 0000000..6cff476 --- /dev/null +++ b/ginie.py @@ -0,0 +1,135 @@ +#!/usr/bin/env python3 +"gINIe Parser for Wish" + +import re +import sys + + +class GinieParseError(ValueError): + "Exception Class for errors in" + + +def loads(string): + """ + Deserialize `string` (a `str` instance containing a gINIe document) + into a Python list. + + Return Value: List of `block` and `section` dicts + + `block` dicts are of form + { + 'type': 'block', + 'name': 'name_of_block_as_in_config', + 'config': {} + } + where `name` and `config` keys are based on the gINIe document. + + `section` dicts are of form + + { + 'type': 'block', + 'name': 'name_of_block_as_in_config', + 'blocks': [] + } + where `name` and `blocks` keys are based on the gINIe document and blocks + is a list of block dicts found in the section. + """ + config = [] + section_re = re.compile(r"^\|(?P
.+)\|$") + block_re = re.compile(r"^\[(?P[^\]]*)\]$") + line_re = re.compile(r"^\s*(?P[^\s=]+)(\s*)=\s*?(?P.*)$") + empty_re = re.compile(r"^\s*$") + comment_re = re.compile(r"^\s*(#|;)") + current_block = None + current_section = None + for idx, line in enumerate(string.splitlines()): + idx += 1 # Since line numbers begin with 1 + # Skip comments and empty lines + if empty_re.match(line) or comment_re.match(line): + continue + + # Section parsing + if line.startswith("|"): + match = section_re.match(line) + if match is None: + err = "Invalid line {}".format(idx) + raise GinieParseError(err) + current_section = { + "type": "section", + "name": match.group("section"), + "blocks": [] + } + config.append(current_section) + continue + + # Block Parsing + if line.startswith("["): + match = block_re.match(line) + if match is None: + err = "Invalid block name on line {}".format(idx) + raise GinieParseError(err) + current_block = { + "type": "block", + "name": match.group("block"), + "config": {} + } + if current_section is None: + config.append(current_block) + else: + current_section["blocks"].append(current_block) + continue + + # If it is neither a comment, nor a section, nor a block, it has to be + # a key, value pair. + if current_block is None: + raise GinieParseError("Found lines outside a block") + + match = line_re.match(line) + if match is None: + raise GinieParseError("Invalid line {}: {}".format(idx, line)) + + value = match.group('value').strip() + if value: + if value[0] == value[-1] == "'" or value[0] == value[-1] == '"': + value = value[1:-1] + current_block["config"][match.group('key').strip()] = value + + return config + + +def load(file): + """ + Deserialize `file` (a file-like object containing a gINIe document) + into a Python list. + + Return Value: List of `block` and `section` dicts + + `block` dicts are of form + { + 'type': 'block', + 'name': 'name_of_block_as_in_config', + 'config': {} + } + where `name` and `config` keys are based on the gINIe document. + + `section` dicts are of form + + { + 'type': 'block', + 'name': 'name_of_block_as_in_config', + 'blocks': [] + } + where `name` and `blocks` keys are based on the gINIe document and blocks + is a list of block dicts found in the section. + """ + return loads(file.read()) + + +if __name__ == "__main__": + if len(sys.argv) == 1: + print("Usage: {} file.gie|-".format(sys.argv[0])) + elif sys.argv[1] == '-': + print(load(sys.stdin)) + else: + with open(sys.argv[1]) as source: + print(load(source))