import sys, os
from types import DictType
from MiscUtils import AbstractError,NoDefault
from Funcs import valueForString
class ConfigurationError(Exception):
pass
class Configurable:
"""Abstract superclass for configuration file functionality.
Subclasses should override:
* defaultConfig() to return a dictionary of default settings
such as { 'Frequency': 5 }
* configFilename() to return the filename by which users can
override the configuration such as
'Pinger.config'
Subclasses typically use the setting() method, for example:
time.sleep(self.setting('Frequency'))
They might also use the printConfig() method, for example:
self.printConfig() # or
self.printConfig(file)
Users of your software can create a file with the same name as
configFilename() and selectively override settings. The format of
the file is a Python dictionary.
Subclasses can also override userConfig() in order to obtain the
user configuration settings from another source.
"""
## Init ##
def __init__(self):
self._config = None
## Configuration
def config(self):
"""Return the configuration of the object as a dictionary.
This is a combination of defaultConfig() and userConfig().
This method caches the config.
"""
if self._config is None:
self._config = self.defaultConfig()
self._config.update(self.userConfig())
self._config.update(self.commandLineConfig())
return self._config
def setting(self, name, default=NoDefault):
"""Return the value of a particular setting in the configuration."""
if default is NoDefault:
try:
return self.config()[name]
except KeyError:
raise KeyError, \
'%s config keys are: %s' % (name, self.config().keys())
else:
return self.config().get(name, default)
def setSetting(self, name, value):
"""Set a particular configuration setting."""
self.config()[name] = value
def hasSetting(self, name):
"""Check whether a configuration setting has been changed."""
return self.config().has_key(name)
def defaultConfig(self):
"""Return a dictionary with all the default values for the settings.
This implementation returns {}. Subclasses should override.
"""
return {}
def configFilename(self):
"""Return the full name of the user config file.
Users can override the configuration by this config file.
Subclasses must override to specify a name.
Returning None is valid, in which case no user config file
will be loaded.
"""
raise AbstractError, self.__class__
def configName(self):
"""Return the name of the configuration file without the extension.
This is the portion of the config file name before the '.config'.
This is used on the command-line.
"""
return os.path.splitext(os.path.basename(self.configFilename()))[0]
def configReplacementValues(self):
"""Return a dictionary for substitutions in the config file.
This must be a dictionary suitable for use with "string % dict"
that should be used on the text in the config file.
If an empty dictionary (or None) is returned, then no substitution
will be attempted.
"""
return {}
def userConfig(self):
"""Return the user config overrides.
These settings can be found in the optional config file.
Returns {} if there is no such file.
The config filename is taken from configFilename().
"""
filename = self.configFilename()
if not filename:
return {}
try:
# open the config file in universal newline mode,
# in case it has been edited on a different platform
contents = open(filename, 'rU').read()
except IOError, e:
print 'WARNING: Config file', filename
print ' not loaded: %s.' % e.strerror
print
return {}
isDict = contents.lstrip().startswith('{')
from WebKit.AppServer import globalAppServer
if globalAppServer:
globalAppServer._imp.watchFile(filename)
replacements = self.configReplacementValues()
if replacements and isDict:
try:
contents %= replacements
except Exception:
raise ConfigurationError, \
'Unable to embed replacement text in %s.' % filename
evalContext = replacements.copy()
try:
True, False
except NameError: # Python < 2.3
evalContext['True'] = 1
evalContext['False'] = 0
try:
if isDict:
config = eval(contents, evalContext)
else:
exec contents in evalContext
config = evalContext
for name in config.keys():
if name.startswith('_'):
del config[name]
except Exception, e:
raise ConfigurationError, \
'Invalid configuration file, %s (%s).' % (filename, e)
if type(config) is not DictType:
raise ConfigurationError, 'Invalid type of configuration.' \
' Expecting dictionary, but got %s.' % type(config)
try:
True, False
except NameError: # Python < 2.3
del evalContext['True']
del evalContext['False']
return config
def printConfig(self, dest=None):
"""Print the configuration to the given destination.
The default destionation is stdout. A fixed with font is assumed
for aligning the values to start at the same column.
"""
if dest is None:
dest = sys.stdout
keys = self.config().keys()
keys.sort()
width = max(map(len, keys))
for key in keys:
dest.write('%s = %s\n'
% (key.ljust(width), str(self.setting(key))))
dest.write('\n')
def commandLineConfig(self):
"""Return the settings that came from the command-line.
These settings come via addCommandLineSetting().
"""
return _settings.get(self.configName(), {})
## Command line settings ##
_settings = {}
def addCommandLineSetting(name, value):
"""Override the configuration with a command-line setting.
Take a setting, like "AppServer.Verbose=0", and call
addCommandLineSetting('AppServer.Verbose', '0'), and
it will override any settings in AppServer.config
"""
configName, settingName = name.split('.', 1)
value = valueForString(value)
if not _settings.has_key(configName):
_settings[configName] = {}
_settings[configName][settingName] = value
def commandLineSetting(configName, settingName, default=NoDefault):
"""Retrieve a command-line setting.
You can use this with non-existent classes, like "Context.Root=/WK",
and then fetch it back with commandLineSetting('Context', 'Root').
"""
if default is NoDefault:
return _settings[configName][settingName]
else:
return _settings.get(configName, {}).get(settingName, default)
|