| 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] |
|---|
| 58 | True |
|---|
| 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 |
|---|
| 73 | u' | ' |
|---|
| 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] |
|---|
| 80 | True |
|---|
| 81 | >>> os.unlink(cfg_file) |
|---|
| 82 | >>> result = config.file_config(vars, cfg_file) |
|---|
| 83 | >>> result == [] |
|---|
| 84 | True |
|---|
| 85 | """ |
|---|
| 86 | |
|---|
| 87 | import sys |
|---|
| 88 | import ConfigParser |
|---|
| 89 | import shlex |
|---|
| 90 | |
|---|
| 91 | from linkexchange.utils import load_plugin |
|---|
| 92 | from linkexchange.platform import Platform |
|---|
| 93 | |
|---|
| 94 | class ConfigError(Exception): |
|---|
| 95 | "Configuration error" |
|---|
| 96 | |
|---|
| 97 | def 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 | |
|---|
| 175 | def _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 | |
|---|
| 206 | def _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 | |
|---|
| 237 | if __name__ == "__main__": |
|---|
| 238 | import doctest |
|---|
| 239 | doctest.testmod() |
|---|