    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)