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