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