Completed
Pull Request — master (#2895)
by Anthony
04:53
created

normalise_alias_format()   B

Complexity

Conditions 3

Size

Total Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 3
c 1
b 0
f 1
dl 0
loc 26
rs 8.8571
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 six
17
import re
18
import copy.deepcopy
19
20
21
def list_patterns_from_aliases(aliases):
22
    '''
23
    List patterns from a collection of alias objects
24
25
    :param aliases: The list of aliases
26
    :type  aliases: ``list`` of :class:`st2common.persistence.actionalias.ActionAlias`
27
28
    :return: A description of potential execution patterns in a list of aliases.
29
    :rtype: ``list`` of ``dict``
30
    '''
31
    patterns = []
32
    for alias in aliases:
33
        for _format in alias.formats:
34
            display, representations = normalise_alias_format(_format)
0 ignored issues
show
Bug introduced by
It seems like a value for argument alias_format is missing in the function call.
Loading history...
35
            for representation in representations:
36
                if isinstance(representation, six.string_types):
37
                    pattern_context, kwargs = alias_format_string_to_pattern(representation)
38
                    patterns.append({
39
                        'context': pattern_context,
40
                        'action_ref': alias.action_ref,
41
                        'kwargs': kwargs
42
                    })
43
    return patterns
44
45
46
def normalise_alias_format(self, alias_format):
47
    '''
48
    StackStorm action aliases can have two types;
49
        1. A simple string holding the format
50
        2. A dictionary which hold numerous alias format "representation(s)"
51
           With a single "display" for help about the action alias.
52
    This function processes both forms and returns a standardized form.
53
54
    :param alias_format: The alias format
55
    :type  alias_format: ``str`` or ``dict``
56
57
    :return: The representation of the alias
58
    :rtype: ``tuple`` of (``str``, ``str``)
59
    '''
60
    display = None
61
    representation = []
62
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
63
    if isinstance(alias_format, six.string_types):
64
        display = alias_format
65
        representation.append(alias_format)
66
    elif isinstance(alias_format, dict):
67
        display = alias_format['display']
68
        representation = alias_format['representation']
69
    else:
70
        raise TypeError("alias_format is neither a dictionary or string type.")
71
    return (display, representation)
72
73
74
75
def alias_format_string_to_pattern(alias_format, prefix=''):
76
    '''
77
    Extract named arguments from format to create a keyword argument list.
78
    Transform tokens into regular expressions.
79
80
    :param alias_format: The alias format
81
    :type  alias_format: ``str`` or ``dict``
82
83
    :return: The representation of the alias
84
    :rtype: ``tuple`` of (``str``, ``str``)
85
    '''
86
    kwargs = {}
87
    # Step 1: Extract action alias arguments so they can be used later
88
    #         when calling the stackstorm action.
89
    tokens = re.findall(r"{{(.*?)}}", alias_format, re.IGNORECASE)
90
    for token in tokens:
91
        if token.find("=") > -1:
92
            name, val = token.split("=")
93
            # Remove unnecessary whitespace
94
            name = name.strip()
95
            val = val.strip()
96
            kwargs[name] = val
97
            name = r"?P<{}>[\s\S]+?".format(name)
98
        else:
99
            name = token.strip()
100
            kwargs[name] = None
101
            name = r"?P<{}>[\s\S]+?".format(name)
102
        # The below code causes a regex exception to be raised under certain conditions.  Using replace() as alternative.
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (121/100).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
103
        #~ alias_format = re.sub( r"\s*{{{{{}}}}}\s*".format(token), r"\\s*({})\\s*".format(name), alias_format)
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (112/100).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
104
        # Replace token with named group match.
105
        alias_format = alias_format.replace(r"{{{{{}}}}}".format(token), r"({})".format(name))
106
107
108
    # Step 2: Append regex to match any extra parameters that weren't declared in the action alias.
109
    extra_params = r"""(:?\s+(\S+)\s*=("([\s\S]*?)"|'([\s\S]*?)'|({[\s\S]*?})|(\S+))\s*)*"""
110
    alias_format = r'^{}{}{}$'.format(prefix, alias_format, extra_params)
111
112
    return (re.compile(alias_format, re.I), kwargs)
113
114
115
def _extract_extra_params(extra_params):
116
    """
117
    Returns a dictionary of extra parameters supplied in the action_alias.
118
    """
119
    kwargs = {}
120
    for arg in extra_params.groups():
121
        if arg and "=" in arg:
122
            k, v = arg.split("=", 1)
123
            kwargs[k.strip()] = v.strip()
124
    return kwargs
125
126
127
def match_text_to_alias(text, aliases):
128
    """
129
    Match the text against an action and return the action reference.
130
    """
131
    results = []
132
    for pattern in aliases:
133
        res = pattern.search(text)
134
        if res:
135
            data = {}
136
            # Create keyword arguments starting with the defaults.
137
            # Deep copy is used here to avoid exposing the reference
138
            # outside the match function.
139
            data.update(copy.deepcopy(pattern.context)) #  check this!
140
            # Merge in the named arguments.
141
            data["kwargs"].update(res.groupdict())
142
            # Merge in any extra arguments supplied as a key/value pair.
143
            data["kwargs"].update(_extract_extra_params(res))
144
            results.append(data)
145
146
    if not results:
147
        return None
148
149
    results.sort(reverse=True)
150
151
    return results[0]
0 ignored issues
show
Coding Style introduced by
Final newline missing
Loading history...