diff --git a/Shared/Config.py b/Shared/Config.py index de65b63..26a509d 100644 --- a/Shared/Config.py +++ b/Shared/Config.py @@ -1,27 +1,38 @@ from imp import load_source from os.path import isfile +from marshal import dumps, loads +from types import FunctionType -class ValidationFailedError(Exception): +class OptionDuplicateError(IndexError): def __init__(self, name): - super(InputError, self).__init__("validation failed for '%s'" % name) + super(IndexError, self).__init__("'%s' already exists" % name) class OptionNotFoundError(IndexError): def __init__(self, name): - super(InputError, self).__init__("'%s' does not exist" % name) + super(IndexError, self).__init__("'%s' does not exist" % name) + + +class NameMustBeStringError(Exception): + def __init__(self): + super(Exception, self).__init__("option names have to be strings") def make_value(value): if type(value) is str: value = '"%s"' % value - elif type(value) in (list, tuple, tuple): + elif type(value) in (list, tuple, dict): value = str(value) + elif type(value) is FunctionType: + value = dumps(value.__code__) return value class Option: def __init__(self, name, default_value, validator=None, comment=""): + if not type(name) is str: + raise NameMustBeStringError() self.name = name self.default_value = default_value self.validator = validator @@ -29,30 +40,52 @@ class Option: class Config: - def __init__(self, options=[]): + def __init__(self, options=[], validation_failed=None, override_on_error=False): + if type(options) in (list, tuple): + for option in options: + if not type(option) is Option: + raise TypeError("all options must be of type Option") + else: + raise TypeError("options must be a list or tuple containing options of type Option") self.options = options + self.validation_failed = validation_failed + self.override_on_error = override_on_error def read_from_file(self, file): if isfile(file): config = load_source("config", file) + error = False for option in self.options: - found = False - for attr in dir(config): - if not attr.startswith("__"): - if option.name == attr: - found = True - break - if not found: - self.write_to_file(file) - return self.read_from_file(file) + # Make sure all options are avaliable + if option.name not in dir(config): + setattr(config, option.name, option.default_value) + error = True + else: + # Make sure all validators pass + if option.validator != None: + value = getattr(config, option.name) + if not option.validator(value): + setattr(config, option.name, option.default_value) + if self.validation_failed != None: + self.validation_failed(option.name, value) + error = True + if self.override_on_error: + if error: + self.write_to_file(file) return config else: raise FileNotFoundError() - def add(self, option): - self.options.append(option) + def add(self, new_option): + if type(new_option) is Option: + for option in self.options: + if new_option.name == option.name: + raise OptionDuplicateError(option.name) + self.options.append(new_option) + else: + raise TypeError("invalid type supplied") def remove(self, option): @@ -67,11 +100,19 @@ class Config: def get(self): + contains_function = False out = "" for option in self.options: - out += "%s = %s %s\n" % (option.name, make_value(option.default_value), ("# %s" % option.comment) if option.comment else "") + value = make_value(option.default_value) + if type(option.default_value) is FunctionType: + if not contains_function: + out = "from marshal import loads; from types import FunctionType\n\n" + out + contains_function = True + value = 'FunctionType(loads(%s), globals(), "%s")' % (value, option.name) + out += "%s = %s%s\n" % (option.name, value, + (" # %s" % option.comment) if option.comment else "") return out def __repr__(self): - return self.get() + return self.get() \ No newline at end of file