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