import re import json from collections import OrderedDict from .log import * import shlex import traceback class Options: # export 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() if len(spec) < 3: return None if spec[0] != '{': spec = '{' + spec + '}' try: return json.loads(spec, object_pairs_hook=cls) except: pass return None def __parse(self, opts_str, cls): r = self.__parse_json(opts_str, cls) if r is not None: return r r = cls() try: opt_strs = shlex.split(opts_str) except Exception as e: slog_m(ERR, traceback.format_exc()) slog(ERR, 'Failed to split options string >{}<'.format(opts_str)) raise for opt_str in opt_strs: opt_str = re.sub(r'\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 if cls == OrderedDict: r[lhs] = rhs elif cls == self.OrderedData: r.add(lhs, rhs) return r def __recache(self): self.__list.clear() self.__dict.clear() 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): if not key in self.__dict.keys(): return None return self.__dict[key] def __str__(self): return ', '.join(str(p[0]) + ': ' + str(p[1]) for p in self.__data.pairs) def __repr__(self): return self.__str__() def __format__(self, fmt): return self.__str__() def __len__(self): return len(self.__data.pairs) 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.__data = self.OrderedData() if spec is None else self.__parse(spec, self.OrderedData) self.__dict = {} #self.__dict = OrderedDict() if spec is None else self.__parse(spec, OrderedDict) self.__list = [] self.__str = None self.__recache() def dump(self, prio, caller=None): if caller is None: caller = get_caller_pos() for key, val in self.__data.pairs: slog(prio, "{}=\"{}\"".format(key, val), caller=caller) def keys(self): return self.__dict.keys() def items(self): #return self.__dict.items() return self.__data.pairs 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), str(self))) if key >= len(self.__data.pairs): if default is not None: return default raise KeyError('Tried to get value from options string with ' + 'index {} of {}: {}'.format(key, len(self.__data.pairs), str(self))) 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, str(self))) 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])