0001"""
0002**Deprecated** (13 Aug 2006)
0003
0004A config file loader that can load and nest multiple config files, the
0005config files can have structure, and the values can be tracked back to
0006their original file and line number.
0007
0008To start, you'd do something like:
0009
0010    >>> config = LazyLoader()
0011
0012You could use ``.load(filename)`` to load a config file; for the
0013examples it is convenient to instead use loadstring, and give a fake
0014filename:
0015
0016    >>> config_data = \"\"\"
0017    ... [server]
0018    ... port = 8000
0019    ... host = localhost
0020    ... document_root = /var/www
0021    ... \"\"\"
0022    >>> config.loadstring(config_data, filename='config_data.conf')
0023    >>> config['server']['port']
0024    '8000'
0025    >>> config['server'].convert('port', int)
0026    8000
0027    >>> config['server'].convert('host', int)
0028    Traceback (most recent call last):
0029        ...
0030    ValueError: Error in config_data.conf (section [server]), line 4 ('localhost'):
0031    ValueError: invalid literal for int(): localhost
0032
0033Note that names are normalized, removing case, underscores, and
0034spaces.  So to get to the document root:
0035
0036    >>> config['server']['documentroot']
0037    '/var/www'
0038
0039You can also merge in values; for instance, consider a virtual host
0040that overrides global values:
0041
0042    >>> vhost_data = \"\"\"
0043    ... [vhost(my.host.com)]
0044    ... document_root = /path/to/root
0045    ... \"\"\"
0046    >>> vhost_config = LazyLoader()
0047    >>> vhost_config.loadstring(vhost_data, filename='vhost_data.conf')
0048    >>> vhost_config['vhost'].keys()
0049    ['my.host.com']
0050
0051Note that key and section names can be nested with .'s, and ()'s quote
0052the values (so the key is ['my.host.com'] instead of
0053['my']['host']['com']).  Then we may want to merge this in, based on a
0054condition (e.g., the hostname matches my.host.com):
0055
0056    >>> config['server'].merge(vhost_config['vhost']['my.host.com'])
0057    >>> config['server']['documentroot']
0058    '/path/to/root'
0059
0060"""
0061
0062# This was deprecated 13 Aug 2006
0063import warnings
0064warnings.warn('initools.lazyloader is not supported or recommended for use.',
0065              DeprecationWarning)
0066
0067from lazyiniparser import LazyINIParser, Item
0068from nested import NestedDict
0069import inischema
0070import re
0071from UserDict import UserDict, DictMixin
0072
0073class LazyLoader(NestedDict):
0074
0075    def __init__(self, configs=None, mutable=False, nest=True,
0076                 section_name=None, master=None):
0077        self.section_name = section_name
0078        self.master = master
0079        NestedDict.__init__(self, configs=configs, mutable=mutable,
0080                            nest=nest)
0081
0082    def load(self, filename):
0083        parser = LazyINIParser(allow_empty_sections=True)
0084        parser.load(filename)
0085        config = self._convert_configuration(parser.configuration)
0086        self.add_config(config)
0087
0088    def loadstring(self, s, filename=None):
0089        parser = LazyINIParser(allow_empty_sections=True)
0090        parser.loadstring(s, filename=filename)
0091        config = self._convert_configuration(parser.configuration)
0092        self.add_config(config)
0093
0094    def merge(self, lazyloader):
0095        if self.master:
0096            self.master._propagate_merge([self.section_name], lazyloader)
0097        else:
0098            self._propagate_merge([], lazyloader)
0099
0100    def _propagate_merge(self, slave_keys, lazyloader):
0101        if self.master:
0102            slave_keys.insert(0, self.section_name)
0103            self.master._propagate_merge(slave_keys, lazyloader)
0104        else:
0105            configs = filter(None, lazyloader.configs)
0106            new_configs = []
0107            for config in configs:
0108                if slave_keys:
0109                    new_config = {}
0110                    set_config = new_config
0111                    for key in slave_keys[:-1]:
0112                        set_config[key] = {}
0113                        set_config = set_config[key]
0114                    set_config[slave_keys[-1]] = config
0115                else:
0116                    new_config = config
0117                self.add_config(new_config)
0118
0119    def __getitem__(self, key):
0120        try:
0121            return NestedDict.__getitem__(self, key)
0122        except KeyError, e:
0123            if self.section_name is None:
0124                section = 'global section'
0125            else:
0126                section = 'section [%s]' % self.section_name
0127            raise KeyError(
0128                "Key %r not found in %s" % (key, section))
0129
0130    def _convert_configuration(self, conf):
0131        data = {}
0132        for section in conf.sections:
0133            section_keys = self._parse_keys(section.name)
0134            if section_keys == ['global']:
0135                section_keys = []
0136            for item in section.items:
0137                item_keys = self._parse_keys(item.name)
0138                all_keys = section_keys + item_keys
0139                pos = data
0140                for key in all_keys[:-1]:
0141                    pos = pos.setdefault(key, {})
0142                pos.setdefault(all_keys[-1], []).append(item)
0143        return data
0144
0145    def _parse_keys(self, name):
0146        keys = []
0147        orig_name = name
0148        name = name.strip()
0149        while 1:
0150            next_period = name.find('.')
0151            next_paren = name.find('(')
0152            if next_paren == -1 and next_period == -1:
0153                next = self._canonical_name(name)
0154                if next:
0155                    keys.append(next)
0156                return keys
0157            elif (next_paren == -1
0158                or next_period != -1 and next_period < next_paren):
0159                next = self._canonical_name(name[:next_period])
0160                if next:
0161                    keys.append(next)
0162                name = name[next_period+1:]
0163            else:
0164                assert next_paren != -1
0165                assert next_period == -1 or next_paren < next_period
0166                next = self._canonical_name(name[:next_paren])
0167                if next:
0168                    keys.append(next)
0169                name = name[next_paren+1:]
0170                next_close = name.find(')')
0171                if next_close == -1:
0172                    raise inischema.ParseValueError(
0173                        "Key name contains a ( with no closing ): %r"
0174                        % orig_name)
0175                next = name[:next_close]
0176                keys.append(next)
0177                name = name[next_close+1:]
0178
0179    _canonical_re = re.compile('[_ \t-]')
0180    def _canonical_name(self, name):
0181        return self._canonical_re.sub('', name.lower())
0182
0183    def _convert_single(self, key, value_list):
0184        if isinstance(value_list[0], (dict, DictMixin, UserDict)):
0185            return self.__class__(
0186                value_list, mutable=self.mutable, nest=True,
0187                section_name=key, master=self)
0188        elif isinstance(value_list[0][-1], Item):
0189            return value_list[0][-1].content
0190        else:
0191            return value_list[0][-1]
0192
0193    def getlist(self, key, add_inherited=True):
0194        results = self.raw_get(key, add_inherited=add_inherited)
0195        converted = []
0196        for result_set in results:
0197            if not isinstance(result_set, (list, tuple)):
0198                result_set = [result_set]
0199            for item in result_set:
0200                if isinstance(item, (dict, UserDict, DictMixin)):
0201                    value = self.__class__(
0202                        [item], mutable=self.mutable, nest=self.nest)
0203                elif isinstance(item, Item):
0204                    value = item.content
0205                else:
0206                    value = item
0207                converted.append(value)
0208        return converted
0209
0210    def _make_converter(self, key, converter):
0211        if not isinstance(converter, inischema.opt):
0212            converter = inischema.optconverter.get_converter(
0213                key, converter_func=converter)
0214        return converter
0215
0216    def convert(self, key, converter):
0217        converter = self._make_converter(key, converter)
0218        try:
0219            return converter.convert(None, self[key])
0220        except inischema.ParseValueError, e:
0221            item = self.raw_get(key)[0][0]
0222            message = ('Error in %s (section [%s]), line %i (%r):\n%s'
0223                       % (item.filename, item.section.name, item.lineno,
0224                          self[key], e))
0225            raise ValueError(message)
0226
0227    def convertlist(self, key, converter, add_inherited=True):
0228        converter = self._make_converter(key, converter)
0229        value_list = self.getlist(key, add_inherited=add_inherited)
0230        new_list = []
0231        try:
0232            for value in value_list:
0233                new_list.append(converter.convert(value))
0234        except inischema.ParseValueError, e:
0235            pass