Replaced Config with newer version

This commit is contained in:
Philip Trauner 2017-02-23 12:01:43 +01:00
parent bf3f32c0d3
commit 5dfeee5977
2 changed files with 328 additions and 118 deletions

View file

@ -1,118 +0,0 @@
from imp import load_source
from os.path import isfile
from marshal import dumps, loads
from types import FunctionType
class OptionDuplicateError(IndexError):
def __init__(self, name):
super(IndexError, self).__init__("'%s' already exists" % name)
class OptionNotFoundError(IndexError):
def __init__(self, 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, 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
self.comment = comment
class Config:
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:
# 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, 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):
if option in self.options:
del self.options[self.options.index(option)]
else:
raise OptionNotFoundError(option.name)
def write_to_file(self, file):
open(file, "w").write(self.get())
def get(self):
contains_function = False
out = ""
for option in self.options:
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()

328
Shared/Meh.py Normal file
View file

@ -0,0 +1,328 @@
from imp import load_source
from os.path import isfile
from sys import version
class OptionDuplicateError(IndexError):
def __init__(self, name):
super(IndexError, self).__init__("'%s' already exists" % name)
class OptionNotFoundError(IndexError):
def __init__(self, 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")
class ValidationError(Exception):
def __init__(self, option):
super(Exception, self).__init__("invalid value for option '%s'" % option)
class UnsupportedTypeError(TypeError):
def __init__(self):
super(TypeError, self).__init__("only list, tuple, dict, bytes, "
"str, float, complex, int and bool are supported (same "
"thing applies to list, dict and tuple contents)")
class ExceptionInConfigError(Exception):
"""
Raised if an exception occurs while importing a config file.
IN: error (hint: error that occured on import)
"""
def __init__(self, error):
self.error = error
super(Exception, self).__init__("error occured during config import (%s)" %
error.__class__.__name__)
def validate_value(value):
type_value = type(value)
if type_value in (list, tuple):
for element in value:
if not validate_value(element):
return False
elif type_value is dict:
return validate_value(tuple(value.keys())) and validate_value(tuple(value.values()))
elif type_value in (bytes, str, float, complex, int, bool):
return True
else:
return False
return True
def make_value(value):
if validate_value(value):
if type(value) is str:
value = '"%s"' % value
elif type(value) in (list, tuple, dict):
value = str(value)
return value
else:
raise UnsupportedTypeError()
class _EditableConfig:
"""
Automatically created proxy class.
HINTS:
_values: All options with their respective values
_options: All Option instances that were originally added to
the Config instance
_file: Path to the config file
_validation_failed: Optional function that's called on a validation error
_debug: Debug mode on/off (obviously)
"""
def __init__(self, values, options, file, validation_failed=None, debug=False):
self._values = values
self._options = options
self._file = file
self._validation_failed = validation_failed
self._debug = debug
def __getattr__(self, name):
if name in self._values:
return self._values[name]
else:
raise AttributeError("config no attribute '%s'" % name)
def __setattr__(self, name, value):
if name == "_values" or name not in self._values:
self.__dict__[name] = value
else:
dump_required = False
for option in self._options:
if option.name == name:
if validate_value(value):
if option.validator != None:
if option.validator(value):
self._values[name] = value
dump_required = True
else:
if self._validation_failed != None:
self._validation_failed(option.name, value)
else:
raise ValidationError(option.name)
else:
self._values[name] = value
dump_required = True
else:
raise UnsupportedTypeError()
if dump_required:
if self._debug:
print("Rewriting config because the value of '%s' changed." % name)
open(self._file, "w").write(self._dumps())
def _dumps(self):
out = ""
for option in self._options:
value = make_value(self._values[option.name])
out += "%s = %s%s\n" % (option.name, value,
(" # %s" % option.comment) if option.comment else "")
return out.rstrip("\n")
def __repr__(self):
return self._dumps()
class Option:
def __init__(self, name, default_value, validator=None, comment=""):
if not type(name) is str:
raise NameMustBeStringError()
if name.startswith("__"):
raise InvalidOptionName()
self._name = name
self.default_value = default_value
self.validator = validator
self.comment = comment
@property
def name(self):
return self._name
@name.setter
def name(self, value):
if name.startswith("__"):
raise InvalidOptionName()
self._name = value
def __eq__(self, other):
if other.__class__ == Option:
return self.__dict__ == other.__dict__
def __repr__(self):
return "%s = %s" % (self.name, str(self.default_value))
class Config:
"""
The central element of Meh (TM).
IN:
options=[] (type: list, hint: a list of options)
validation_failed=None (type: function, hint: function accepting two
parameters that's called when a validation fails)
Example usage:
from Meh import Config, Option
config = Config()
config.add(Option("number", 42, validator=lambda x: type(x) is int))
CONFIG_PATH = "awesome_config.cfg"
try:
config = config.load(CONFIG_PATH)
except IOError:
config.dump(CONFIG_PATH)
config = config.load(CONFIG_PATH)
print(config.number)
"""
def __init__(self, options=[], validation_failed=None, debug=False):
if type(options) in (list, tuple):
for option in options:
if not option.__class__ == 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.debug = debug
self._iterator_index = 0
def __iter__(self):
return self
def __next__(self):
if self._iterator_index < len(self.options):
self._iterator_index += 1
return self.options[self._iterator_index - 1]
self._iterator_index = 0
raise StopIteration
def load(self, file):
"""
Returns the actual read- and editable config
IN: file (type: str, hint: should be a valid path)
"""
if isfile(file):
try:
config = load_source("config", file)
except Exception as e:
raise ExceptionInConfigError(e)
option_missing = False
values = {}
for option in self.options:
# Make sure all options are avaliable (validators aren't run in this case
# because there are no values defined)
if option.name not in dir(config):
values[option.name] = option.default_value
option_missing = True
else:
# Retrieve the option value
value = getattr(config, option.name)
# Make sure validator passes
if option.validator != None:
# If validation doesn't pass
if not option.validator(value):
# Resort to default value
values[option.name] = option.default_value
if self.validation_failed != None:
self.validation_failed(option.name, value)
else:
raise ValidationError(option.name)
option_missing = True
# If validation passes
else:
values[option.name] = value
else:
values[option.name] = value
if option_missing:
self.dump(file)
return _EditableConfig(values, self.options, file,
validation_failed=self.validation_failed, debug=self.debug)
else:
error = "'%s' not found" % file
raise FileNotFoundError(error) if version.startswith("3") else IOError(error)
def __add__(self, other):
try:
self.add(other)
except TypeError:
return NotImplemented
return self
def __sub__(self, other):
try:
self.remove(other)
except TypeError:
return NotImplemented
return self
def add(self, option):
"""
Adds an option to a Config instance
IN: option (type: Option)
"""
if option.__class__ == Option:
for _option in self.options:
if option.name == _option.name:
raise OptionDuplicateError(_option.name)
self.options.append(option)
else:
raise TypeError("invalid type supplied")
def remove(self, option):
"""
Removes an option from a Config instance
IN: option (type: Option)
"""
if option.__class__ == Option:
if option in self.options:
del self.options[self.options.index(option)]
else:
raise OptionNotFoundError(option.name)
else:
raise TypeError("invalid type supplied")
def dump(self, file):
"""
Writes output of dumps() to the path provided
IN: file (type: str, hint: should be a valid path)
"""
open(file, "w").write(self.dumps())
def dumps(self):
"""
Returns contents of config file as string
OUT: out (type: str, hint: config content)
"""
out = ""
for option in self.options:
value = make_value(option.default_value)
out += "%s = %s%s\n" % (option.name, value,
(" # %s" % option.comment) if option.comment else "")
return out.rstrip("\n")
def __repr__(self):
return self.dumps()