From 1ffdb8728a4b22667a08e43fdf4d14fd8c31bb85 Mon Sep 17 00:00:00 2001 From: Jan Lindemann Date: Thu, 1 Dec 2022 14:29:01 +0100 Subject: [PATCH] Add class Options The Options constructor takes an options string, parses it and makes the options available via __getitem__(). Signed-off-by: Jan Lindemann --- test/parse-opts/Makefile | 5 ++ test/parse-opts/test.py | 32 ++++++++ tools/python/jwutils/Options.py | 125 ++++++++++++++++++++++++++++++++ 3 files changed, 162 insertions(+) create mode 100644 test/parse-opts/Makefile create mode 100644 test/parse-opts/test.py create mode 100644 tools/python/jwutils/Options.py diff --git a/test/parse-opts/Makefile b/test/parse-opts/Makefile new file mode 100644 index 0000000..fa609d2 --- /dev/null +++ b/test/parse-opts/Makefile @@ -0,0 +1,5 @@ +TOPDIR = ../.. + +include $(TOPDIR)/make/proj.mk +include $(JWBDIR)/make/py-run.mk + diff --git a/test/parse-opts/test.py b/test/parse-opts/test.py new file mode 100644 index 0000000..f974c89 --- /dev/null +++ b/test/parse-opts/test.py @@ -0,0 +1,32 @@ +from jwutils.log import * +from jwutils.Options import * + +delim = "======================= " +d = "----------------------- " + +s = "this=that a=b doit" +slog(NOTICE, d + s) +a = Options(s) +slog(NOTICE, d + "dump()") +a.dump(NOTICE) +slog(NOTICE, d + "by index") +for i in range(0, 4): + slog(NOTICE, "attr({}) = {}".format(i, a.get(i, by_index=True, default=False))) +slog(NOTICE, d + "by key") +for i in [ "this", "that", "b", "a", "doit"]: + slog(NOTICE, "attr({}) = {}".format(i, a[i])) + +s = '"me": "too", "Pfalse": true, "myarr": ["a", "b", "c"], "me": "not"' +slog(NOTICE, delim + s) +a = Options(s) +slog(NOTICE, d + "dump()") +a.dump(NOTICE) +slog(NOTICE, d + "by index") +for i in range(0, 5): + slog(NOTICE, "attr({}) = {}".format(i, a.get(i, by_index=True, default=False))) +slog(NOTICE, d + "by key in keys()") +for i in a.keys(): + slog(NOTICE, "attr({}) = {}".format(i, a[i])) +slog(NOTICE, d + "by key") +for i in [ "me", "Pfalse", "myarr" ]: + slog(NOTICE, "attr({}) = {}".format(i, a[i])) diff --git a/tools/python/jwutils/Options.py b/tools/python/jwutils/Options.py new file mode 100644 index 0000000..a2e7732 --- /dev/null +++ b/tools/python/jwutils/Options.py @@ -0,0 +1,125 @@ +import re +import json +from collections import OrderedDict +from . import log +import shlex + +class Options: # export + + def __parse_json(self, spec): + spec = spec.strip() + if len(spec) < 3: + return None + if spec[0] != '{': + spec = '{' + spec + '}' + try: + return json.loads(spec, object_pairs_hook=OrderedDict) + except: + pass + return None + + def __parse(self, opts_str): + r = self.__parse_json(opts_str) + if r is not None: + return r + r = OrderedDict() + opt_strs = shlex.split(opts_str) + for opt_str in opt_strs: + opt_str = re.sub('\s*=\s*', '=', opt_str) + sides = opt_str.split('=') + lhs = sides[0].strip() + if not len(lhs): + continue + if self.__allowed_keys and not lhs in self.__allowed_keys: + raise Exception('Field "{}" not supported'.format(lhs)) + rhs = ' '.join(sides[1:]).strip() if len(sides) > 1 else self.__true_val + r[lhs] = rhs + return r + + def __recache(self): + self.__list.clear() + self.__list = list(self.__dict) + self.__str = str(dict(self.__dict)) + + def __getitem__(self, key): + if not key in self.__dict.keys(): + return None + return self.__dict[key] + + def __str__(self): + return self.__str + + def __repr__(self): + return self.__str + + def __format__(self, fmt): + return self.__str + + def __len__(self): + return len(self.__list) + + def __contains__(self, keys): + if not type(keys) in [list, set]: + return keys in self.__dict.keys() + for key in keys: + if not key in self.__dict.keys(): + return False + return True + + def __iter__(self): + return iter(self.__list) + + def __next__(self): + return next(self.__list) + + def __init__(self, spec=None, delimiter=',', allowed_keys=None, true_val=True): + self.__true_val = true_val + self.__allowed_keys = None + self.__delimiter = delimiter + self.__spec = spec + self.__dict = OrderedDict() if spec is None else self.__parse(spec) + self.__list = [] + self.__str = None + self.__recache() + + def dump(self, prio): + caller = log.get_caller_pos() + for key, val in self.__dict.items(): + log.slog(prio, "{}=\"{}\"".format(key, val)) + + def keys(self): + return self.__dict.keys() + + def items(self): + return self.__dict.items() + + def get(self, key, default=None, by_index=False): + if by_index: + if type(key) != int: + raise KeyError('Tried to get value from options string with ' + + 'index {} of type "{}": {}'.format(key, type(key), self.__spec)) + if key >= len(self.__list): + if default is not None: + return default + raise KeyError('Tried to get value from options string with ' + + 'index {} of {}: {}'.format(key, len(self.__list), self.__spec)) + return self.__list[key] + if key in self.__dict.keys(): + return self.__dict[key] + if default is not None: + return default + raise KeyError('Key "{}" is not present in options string: {}'.format(key, self.__spec)) + + def update(self, rhs): + if hasattr(rhs, 'items'): + for key, val in rhs.items(): + self.__dict[key] = val + return + if isinstance(rhs, str): + self.update(self.__parse(rhs)) + return + raise Exception('Tried to update options with object of incompatible type {}'.format(type(rhs))) + + def append_to(self, obj): + for opt in self.__list: + setattr(obj, opt[0], opt[1])