Test Failed
Pull Request — master (#888)
by Daniil
04:00
created

  A

Complexity

Conditions 3

Size

Total Lines 20
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

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