Completed
Pull Request — master (#2489)
by Edward
05:35
created

get_extracted_param_value()   F

Complexity

Conditions 11

Size

Total Lines 65

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 65
rs 3.8571
cc 11

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Complexity

Complex classes like st2common.models.utils.ActionAliasFormatParser.get_extracted_param_value() often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
# Licensed to the StackStorm, Inc ('StackStorm') under one or more
2
# contributor license agreements.  See the NOTICE file distributed with
3
# this work for additional information regarding copyright ownership.
4
# The ASF licenses this file to You under the Apache License, Version 2.0
5
# (the "License"); you may not use this file except in compliance with
6
# the License.  You may obtain a copy of the License at
7
#
8
#     http://www.apache.org/licenses/LICENSE-2.0
9
#
10
# Unless required by applicable law or agreed to in writing, software
11
# distributed under the License is distributed on an "AS IS" BASIS,
12
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
# See the License for the specific language governing permissions and
14
# limitations under the License.
15
16
import re
17
from st2common.exceptions import content
18
19
__all__ = [
20
    'ActionAliasFormatParser'
21
]
22
23
24
class ActionAliasFormatParser(object):
25
26
    def __init__(self, alias_format=None, param_stream=None):
27
        self._format = alias_format or ''
28
        self._param_stream = param_stream or ''
29
30
    def get_extracted_param_value(self):
31
32
        result = {}
33
34
        # As there's a lot of questions about using regular expressions,
35
        # I'll try to be thorough when documenting this code.
36
37
        # We're parsing the arbitrary key-value pairs at the end of the stream
38
        # to support passing of parameters not specified in the format string,
39
        # and cutting them from the stream as they're no longer needed.
40
        # Possible values are quoted strings, a word, or anything inside "{}".
41
        pairs_match = r'(?:^|\s+)(\S+)=("(.*?)"|\'(.*?)\'|({.*?})|(\S+))'
42
        extra = re.match(r'.*?((' + pairs_match + r'\s*)*)$',
43
                         self._param_stream, re.DOTALL)
44
        if extra:
45
            kv_pairs = re.findall(pairs_match,
46
                                  extra.group(1), re.DOTALL)
47
            self._param_stream = self._param_stream.replace(extra.group(1), '')
48
        self._param_stream = " %s " % self._param_stream
49
50
        # Now we'll match parameters with default values in form of
51
        # {{ value = parameter }} (and all possible permutations of spaces),
52
        # compiling them into a list.
53
        # "test {{ url = http://google.com }} {{ extra = Test }}" will become
54
        # [ ["url", "http://google.com"], ["extra", "Test"] ]
55
        params = re.findall(r'{{\s*(.+?)\s*(?:=\s*[\'"]?({.+?}|.+?)[\'"]?)?\s*}}',
56
                            self._format, re.DOTALL)
57
58
        # Now we're transforming our format string into a regular expression,
59
        # substituting {{ ... }} with regex named groups, so that param_stream
60
        # matched against this expression yields a dict of params with values.
61
        param_match = r'["\']?(?P<\2>(?:(?<=\').+?(?=\')|(?<=").+?(?=")|{.+?}|.+?))["\']?'
62
        reg = re.sub(r'(\s*){{\s*([^=}]+?)\s*}}(?![\'"]?\s+}})',
63
                     r'\1' + param_match,
64
                     self._format)
65
        reg = re.sub(r'(\s*){{\s*(\S+)\s*=\s*(?:{.+?}|.+?)\s*}}',
66
                     r'(?:\1' + param_match + r')?',
67
                     reg)
68
        reg = re.sub(r'(\s*){{\s*(.+?)\s*}}',
69
                     r'\1' + param_match,
70
                     reg)
71
        reg = '^\s*' + reg + r'\s*$'
0 ignored issues
show
Bug introduced by
A suspicious escape sequence \s was found. Did you maybe forget to add an r prefix?

Escape sequences in Python are generally interpreted according to rules similar to standard C. Only if strings are prefixed with r or R are they interpreted as regular expressions.

The escape sequence that was used indicates that you might have intended to write a regular expression.

Learn more about the available escape sequences. in the Python documentation.

Loading history...
72
73
        # Now we're matching param_stream against our format string regex,
74
        # getting a dict of values. We'll also get default values from
75
        # "params" list if something is not present.
76
        # Priority, from lowest to highest:
77
        # 1. Default parameters
78
        # 2. Matched parameters
79
        # 3. Extra parameters
80
        matched_stream = re.match(reg, self._param_stream, re.DOTALL)
81
        if matched_stream:
82
            values = matched_stream.groupdict()
83
        for param in params:
84
            matched_value = values[param[0]] if matched_stream else param[1] or None
85
            if matched_value:
86
                result[param[0]] = matched_value
87
        if extra:
88
            for pair in kv_pairs:
89
                result[pair[0]] = ''.join(pair[2:])
90
91
        if self._format and not (self._param_stream.strip() or any(result.values())):
92
            raise content.ParseException('No value supplied and no default value found.')
93
94
        return result
95