|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2016 - 2019 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing a ConfigParser wrapper for Python 2 to provide the |
|
8 dictionary like interface of the Python 3 variant. |
|
9 """ |
|
10 |
|
11 from __future__ import unicode_literals |
|
12 |
|
13 try: |
|
14 from configparser import ConfigParser as E5ConfigParser |
|
15 except ImportError: |
|
16 # Py2 part with the compatibility wrapper class |
|
17 try: |
|
18 from collections import OrderedDict as _default_dict |
|
19 # __IGNORE_WARNING_N813__ |
|
20 except ImportError: |
|
21 # fallback for setup.py which hasn't yet built _collections |
|
22 _default_dict = dict |
|
23 |
|
24 import re |
|
25 import itertools |
|
26 from ConfigParser import SafeConfigParser, DEFAULTSECT |
|
27 |
|
28 class E5ConfigParser(SafeConfigParser): |
|
29 """ |
|
30 Class implementing a wrapper of the ConfigParser class implementing |
|
31 dictionary like special methods and some enhancements from Python 3. |
|
32 """ |
|
33 _OPT_TMPL = r""" |
|
34 (?P<option>.*?) # very permissive! |
|
35 \s*(?P<vi>{delim})\s* # any number of space/tab, |
|
36 # followed by any of the |
|
37 # allowed delimiters, |
|
38 # followed by any space/tab |
|
39 (?P<value>.*)$ # everything up to eol |
|
40 """ |
|
41 _OPT_NV_TMPL = r""" |
|
42 (?P<option>.*?) # very permissive! |
|
43 \s*(?: # any number of space/tab, |
|
44 (?P<vi>{delim})\s* # optionally followed by |
|
45 # any of the allowed |
|
46 # delimiters, followed by any |
|
47 # space/tab |
|
48 (?P<value>.*))?$ # everything up to eol |
|
49 """ |
|
50 # Compiled regular expression for matching options with typical |
|
51 # separators |
|
52 OPTCRE = re.compile(_OPT_TMPL.format(delim="=|:"), re.VERBOSE) |
|
53 # Compiled regular expression for matching options with optional |
|
54 # values delimited using typical separators |
|
55 OPTCRE_NV = re.compile(_OPT_NV_TMPL.format(delim="=|:"), re.VERBOSE) |
|
56 |
|
57 def __init__(self, defaults=None, dict_type=_default_dict, |
|
58 allow_no_value=False, delimiters=('=', ':')): |
|
59 """ |
|
60 Constructor |
|
61 """ |
|
62 SafeConfigParser.__init__( |
|
63 self, |
|
64 defaults=defaults, dict_type=dict_type, |
|
65 allow_no_value=allow_no_value) |
|
66 |
|
67 if delimiters == ('=', ':'): |
|
68 self._optcre = \ |
|
69 self.OPTCRE_NV if allow_no_value else self.OPTCRE |
|
70 else: |
|
71 d = "|".join(re.escape(d) for d in delimiters) |
|
72 if allow_no_value: |
|
73 self._optcre = re.compile( |
|
74 self._OPT_NV_TMPL.format(delim=d), re.VERBOSE) |
|
75 else: |
|
76 self._optcre = re.compile( |
|
77 self._OPT_TMPL.format(delim=d), re.VERBOSE) |
|
78 |
|
79 def __getitem__(self, key): |
|
80 """ |
|
81 Special method to get a section. |
|
82 |
|
83 @param key name of the section |
|
84 @type str |
|
85 @return section for the given key |
|
86 @rtype dict |
|
87 @exception KeyError raised if a non-existent key is given |
|
88 """ |
|
89 if key == DEFAULTSECT: |
|
90 return self._defaults |
|
91 elif self.has_section(key): |
|
92 return self._sections[key] |
|
93 else: |
|
94 raise KeyError(key) |
|
95 |
|
96 def __setitem__(self, key, values): |
|
97 """ |
|
98 Special method to set the values of a section. |
|
99 |
|
100 @param key name of the section |
|
101 @type str |
|
102 @param values value for the section |
|
103 @type dict |
|
104 """ |
|
105 # To conform with the mapping protocol, overwrites existing values |
|
106 # in the section. |
|
107 if key == DEFAULTSECT: |
|
108 self._defaults.clear() |
|
109 elif self.has_section(key): |
|
110 self._sections[key].clear() |
|
111 else: |
|
112 self.add_section(key) |
|
113 for subkey, value in values.items(): |
|
114 subkey = self.optionxform(str(subkey)) |
|
115 if value is not None: |
|
116 value = str(value) |
|
117 self.set(key, subkey, value) |
|
118 |
|
119 def __delitem__(self, key): |
|
120 """ |
|
121 Special method to delete a section. |
|
122 |
|
123 @param key name of the section |
|
124 @type str |
|
125 @exception ValueError raised to indicate non-removal of the |
|
126 default section |
|
127 @exception KeyError raised to indicate a non-existent section |
|
128 """ |
|
129 if key == DEFAULTSECT: |
|
130 raise ValueError("Cannot remove the default section.") |
|
131 if not self.has_section(key): |
|
132 raise KeyError(key) |
|
133 self.remove_section(key) |
|
134 |
|
135 def __contains__(self, key): |
|
136 """ |
|
137 Special method to test, if a section is contained in the config. |
|
138 |
|
139 @param key name of the section |
|
140 @type str |
|
141 @return flag indicating containment |
|
142 @rtype bool |
|
143 """ |
|
144 return key == DEFAULTSECT or self.has_section(key) |
|
145 |
|
146 def __len__(self): |
|
147 """ |
|
148 Special method get the number of sections of the config. |
|
149 |
|
150 @return number of sections |
|
151 @rtype int |
|
152 """ |
|
153 return len(self._sections) + 1 # the default section |
|
154 |
|
155 def __iter__(self): |
|
156 """ |
|
157 Special method to return an iterator of the section names starting |
|
158 with the default section. |
|
159 |
|
160 @return iterator of the section names contained in the config |
|
161 @rtype iterator of str |
|
162 """ |
|
163 return itertools.chain((DEFAULTSECT,), self._sections.keys()) |
|
164 |
|
165 |
|
166 if __name__ == "__main__": |
|
167 # This is some test code. |
|
168 import sys |
|
169 |
|
170 c = E5ConfigParser() |
|
171 c["DEFAULT"] = {'ServerAliveInterval': '45', |
|
172 'Compression': 'yes', |
|
173 'CompressionLevel': '9'} |
|
174 c['bitbucket.org'] = {} |
|
175 c['bitbucket.org']['User'] = 'hg' |
|
176 c['topsecret.server.com'] = {} |
|
177 topsecret = c['topsecret.server.com'] |
|
178 topsecret['Port'] = '50022' |
|
179 topsecret['ForwardX11'] = 'no' |
|
180 c['DEFAULT']['ForwardX11'] = 'yes' |
|
181 |
|
182 c.write(sys.stdout) |