diff --git a/overviewer_core/settingsValidators.py b/overviewer_core/settingsValidators.py index f674286..8241473 100644 --- a/overviewer_core/settingsValidators.py +++ b/overviewer_core/settingsValidators.py @@ -1,70 +1,81 @@ # see settingsDefinition.py +import logging import os import os.path import rendermodes import util from optimizeimages import Optimizer -from world import UPPER_LEFT, UPPER_RIGHT, LOWER_LEFT, LOWER_RIGHT -import logging +from world import LOWER_LEFT, LOWER_RIGHT, UPPER_LEFT, UPPER_RIGHT + class ValidationException(Exception): pass + class Setting(object): __slots__ = ['required', 'validator', 'default'] + def __init__(self, required, validator, default): self.required = required self.validator = validator self.default = default + def expand_path(p): p = os.path.expanduser(p) p = os.path.expandvars(p) p = os.path.abspath(p) return p + def checkBadEscape(s): # If any of these weird characters are in the path, raise an exception # instead of fixing this should help us educate our users about pathslashes bad_escapes = ['\a', '\b', '\t', '\n', '\v', '\f', '\r'] for b in bad_escapes: if b in s: - raise ValueError("Invalid character %s in path. Please use " + raise ValueError("Invalid character %s in path. Please use " "forward slashes ('/'). Please see our docs for " "more info." % repr(b)) for c in range(10): if chr(c) in s: - raise ValueError("Invalid character '\\%s' in path. Please use forward slashes ('/'). Please see our docs for more info." % c) + raise ValueError("Invalid character '\\%s' in path. " + "Please use forward slashes ('/'). " + "See our docs for more info." % c) return s + def validateMarkers(filterlist): if type(filterlist) != list: - raise ValidationException("Markers must specify a list of filters. This has recently changed, so check the docs.") + raise ValidationException("Markers must specify a list of filters.") for x in filterlist: if type(x) != dict: - raise ValidationException("Markers must specify a list of dictionaries. This has recently changed, so check the docs.") + raise ValidationException("Markers must specify a list of dictionaries.") if "name" not in x: - raise ValidationException("Must define a name") + raise ValidationException("Filter must define a name.") if "filterFunction" not in x: - raise ValidationException("Must define a filter function") + raise ValidationException("Filter must define a filter function.") if not callable(x['filterFunction']): - raise ValidationException("%r must be a function"% x['filterFunction']) + raise ValidationException("%r must be a function." % x['filterFunction']) return filterlist + def validateOverlays(renderlist): if type(renderlist) != list: - raise ValidationException("Overlay must specify a list of renders") + raise ValidationException("Overlay must specify a list of renders.") for x in renderlist: if validateStr(x) == '': - raise ValidationException("%r must be a string"% x) + raise ValidationException("%r must be a string." % x) return renderlist + def validateWorldPath(worldpath): checkBadEscape(worldpath) abs_path = expand_path(worldpath) if not os.path.exists(os.path.join(abs_path, "level.dat")): - raise ValidationException("No level.dat file in '%s'. Are you sure you have the right path?" % (abs_path,)) + raise ValidationException("No level.dat file in '%s'. Are you sure you have the right " + "path?" % (abs_path,)) return abs_path @@ -72,65 +83,77 @@ def validateRenderMode(mode): # make sure that mode is a list of things that are all rendermode primative if isinstance(mode, str): # Try and find an item named "mode" in the rendermodes module - mode = mode.lower().replace("-","_") + mode = mode.lower().replace("-", "_") try: mode = getattr(rendermodes, mode) except AttributeError: - raise ValidationException("You must specify a valid rendermode, not '%s'. See the docs for valid rendermodes." % mode) + raise ValidationException("You must specify a valid rendermode, not '%s'. " + "See the docs for valid rendermodes." % mode) if isinstance(mode, rendermodes.RenderPrimitive): mode = [mode] if not isinstance(mode, list): - raise ValidationException("%r is not a valid list of rendermodes. It should be a list"% mode) + raise ValidationException("%r is not a valid list of rendermodes. " + "It should be a list." % mode) for m in mode: if not isinstance(m, rendermodes.RenderPrimitive): raise ValidationException("%r is not a valid rendermode primitive." % m) - return mode + def validateNorthDirection(direction): # normalize to integers - intdir = 0 #default + intdir = 0 # default if type(direction) == int: intdir = direction elif isinstance(direction, str): - direction = direction.lower().replace("-","").replace("_","") - if direction == "upperleft": intdir = UPPER_LEFT - elif direction == "upperright": intdir = UPPER_RIGHT - elif direction == "lowerright": intdir = LOWER_RIGHT - elif direction == "lowerleft": intdir = LOWER_LEFT + direction = direction.lower().replace("-", "").replace("_", "") + if direction == "upperleft": + intdir = UPPER_LEFT + elif direction == "upperright": + intdir = UPPER_RIGHT + elif direction == "lowerright": + intdir = LOWER_RIGHT + elif direction == "lowerleft": + intdir = LOWER_LEFT else: - raise ValidationException("'%s' is not a valid north direction" % direction) + raise ValidationException("'%s' is not a valid north direction." % direction) if intdir < 0 or intdir > 3: - raise ValidationException("%r is not a valid north direction" % direction) + raise ValidationException("%r is not a valid north direction." % direction) return intdir + def validateRerenderprob(s): val = float(s) if val < 0 or val >= 1: - raise ValidationException("%r is not a valid rerender probability value. Should be between 0.0 and 1.0." % s) + raise ValidationException("%r is not a valid rerender probability value. " + "Should be between 0.0 and 1.0." % s) return val + def validateImgFormat(fmt): if fmt not in ("png", "jpg", "jpeg", "webp"): - raise ValidationException("%r is not a valid image format" % fmt) - if fmt == "jpeg": fmt = "jpg" + raise ValidationException("%r is not a valid image format." % fmt) + if fmt == "jpeg": + fmt = "jpg" if fmt == "webp": try: from PIL import _webp except ImportError: - raise ValidationException("WebP is not supported by your PIL/Pillow installation") + raise ValidationException("WebP is not supported by your PIL/Pillow installation.") return fmt + def validateImgQuality(qual): intqual = int(qual) if (intqual < 0 or intqual > 100): - raise ValidationException("%r is not a valid image quality" % intqual) + raise ValidationException("%r is not a valid image quality." % intqual) return intqual + def validateBGColor(color): """BG color must be an HTML color, with an option leading # (hash symbol) returns an (r,b,g) 3-tuple @@ -139,28 +162,30 @@ def validateBGColor(color): if color[0] != "#": color = "#" + color if len(color) != 7: - raise ValidationException("%r is not a valid color. Expected HTML color syntax (i.e. #RRGGBB)" % color) + raise ValidationException("%r is not a valid color. Expected HTML color syntax. " + "(i.e. #RRGGBB)" % color) try: r = int(color[1:3], 16) g = int(color[3:5], 16) b = int(color[5:7], 16) - return (r,g,b,0) + return (r, g, b, 0) except ValueError: - raise ValidationException("%r is not a valid color. Expected HTML color syntax (i.e. #RRGGBB)" % color) + raise ValidationException("%r is not a valid color. Expected HTML color syntax. " + "(i.e. #RRGGBB)" % color) elif type(color) == tuple: if len(color) != 4: - raise ValidationException("%r is not a valid color. Expected a 4-tuple" % (color,)) + raise ValidationException("%r is not a valid color. Expected a 4-tuple." % (color,)) return color def validateOptImg(optimizers): if isinstance(optimizers, (int, long)): from optimizeimages import pngcrush - logging.warning("You're using a deprecated definition of optimizeimg. "\ + logging.warning("You're using a deprecated definition of optimizeimg. " "We'll do what you say for now, but please fix this as soon as possible.") optimizers = [pngcrush()] if not isinstance(optimizers, list): - raise ValidationException("What you passed to optimizeimg is not a list. "\ + raise ValidationException("What you passed to optimizeimg is not a list. " "Make sure you specify them like [foo()], with square brackets.") if optimizers: @@ -173,34 +198,38 @@ def validateOptImg(optimizers): # Check whether the chaining is somewhat sane if next_opt: if opt.is_crusher() and not next_opt.is_crusher(): - logging.warning("You're feeding a crushed output into an optimizer that does not crush. "\ - "This is most likely pointless, and wastes time.") - + logging.warning("You're feeding a crushed output into an optimizer that " + "does not crush. This is most likely pointless, and wastes " + "time.") return optimizers + def validateTexturePath(path): # Expand user dir in directories strings path = expand_path(path) if not os.path.exists(path): - raise ValidationException("%r does not exist" % path) + raise ValidationException("%r does not exist." % path) return path def validateBool(b): return bool(b) + def validateFloat(f): return float(f) + def validateInt(i): return int(i) + def validateStr(s): return str(s) + def validateDimension(d): # returns (original, argument to get_type) - # these are provided as arguments to RegionSet.get_type() pretty_names = { "nether": "DIM-1", @@ -208,48 +237,53 @@ def validateDimension(d): "end": "DIM1", "default": 0, } - try: return (d, pretty_names[d]) except KeyError: return (d, d) + def validateOutputDir(d): checkBadEscape(d) if not d.strip(): - raise ValidationException("You must specify a valid output directory") + raise ValidationException("You must specify a valid output directory.") return expand_path(d) + def validateCrop(value): if not isinstance(value, list): value = [value] - + cropZones = [] for zone in value: if not isinstance(zone, tuple) or len(zone) != 4: - raise ValidationException("The value for the 'crop' setting must be an array of tuples of length 4") + raise ValidationException("The value for the 'crop' setting must be an array of " + "tuples of length 4.") a, b, c, d = tuple(int(x) for x in zone) - + if a >= c: a, c = c, a if b >= d: b, d = d, b - + cropZones.append((a, b, c, d)) - + return cropZones + def validateObserver(observer): if all(map(lambda m: hasattr(observer, m), ['start', 'add', 'update', 'finish'])): return observer else: - raise ValidationException("%r does not look like an observer" % repr(observer)) + raise ValidationException("%r does not look like an observer." % repr(observer)) + def validateDefaultZoom(z): if z > 0: return int(z) else: - raise ValidationException("The default zoom is set below 1") + raise ValidationException("The default zoom is set below 1.") + def validateWebAssetsPath(p): try: @@ -257,18 +291,22 @@ def validateWebAssetsPath(p): except ValidationException as e: raise ValidationException("Bad custom web assets path: %s" % e.message) + def validatePath(p): checkBadEscape(p) abs_path = expand_path(p) if not os.path.exists(abs_path): - raise ValidationException("'%s' does not exist. Path initially given as '%s'" % (abs_path,p)) + raise ValidationException("'%s' does not exist. Path initially given as '%s'" + % (abs_path, p)) + def validateManualPOIs(d): for poi in d: - if not 'x' in poi or not 'y' in poi or not 'z' in poi or not 'id' in poi: - raise ValidationException("Not all POIs have x/y/z coordinates or an id: %r" % poi) + if 'x' not in poi or 'y' not in poi or 'z' not in poi or 'id' not in poi: + raise ValidationException("Not all POIs have x/y/z coordinates or an id: %r." % poi) return d + def make_dictValidator(keyvalidator, valuevalidator): """Compose and return a dict validator -- a validator that validates each key and value in a dictionary. @@ -288,6 +326,7 @@ def make_dictValidator(keyvalidator, valuevalidator): v.valuevalidator = valuevalidator return v + def make_configDictValidator(config, ignore_undefined=False): """Okay, stay with me here, this may get confusing. This function returns a validator that validates a "configdict". This is a term I just made up to @@ -326,10 +365,9 @@ def make_configDictValidator(config, ignore_undefined=False): newdict[key] = d[key] elif match: raise ValidationException( - "'%s' is not a configuration item. Did you mean '%s'?" - % (key, match)) + "'%s' is not a configuration item. Did you mean '%s'?" % (key, match)) elif not ignore_undefined: - raise ValidationException("'%s' is not a configuration item" % key) + raise ValidationException("'%s' is not a configuration item." % key) else: # the key is to be ignored. Copy it as-is to the `newdict` # to be returned. It shouldn't conflict, and may be used as @@ -350,13 +388,12 @@ def make_configDictValidator(config, ignore_undefined=False): # The user did not give us this key, there is no default, AND # it's required. This is an error. if configkey in undefined_key_matches: - raise ValidationException("Key '%s' is not a valid " - "configuration item. Did you mean '%s'?" - % (undefined_key_matches[configkey], configkey)) + raise ValidationException( + "Key '%s' is not a valid configuration item. Did you mean '%s'?" + % (undefined_key_matches[configkey], configkey)) else: - raise ValidationException("Required key '%s' was not " - "specified. You must give a value for this setting" - % configkey) + raise ValidationException("Required key '%s' was not specified. You must give " + "a value for this setting." % configkey) return newdict # Put these objects as attributes of the function so they can be accessed @@ -365,26 +402,31 @@ def make_configDictValidator(config, ignore_undefined=False): configDictValidator.ignore_undefined = ignore_undefined return configDictValidator + def error(errstr): def validator(_): raise ValidationException(errstr) return validator + # Activestate recipe 576874 def _levenshtein(s1, s2): - l1 = len(s1) - l2 = len(s2) + l1 = len(s1) + l2 = len(s2) + + matrix = [range(l1 + 1)] * (l2 + 1) + for zz in range(l2 + 1): + matrix[zz] = range(zz, zz + l1 + 1) + for zz in range(0, l2): + for sz in range(0, l1): + if s1[sz] == s2[zz]: + matrix[zz + 1][sz + 1] = min(matrix[zz + 1][sz] + 1, matrix[zz][sz + 1] + 1, + matrix[zz][sz]) + else: + matrix[zz + 1][sz + 1] = min(matrix[zz + 1][sz] + 1, matrix[zz][sz + 1] + 1, + matrix[zz][sz] + 1) + return matrix[l2][l1] - matrix = [range(l1 + 1)] * (l2 + 1) - for zz in range(l2 + 1): - matrix[zz] = range(zz,zz + l1 + 1) - for zz in range(0,l2): - for sz in range(0,l1): - if s1[sz] == s2[zz]: - matrix[zz+1][sz+1] = min(matrix[zz+1][sz] + 1, matrix[zz][sz+1] + 1, matrix[zz][sz]) - else: - matrix[zz+1][sz+1] = min(matrix[zz+1][sz] + 1, matrix[zz][sz+1] + 1, matrix[zz][sz] + 1) - return matrix[l2][l1] def _get_closest_match(s, keys): """Returns a probable match for the given key `s` out of the possible keys in @@ -396,7 +438,7 @@ def _get_closest_match(s, keys): threshold = 3 minmatch = None - mindist = threshold+1 + mindist = threshold + 1 for key in keys: d = _levenshtein(s, key)