    import string, sys, os
    from types import DictType
    from MiscUtils import AbstractError, NoDefault
    from WebKit.ImportSpy import modloader
    from Funcs import valueForString

    class ConfigurationError(Exception):
        pass

    class Configurable:
        """
        Configurable is an abstract superclass that provides configuration
        file functionality for subclasses.

        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):
            """ Returns 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):
            """ Returns the value of a particular setting in the configuration. """
            if default is NoDefault:
                return self.config()[name]
            else:
                return self.config().get(name, default)

        def hasSetting(self, name):
            return self.config().has_key(name)

        def defaultConfig(self):
            """ Returns a dictionary containing all the default values for the settings. This implementation returns {}. Subclasses should override. """
            return {}

        def configFilename(self):
            """ Returns the filename by which users can override the configuration. 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):
            """
            Returns the name of the configuration file (the portion
            before the '.config').  This is used on the command-line.
            """
            return os.path.splitext(os.path.basename(self.configFilename()))[0]

        def configReplacementValues(self):
            """
            Returns 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):
            """ Returns the user config overrides found in the optional config file, or {} if there is no such file. The config filename is taken from configFilename(). """
            try:
                filename = self.configFilename()
                if filename is None:
                    return {}
                file = open(filename)
            except IOError:
                return {}
            else:
                contents = file.read()
                file.close()
                modloader.watchFile(filename)
                replacements = self.configReplacementValues()
                if replacements:
                    try:
                        contents = contents % replacements
                    except:
                        raise ConfigurationError, 'Unable to embed replacement text in %s.' % self.configFilename()

                try:
                    config = eval(contents, {})
                except:
                    raise ConfigurationError, 'Invalid configuration file, %s.' % self.configFilename()
                if type(config) is not DictType:
                    raise ConfigurationError, 'Invalid type of configuration. Expecting dictionary, but got %s.'  % type(config)
                return config

        def printConfig(self, dest=None):
            """ Prints the configuration to the given destination, which defaults to 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(lambda key: len(key), keys))
            for key in keys:
                dest.write(string.ljust(key, width)+' = '+str(self.setting(key))+'\n')
            dest.write('\n')

        def commandLineConfig(self):
            """
            Settings that came from the command line (via
            addCommandLineSetting).
            """
            return _settings.get(self.configName(), {})

    _settings = {}
    def addCommandLineSetting(name, value):
        """
        Take a setting, like --AppServer.Verbose=0, and call
        addCommandLineSetting('AppServer.Verbose', '0'), and
        it will override any settings in AppServer.config
        """
        configName, settingName = string.split(name, '.', 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)