Merge lp:~canonical-platform-qa/uci-config/decreased-complexity into lp:~canonical-ci-engineering/uci-config/trunk

Proposed by Federico Gimenez
Status: Work in progress
Proposed branch: lp:~canonical-platform-qa/uci-config/decreased-complexity
Merge into: lp:~canonical-ci-engineering/uci-config/trunk
Prerequisite: lp:~canonical-platform-qa/uci-config/py3-packaging
Diff against target: 295 lines (+83/-125)
4 files modified
setup.py (+3/-3)
uciconfig/options.py (+3/-12)
uciconfig/parsers.py (+59/-93)
uciconfig/stacks.py (+18/-17)
To merge this branch: bzr merge lp:~canonical-platform-qa/uci-config/decreased-complexity
Reviewer Review Type Date Requested Status
Canonical CI Engineering Pending
Review via email: mp+251433@code.launchpad.net

Commit message

mccabe complexity reduction

Description of the change

There are methods reported to be too complex by the McCabe script. This is attempt to reduce the reported complexity, while keeping the api interface and the test suite passing.

To post a comment you must log in.
67. By Federico Gimenez

some tests fixed

68. By Federico Gimenez

only 4 tests red

69. By Federico Gimenez

3 to go

Unmerged revisions

69. By Federico Gimenez

3 to go

68. By Federico Gimenez

only 4 tests red

67. By Federico Gimenez

some tests fixed

66. By Federico Gimenez

reported complexity reduced to 10

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'setup.py'
--- setup.py 2014-04-01 07:41:42 +0000
+++ setup.py 2015-03-03 15:56:20 +0000
@@ -3,16 +3,16 @@
3# This file is part of Ubuntu Continuous Integration configuration framework.3# This file is part of Ubuntu Continuous Integration configuration framework.
4#4#
5# Copyright 2013, 2014 Canonical Ltd.5# Copyright 2013, 2014 Canonical Ltd.
6# 6#
7# This program is free software: you can redistribute it and/or modify it under7# This program is free software: you can redistribute it and/or modify it under
8# the terms of the GNU General Public License version 3, as published by the8# the terms of the GNU General Public License version 3, as published by the
9# Free Software Foundation.9# Free Software Foundation.
10# 10#
11# This program is distributed in the hope that it will be useful, but WITHOUT11# This program is distributed in the hope that it will be useful, but WITHOUT
12# ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,12# ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
13# SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU13# SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14# General Public License for more details.14# General Public License for more details.
15# 15#
16# You should have received a copy of the GNU General Public License along with16# You should have received a copy of the GNU General Public License along with
17# this program. If not, see <http://www.gnu.org/licenses/>.17# this program. If not, see <http://www.gnu.org/licenses/>.
1818
1919
=== modified file 'uciconfig/options.py'
--- uciconfig/options.py 2014-02-15 16:49:46 +0000
+++ uciconfig/options.py 2015-03-03 15:56:20 +0000
@@ -77,19 +77,12 @@
77 safely unquote them. It is provided so daughter classes can handle77 safely unquote them. It is provided so daughter classes can handle
78 the quoting themselves.78 the quoting themselves.
79 """79 """
80 if override_from_env is None:
81 override_from_env = []
82 if default_from_env is None:
83 default_from_env = []
84 self.name = name80 self.name = name
85 self._help = help_string81 self._help = help_string
86 self.override_from_env = override_from_env82 self.override_from_env = override_from_env or []
87 # Convert the default value to a unicode string so all values are83 # Convert the default value to a unicode string so all values are
88 # strings internally before conversion (via from_unicode) is attempted.84 # strings internally before conversion (via from_unicode) is attempted.
89 if default is None:85 if default is None or default is MANDATORY or callable(default):
90 self.default = None
91 elif default is MANDATORY:
92 # Special case, no further check needed
93 self.default = default86 self.default = default
94 elif isinstance(default, list):87 elif isinstance(default, list):
95 # Only the empty list is supported88 # Only the empty list is supported
@@ -100,13 +93,11 @@
100 elif isinstance(default, (unicode, bool, int, float)):93 elif isinstance(default, (unicode, bool, int, float)):
101 # Rely on python to convert strings, booleans, floats and integers94 # Rely on python to convert strings, booleans, floats and integers
102 self.default = '{}'.format(default)95 self.default = '{}'.format(default)
103 elif callable(default):
104 self.default = default
105 else:96 else:
106 # other python objects are not expected97 # other python objects are not expected
107 raise AssertionError(98 raise AssertionError(
108 '{!r} is not supported as a default value'.format(default))99 '{!r} is not supported as a default value'.format(default))
109 self.default_from_env = default_from_env100 self.default_from_env = default_from_env or []
110 self.from_unicode = from_unicode101 self.from_unicode = from_unicode
111 self.unquote = unquote102 self.unquote = unquote
112 if invalid and invalid not in ('warning', 'error'):103 if invalid and invalid not in ('warning', 'error'):
113104
=== modified file 'uciconfig/parsers.py'
--- uciconfig/parsers.py 2015-01-29 10:17:25 +0000
+++ uciconfig/parsers.py 2015-03-03 15:56:20 +0000
@@ -174,109 +174,75 @@
174 pre = None174 pre = None
175 while remaining:175 while remaining:
176 pre_comment = self.comment_re.match(remaining)176 pre_comment = self.comment_re.match(remaining)
177 if pre_comment:
178 # Buffer the comment lines until a section or an option collect
179 # it
180 if pre is None:
181 pre = pre_comment.group(1)
182 else:
183 pre = pre + pre_comment.group(1)
184 remaining = remaining[pre_comment.end():]
185 self.line += 1
186 continue
187
188 section = self.section_re.match(remaining)177 section = self.section_re.match(remaining)
189 if section:
190 # A new section
191 name = section.group(1)
192 if name == '':
193 raise errors.SectionEmptyName(self.path, self.line)
194 remaining = remaining[section.end():]
195 post_comment = self.comment_re.match(remaining)
196 if post_comment:
197 post = post_comment.group(1)
198 remaining = remaining[post_comment.end():]
199 else:
200 post = None
201 tokens.append(SectionDefinition(name, pre, post))
202 pre = None
203 if post is not None:
204 self.line += 1
205 post = None
206 continue
207
208 key_matches = self.option_re.match(remaining)178 key_matches = self.option_re.match(remaining)
209 if key_matches:179 if pre_comment:
210 if not tokens:180 remaining, pre = self._parse_precomment(
211 # First option definition without a previous section, it's181 pre_comment, remaining, pre)
212 # the None section.182 elif section:
213 tokens.append(SectionDefinition(None))183 tokens, remaining, pre = self._parse_section(
214 key = key_matches.group(1)184 section, tokens, remaining, pre)
215 remaining = remaining[key_matches.end():]185 elif key_matches:
216 # the option value is still in 'remaining' which can be empty186 tokens, remaining, pre = self._parse_key(
217 # or start with a newline if no value is defined for the187 key_matches, tokens, remaining, pre)
218 # option.188 elif remaining.startswith('\n'):
219 value, post, remaining = self.parse_value(remaining)
220 tokens.append(OptionDefinition(key, value, pre, post))
221 pre = None
222 continue
223
224 if remaining.startswith('\n'):
225 # Keep track of the current line189 # Keep track of the current line
226 self.line += 1190 self.line += 1
227 remaining = remaining[1:]191 remaining = remaining[1:]
228 continue192 elif remaining.startswith(' '):
229
230 if remaining.startswith(' '):
231 # Consume spaces to get rid of empty lines193 # Consume spaces to get rid of empty lines
232 remaining = remaining[1:]194 remaining = remaining[1:]
233 continue195 else:
234196 raise errors.InvalidSyntax(self.path, self.line)
235 raise errors.InvalidSyntax(self.path, self.line)
236 return tokens197 return tokens
237198
238 def parse_value(self, text):199 def _parse_precomment(self, pre_comment, remaining, pre):
239 """Parse an option value.200 # Buffer the comment lines until a section or an option collect
240201 # it
241 :param text: The unicode string to parse containing the value.202 if pre is None:
242203 pre = pre_comment.group(1)
243 :return: A (value, post, remaining) tuple where 'value' is the string204 else:
244 representing the value, 'post' the associated comment (can be None)205 pre = pre + pre_comment.group(1)
245 and 'remaining' the rest of text after parsing.206 remaining = remaining[pre_comment.end():]
246 """207 self.line += 1
247 value = ''208 return (remaining, pre)
248 post = ''209
249 cur = 0210 def _parse_section(self, section, tokens, remaining, pre):
250 post_last = len(text)211 name = section.group(1)
251 # ignore leading spaces212 if name == '':
252 while cur < post_last and text[cur] == ' ':213 raise errors.SectionEmptyName(self.path, self.line)
253 cur += 1214 remaining = remaining[section.end():]
254 # build value until we encounter end of line or a comment215 post_comment = self.comment_re.match(remaining)
255 while cur < post_last and text[cur] not in ('#', '\n'):216 if post_comment:
256 value += text[cur]217 post = post_comment.group(1)
257 cur += 1218 remaining = remaining[post_comment.end():]
258 # remove trailing spaces219 else:
259 spaces = ''
260 while value.endswith(' '):
261 spaces += value[-1]
262 value = value[:-1]
263 # comment (if present) ends at end of line
264 if cur < post_last and text[cur] == '#':
265 post = spaces
266 while cur < post_last and text[cur] != '\n':
267 post += text[cur]
268 cur += 1
269 if post == '':
270 post = None220 post = None
271 # Consume end of line if present221 tokens.append(SectionDefinition(name, pre, post))
272 if cur < post_last and text[cur] == '\n':222 pre = None
273 if post is not None:223 if post is not None:
274 # If there is a post comment, it needs to include the '\n',224 self.line += 1
275 # yet, the current line shouldn't be updated yet, so we leave225
276 # it in remaining.226 return (tokens, remaining, pre)
277 post += '\n'227
278 remaining = text[cur:]228 def _parse_key(self, key_matches, tokens, remaining, pre):
279 return value, post, remaining229 if not tokens:
230 # First option definition without a previous section, it's
231 # the None section.
232 tokens.append(SectionDefinition(None))
233 key = key_matches.group(1)
234 remaining = remaining[key_matches.end():]
235 result = re.match(
236 r'\ *([\\,\{\}\w /\-\._]*)?([ \t\r\f\v]*#.*\n)?(.*)',
237 remaining,
238 re.DOTALL)
239 value, post, remaining = (
240 result.group(1).strip(), result.group(2), result.group(3))
241 tokens.append(OptionDefinition(key, value, pre, post))
242 if post is not None:
243 self.line += 1
244 pre = None
245 return (tokens, remaining, pre)
280246
281 def make_sections(self, tokens):247 def make_sections(self, tokens):
282 """Yields the sections built from the received tokens.248 """Yields the sections built from the received tokens.
283249
=== modified file 'uciconfig/stacks.py'
--- uciconfig/stacks.py 2014-03-26 23:22:19 +0000
+++ uciconfig/stacks.py 2015-03-03 15:56:20 +0000
@@ -225,36 +225,37 @@
225 # Not registered225 # Not registered
226 opt = None226 opt = None
227227
228 def expand_and_convert(val):
229 # This may need to be called in different contexts if the value is
230 # None or ends up being None during expansion or conversion.
231 if val is not None:
232 if expand:
233 val = self._expand_options_in_string(val)
234 if opt is None:
235 val = found_store.unquote(val)
236 elif convert:
237 val = opt.convert_from_unicode(found_store, val)
238 return val
239
240 # First of all, check if the environment can override the configuration228 # First of all, check if the environment can override the configuration
241 # value229 # value
242 if opt is not None and opt.override_from_env:230 if opt is not None and opt.override_from_env:
243 value = opt.get_override()231 value = self.expand_and_convert(
244 value = expand_and_convert(value)232 opt.get_override(), expand, opt, found_store, convert)
245 if value is None:233 if value is None:
246 for store, section in self.iter_sections():234 for store, section in self.iter_sections():
247 value = section.get(name)235 value = section.get(name)
248 if value is not None:236 if value is not None:
249 found_store = store237 found_store = store
250 break238 break
251 value = expand_and_convert(value)239 value = self.expand_and_convert(
240 value, expand, opt, found_store, convert)
252 if opt is not None and value is None:241 if opt is not None and value is None:
253 # If the option is registered, it may provide a default value242 # If the option is registered, it may provide a default value
254 value = opt.get_default()243 value = self.expand_and_convert(
255 value = expand_and_convert(value)244 opt.get_default(), expand, opt, found_store, convert)
256 return value245 return value
257246
247 def expand_and_convert(self, val, expand, opt, found_store, convert):
248 # This may need to be called in different contexts if the value is
249 # None or ends up being None during expansion or conversion.
250 if val is not None:
251 if expand:
252 val = self._expand_options_in_string(val)
253 if opt is None:
254 val = found_store.unquote(val)
255 elif convert:
256 val = opt.convert_from_unicode(found_store, val)
257 return val
258
258 def expand_options(self, string, env=None):259 def expand_options(self, string, env=None):
259 """Expand option references in the string in the configuration context.260 """Expand option references in the string in the configuration context.
260261

Subscribers

People subscribed via source and target branches