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
1=== modified file 'setup.py'
2--- setup.py 2014-04-01 07:41:42 +0000
3+++ setup.py 2015-03-03 15:56:20 +0000
4@@ -3,16 +3,16 @@
5 # This file is part of Ubuntu Continuous Integration configuration framework.
6 #
7 # Copyright 2013, 2014 Canonical Ltd.
8-#
9+#
10 # This program is free software: you can redistribute it and/or modify it under
11 # the terms of the GNU General Public License version 3, as published by the
12 # Free Software Foundation.
13-#
14+#
15 # This program is distributed in the hope that it will be useful, but WITHOUT
16 # ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
17 # SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 # General Public License for more details.
19-#
20+#
21 # You should have received a copy of the GNU General Public License along with
22 # this program. If not, see <http://www.gnu.org/licenses/>.
23
24
25=== modified file 'uciconfig/options.py'
26--- uciconfig/options.py 2014-02-15 16:49:46 +0000
27+++ uciconfig/options.py 2015-03-03 15:56:20 +0000
28@@ -77,19 +77,12 @@
29 safely unquote them. It is provided so daughter classes can handle
30 the quoting themselves.
31 """
32- if override_from_env is None:
33- override_from_env = []
34- if default_from_env is None:
35- default_from_env = []
36 self.name = name
37 self._help = help_string
38- self.override_from_env = override_from_env
39+ self.override_from_env = override_from_env or []
40 # Convert the default value to a unicode string so all values are
41 # strings internally before conversion (via from_unicode) is attempted.
42- if default is None:
43- self.default = None
44- elif default is MANDATORY:
45- # Special case, no further check needed
46+ if default is None or default is MANDATORY or callable(default):
47 self.default = default
48 elif isinstance(default, list):
49 # Only the empty list is supported
50@@ -100,13 +93,11 @@
51 elif isinstance(default, (unicode, bool, int, float)):
52 # Rely on python to convert strings, booleans, floats and integers
53 self.default = '{}'.format(default)
54- elif callable(default):
55- self.default = default
56 else:
57 # other python objects are not expected
58 raise AssertionError(
59 '{!r} is not supported as a default value'.format(default))
60- self.default_from_env = default_from_env
61+ self.default_from_env = default_from_env or []
62 self.from_unicode = from_unicode
63 self.unquote = unquote
64 if invalid and invalid not in ('warning', 'error'):
65
66=== modified file 'uciconfig/parsers.py'
67--- uciconfig/parsers.py 2015-01-29 10:17:25 +0000
68+++ uciconfig/parsers.py 2015-03-03 15:56:20 +0000
69@@ -174,109 +174,75 @@
70 pre = None
71 while remaining:
72 pre_comment = self.comment_re.match(remaining)
73- if pre_comment:
74- # Buffer the comment lines until a section or an option collect
75- # it
76- if pre is None:
77- pre = pre_comment.group(1)
78- else:
79- pre = pre + pre_comment.group(1)
80- remaining = remaining[pre_comment.end():]
81- self.line += 1
82- continue
83-
84 section = self.section_re.match(remaining)
85- if section:
86- # A new section
87- name = section.group(1)
88- if name == '':
89- raise errors.SectionEmptyName(self.path, self.line)
90- remaining = remaining[section.end():]
91- post_comment = self.comment_re.match(remaining)
92- if post_comment:
93- post = post_comment.group(1)
94- remaining = remaining[post_comment.end():]
95- else:
96- post = None
97- tokens.append(SectionDefinition(name, pre, post))
98- pre = None
99- if post is not None:
100- self.line += 1
101- post = None
102- continue
103-
104 key_matches = self.option_re.match(remaining)
105- if key_matches:
106- if not tokens:
107- # First option definition without a previous section, it's
108- # the None section.
109- tokens.append(SectionDefinition(None))
110- key = key_matches.group(1)
111- remaining = remaining[key_matches.end():]
112- # the option value is still in 'remaining' which can be empty
113- # or start with a newline if no value is defined for the
114- # option.
115- value, post, remaining = self.parse_value(remaining)
116- tokens.append(OptionDefinition(key, value, pre, post))
117- pre = None
118- continue
119-
120- if remaining.startswith('\n'):
121+ if pre_comment:
122+ remaining, pre = self._parse_precomment(
123+ pre_comment, remaining, pre)
124+ elif section:
125+ tokens, remaining, pre = self._parse_section(
126+ section, tokens, remaining, pre)
127+ elif key_matches:
128+ tokens, remaining, pre = self._parse_key(
129+ key_matches, tokens, remaining, pre)
130+ elif remaining.startswith('\n'):
131 # Keep track of the current line
132 self.line += 1
133 remaining = remaining[1:]
134- continue
135-
136- if remaining.startswith(' '):
137+ elif remaining.startswith(' '):
138 # Consume spaces to get rid of empty lines
139 remaining = remaining[1:]
140- continue
141-
142- raise errors.InvalidSyntax(self.path, self.line)
143+ else:
144+ raise errors.InvalidSyntax(self.path, self.line)
145 return tokens
146
147- def parse_value(self, text):
148- """Parse an option value.
149-
150- :param text: The unicode string to parse containing the value.
151-
152- :return: A (value, post, remaining) tuple where 'value' is the string
153- representing the value, 'post' the associated comment (can be None)
154- and 'remaining' the rest of text after parsing.
155- """
156- value = ''
157- post = ''
158- cur = 0
159- post_last = len(text)
160- # ignore leading spaces
161- while cur < post_last and text[cur] == ' ':
162- cur += 1
163- # build value until we encounter end of line or a comment
164- while cur < post_last and text[cur] not in ('#', '\n'):
165- value += text[cur]
166- cur += 1
167- # remove trailing spaces
168- spaces = ''
169- while value.endswith(' '):
170- spaces += value[-1]
171- value = value[:-1]
172- # comment (if present) ends at end of line
173- if cur < post_last and text[cur] == '#':
174- post = spaces
175- while cur < post_last and text[cur] != '\n':
176- post += text[cur]
177- cur += 1
178- if post == '':
179+ def _parse_precomment(self, pre_comment, remaining, pre):
180+ # Buffer the comment lines until a section or an option collect
181+ # it
182+ if pre is None:
183+ pre = pre_comment.group(1)
184+ else:
185+ pre = pre + pre_comment.group(1)
186+ remaining = remaining[pre_comment.end():]
187+ self.line += 1
188+ return (remaining, pre)
189+
190+ def _parse_section(self, section, tokens, remaining, pre):
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- # Consume end of line if present
202- if cur < post_last and text[cur] == '\n':
203- if post is not None:
204- # If there is a post comment, it needs to include the '\n',
205- # yet, the current line shouldn't be updated yet, so we leave
206- # it in remaining.
207- post += '\n'
208- remaining = text[cur:]
209- return value, post, remaining
210+ tokens.append(SectionDefinition(name, pre, post))
211+ pre = None
212+ if post is not None:
213+ self.line += 1
214+
215+ return (tokens, remaining, pre)
216+
217+ def _parse_key(self, key_matches, tokens, remaining, pre):
218+ if not tokens:
219+ # First option definition without a previous section, it's
220+ # the None section.
221+ tokens.append(SectionDefinition(None))
222+ key = key_matches.group(1)
223+ remaining = remaining[key_matches.end():]
224+ result = re.match(
225+ r'\ *([\\,\{\}\w /\-\._]*)?([ \t\r\f\v]*#.*\n)?(.*)',
226+ remaining,
227+ re.DOTALL)
228+ value, post, remaining = (
229+ result.group(1).strip(), result.group(2), result.group(3))
230+ tokens.append(OptionDefinition(key, value, pre, post))
231+ if post is not None:
232+ self.line += 1
233+ pre = None
234+ return (tokens, remaining, pre)
235
236 def make_sections(self, tokens):
237 """Yields the sections built from the received tokens.
238
239=== modified file 'uciconfig/stacks.py'
240--- uciconfig/stacks.py 2014-03-26 23:22:19 +0000
241+++ uciconfig/stacks.py 2015-03-03 15:56:20 +0000
242@@ -225,36 +225,37 @@
243 # Not registered
244 opt = None
245
246- def expand_and_convert(val):
247- # This may need to be called in different contexts if the value is
248- # None or ends up being None during expansion or conversion.
249- if val is not None:
250- if expand:
251- val = self._expand_options_in_string(val)
252- if opt is None:
253- val = found_store.unquote(val)
254- elif convert:
255- val = opt.convert_from_unicode(found_store, val)
256- return val
257-
258 # First of all, check if the environment can override the configuration
259 # value
260 if opt is not None and opt.override_from_env:
261- value = opt.get_override()
262- value = expand_and_convert(value)
263+ value = self.expand_and_convert(
264+ opt.get_override(), expand, opt, found_store, convert)
265 if value is None:
266 for store, section in self.iter_sections():
267 value = section.get(name)
268 if value is not None:
269 found_store = store
270 break
271- value = expand_and_convert(value)
272+ value = self.expand_and_convert(
273+ value, expand, opt, found_store, convert)
274 if opt is not None and value is None:
275 # If the option is registered, it may provide a default value
276- value = opt.get_default()
277- value = expand_and_convert(value)
278+ value = self.expand_and_convert(
279+ opt.get_default(), expand, opt, found_store, convert)
280 return value
281
282+ def expand_and_convert(self, val, expand, opt, found_store, convert):
283+ # This may need to be called in different contexts if the value is
284+ # None or ends up being None during expansion or conversion.
285+ if val is not None:
286+ if expand:
287+ val = self._expand_options_in_string(val)
288+ if opt is None:
289+ val = found_store.unquote(val)
290+ elif convert:
291+ val = opt.convert_from_unicode(found_store, val)
292+ return val
293+
294 def expand_options(self, string, env=None):
295 """Expand option references in the string in the configuration context.
296

Subscribers

People subscribed via source and target branches