scripts.config_generator.display_formatter   F
last analyzed

Complexity

Total Complexity 124

Size/Duplication

Total Lines 819
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 517
dl 0
loc 819
rs 2
c 0
b 0
f 0
wmc 124

1 Function

Rating   Name   Duplication   Size   Complexity  
A get_terminal_width() 0 7 3

40 Methods

Rating   Name   Duplication   Size   Complexity  
A DisplayFormatter.__init__() 0 3 1
A DisplayFormatter._get_description() 0 16 5
A DisplayFormatter._separator_string() 0 12 1
B DisplayFormatter._get_string() 0 47 5
A DispDisplay._get_default() 0 4 1
A ParameterFormatter._create_display_string() 0 32 4
A DisplayFormatter._get_quiet() 0 10 4
B DisplayFormatter._get_equal_lines() 0 25 6
A DisplayFormatter._get_synopsis() 0 6 1
A ParameterFormatter.__init__() 0 2 1
C DispDisplay._get_param_details() 0 69 10
A DispDisplay._level_separator() 0 16 2
A DispDisplay.__init__() 0 2 1
A DisplayFormatter._remove_quotes() 0 6 2
A ParameterFormatter._dict_to_str() 0 14 3
C DisplayFormatter._get_plugin_title() 0 22 10
A ParameterFormatter._get_param_details() 0 30 3
A ListDisplay.__init__() 0 2 1
A DispDisplay._get_option_format() 0 43 4
A ListDisplay._get_verbose() 0 10 1
A DisplayFormatter._get_extra_info() 0 11 3
A DispDisplay._get_verbose_verbose() 0 13 1
A CiteDisplay._get_citation_dependency_str() 0 26 3
B CiteDisplay._get_citation_str() 0 32 6
A DispDisplay._get_verbose() 0 23 2
A DispDisplay._get_verbose_option_colours() 0 18 2
A CiteDisplay._get_citation_lines() 0 29 3
A DisplayFormatter._get_doc_link() 0 4 2
A ListDisplay._get_verbose_verbose() 0 10 1
A CiteDisplay.__init__() 0 2 1
A CiteDisplay._get_framework_citations() 0 15 2
B DispDisplay._append_description() 0 45 8
A ListDisplay._get_quiet() 0 3 1
A ListDisplay._get_default() 0 4 1
A DispDisplay._notices() 0 14 2
A CiteDisplay._get_default() 0 15 2
A DispDisplay._get_verbose_option_string() 0 31 4
A CiteDisplay._get_framework_title() 0 7 1
B DispDisplay._get_verbose_param_details() 0 29 5
A DispDisplay.get_warnings() 0 20 5

How to fix   Complexity   

Complexity

Complex classes like scripts.config_generator.display_formatter 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
# Copyright 2014 Diamond Light Source Ltd.
2
#
3
# Licensed under the Apache License, Version 2.0 (the "License");
4
# you may not use this file except in compliance with the License.
5
# You may obtain a copy of the License at
6
#
7
#     http://www.apache.org/licenses/LICENSE-2.0
8
#
9
# Unless required by applicable law or agreed to in writing, software
10
# distributed under the License is distributed on an "AS IS" BASIS,
11
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
# See the License for the specific language governing permissions and
13
# limitations under the License.
14
15
"""
16
.. module:: display_formatter
17
   :platform: Unix
18
   :synopsis: Classes for formatting plugin list output in the configurator.
19
20
.. moduleauthor:: Nicola Wadeson <[email protected]>
21
22
"""
23
import os
24
import textwrap
25
26
from colorama import Fore, Back, Style
27
28
from savu.plugins import utils as pu
29
import savu.data.framework_citations as fc
30
from savu.data.plugin_list import CitationInformation
31
32
33
WIDTH = 95
34
35
36
def get_terminal_width():
37
    """Return the width of the terminal"""
38
    try:
39
        terminal_width = os.get_terminal_size().columns
40
        return terminal_width if terminal_width < WIDTH else WIDTH
41
    except (AttributeError, OSError) as ae:
42
        return WIDTH
43
44
45
class DisplayFormatter(object):
46
    def __init__(self, plugin_list):
47
        self.plugin_list_inst = plugin_list
48
        self.plugin_list = plugin_list.plugin_list
49
50
    def _get_string(self, **kwargs):
51
        out_string = []
52
        width = get_terminal_width()
53
54
        verbosity = kwargs.get("verbose", False)
55
        level = kwargs.get("current_level", "basic")
56
        datasets = kwargs.get("datasets", False)
57
        expand_dim = kwargs.get("expand_dim", None)
58
59
        start = kwargs.get("start", 0)
60
        stop = kwargs.get("stop", len(self.plugin_list))
61
        subelem = kwargs.get("subelem", False)
62
63
        if stop == -1:
64
            stop = len(self.plugin_list)
65
66
        count = start
67
        plugin_list = self.plugin_list[start:stop]
68
        line_break = "%s" % ("*" * width)
69
        out_string.append(line_break)
70
71
        display_args = {
72
            "subelem": subelem,
73
            "datasets": datasets,
74
            "expand_dim": expand_dim,
75
        }
76
77
        iterative_grp = self.plugin_list_inst.get_iterate_plugin_group_dicts()
78
        start_indices = {grp['start_index']:grp['iterations']
79
                         for grp in iterative_grp}
80
        end_indices = [grp['end_index'] for grp in iterative_grp]
81
82
        for p_dict in plugin_list:
83
            count += 1
84
            if count in start_indices.keys():
85
                iter_start = f"Iterative loop of plugin(s) starts " \
86
                f"({start_indices[count]} iteration{'s'[:start_indices[count]^1]})"
87
                out_string.append(self._separator_string(iter_start, "-", width, 2))
88
            description = self._get_description(
89
                width, level, p_dict, count, verbosity, display_args
90
            )
91
            out_string.append(description)
92
            if count in end_indices:
93
                iter_end = f"Iterative loop of plugin{'s'[:count^1]} ends"
94
                out_string.append(self._separator_string(iter_end, "-", width, 2))
95
        out_string.append(line_break)
96
        return "\n".join(out_string)
97
98
    def _separator_string(self, text, symbol, width, offset):
99
        """ Create a string to separate lines inside the terminal
100
101
        :param text: text to include in the  separator string
102
        :param symbol: symbol for the separator
103
        :param width: width of the line
104
        :param offset: offset of the text
105
        :return: a string to separate lines in terminal display
106
        """
107
        length = width - offset
108
        separating_line = symbol * offset + f"{text:{symbol}<{length}}"
109
        return separating_line
110
111
    def _get_description(
112
        self, width, level, p_dict, count, verbose, display_args
113
    ):
114
        if verbose == "-q":
115
            return self._get_quiet(p_dict, count, width)
116
        if not verbose:
117
            return self._get_default(
118
                level, p_dict, count, width, display_args
119
            )
120
        if verbose == "-v":
121
            return self._get_verbose(
122
                level, p_dict, count, width, display_args
123
            )
124
        if verbose == "-vv":
125
            return self._get_verbose_verbose(
126
                level, p_dict, count, width, display_args
127
            )
128
129
    def _get_plugin_title(self, p_dict, width, fore_colour, back_colour,
130
                          active="", quiet=False, pos=None):
131
        pos = f"{pos})" if pos else ""
132
        active = f"{active} " if active else ""
133
        title = f"{active}{pos} {p_dict['name']}"
134
        title = title if quiet else f"{title} ({p_dict['id']})"
135
136
        # determine if the given plugin is in an iterative loop or not
137
        is_in_loop = False
138
        int_pos = int(p_dict['pos'])
139
        for grp in self.plugin_list_inst.iterate_plugin_groups:
140
            if grp['start_index'] <= int_pos and int_pos <= grp['end_index']:
141
                is_in_loop = True
142
143
        text_indent = '   ' if is_in_loop else ''
144
        if is_in_loop and active == '':
145
            back_colour = Back.BLUE
146
147
        title_str = self._get_equal_lines(
148
            text_indent+title, width, back_colour+fore_colour, Style.RESET_ALL,
149
            " ")
150
        return title_str
151
152
    def _get_quiet(self, p_dict, count, width, quiet=True):
153
        active = (
154
            "***OFF***" if "active" in p_dict and not p_dict["active"] else ""
155
        )
156
        p_dict["data"] = self._remove_quotes(p_dict["data"])
157
        pos = p_dict["pos"].strip() if "pos" in list(p_dict.keys()) else count
158
        fore = Fore.LIGHTWHITE_EX
159
        back = Back.RED if active else Back.LIGHTBLACK_EX
160
        return self._get_plugin_title(
161
            p_dict, width, fore, back, active=active, quiet=quiet, pos=pos
162
        )
163
164
    def _get_synopsis(self, p_dict, width, colour_on, colour_off):
165
        doc_str = p_dict["doc"]
166
        synopsis = self._get_equal_lines(
167
            doc_str.get("synopsis"), width, colour_on, colour_off, " " * 2
168
        )
169
        return "\n" + synopsis
170
171
    def _get_doc_link(self, p_dict):
172
        doc_str = p_dict["doc"]
173
        doc_link = doc_str.get("documentation_link")
174
        return f"\n  {doc_link}" if doc_link else ""
175
176
    def _get_extra_info(self, p_dict, width, colour_off, info_colour,
177
                        warn_colour):
178
        doc_str = p_dict["doc"]
179
        info = self._get_equal_lines(doc_str.get("info"), width, info_colour,
180
                                     colour_off, " " * 2)
181
        info = "\n"+info if info else ''
182
        info = info + self._get_doc_link(p_dict)
183
        warn = self._get_equal_lines(doc_str.get('warn'), width, warn_colour,
184
                                     colour_off, " "*2)
185
        warn = "\n"+warn if warn else ""
186
        return info, warn
187
188
    def _get_equal_lines(self, string, width, colour_on, colour_off, offset,
189
                         option_colour=False):
190
        """ Format the input string so that it is the width specified.
191
        Surround the string with provided colour.
192
        """
193
        if not string or not colour_on:
194
            return ""
195
        # Call method directly for split to be used with string and unicode
196
        string = string.splitlines()
197
        str_list = []
198
        for s in string:
199
            str_list += textwrap.wrap(s, width=width - len(offset))
200
        new_str_list = []
201
        if option_colour:
202
            # Alternate colour choice and doesn't colour the full width
203
            new_str_list = self._get_option_format(str_list, width,
204
                            colour_on, colour_off, offset, option_colour,
205
                            new_str_list)
206
        else:
207
            # Fill the whole line with the colour
208
            for line in str_list:
209
                lwidth = width - len(line) - len(offset)
210
                new_str_list.append(
211
                    colour_on + offset + line + " " * lwidth + colour_off)
212
        return "\n".join(new_str_list)
213
214
    def _remove_quotes(self, data_dict):
215
        """Remove quotes around variables for display"""
216
        for key, val in data_dict.items():
217
            val = str(val).replace("'", "")
218
            data_dict[key] = val
219
        return data_dict
220
221
class ParameterFormatter(DisplayFormatter):
222
    def __init__(self, plugin_list):
223
        super(ParameterFormatter, self).__init__(plugin_list)
224
225
    def _get_param_details(self, level, p_dict, width,
226
                           desc=False, breakdown=False):
227
        """ Return a list of parameters organised by visibility level
228
229
        :param level: The visibility level controls which parameters the
230
           user can see
231
        :param p_dict: Parameter dictionary for one plugin
232
        :param width: The terminal display width for output string
233
        :param desc: The description for use later on within the display
234
          formatter
235
        :param breakdown: Boolean True if the verbose verbose information
236
          should be shown
237
        :return: List of parameters in order
238
        """
239
        params = ""
240
        keycount = 0
241
        # Return the list of parameters according to the visibility level
242
        keys = pu.set_order_by_visibility(p_dict["param"], level=level)
243
        longest_key = max(keys, key=len)
244
        try:
245
            for key in keys:
246
                keycount += 1
247
                params = self._create_display_string(
248
                    desc, key, p_dict, params, keycount, longest_key,
249
                    width, breakdown
250
                )
251
            return params
252
        except Exception as e:
253
            print("ERROR: " + str(e))
254
            raise
255
256
    def _create_display_string(self, desc, key, p_dict, params, keycount,
257
                               longest_key, width, breakdown, expand_dim=None):
258
        """Create a string to describe the current parameter (key)
259
        to display to the terminal
260
261
        :param desc: the verbose description to display
262
        :param key: current parameter name
263
        :param p_dict: parameter dictionary for one plugin
264
        :param params: parameter info string to display
265
        :param keycount: current parameter number
266
        :param longest_key: longest parameter name for this plugin
267
        :param width: display text width
268
        :param breakdown: bool True if the verbose verbose information
269
          should be shown
270
        :param expand_dim: number of dimensions
271
        :return: string to describe parameter (key)
272
        """
273
        margin = 6
274
        str_margin = " " * margin
275
        sp = (len(longest_key) - len(key)) * " "
276
        # Align the parameter numbers
277
        temp = f"{keycount}) {sp}{key} : "
278
        temp = "\n   %2i) %20s : %s"
279
        val = p_dict["data"][key]
280
        if key == "preview" and expand_dim is not None:
281
            expand_dict = p_dict["tools"].get_expand_dict(val, expand_dim)
282
            val = self._dict_to_str(expand_dict, 15*" ")
283
        params += temp % (keycount, key, val)
284
        if desc:
285
            params = self._append_description(desc, key, p_dict, str_margin,
286
                                              width, params, breakdown)
287
        return params
288
289
290
    def _dict_to_str(self, _dict, indent):
291
        """Change the dictionary to a formatted string
292
293
        :param _dict:
294
        :param indent: number of space to indent by
295
        :return: String containing the formatted dict
296
        """
297
        dict_str = ""
298
        indent += (3 * " ")
299
        for k,v in _dict.items():
300
            if isinstance(v, dict):
301
                v = self._dict_to_str(v, indent+"  ")
302
            dict_str += f"\n{indent}{k:>11} : {v}"
303
        return dict_str
304
305
306
class DispDisplay(ParameterFormatter):
307
    def __init__(self, plugin_list):
308
        super(DispDisplay, self).__init__(plugin_list)
309
310
    def _get_default(self, level, p_dict, count, width, display_args=False):
311
        title = self._get_quiet(p_dict, count, width)
312
        params = self._get_param_details(level, p_dict, width, display_args)
313
        return title + params
314
315
    def _get_param_details(self, level, p_dict, width, display_args=False,
316
                           desc=False, breakdown=False):
317
        """
318
        Return a list of parameters organised by visibility level
319
320
        :param level: The visibility level controls which parameters the
321
           user can see
322
        :param p_dict: Parameter dictionary for one plugin
323
        :param width: The terminal display width for output strings
324
        :param display_args: A dictionary with subelem and datasets.
325
          This filters the visible parameters again. If subelem is chosen,
326
          only that one parameter is shown (this is useful when ONE parameter
327
          is modified in the terminal). If datasets is chosen, ONLY the
328
          in/out dataset parameters are shown
329
        :param desc: The description for use later on within the display
330
          formatter
331
        :param breakdown: Boolean True if the verbose verbose information
332
          should be shown
333
        :return: List of parameters in order
334
        """
335
        # Check if the parameters need to be filtered
336
        subelem = display_args.get("subelem")
337
        datasets = display_args.get("datasets")
338
        expand_dim = display_args.get("expand_dim")
339
340
        level = level if not (subelem or datasets) else False
341
        # Return the list of parameters according to the visibility level
342
        # unless a subelement or dataset choice is specified
343
        keys = pu.set_order_by_visibility(p_dict["param"], level=level)
344
345
        filter = subelem if subelem else datasets
346
        # If datasets parameter specified, only show these
347
        filter_items = (
348
            [pu.param_to_str(subelem, keys)]
349
            if subelem
350
            else ["in_datasets", "out_datasets"]
351
        )
352
        # Get the longest parameter name, to align values in console
353
        if filter:
354
            longest_key = max(filter_items, key=len)
355
        else:
356
            longest_key = max(keys, key=len) if keys else 0
357
        params = ""
358
        keycount = 0
359
        prev_visibility = ""
360
        try:
361
            for key in keys:
362
                keycount += 1
363
                if filter:
364
                    if key in filter_items:
365
                        params = \
366
                            self._level_separator(key, p_dict, prev_visibility,
367
                                            params, width)
368
                        params = self._create_display_string(desc, key,
369
                                       p_dict, params, keycount, longest_key,
370
                                       width, breakdown, expand_dim)
371
                else:
372
                    params = \
373
                        self._level_separator(key, p_dict, prev_visibility,
374
                                        params, width)
375
                    params = self._create_display_string(desc, key, p_dict,
376
                                        params, keycount, longest_key, width,
377
                                        breakdown, expand_dim)
378
                prev_visibility = p_dict["param"][key]["visibility"]
379
380
            return params
381
        except Exception as e:
382
            print("ERROR: " + str(e))
383
            raise
384
385
    def _level_separator(self, key, p_dict, prev_visibility, params, width):
386
        """Add a line separator to the parameter string 'params' based
387
        on the parameter visibility level
388
389
        :param key: parameter name
390
        :param p_dict: dictionary of parameter definitions
391
        :param prev_visibility: visibility level for the previous parameter
392
        :param params: parameter string
393
        :param width: width of the console display
394
        :return: parameter string
395
        """
396
        cur_visibility = p_dict["param"][key]["visibility"]
397
        if cur_visibility != prev_visibility:
398
            separating_str = self._separator_string(cur_visibility, "-", width, 2)
399
            params += "\n" + separating_str
400
        return params
401
402
    def _get_verbose(
403
        self, level, p_dict, count, width, display_args, breakdown=False
404
    ):
405
        title = self._get_quiet(p_dict, count, width, quiet=False)
406
        colour_on = Back.LIGHTBLACK_EX + Fore.LIGHTWHITE_EX
407
        colour_off = Back.RESET + Fore.RESET
408
        synopsis = self._get_synopsis(p_dict, width, colour_on, colour_off)
409
        param_desc = {k: v["description"] for k, v in p_dict["param"].items()}
410
        params = self._get_param_details(
411
            level, p_dict, width, display_args, desc=param_desc
412
        )
413
        doc_link = self._get_doc_link(p_dict)
414
        if breakdown:
415
            params = self._get_param_details(
416
                level,
417
                p_dict,
418
                width,
419
                display_args,
420
                desc=param_desc,
421
                breakdown=breakdown,
422
            )
423
            return title, synopsis, params
424
        return title + synopsis + doc_link + params
425
426
    def _get_verbose_verbose(self, level, p_dict, count, width, display_args):
427
        title, synopsis, param_details = self._get_verbose(
428
            level, p_dict, count, width, display_args, breakdown=True
429
        )
430
        info_c = Back.CYAN + Fore.LIGHTWHITE_EX
431
        warn_c = Style.RESET_ALL + Fore.RED
432
        c_off = Back.RESET + Fore.RESET
433
        info, warn = self._get_extra_info(
434
            p_dict, width, c_off, info_c, warn_c
435
        )
436
        # Synopsis and get_extra info both call plugin instance and populate
437
        # parameters which means yaml_load will be called twice
438
        return title + synopsis + info + warn + param_details
439
440
    def _notices(self):
441
        width = 86
442
        warnings = self.get_warnings(width)
443
        if warnings:
444
            notice = (
445
                Back.RED
446
                + Fore.WHITE
447
                + "IMPORTANT PLUGIN NOTICES"
448
                + Back.RESET
449
                + Fore.RESET
450
                + "\n"
451
            )
452
            border = "*" * width + "\n"
453
            print((border + notice + warnings + "\n" + border))
454
455
    def get_warnings(self, width):
456
        # remove display styling outside of this class
457
        colour = Back.RESET + Fore.RESET
458
        warnings = []
459
        names = []
460
        for plugin in self.plugin_list:
461
            if plugin["name"] not in names:
462
                names.append(plugin["name"])
463
                doc_str = plugin["doc"]
464
                warn = doc_str.get("warn")
465
                if warn:
466
                    for w in warn.split("\n"):
467
                        string = plugin["name"] + ": " + w
468
                        warnings.append(
469
                            self._get_equal_lines(
470
                                string, width - 1, colour, colour, " " * 2
471
                            )
472
                        )
473
        return "\n".join(
474
            ["*" + "\n ".join(w.split("\n")) for w in warnings if w]
475
        )
476
477
    def _append_description(self, desc, key, p_dict, str_margin, width,
478
                            params, breakdown):
479
        c_off = Back.RESET + Fore.RESET
480
        description_verbose = False
481
        description_keys = ""
482
        if isinstance(desc[key], str):
483
            pdesc = " ".join(desc[key].split())
484
            pdesc = self._get_equal_lines(
485
                pdesc, width, Fore.CYAN, Fore.RESET, str_margin
486
            )
487
            params += "\n" + pdesc
488
        elif isinstance(desc[key], dict):
489
            description_keys = desc[key].keys()
490
            for param_key in description_keys:
491
                if param_key == "summary":
492
                    pdesc = desc[key][param_key]
493
                    pdesc = self._get_equal_lines(
494
                        pdesc, width, Fore.CYAN, Fore.RESET, str_margin
495
                    )
496
                    params += "\n" + pdesc
497
498
                if breakdown:
499
                    params = self._get_verbose_param_details(
500
                        p_dict, param_key, desc, key, params, width
501
                    )
502
                    description_verbose = True
503
504
        options = p_dict["param"][key].get("options")
505
        if options:
506
            option_text = "Options:"
507
            option_text = self._get_equal_lines(
508
                option_text, width, Fore.BLUE, Fore.RESET, str_margin
509
            )
510
            params += "\n" + option_text
511
            for opt in options:
512
                current_opt = p_dict["data"][key]
513
                option_verbose = \
514
                    self._get_verbose_option_string(opt, current_opt,
515
                            description_keys, description_verbose,
516
                            desc, key, c_off, width, str_margin)
517
518
                temp = "\n" + "%s" + c_off + Style.RESET_ALL
519
                params += temp % option_verbose
520
521
        return params
522
523
    def _get_verbose_option_string(self, opt, current_opt, description_keys,
524
                                   description_verbose, desc, key, c_off,
525
                                   width, str_margin):
526
        """ Get the option description string and correctly format it """
527
        colour, v_colour = self._get_verbose_option_colours(opt, current_opt)
528
        unicode_bullet_point = "\u2022"
529
        opt_margin = str_margin + (2 * " ")
530
        if (description_verbose is True) and ("options" in description_keys):
531
            # If there are option descriptions present
532
            options_desc = {k: v
533
                            for k, v in desc[key]["options"].items()
534
                            if v}
535
            if opt in options_desc.keys():
536
                # Append the description
537
                opt_d = unicode_bullet_point + str(opt) + ": " \
538
                        + options_desc[opt]
539
            else:
540
                # No description if the field is blank
541
                opt_d = unicode_bullet_point + str(opt) + ": "
542
            option_verbose = "\n" + opt_d
543
            option_verbose = \
544
                self._get_equal_lines(option_verbose,
545
                                       width, v_colour, c_off,
546
                                       opt_margin, option_colour=colour)
547
        else:
548
            option_verbose = unicode_bullet_point + str(opt)
549
            option_verbose = \
550
                self._get_equal_lines(option_verbose,
551
                                       width, colour, c_off, opt_margin,
552
                                       option_colour=colour)
553
        return option_verbose
554
555
    def _get_verbose_option_colours(self, opt, current_opt):
556
        """Set the colour of the option text
557
558
        :param opt: Option string
559
        :param current_opt: The curently selected option value
560
        :return: colour, verbose_color
561
        """
562
        if current_opt == opt:
563
            # Highlight the currently selected option by setting a
564
            # background colour and white text
565
            colour = Back.BLUE + Fore.LIGHTWHITE_EX
566
            verbose_color = Back.BLACK + Fore.LIGHTWHITE_EX
567
        else:
568
            # Make the option bold using Style.BRIGHT
569
            colour = Fore.BLUE + Style.BRIGHT
570
            # Remove bold style for the description
571
            verbose_color = Style.RESET_ALL + Fore.BLACK
572
        return colour, verbose_color
573
574
    def _get_verbose_param_details(
575
        self, p_dict, param_key, desc, key, params, width
576
    ):
577
        margin = 6
578
        str_margin = " " * margin
579
        if param_key == "verbose":
580
            verbose = desc[key][param_key]
581
            # Account for margin space
582
            style_on = Fore.CYAN + "\033[3m"
583
            style_off = Fore.RESET + "\033[0m"
584
            verbose = self._get_equal_lines(
585
                verbose, width, style_on, style_off, str_margin
586
            )
587
            params += "\n" + verbose
588
589
        if param_key == "range":
590
            p_range = desc[key][param_key]
591
            if p_range:
592
                try:
593
                    r_color = Fore.MAGENTA
594
                    r_off = Fore.RESET
595
                    p_range = self._get_equal_lines(
596
                        p_range, width, r_color, r_off, str_margin
597
                    )
598
                    params += "\n" + p_range
599
                except TypeError:
600
                    print(f"You have not filled in the {param_key} field "
601
                          f"within the yaml information.")
602
        return params
603
604
    def _get_option_format(self, str_list, width, colour_on, colour_off,
605
                           offset, option_colour, new_str_list):
606
        """ Special format for the options list """
607
        count = 0
608
        for line in str_list:
609
            lwidth = width - len(line) - len(offset)
610
            count += 1
611
            if count == 1:
612
                """At the first line, split the key so that it's colour is
613
                different. This is done here so that I keep the key and
614
                value on the same line.
615
                I have not passed in the unicode colour before this
616
                point as the textwrap does not take unicode into
617
                account when calculating the final string width.
618
                """
619
                if ":" in line:
620
                    option_text = line.split(":")[0]
621
                    opt_descr_text = line.split(":")[1]
622
                    line = (
623
                        option_colour
624
                        + option_text
625
                        + ":"
626
                        + colour_on
627
                        + opt_descr_text
628
                    )
629
                    new_str_list.append(
630
                        offset + line + colour_off + " " * lwidth
631
                    )
632
                else:
633
                    # Assumes that the option string is one line, the length
634
                    # of the width
635
                    new_str_list.append(
636
                        offset
637
                        + option_colour
638
                        + line
639
                        + colour_off
640
                        + " " * lwidth
641
                    )
642
            else:
643
                new_str_list.append(
644
                    offset + colour_on + line + colour_off + " " * lwidth
645
                )
646
        return new_str_list
647
648
649
class ListDisplay(ParameterFormatter):
650
    def __init__(self, plugin_list):
651
        super(ListDisplay, self).__init__(plugin_list)
652
653
    def _get_quiet(self, p_dict, count, width):
654
        return self._get_plugin_title(
655
            p_dict, width, Fore.RESET, Back.RESET, quiet=True
656
        )
657
658
    def _get_default(self, level, p_dict, count, width, display_args=False):
659
        title = self._get_quiet(p_dict, count, width)
660
        synopsis = self._get_synopsis(p_dict, width, Fore.CYAN, Fore.RESET)
661
        return title + synopsis
662
663
    def _get_verbose(
664
        self, level, p_dict, count, width, display_args, breakdown=False
665
    ):
666
        default_str = self._get_default(level, p_dict, count, width)
667
        info_c = Fore.CYAN
668
        c_off = Back.RESET + Fore.RESET
669
        info, warn = self._get_extra_info(
670
            p_dict, width, c_off, info_c, info_c
671
        )
672
        return default_str + info
673
674
    def _get_verbose_verbose(self, level, p_dict, count, width, display_args):
675
        all_params = self._get_param_details("all", p_dict, width)
676
        default_str = self._get_default(level, p_dict, count, width)
677
        info_c = Fore.CYAN
678
        warn_c = Fore.RED
679
        c_off = Back.RESET + Fore.RESET
680
        info, warn = self._get_extra_info(
681
            p_dict, width, c_off, info_c, warn_c
682
        )
683
        return default_str + info + warn + all_params
684
685
686
class CiteDisplay(DisplayFormatter):
687
    def __init__(self, plugin_list):
688
        super(CiteDisplay, self).__init__(plugin_list)
689
690
    def _get_default(self, level, p_dict, count, width, display_args=False):
691
        """Find the citations and print them. Only display citations
692
        if they are required. For example, if certain methods are being
693
        used.
694
        """
695
        title = self._get_quiet(p_dict, count, width)
696
        citation = self._get_citation_str(
697
            p_dict["tools"].get_citations(), width, parameters=p_dict["data"]
698
        )
699
        if count == 1:
700
            # Display framework citations before the first citation
701
            framework_citations = self._get_framework_citations(width)
702
            return framework_citations + title + citation
703
704
        return title + citation
705
706
    def _get_citation_str(self, citation_dict, width, parameters=""):
707
        """Get the plugin citation information
708
709
        :param: citation_dict: Dictionay containing citation information
710
        :param parameters: Dictionary containing parameter information
711
        :param width: The terminal display width for output strings
712
        :return: cite, A string containing plugin citations
713
        """
714
        margin = 6
715
        str_margin = " " * margin
716
        line_break = "\n" + str_margin + "-" * (width - margin) + "\n"
717
        cite = ""
718
        if citation_dict:
719
            for citation in citation_dict.values():
720
                if citation.dependency and parameters:
721
                    # If the citation is dependent upon a certain parameter
722
                    # value being chosen
723
                    str_dep = self._get_citation_dependency_str(
724
                        citation, parameters, width, str_margin
725
                    )
726
                    if str_dep:
727
                        cite += line_break + str_dep + "\n" \
728
                                + self._get_citation_lines(citation,
729
                                                           width,
730
                                                           str_margin)
731
                else:
732
                    cite += line_break \
733
                            + self._get_citation_lines(citation,
734
                                                       width, str_margin)
735
        else:
736
            cite = f"\n\n{' '}No citations"
737
        return cite
738
739
    def _get_citation_dependency_str(
740
        self, citation, parameters, width, str_margin
741
    ):
742
        """Create a message for citations dependent on a
743
        certain parameter
744
745
        :param citation: Single citation dictionary
746
        :param parameters: List of current parameter values
747
        :param width: The terminal display width for output strings
748
        :param str_margin: The terminal display margin
749
        :return: str_dep, A string to identify citations dependent on
750
        certain parameter values
751
        """
752
        str_dep = ""
753
        for (citation_dependent_parameter, citation_dependent_value) \
754
            in citation.dependency.items():
755
            current_value = parameters[citation_dependent_parameter]
756
            if current_value == citation_dependent_value:
757
                str_dep = (
758
                    f"This citation is for the {citation_dependent_value}"
759
                    f" {citation_dependent_parameter}"
760
                )
761
                str_dep = self._get_equal_lines(
762
                    str_dep, width, Style.BRIGHT, Style.RESET_ALL, str_margin
763
                )
764
        return str_dep
765
766
    def _get_framework_title(self, width, fore_colour, back_colour):
767
        title = "Framework Citations "
768
        width -= len(title)
769
        title_str = (
770
            back_colour + fore_colour + title + " " * width + Style.RESET_ALL
771
        )
772
        return title_str
773
774
    def _get_framework_citations(self, width):
775
        """Create a string containing framework citations
776
777
        :param width: Width of formatted text
778
        :return: String with framework citations
779
        """
780
        citation_dict = {}
781
        framework_cites = fc.get_framework_citations()
782
        for name, cite in framework_cites.items():
783
            citation_dict.update({name: cite})
784
        title = \
785
            self._get_framework_title(width, Fore.LIGHTWHITE_EX,
786
                                             Back.LIGHTBLACK_EX)
787
        cite = self._get_citation_str(citation_dict, width)
788
        return title + cite + "\n"
789
790
    def _get_citation_lines(self, citation, width, str_margin):
791
        """Print certain information about the citation in order.
792
793
        :param citation: Single citation dictionary
794
        :param width: The terminal display width for output strings
795
        :param str_margin: The terminal display margin
796
        :return: A string containing citation details
797
        """
798
        cite_keys = ["name", "description", "doi", "bibtex", "endnote"]
799
        cite_dict = citation.__dict__
800
        cite_str = ""
801
802
        style_on = Style.BRIGHT
803
        style_off = Style.RESET_ALL
804
805
        for key in cite_keys:
806
            if cite_dict[key]:
807
                # Set the key name to be bold
808
                cite_key = self._get_equal_lines(
809
                    key.title(), width, style_on, style_off, str_margin
810
                )
811
                # No style for the citation content
812
                cite_value = self._get_equal_lines(
813
                    cite_dict[key], width, style_off, style_off, str_margin
814
                )
815
                # New line for each item
816
                cite_str += "\n" + cite_key + "\n" + cite_value + "\n"
817
818
        return cite_str
819