Passed
Push — master ( 21ae0e...e3a2b6 )
by Matěj
01:53 queued 11s
created

oval_graph.client   B

Complexity

Total Complexity 50

Size/Duplication

Total Lines 283
Duplicated Lines 0 %

Test Coverage

Coverage 94.63%

Importance

Changes 0
Metric Value
eloc 230
dl 0
loc 283
ccs 141
cts 149
cp 0.9463
rs 8.4
c 0
b 0
f 0
wmc 50

27 Methods

Rating   Name   Duplication   Size   Complexity  
A Client.get_list_of_lines() 0 15 4
A Client._build_and_save_html() 0 3 1
A Client._check_rules_id() 0 14 5
A Client.get_questions() 0 13 1
A Client.search_rules_id() 0 6 1
A Client.run_gui_and_return_answers() 0 13 4
A Client.open_web_browser() 0 6 3
A Client.get_list_of_matched_rules() 0 3 2
A Client.print_red_text() 0 4 1
A Client._get_rules() 0 5 2
A Client.prepare_args_when_output_is_html() 0 13 1
A Client._get_message() 0 6 1
A Client._get_wanted_rules_from_array_of_IDs() 0 4 1
A Client.open_html() 0 3 2
A Client.__init__() 0 27 1
A Client._get_only_fail_rule() 0 5 2
A Client.get_save_src() 0 9 2
A Client.get_choices() 0 4 2
A Client.prepare_data() 0 6 1
A Client.parse_arguments() 0 3 1
A Client.prepare_args_when_user_can_list_in_rules() 0 11 1
A Client._set_attributes() 0 2 1
A Client.get_lines_of_wanted_not_selected_rules() 0 7 2
A Client.get_selection_rules() 0 2 1
A Client.get_src() 0 3 1
A Client._prepare_data() 0 16 5
B Client.prepare_parser() 0 39 1

How to fix   Complexity   

Complexity

Complex classes like oval_graph.client 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 1
import re
2 1
import argparse
3 1
import tempfile
4 1
import os
5 1
import webbrowser
6 1
from datetime import datetime
7 1
import sys
8
9 1
from .xml_parser import XmlParser
10 1
from .exceptions import NotChecked
11 1
from .__init__ import __version__
12
13
14 1
class Client():
15 1
    def __init__(self, args):
16 1
        self.parser = None
17 1
        self.MESSAGES = self._get_message()
18 1
        self.arg = self.parse_arguments(args)
19 1
        self.hide_passing_tests = self.arg.hide_passing_tests
20 1
        self.source_filename = self.arg.source_filename
21 1
        self.rule_name = self.arg.rule_id
22 1
        self.out = self.arg.output
23 1
        self.verbose = self.arg.verbose
24
25 1
        self.parts = self.get_src('parts')
26 1
        self.START_OF_FILE_NAME = 'graph-of-'
27 1
        self.date = str(datetime.now().strftime("-%d_%m_%Y-%H_%M_%S"))
28 1
        self.isatty = sys.stdout.isatty()
29
30 1
        self.all_rules = self.arg.all
31 1
        self.all_in_one = None
32 1
        self.display_html = None
33
34 1
        self.show_failed_rules = False
35 1
        self.show_not_selected_rules = False
36
37 1
        self.xml_parser = None
38
39 1
        self.html_builder = None
40
41 1
        self._set_attributes()
42
43 1
    def _set_attributes(self):
44 1
        self.xml_parser = self.xml_parser = XmlParser(self.source_filename)
45
46 1
    def _get_message(self):
47 1
        MESSAGES = {
48
            'description': '',
49
            'source_filename': '',
50
        }
51 1
        return MESSAGES
52
53 1
    def print_red_text(self, text):
54
        CRED = '\033[91m'
55
        CEND = '\033[0m'
56
        print(CRED + str(text) + CEND)
57
58 1
    def run_gui_and_return_answers(self):
59 1
        if self.isatty:
60 1
            if self.all_rules:
61
                return self._get_rules()
62
            else:
63 1
                try:
64 1
                    import inquirer
65 1
                    return inquirer.prompt(self.get_questions())
66 1
                except ImportError:
67 1
                    print(self.get_selection_rules())
68 1
                    return None
69
        else:
70 1
            return self._get_rules()
71
72 1
    def _get_rules(self):
73 1
        return {
74
            'rules': self._get_only_fail_rule(
75
                self.search_rules_id())} if self.show_failed_rules else {
76
            'rules': self.search_rules_id()}
77
78 1
    def get_list_of_matched_rules(self):
79 1
        return self._get_only_fail_rule(
80
            self.search_rules_id()) if self.show_failed_rules else self.search_rules_id()
81
82 1
    def get_list_of_lines(self):
83 1
        lines = ['== The Rule ID regular expressions ==']
84 1
        for rule in self.get_list_of_matched_rules():
85 1
            lines.append("^" + rule + "$")
86 1
        if self.show_not_selected_rules:
87 1
            for line in self.get_lines_of_wanted_not_selected_rules():
88 1
                lines.append(line)
89 1
        lines.append(
90
            "Interactive rule selection is not available,"
91
            " because inquirer is not installed."
92
            " Copy id of the rule you want to visualize and"
93
            " paste it into a command with regular"
94
            " expression characters(^$).\n"
95
            "Alternatively, use the --all or --all-in-one arguments.")
96 1
        return lines
97
98 1
    def get_selection_rules(self):
99 1
        return "\n".join(self.get_list_of_lines())
100
101 1
    def get_lines_of_wanted_not_selected_rules(self):
102 1
        out = []
103 1
        out.append('== The not selected rule IDs ==')
104 1
        for rule in self._get_wanted_rules_from_array_of_IDs(
105
                self.xml_parser.notselected_rules):
106 1
            out.append(rule + '(Not selected)')
107 1
        return out
108
109 1
    def get_choices(self):
110 1
        if self.show_not_selected_rules:
111 1
            print("\n".join(self.get_lines_of_wanted_not_selected_rules()))
112 1
        return self.get_list_of_matched_rules()
113
114 1
    def get_questions(self):
115 1
        choices = self.get_choices()
116 1
        from inquirer.questions import Checkbox as checkbox
117 1
        questions = [
118
            checkbox(
119
                'rules',
120
                message=(
121
                    "= The Rules IDs = (move - UP and DOWN arrows,"
122
                    " select - SPACE or LEFT and RIGHT arrows, submit - ENTER)"),
123
                choices=choices,
124
            ),
125
        ]
126 1
        return questions
127
128 1
    def _get_only_fail_rule(self, rules):
129 1
        return list(
130
            filter(
131
                lambda rule: self.xml_parser.used_rules[rule]['result'] == 'fail',
132
                rules))
133
134 1
    def _get_wanted_rules_from_array_of_IDs(self, rules):
135 1
        return [
136
            x for x in rules if re.search(
137
                self.rule_name, x)]
138
139 1
    def search_rules_id(self):
140 1
        return self._check_rules_id(
141
            self._get_wanted_rules_from_array_of_IDs(
142
                self.xml_parser.used_rules.keys()),
143
            self._get_wanted_rules_from_array_of_IDs(
144
                self.xml_parser.notselected_rules))
145
146 1
    def _check_rules_id(self, rules, notselected_rules):
147 1
        if len(notselected_rules) and not rules:
148 1
            raise ValueError(
149
                ('Rule(s) "{}" was not selected, '
150
                 "so there are no results. The rule is"
151
                 ' "notselected" because it'
152
                 " wasn't a part of the executed profile"
153
                 " and therefore it wasn't evaluated "
154
                 "during the scan.")
155
                .format(notselected_rules))
156 1
        elif not notselected_rules and not rules:
157 1
            raise ValueError('404 rule "{}" not found!'.format(self.rule_name))
158
        else:
159 1
            return rules
160
161 1
    def get_save_src(self, rule):
162 1
        if self.out is not None:
163 1
            os.makedirs(self.out, exist_ok=True)
164 1
            return os.path.join(
165
                self.out,
166
                self.START_OF_FILE_NAME + rule + '.html')
167 1
        return os.path.join(
168
            tempfile.gettempdir(),
169
            self.START_OF_FILE_NAME + rule + '.html')
170
171 1
    def get_src(self, src):
172 1
        _dir = os.path.dirname(os.path.realpath(__file__))
173 1
        return str(os.path.join(_dir, src))
174
175 1
    def _build_and_save_html(self, dict_oval_trees, src, rules, out_src):
176 1
        self.html_builder.save_html(dict_oval_trees, src, rules)
177 1
        out_src.append(src)
178
179 1
    def open_html(self, out):
180 1
        for src in out:
181 1
            self.open_web_browser(src)
182
183 1
    def open_web_browser(self, src):
184 1
        if self.display_html:
185 1
            try:
186 1
                webbrowser.get('firefox').open_new_tab(src)
187
            except BaseException:
188
                webbrowser.open_new_tab(src)
189
190 1
    def _prepare_data(self, rules, dict_oval_trees, out_src):
191 1
        for rule in rules['rules']:
192 1
            try:
193 1
                self._put_to_dict_oval_trees(dict_oval_trees, rule)
194 1
                if not self.all_in_one:
195 1
                    self._build_and_save_html(
196
                        dict_oval_trees, self._get_src_for_one_graph(
197
                            rule), dict(
198
                            rules=[rule]), out_src)
199 1
                    dict_oval_trees = {}
200 1
            except NotChecked as error:
201
                self.print_red_text(error)
202 1
        if self.all_in_one:
203
            self._build_and_save_html(
204
                dict_oval_trees, self.get_save_src(
205
                    'rules' + self.date), rules, out_src)
206
207 1
    def prepare_data(self, rules):
208 1
        out_src = []
209 1
        oval_tree_dict = dict()
210 1
        self._prepare_data(rules, oval_tree_dict, out_src)
211 1
        self.open_html(out_src)
212 1
        return out_src
213
214 1
    def parse_arguments(self, args):
215 1
        self.prepare_parser()
216 1
        return self.parser.parse_args(args)
217
218 1
    def prepare_args_when_output_is_html(self):
219 1
        self.parser.add_argument(
220
            '-i',
221
            '--all-in-one',
222
            action="store_true",
223
            default=False,
224
            help="Processes all rules into one file.")
225 1
        self.parser.add_argument(
226
            '-d',
227
            '--display',
228
            action="store_true",
229
            default=False,
230
            help="Enables opening a web browser with a graph, when is used --output.")
231
232 1
    def prepare_args_when_user_can_list_in_rules(self):
233 1
        self.parser.add_argument(
234
            '--show-failed-rules',
235
            action="store_true",
236
            default=False,
237
            help="Show only FAILED rules")
238 1
        self.parser.add_argument(
239
            '--show-not-selected-rules',
240
            action="store_true",
241
            default=False,
242
            help="Show notselected rules. These rules will not be visualized.")
243
244 1
    def prepare_parser(self):
245 1
        self.parser = argparse.ArgumentParser(
246
            prog='oval-graph',
247
            description=self.MESSAGES.get('description'))
248 1
        self.parser.add_argument(
249
            '--version',
250
            action='version',
251
            version='%(prog)s ' + __version__)
252 1
        self.parser.add_argument(
253
            '-a',
254
            '--all',
255
            action="store_true",
256
            default=False,
257
            help="Process all matched rules.")
258 1
        self.parser.add_argument(
259
            '--hide-passing-tests',
260
            action="store_true",
261
            default=False,
262
            help=(
263
                "Do not display passing tests for better orientation in"
264
                " graphs that contain a large amount of nodes."))
265 1
        self.parser.add_argument(
266
            '-v',
267
            '--verbose',
268
            action="store_true",
269
            default=False,
270
            help="Displays details about the results of the running command.")
271 1
        self.parser.add_argument(
272
            '-o',
273
            '--output',
274
            action="store",
275
            default=None,
276
            help='The file where to save output.')
277 1
        self.parser.add_argument(
278
            "source_filename",
279
            help=self.MESSAGES.get('source_filename'))
280 1
        self.parser.add_argument(
281
            "rule_id", help=(
282
                "Rule ID to be visualized. A part from the full rule ID"
283
                " a part of the ID or a regular expression can be used."
284
                " If brackets are used in the regular expression "
285
                "the regular expression must be quoted."))
286