0

overhaul to configParser. Parsing config works now.

This commit is contained in:
Andrew Brown
2012-02-04 21:23:44 -05:00
parent ceb98c4441
commit 6d95d80a73
8 changed files with 321 additions and 245 deletions

View File

@@ -4,103 +4,72 @@ import os.path
import logging
import settingsDefinition
import settingsValidators
class MultiWorldParser(object):
"""A class that is used to parse a settings.py file. It should replace
ConfigOptionParser class above."""
"""A class that is used to parse a settings.py file.
This class's job is to compile and validate the configuration settings for
a set of renders. It can read in configuration from the given file with the
parse() method, and one can set configuration options directly with the
set_config_item() method.
def __init__(self, settings):
"""Settings is a path to a settings.py file"""
if not os.path.exists(settings) and not os.path.isfile(settings):
get_validated_config() validates and returns the validated config
"""
def __init__(self):
"""Initialize this parser object"""
# This maps config names to their values
self._config_state = {}
# Scan the settings definition and build the config state heirarchy.
# Also go ahead and set default values for non-required settings.
# This maps setting names to their values as given in
# settingsDefinition.py
self._settings = {}
for settingname in settingsDefinition.__all__:
setting = getattr(settingsDefinition, settingname)
assert isinstance(setting, settingsValidators.Setting)
self._settings[settingname] = setting
if not setting.required:
self._config_state[settingname] = setting.default
def set_config_item(self, itemname, itemvalue):
self._config_state[itemname] = itemvalue
def parse(self, settings_file):
"""Reads in the named file and parses it, storing the results in an
internal state awating to be validated and returned upon call to
get_render_settings()
"""
if not os.path.exists(settings_file) and not os.path.isfile(settings_file):
raise ValueError("bad settings file")
self.settings_file = settings
def parse(self):
# settingsDefinition.py defines what types of things you can put in a configfile
# anything in settingsDefinitino that is a dict with a "type" key is a definiton
defDicts = [x for x in dir(settingsDefinition) if type(getattr(settingsDefinition,x)) == dict and getattr(settingsDefinition,x).has_key("type") and x != "__builtins__"]
glob = dict()
for name in defDicts:
d = getattr(settingsDefinition, name)
glob[name] = d['type']()
# The global environment should be the rendermode module, so the config
# file has access to those resources.
import rendermodes
loc=dict()
for thing in dir(rendermodes):
thething = getattr(rendermodes, thing)
if isinstance(thething, type) and issubclass(thething, rendermodes.RenderPrimitive):
loc[thing] = thething
try:
execfile(self.settings_file, glob, loc)
# delete the builtins, we don't need it
del glob['__builtins__']
execfile(settings_file, rendermodes.__dict__, self._config_state)
except NameError, ex:
import traceback
traceback.print_exc()
logging.error("Error parsing %s. Please check the trackback above" % self.settings_file)
logging.exception("Error parsing %s. Please check the trackback for more info" % settings_file)
sys.exit(1)
except SyntaxError, ex:
import traceback
traceback.print_exc()
tb = sys.exc_info()[2]
#print tb.tb_frame.f_code.co_filename
logging.error("Error parsing %s. Please check the trackback above" % self.settings_file)
logging.exception("Error parsing %s. Please check the trackback for more info" % self.settings_file)
sys.exit(1)
for name in defDicts:
setattr(self, name, glob[name])
del glob[name]
# seed with the Overviewer defaults, then update with the user defaults
self.defaults = dict()
for name in defDicts:
d = getattr(settingsDefinition, name)
if d['type'] == dict and d['valuetype'] == dict:
for key in d['values']:
option = d['values'][key]
if option.has_key("default"):
self.defaults[key] = option["default"]
self.defaults.update(glob)
def validate(self):
origs = dict()
for worldname in self.render:
world = dict()
world.update(self.defaults)
world.update(self.render[worldname])
for key in world:
if key not in settingsDefinition.render['values']:
logging.warning("%r is not a known setting", key)
continue
definition = settingsDefinition.render['values'][key]
try:
val = definition['validator'](world[key], world = self.world)
if definition.get('save_orig', False):
origs[key + "_orig"] = world[key]
world[key] = val
except Exception as e:
logging.error("Error validating '%s' option in render definition for '%s':", key, worldname)
logging.error(e)
raise e
world['name'] = worldname
world.update(origs)
self.render[worldname] = world
def get_render_things(self):
return self.render
def get_validated_config(self):
"""Validate and return the configuration"""
# Okay, this is okay, isn't it? We're going to create the validation
# routine right here, right now. I hope this works!
validator = settingsValidators.make_configdictvalidator(self._settings)
# Woah. What just happened? No. WAIT, WHAT ARE YOU...
validated_config = validator(self._config_state)
# WHAT HAVE YOU DONE?
return validated_config
# WHAT HAVE YOU DOOOOOOOOOOONE????

View File

@@ -1,54 +1,73 @@
# This file defines all of the things than can
# appear in a settings.py file, as well as the
# function that can validate them
# This file describes the format of the config file. Each item defined in this
# module is expected to appear in the same format in a settings file. The only
# difference is, instead of actual values for the settings, one is to use a
# Setting object. Here is its signature:
# the validator should raise an exception if there
# is a problem parsing or validating the value.
# it should return the value to use (which gives
# the validator an oppertunity to cleanup/normalize/
# whaterver)
# Setting(required, validator, default)
# if a setting is not required, the validator is
# expected to return the default option
# required is a boolean indicating the user is required to provide this
# setting. In this case, default is unused and can be set to anything (None is
# a good choice).
# validator is a callable that takes the provided value and returns a
# cleaned/normalized value to use. It should raise an exception if there is a
# problem parsing or validating the value given.
# default is used instead of the user-provided value in the event that required
# is false. It is passed into the validator just the same. If default is None
# and required is False, then that value is skipped entirely and will not
# appear in the resulting parsed options.
# The signature for validators is validator(value_given). Remember that the
# default is passed in as value_given in the event that required is False and
# default is not None.
# This file doesn't specify the format or even the type of the setting values,
# that is up to the validators used.
from settingsValidators import *
# note that all defaults go thought the validator
render = {
"type": dict,
"valuetype": dict,
"values": {
"worldname": dict(required=True, validator=validateWorldPath, save_orig=True),
"dimension": dict(required=False, validator=validateDimension, default="default"),
"title": dict(required=True, validator=validateStr),
"rendermode": dict(required=False, validator=validateRenderMode),
"northdirection": dict(required=False, validator=validateNorthDirection, default=0),
"renderrange": dict(required=False, validator=validateRenderRange),
"forcerender": dict(required=False, validator=validateBool),
"stochasticrender": dict(required=False, validator=validateStochastic),
"imgformat": dict(required=False, validator=validateImgFormat, default="png"),
"imgquality": dict(required=False, validator=validateImgQuality),
"bgcolor": dict(required=False, validator=validateBGColor, default="1a1a1a"),
"optimizeimg": dict(required=False, validator=validateOptImg, default=0),
"nomarkers": dict(required=False, validator=validateBool),
"texturepath": dict(required=False, validator=validateTexturePath),
"renderchecks": dict(required=False, validator=validateInt, default=0),
"rerenderprob": dict(required=False, validator=validateFloat, default=0),
}
# This is the export list for this module. It defines which items defined in
# this module are recognized by the config parser. Don't forget to update this
# if you add new items!
__all__ = ['render', 'world', 'outputdir']
# render is a dictionary mapping names to dicts describing the configuration
# for that render. It is therefore set to a settings object with a dict
# validator configured to validate keys as strings and values as... values are
# set to validate as a "configdict", which is a dict mapping a set of strings
# to some value. the make_configdictvalidator function creates a validator to
# use here configured with the given set of keys and Setting objects with their
# respective validators.
# Perhaps unintuitively, this is set to required=False. Of course, if no
# renders are specified, this is an error. However, this is caught later on in
# the code, and it also lets an empty dict get defined beforehand for the
# config file.
render = Setting(required=False, default={},
validator=dictValidator(validateStr, make_configdictvalidator(
{
"worldname": Setting(required=True, validator=validateStr, default=None),
"dimension": Setting(required=False, validator=validateDimension, default="default"),
"title": Setting(required=True, validator=validateStr, default=None),
"rendermode": Setting(required=False, validator=validateRenderMode, default=None),
"northdirection": Setting(required=False, validator=validateNorthDirection, default=0),
"renderrange": Setting(required=False, validator=validateRenderRange, default=None),
"forcerender": Setting(required=False, validator=validateBool, default=None),
"stochasticrender": Setting(required=False, validator=validateStochastic, default=None),
"imgformat": Setting(required=False, validator=validateImgFormat, default="png"),
"imgquality": Setting(required=False, validator=validateImgQuality, default=None),
"bgcolor": Setting(required=False, validator=validateBGColor, default="1a1a1a"),
"optimizeimg": Setting(required=False, validator=validateOptImg, default=0),
"nomarkers": Setting(required=False, validator=validateBool, default=None),
"texturepath": Setting(required=False, validator=validateTexturePath, default=None),
"renderchecks": Setting(required=False, validator=validateInt, default=0),
"rerenderprob": Setting(required=False, validator=validateFloat, default=0),
}
)))
world = {
"type": dict,
"valuetype": str,
"value": dict(validator=validateStr)
}
outputdir = {
"type": str,
"value": dict(validator=validateOutputDir)
}
#defines the values for each member of the world dict
#world = dict(require
# The world dict, mapping world names to world paths
world = Setting(required=False, validator=dictValidator(validateStr, validateWorldPath), default={})
outputdir = Setting(required=True, validator=validateOutputDir, default=None)

View File

@@ -1,6 +1,7 @@
# see settingsDefinition.py
import os
import os.path
from collections import namedtuple
import rendermodes
from world import UPPER_LEFT, UPPER_RIGHT, LOWER_LEFT, LOWER_RIGHT
@@ -8,20 +9,34 @@ from world import UPPER_LEFT, UPPER_RIGHT, LOWER_LEFT, LOWER_RIGHT
class ValidationException(Exception):
pass
def validateWorldPath(name, **kwargs):
world = kwargs.get('world', dict())
if name not in world.keys():
raise ValidationException("bad world name")
abs_path = os.path.abspath(world[name])
Setting = namedtuple("Setting", [
'required',
'validator',
'default',
])
def validateWorldPath(worldpath):
abs_path = os.path.abspath(worldpath)
if not os.path.exists(os.path.join(abs_path, "level.dat")):
raise ValidationException("No level.dat file in %r. Check the path for world %r" % (abs_path, name))
raise ValidationException("No level.dat file in %r. Are you sure you have the right path?" % (abs_path,))
return abs_path
def validateRenderMode(mode, **kwargs):
def validateRenderMode(mode):
# make sure that mode is a list of things that are all rendermode primative
if type(mode) != list:
if isinstance(mode, str):
# Try and find an item named "mode" in the rendermodes module
try:
mode = getattr(rendermodes, mode)
except AttributeError:
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)
for m in mode:
if not isinstance(m, rendermodes.RenderPrimitive):
raise ValidationException("%r is not a valid rendermode primitive." % m)
@@ -29,7 +44,7 @@ def validateRenderMode(mode, **kwargs):
return mode
def validateNorthDirection(direction, **kwargs):
def validateNorthDirection(direction):
# normalize to integers
intdir = 0 #default
if type(direction) == int:
@@ -43,28 +58,28 @@ def validateNorthDirection(direction, **kwargs):
raise ValidationException("%r is not a valid north direction" % direction)
return intdir
def validateRenderRange(r, **kwargs):
def validateRenderRange(r):
raise NotImplementedError("render range")
def validateStochastic(s, **kwargs):
def validateStochastic(s):
val = float(s)
if val < 0 or val > 1:
raise ValidationException("%r is not a valid stochastic value. Should be between 0.0 and 1.0" % s)
return val
def validateImgFormat(fmt, **kwargs):
def validateImgFormat(fmt):
if fmt not in ("png", "jpg", "jpeg"):
raise ValidationException("%r is not a valid image format" % fmt)
if fmt == "jpeg": fmt = "jpg"
return fmt
def validateImgQuality(qual, **kwargs):
def validateImgQuality(qual):
intqual = int(qual)
if (intqual < 0 or intqual > 100):
raise ValidationException("%r is not a valid image quality" % intqual)
return intqual
def validateBGColor(color, **kwargs):
def validateBGColor(color):
"""BG color must be an HTML color, with an option leading # (hash symbol)
returns an (r,b,g) 3-tuple
"""
@@ -86,32 +101,80 @@ def validateBGColor(color, **kwargs):
return color
def validateOptImg(opt, **kwargs):
def validateOptImg(opt):
return bool(opt)
def validateTexturePath(path, **kwargs):
def validateTexturePath(path):
# Expand user dir in directories strings
path = os.path.expanduser(path)
# TODO assert this path exists?
return path
def validateBool(b, **kwargs):
def validateBool(b):
return bool(b)
def validateFloat(f, **kwargs):
def validateFloat(f):
return float(f)
def validateInt(i, **kwargs):
def validateInt(i):
return int(i)
def validateStr(s, **kwargs):
def validateStr(s):
return str(s)
def validateDimension(d, **kwargs):
def validateDimension(d):
if d in ["nether", "overworld", "end", "default"]:
return d
raise ValidationException("%r is not a valid dimension" % d)
def validateOutputDir(d, **kwargs):
return os.path.abs(d)
def validateOutputDir(d):
if not d.strip():
raise ValidationException("You must specify a valid output directory")
return os.path.abspath(d)
def dictValidator(keyvalidator, valuevalidator):
"""Compose a dict validator by validating each key/value combination with
the given key validator and value validator
"""
def v(d):
newd = {}
for key, value in d.iteritems():
newd[keyvalidator(key)] = valuevalidator(value)
return newd
return v
def make_configdictvalidator(config):
"""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
refer to a dict where keys are strings and values are something. The
argument to /this/ function is a dictionary mapping those key names to
Setting objects. When the validator validates, it calls all the appropriate
validators to validate each item in the configdict.
I hope that makes sense.
"""
def configDictValidator(d):
newdict = {}
for configkey, configsetting in config.iteritems():
if configkey in d:
# This key /was/ specified in the user's dict. Make sure it validates.
newdict[configkey] = configsetting.validator(d[configkey])
else:
# The user did not give us this key. If it's required, send up
# an error. Otherwise, just return the default.
if configsetting.required:
raise ValidationException("Required key '%s' was not specified" % configkey)
elif configsetting.default is not None:
newdict[configkey] = configsetting.validator(configsetting.default)
# Now that all the defined keys have been accounted for, check to make
# sure any unauthorized keys were not specified.
for key in d.iterkeys():
if key not in config:
raise ValidationException("'%s' is not a configuration item" % key)
return newdict
return configDictValidator