Added a "did you mean" feature for config parsing
This commit is contained in:
@@ -61,7 +61,12 @@ class MultiWorldParser(object):
|
|||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
def get_validated_config(self):
|
def get_validated_config(self):
|
||||||
"""Validate and return the configuration"""
|
"""Validate and return the configuration. Raises a ValidationException
|
||||||
|
if there was a problem validating the config.
|
||||||
|
|
||||||
|
Could also raise a ValueError
|
||||||
|
|
||||||
|
"""
|
||||||
# Okay, this is okay, isn't it? We're going to create the validation
|
# Okay, this is okay, isn't it? We're going to create the validation
|
||||||
# routine right here, right now. I hope this works!
|
# routine right here, right now. I hope this works!
|
||||||
validator = settingsValidators.make_configDictValidator(self._settings, ignore_undefined=True)
|
validator = settingsValidators.make_configDictValidator(self._settings, ignore_undefined=True)
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ from collections import namedtuple
|
|||||||
|
|
||||||
import rendermodes
|
import rendermodes
|
||||||
from world import UPPER_LEFT, UPPER_RIGHT, LOWER_LEFT, LOWER_RIGHT
|
from world import UPPER_LEFT, UPPER_RIGHT, LOWER_LEFT, LOWER_RIGHT
|
||||||
|
import logging
|
||||||
|
|
||||||
class ValidationException(Exception):
|
class ValidationException(Exception):
|
||||||
pass
|
pass
|
||||||
@@ -166,7 +167,32 @@ def make_configDictValidator(config, ignore_undefined=False):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
def configDictValidator(d):
|
def configDictValidator(d):
|
||||||
|
|
||||||
|
# values are config keys that the user specified that don't match any
|
||||||
|
# valid key
|
||||||
|
# keys are the correct configuration key
|
||||||
|
undefined_key_matches = {}
|
||||||
|
|
||||||
|
# Go through the keys the user gave us and make sure they're all valid.
|
||||||
|
for key in d.iterkeys():
|
||||||
|
if key not in config:
|
||||||
|
# Try to find a probable match
|
||||||
|
match = _get_closest_match(key, config.iterkeys())
|
||||||
|
if match and ignore_undefined:
|
||||||
|
# Save this for later. It only matters if this is a typo of
|
||||||
|
# some required key that wasn't specified. (If all required
|
||||||
|
# keys are specified, then this should be ignored)
|
||||||
|
undefined_key_matches[match] = key
|
||||||
|
elif match:
|
||||||
|
raise ValidationException(
|
||||||
|
"'%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)
|
||||||
|
|
||||||
newdict = {}
|
newdict = {}
|
||||||
|
# Iterate through the defined keys in the configuration (`config`),
|
||||||
|
# checking each one to see if the user specified it (in `d`)
|
||||||
for configkey, configsetting in config.iteritems():
|
for configkey, configsetting in config.iteritems():
|
||||||
if configkey in d:
|
if configkey in d:
|
||||||
# This key /was/ specified in the user's dict. Make sure it validates.
|
# This key /was/ specified in the user's dict. Make sure it validates.
|
||||||
@@ -177,14 +203,15 @@ def make_configDictValidator(config, ignore_undefined=False):
|
|||||||
elif configsetting.required:
|
elif configsetting.required:
|
||||||
# The user did not give us this key, there is no default, AND
|
# The user did not give us this key, there is no default, AND
|
||||||
# it's required. This is an error.
|
# it's required. This is an error.
|
||||||
raise ValidationException("Required key '%s' was not specified. You must give a value for this setting" % configkey)
|
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))
|
||||||
|
else:
|
||||||
|
raise ValidationException("Required key '%s' was not "
|
||||||
|
"specified. You must give a value for this setting"
|
||||||
|
% configkey)
|
||||||
|
|
||||||
# Now that all the defined keys have been accounted for, check to make
|
|
||||||
# sure any unauthorized keys were not specified.
|
|
||||||
if not ignore_undefined:
|
|
||||||
for key in d.iterkeys():
|
|
||||||
if key not in config:
|
|
||||||
raise ValidationException("'%s' is not a configuration item" % key)
|
|
||||||
return newdict
|
return newdict
|
||||||
|
|
||||||
return configDictValidator
|
return configDictValidator
|
||||||
@@ -193,3 +220,41 @@ def error(errstr):
|
|||||||
def validator(_):
|
def validator(_):
|
||||||
raise ValidationException(errstr)
|
raise ValidationException(errstr)
|
||||||
return validator
|
return validator
|
||||||
|
|
||||||
|
# Activestate recipe 576874
|
||||||
|
def _levenshtein(s1, 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]
|
||||||
|
|
||||||
|
def _get_closest_match(s, keys):
|
||||||
|
"""Returns a probable match for the given key `s` out of the possible keys in
|
||||||
|
`keys`. Returns None if no matches are very close.
|
||||||
|
|
||||||
|
"""
|
||||||
|
# Adjust this. 3 is probably a good number, it's probably not a typo if the
|
||||||
|
# distance is >3
|
||||||
|
threshold = 3
|
||||||
|
|
||||||
|
minmatch = None
|
||||||
|
mindist = threshold+1
|
||||||
|
|
||||||
|
for key in keys:
|
||||||
|
d = _levenshtein(s, key)
|
||||||
|
if d < mindist:
|
||||||
|
minmatch = key
|
||||||
|
mindist = d
|
||||||
|
|
||||||
|
if mindist <= threshold:
|
||||||
|
return minmatch
|
||||||
|
return None
|
||||||
|
|||||||
Reference in New Issue
Block a user