source: LinkExchange/trunk/linkexchange/config.py @ 260

Revision 260, 7.3 KB checked in by lostclus, 5 months ago (diff)

Fixed string interpolation in configuration file.

Line 
1# LinkExchange - Universal link exchange service client
2# Copyright (C) 2009 Konstantin Korikov
3#
4# This library is free software; you can redistribute it and/or
5# modify it under the terms of the GNU Lesser General Public
6# License as published by the Free Software Foundation; either
7# version 2.1 of the License, or (at your option) any later version.
8#
9# This library is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12# Lesser General Public License for more details.
13#
14# You should have received a copy of the GNU Lesser General Public
15# License along with this library; if not, write to the Free Software
16# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
17#
18# NOTE: In the context of the Python environment, I interpret "dynamic
19# linking" as importing -- thus the LGPL applies to the contents of
20# the modules, but make no requirements on code importing these
21# modules.
22
23"""
24>>> import StringIO
25>>> from linkexchange import config
26>>> cfg_lines = [
27...   '[options]',
28...   'host=example.com',
29...   '',
30...   '[client-1]',
31...   'type=sape',
32...   'user=user12345',
33...   'db_driver.type=mem',
34...   'server-1=http://server1.com',
35...   'server-2=http://server2.com',
36...   '',
37...   '[client-2]',
38...   'type=linkfeed',
39...   'user=user12345',
40...   'db_driver.type=shelve',
41...   'db_driver.filename=linkfeed-XXX.db',
42...   '',
43...   '[client-3]',
44...   'type=trustlink',
45...   'user=user12345',
46...   'db_driver.type=mem',
47...   'link_template = "<a href=\\\\"%%(href)s\\\\">%%(anchor)s</a> %%(text)s"',
48...   '',
49...   '[formatter-1]',
50...   'type=inline',
51...   'count=none',
52...   'delimiter=u" | "',
53...   ]
54>>> vars = {}
55>>> cfg_file = StringIO.StringIO('\\n'.join(cfg_lines))
56>>> result = config.file_config(vars, cfg_file)
57>>> result == [cfg_file]
58True
59>>> vars['options']['host']
60'example.com'
61>>> vars['platform'].clients[0].user
62'user12345'
63>>> vars['platform'].clients[0].server_list[0]
64'http://server1.com'
65>>> vars['platform'].clients[0].server_list[1]
66'http://server2.com'
67>>> vars['platform'].clients[1].db_driver.filename
68'linkfeed-XXX.db'
69>>> vars['platform'].clients[2].link_template
70'<a href="%(href)s">%(anchor)s</a> %(text)s'
71>>> vars['formatters'][0].count
72>>> vars['formatters'][0].delimiter
73u' | '
74>>> import os, tempfile
75>>> cfg_fd, cfg_file = tempfile.mkstemp()
76>>> os.close(cfg_fd)
77>>> open(cfg_file, 'w').write('\\n'.join(cfg_lines))
78>>> result = config.file_config(vars, cfg_file)
79>>> result == [cfg_file]
80True
81>>> os.unlink(cfg_file)
82>>> result = config.file_config(vars, cfg_file)
83>>> result == []
84True
85"""
86
87import sys
88import ConfigParser
89import shlex
90
91from linkexchange.utils import load_plugin
92from linkexchange.platform import Platform
93
94class ConfigError(Exception):
95    "Configuration error"
96
97def file_config(vars, fname, defaults = None, prefix = '',
98        default_encoding = 'utf-8'):
99    if defaults is None:
100        defaults = {}
101
102    cp = ConfigParser.SafeConfigParser(defaults)
103    try:
104        if hasattr(cp, 'readfp') and hasattr(fname, 'readline'):
105            cp.readfp(fname)
106            result = [fname]
107        else:
108            result = cp.read(fname)
109            if sys.version_info < (2, 4):
110                if cp.sections():
111                    if type(fname) == list:
112                        result = fname
113                    else:
114                        result = [fname]
115                else:
116                    result = []
117    except (ConfigParser.MissingSectionHeaderError,
118            ConfigParser.ParsingError), e:
119        raise ConfigError("Parsing error: %s" % str(e))
120
121    if not result:
122        return result
123
124    _clients = []
125    _formatters = []
126    _options = {}
127
128    try:
129        encoding = cp.get('options', 'config_encoding')
130    except (ConfigParser.NoOptionError, ConfigParser.NoSectionError):
131        encoding = default_encoding
132
133    for sec in cp.sections():
134        opts = dict([(o, cp.get(sec, o))
135            for o in cp.options(sec) if o not in defaults])
136        cn = fn = None
137        if sec == 'client':
138            cn = 1
139        elif sec.startswith('client-'):
140            cn = int(sec.split('-', 1)[1])
141        elif sec == 'formatter':
142            fn = 1
143        elif sec.startswith('formatter-'):
144            fn = int(sec.split('-', 1)[1])
145        elif sec == 'options':
146            for k, v in opts.items():
147                _options[k] = _eval_value(v, encoding)
148        if cn:
149            _clients.extend([None] * max(0, cn - len(_clients)))
150            _clients[cn - 1] = _parse_plugin_spec(opts, encoding)
151        if fn:
152            _formatters.extend([None] * max(0, fn - len(_formatters)))
153            _formatters[fn - 1] = _parse_plugin_spec(opts, encoding)
154
155    def load_client(spec):
156        try:
157            return load_plugin('linkexchange.clients', spec)
158        except ImportError:
159            raise ConfigError("Client not found: %s" % spec[0])
160
161    def load_formatter(spec):
162        try:
163            return load_plugin('linkexchange.formatters', spec)
164        except ImportError:
165            raise ConfigError("Formatter not found: %s" % spec[0])
166
167    clients = [load_client(x) for x in _clients if x is not None]
168    vars[prefix + 'platform'] = Platform(clients)
169    vars[prefix + 'formatters'] = [load_formatter(x)
170            for x in _formatters if x is not None]
171    vars[prefix + 'options'] = _options
172
173    return result
174
175def _eval_value(value, encoding):
176    value = value.strip()
177    try:
178        return int(value)
179    except ValueError:
180        pass
181    try:
182        return long(value)
183    except ValueError:
184        pass
185    try:
186        return float(value)
187    except ValueError:
188        pass
189    value_lower = value.lower()
190    if value_lower in ('true', 'on', 'enabled'):
191        return True
192    if value_lower in ('false', 'off', 'disabled'):
193        return False
194    if value_lower == 'none':
195        return None
196    conv_str = lambda x: x
197    unquote_str = lambda x: x
198    if value.endswith('"'):
199        if value.startswith('u"'):
200            conv_str = lambda x: unicode(x, encoding)
201            value = value[1:]
202        if value.startswith('"'):
203            unquote_str = lambda x: ''.join(shlex.split(x))
204    return conv_str(unquote_str(value))
205
206def _parse_plugin_spec(dic, encoding, toplevel=''):
207    opts = {}
208    compound = {}
209    for k, v in dic.items():
210        if '.' in k:
211            k1, k2 = k.split('.', 1)
212            compound.setdefault(k1, {})
213            compound[k1][k2] = v
214        elif k.endswith('_list'):
215            opts[k] = [_eval_value(x, encoding) for x in v.split(',')]
216        elif '-' in k and k.split('-')[-1].isdigit():
217            a, n = k.split('-')
218            kk = a + '_list'
219            nn = int(n) - 1
220            opts.setdefault(kk, [])
221            opts[kk].extend([None] * max(0, nn - len(opts[kk]) + 1))
222            opts[kk][nn] = _eval_value(v, encoding)
223        else:
224            opts[k] = _eval_value(v, encoding)
225    for k, v in compound.items():
226        opts[k] = _parse_plugin_spec(v, encoding,
227                toplevel + k + '.')
228    try:
229        plugin_type = opts.pop('type')
230    except KeyError:
231        raise ConfigError(("The %s option(s) is set, "
232            "but required %s is not set" % (', '.join(
233                [toplevel + x for x in opts.keys()]),
234                toplevel + 'type')))
235    return (plugin_type, [], opts)
236
237if __name__ == "__main__":
238    import doctest
239    doctest.testmod()
Note: See TracBrowser for help on using the repository browser.