From cbc0c09d657af47fa3affa82a4729e3f03e0ffa7 Mon Sep 17 00:00:00 2001 From: Thomas Hooge Date: Tue, 5 Aug 2025 12:07:07 +0200 Subject: [PATCH] Tool for creating JSON from configuration file --- lib/obp60task/utils/make_json.py | 172 +++++++++++++++++++++++++++++++ 1 file changed, 172 insertions(+) create mode 100755 lib/obp60task/utils/make_json.py diff --git a/lib/obp60task/utils/make_json.py b/lib/obp60task/utils/make_json.py new file mode 100755 index 0000000..da2de5f --- /dev/null +++ b/lib/obp60task/utils/make_json.py @@ -0,0 +1,172 @@ +#!/usr/bin/python + +""" +Convert config file to JSON + +The JSON contains array of single fields. There is no hierarchy. +Fields are being grouped in GUI with "category". +A group is shown if a minimum of one field is visible. + +Hints + capabilities is a dictionary + field:[true | false] + optional comma separated multiple values + +""" + +import os +import sys +import getopt +import configparser +from io import StringIO +from pyparsing import alphas, alphanums, Word, Literal, Combine, Group, Forward, ZeroOrMore, delimitedList + +__author__ = "Thomas Hooge" +__copyright__ = "Copyleft 2025, all rights reversed" +__version__ = "0.1" +__email__ = "thomas@hoogi.de" +__status__ = "Development" + +infile = None +outfile = "" +force = False # overwrite outfile + +# Variables for condition parsing +fieldname = Combine(Word(alphas, exact=1) + Word(alphanums, max=15)) +fieldvalue = Word(alphanums, max=16) +equals = Literal("=") +in_op = Literal("IN") +and_op = Literal("AND") +or_op = Literal("OR") +comparison = Group(fieldname + equals + fieldvalue) \ + | Group(fieldname + in_op + delimitedList(fieldvalue)) +expr = Forward() +expr <<= comparison + ZeroOrMore((and_op | or_op) + comparison) + +def parse_condition(condition): + try: + result = expr.parseString(condition, parseAll=True) + except Exception as e: + return "" + out = StringIO() + andlist = [] + for token in result: + # list: field = value or field IN value [, value ...] + # str: AND, OR + # combine ANDs and output reaching OR + if type(token) == str: + if token == "OR": + andstr = ",\n".join(andlist) + out.write(f'\t\t{{ {andstr} }},\n') + andlist = [] + else: + if token[1] == '=': + andlist.append(f'"{token[0]}": "{token[2]}"') + elif token[1] == 'IN': + n = len(token) - 2 + if n == 1: + # no list, write single value + andlist.append(f'"{token[0]}": "{token[2]}"') + else: + # write list + inlist = '", "'.join(token[2:]) + andlist.append(f'"{token[0]}": [ "{inlist}" ]\n') + + if len(andlist) > 0: + out.write("\t\t{{ {} }}".format(", ".join(andlist))) + return out.getvalue() + +def create_flist(): + flist = [] + for field in config.sections(): + properties = [f'\t"name": "{field}"'] + for prop, val in config.items(field): + if prop in ["label", "type", "default", "description", "category", "check"]: + properties.append(f'\t"{prop}": "{val}"') + elif prop == "capabilities": + # multiple values possible + capas = [] + for capa in val.split(','): + k, v = capa.split(':') + capas.append(f'"{k.strip()}":"{v.strip()}"') + capalist = ','.join(capas) + properties.append(f'\t"{prop}": {{{capalist}}}') + elif prop in ("min", "max"): + properties.append(f'\t"{prop}": {val}') + elif prop == "list": + entries = '", "'.join([x.strip() for x in val.split(',')]) + properties.append(f'\t"list": ["{entries}"]') + elif prop == "dict": + d = {} + for l in val.splitlines(): + if len(l) < 3: + continue + k, v = l.split(':') + d[k.strip()] = v.strip() + lines = [] + for k,v in d.items(): + lines.append(f'\t\t{{"l":"{v}","v":"{k}"}}') + entries = ",\n".join(lines) + properties.append(f'\t"list": [\n{entries}\n\t]') + elif prop == "condition": + jsoncond = parse_condition(val) + properties.append(f'\t"{prop}": [\n{jsoncond}\n\t]\n') + else: + pass # ignore unknown stuff + fieldprops = ",\n".join(properties) + flist.append(f'{{\n{fieldprops}\n}}') + return flist + +def usage(): + print("{} v{}".format(os.path.basename(__file__), __version__)) + print(__copyright__) + print() + print("Command line options") + print(" -c --config config file name to use") + print(" -j --json json file name to generate") + print(" -f force overwrite of existing json file") + print(" -h show this help") + print() + +if __name__ == '__main__': + try: + options, remainder = getopt.getopt(sys.argv[1:], 'c:j:fh', ['config=', 'json=']) + except getopt.GetoptError as e: + print(e) + sys.exit(1) + filename = None + for opt, arg in options: + if opt in ('-c', '--config'): + infile = arg + elif opt in ('-j', '--json'): + outfile = arg + elif opt == '-h': + usage() + sys.exit(0) + elif opt == '-f': + force = True + if not infile: + print("Error: config filename missing") + sys.exit(1) + if not os.path.isfile(infile): + print(f"Error: configuration file '{filename} not found'") + sys.exit(1) + if os.path.isfile(outfile) and not force: + print(f"Error: json file '{outfile}' already exists") + sys.exit(1) + + config = configparser.ConfigParser() + ret = config.read(infile) + if len(ret) == 0: + print(f"ERROR: Config file '{infile}' not found") + sys.exit(1) + + flist = create_flist() + out = "[\n{}\n]\n".format(",\n".join(flist)) + if not outfile: + # print to console + print(out) + else: + # write to file + with open(outfile, "w") as fh: + fh.write(out)