Completed
Push — master ( 98e1d4...d1821a )
by Satoru
01:27
created

_make_parser()   A

Complexity

Conditions 2

Size

Total Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

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