jwutils.Options: Add support for duplicate keys

Signed-off-by: Jan Lindemann <jan@janware.com>
This commit is contained in:
Jan Lindemann 2023-01-17 13:33:44 +01:00
commit 82c0e6fe2e

View file

@ -1,28 +1,44 @@
import re import re
import json import json
from collections import OrderedDict from collections import OrderedDict
from . import log from .log import *
import shlex import shlex
class Options: # export class Options: # export
def __parse_json(self, spec): class OrderedData:
def __init__(self, pairs = None):
self.__pairs = [] if pairs is None else pairs
def add(self, lhs, rhs):
self.__pairs.append((lhs, rhs))
def dump(self):
for p in self.__pairs:
print(p)
@property
def pairs(self):
return self.__pairs
def __parse_json(self, spec, cls):
spec = spec.strip() spec = spec.strip()
if len(spec) < 3: if len(spec) < 3:
return None return None
if spec[0] != '{': if spec[0] != '{':
spec = '{' + spec + '}' spec = '{' + spec + '}'
try: try:
return json.loads(spec, object_pairs_hook=OrderedDict) return json.loads(spec, object_pairs_hook=cls)
except: except:
pass pass
return None return None
def __parse(self, opts_str): def __parse(self, opts_str, cls):
r = self.__parse_json(opts_str) r = self.__parse_json(opts_str, cls)
if r is not None: if r is not None:
return r return r
r = OrderedDict() r = cls()
opt_strs = shlex.split(opts_str) opt_strs = shlex.split(opts_str)
for opt_str in opt_strs: for opt_str in opt_strs:
opt_str = re.sub('\s*=\s*', '=', opt_str) opt_str = re.sub('\s*=\s*', '=', opt_str)
@ -33,13 +49,31 @@ class Options: # export
if self.__allowed_keys and not lhs in self.__allowed_keys: if self.__allowed_keys and not lhs in self.__allowed_keys:
raise Exception('Field "{}" not supported'.format(lhs)) raise Exception('Field "{}" not supported'.format(lhs))
rhs = ' '.join(sides[1:]).strip() if len(sides) > 1 else self.__true_val rhs = ' '.join(sides[1:]).strip() if len(sides) > 1 else self.__true_val
if cls == OrderedDict:
r[lhs] = rhs r[lhs] = rhs
elif cls == self.OrderedData:
r.add(lhs, rhs)
return r return r
def __recache(self): def __recache(self):
self.__list.clear() self.__list.clear()
self.__list = list(self.__dict) self.__dict.clear()
self.__str = str(dict(self.__dict)) for key, val in self.__data.pairs:
self.__list.append(key)
if key not in self.__dict.keys():
self.__dict[key] = val
else:
cur = self.__dict[key]
if isinstance(cur, str):
cur = [cur, val]
elif isinstance(cur, set):
cur.add(val)
elif isinstance(cur, list):
cur.append(val)
else:
cur = [cur, val]
self.__dict[key] = cur
self.__str = self.__str__()
def __getitem__(self, key): def __getitem__(self, key):
if not key in self.__dict.keys(): if not key in self.__dict.keys():
@ -47,16 +81,16 @@ class Options: # export
return self.__dict[key] return self.__dict[key]
def __str__(self): def __str__(self):
return self.__str return ', '.join(str(p[0]) + ': ' + str(p[1]) for p in self.__data.pairs)
def __repr__(self): def __repr__(self):
return self.__str return self.__str__()
def __format__(self, fmt): def __format__(self, fmt):
return self.__str return self.__str__()
def __len__(self): def __len__(self):
return len(self.__list) return len(self.__data.pairs)
def __contains__(self, keys): def __contains__(self, keys):
if not type(keys) in [list, set]: if not type(keys) in [list, set]:
@ -76,39 +110,42 @@ class Options: # export
self.__true_val = true_val self.__true_val = true_val
self.__allowed_keys = None self.__allowed_keys = None
self.__delimiter = delimiter self.__delimiter = delimiter
self.__spec = spec self.__data = self.OrderedData() if spec is None else self.__parse(spec, self.OrderedData)
self.__dict = OrderedDict() if spec is None else self.__parse(spec) self.__dict = {}
#self.__dict = OrderedDict() if spec is None else self.__parse(spec, OrderedDict)
self.__list = [] self.__list = []
self.__str = None self.__str = None
self.__recache() self.__recache()
def dump(self, prio): def dump(self, prio, caller=None):
caller = log.get_caller_pos() if caller is None:
for key, val in self.__dict.items(): caller = get_caller_pos()
log.slog(prio, "{}=\"{}\"".format(key, val)) for key, val in self.__data.pairs:
slog(prio, "{}=\"{}\"".format(key, val), caller=caller)
def keys(self): def keys(self):
return self.__dict.keys() return self.__dict.keys()
def items(self): def items(self):
return self.__dict.items() #return self.__dict.items()
return self.__data.pairs
def get(self, key, default=None, by_index=False): def get(self, key, default=None, by_index=False):
if by_index: if by_index:
if type(key) != int: if type(key) != int:
raise KeyError('Tried to get value from options string with ' + raise KeyError('Tried to get value from options string with ' +
'index {} of type "{}": {}'.format(key, type(key), self.__spec)) 'index {} of type "{}": {}'.format(key, type(key), str(self)))
if key >= len(self.__list): if key >= len(self.__data.pairs):
if default is not None: if default is not None:
return default return default
raise KeyError('Tried to get value from options string with ' + raise KeyError('Tried to get value from options string with ' +
'index {} of {}: {}'.format(key, len(self.__list), self.__spec)) 'index {} of {}: {}'.format(key, len(self.__data.pairs), str(self)))
return self.__list[key] return self.__list[key]
if key in self.__dict.keys(): if key in self.__dict.keys():
return self.__dict[key] return self.__dict[key]
if default is not None: if default is not None:
return default return default
raise KeyError('Key "{}" is not present in options string: {}'.format(key, self.__spec)) raise KeyError('Key "{}" is not present in options string: {}'.format(key, str(self)))
def update(self, rhs): def update(self, rhs):
if hasattr(rhs, 'items'): if hasattr(rhs, 'items'):