Completed
Push — master ( 613b11...22be07 )
by Satoru
01:00
created

_make_parser()   B

Complexity

Conditions 6

Size

Total Lines 24

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
dl 0
loc 24
rs 7.6129
c 0
b 0
f 0
1
#
2
# Copyright (C) 2011 - 2017 Satoru SATOH <ssato @ redhat.com>
3
# License: MIT
4
#
5
#  pylint: disable=unused-argument
6
"""INI or INI like config files backend.
7
8
- Format to support: INI or INI like ones
9
- Requirements: It should be available always.
10
11
  - ConfigParser in python 2 standard library:
12
    https://docs.python.org/2.7/library/configparser.html
13
14
  - configparser in python 3 standard library:
15
    https://docs.python.org/3/library/configparser.html
16
17
- Development Status :: 4 - Beta
18
- Limitations: It cannot process nested configuration dicts correctly due to
19
  the limitation of configparser module.
20
21
- Special options:
22
23
  - Use 'ac_parse_value' boolean keyword option if you want to parse values by
24
    custom parser, anyconfig.backend.ini._parse.
25
26
History:
27
28
.. versionchanged:: 0.3
29
   Introduce 'ac_parse_value' keyword option to switch behaviors, same as
30
   original configparser and rich backend parsing each parameter values.
31
"""
32
from __future__ import absolute_import
33
34
import anyconfig.backend.base
35
import anyconfig.parser as P
36
import anyconfig.utils
37
38
from anyconfig.compat import configparser, iteritems, OrderedDict
39
from anyconfig.backend.base import mk_opt_args
40
41
42
_SEP = ','
43
44
45
def _noop(val, *args, **kwargs):
46
    """
47
    Parser does nothing.
48
    """
49
    # It means nothing but can suppress 'Unused argument' pylint warns.
50
    # (val, args, kwargs)[0]
51
    return val
52
53
54
def _parse(val_s, sep=_SEP):
55
    """
56
    FIXME: May be too naive implementation.
57
58
    :param val_s: A string represents some value to parse
59
    :param sep: separator between values
60
61
    >>> _parse(r'"foo string"')
62
    'foo string'
63
    >>> _parse("a, b, c")
64
    ['a', 'b', 'c']
65
    >>> _parse("aaa")
66
    'aaa'
67
    """
68
    if (val_s.startswith('"') and val_s.endswith('"')) or \
69
            (val_s.startswith("'") and val_s.endswith("'")):
70
        return val_s[1:-1]
71
    elif sep in val_s:
72
        return [P.parse(x) for x in P.parse_list(val_s)]
73
    else:
74
        return P.parse(val_s)
75
76
77
def _to_s(val, sep=", "):
78
    """Convert any to string.
79
80
    :param val: An object
81
    :param sep: separator between values
82
83
    >>> _to_s([1, 2, 3])
84
    '1, 2, 3'
85
    >>> _to_s("aaa")
86
    'aaa'
87
    """
88
    if anyconfig.utils.is_iterable(val):
89
        return sep.join(str(x) for x in val)
90
    else:
91
        return str(val)
92
93
94
def _make_parser(to_container, **kwargs):
95
    """
96
    :param to_container: any callable to make container
97
    :return: (to_container, keyword args to be used, parser object)
98
    """
99
    if kwargs.get("ac_ordered", False) or kwargs.get("dict_type", False):
100
        kwargs["dict_type"] = to_container = OrderedDict
101
    if "dict_type" not in kwargs and not kwargs.get("ac_ordered", True):
102
        kwargs["dict_type"] = to_container
103
104
    # Optional arguements for configparser.SafeConfigParser{,readfp}
105
    kwargs_0 = mk_opt_args(("defaults", "dict_type", "allow_no_value"), kwargs)
106
    kwargs_1 = mk_opt_args(("filename", ), kwargs)
107
108
    try:
109
        parser = configparser.SafeConfigParser(**kwargs_0)
110
    except TypeError:
111
        # .. note::
112
        #    It seems ConfigPaser.*ConfigParser in python 2.6 does not support
113
        #    'allow_no_value' option parameter, and TypeError will be thrown.
114
        kwargs_0 = mk_opt_args(("defaults", "dict_type"), kwargs)
115
        parser = configparser.SafeConfigParser(**kwargs_0)
116
117
    return (to_container, kwargs_1, parser)
118
119
120
def _load(stream, to_container=dict, sep=_SEP, **kwargs):
121
    """
122
    :param stream: File or file-like object provides ini-style conf
123
    :param to_container: any callable to make container
124
    :param sep: Seprator string
125
126
    :return: Dict or dict-like object represents config values
127
    """
128
    (to_container, kwargs_1, parser) = _make_parser(to_container, **kwargs)
129
    _parse_val = _parse if kwargs.get("ac_parse_value", False) else _noop
130
131
    cnf = to_container()
132
    parser.readfp(stream, **kwargs_1)
133
134
    # .. note:: Process DEFAULT config parameters as special ones.
135
    defaults = parser.defaults()
136
    if defaults:
137
        cnf["DEFAULT"] = to_container()
138
        for key, val in iteritems(defaults):
139
            cnf["DEFAULT"][key] = _parse_val(val, sep)
140
141
    for sect in parser.sections():
142
        cnf[sect] = to_container()
143
        for key, val in parser.items(sect):
144
            cnf[sect][key] = _parse_val(val, sep)
145
146
    return cnf
147
148
149
def _dumps_itr(cnf):
150
    """
151
    :param cnf: Configuration data to dump
152
    """
153
    dkey = "DEFAULT"
154
    for sect, params in iteritems(cnf):
155
        yield "[%s]" % sect
156
157
        for key, val in iteritems(params):
158
            if sect != dkey and dkey in cnf and cnf[dkey].get(key) == val:
159
                continue  # It should be in [DEFAULT] section.
160
161
            yield "%s = %s" % (key, _to_s(val))
162
163
        yield ''  # it will be a separator between each sections.
164
165
166
def _dumps(cnf, **kwargs):
167
    """
168
    :param cnf: Configuration data to dump
169
    :param kwargs: optional keyword parameters to be sanitized :: dict
170
171
    :return: String representation of `cnf` object in INI format
172
    """
173
    return '\n'.join(l for l in _dumps_itr(cnf))
174
175
176
class Parser(anyconfig.backend.base.FromStreamLoader,
177
             anyconfig.backend.base.ToStringDumper):
178
    """
179
    Ini config files parser.
180
    """
181
    _type = "ini"
182
    _extensions = ["ini"]
183
    _load_opts = ["defaults", "dict_type", "allow_no_value", "filename",
184
                  "ac_parse_value"]
185
186
    dump_to_string = anyconfig.backend.base.to_method(_dumps)
187
188
    def load_from_stream(self, stream, to_container, **options):
189
        """
190
        Load config from given file like object `stream`.
191
192
        :param stream:  Config file or file like object
193
        :param to_container: callble to make a container object
194
        :param options: optional keyword arguments
195
196
        :return: Dict-like object holding config parameters
197
        """
198
        return _load(stream, to_container=to_container, **options)
199
200
# vim:sw=4:ts=4:et:
201