Test Failed
Pull Request — master (#785)
by
unknown
04:32
created

scripts.config_generator.content   F

Complexity

Total Complexity 184

Size/Duplication

Total Lines 976
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 547
dl 0
loc 976
rs 2
c 0
b 0
f 0
wmc 184

61 Methods

Rating   Name   Duplication   Size   Complexity  
A Content.check_plugin_list_exists() 0 6 2
A Content.add() 0 16 4
A Content.save() 0 11 3
A Content.check_mutations() 0 14 4
A Content._version_to_float() 0 7 3
A Content.check_file() 0 10 4
A Content.fopen() 0 12 3
A Content.set_finished() 0 2 2
A Content.clear() 0 3 2
A Content.is_finished() 0 2 1
A Content.__init__() 0 9 1
A Content.refresh() 0 12 4
A Content.display() 0 14 4
B Content._mutate_plugins() 0 30 8
A Content.check_preview_param() 0 12 2
D Content._apply_plugin_updates() 0 43 13
A Content.move() 0 9 1
B Content._update_parameters() 0 21 7
A Content.set_preview_display() 0 20 3
A Content.increment_positions() 0 8 3
A Content.split_plugin_string() 0 19 4
A Content._modify_preview_dimension() 0 13 2
A Content.inc_numbers() 0 4 2
A Content.find_position() 0 10 3
A Content.remove() 0 10 2
A Content.convert_to_ascii() 0 5 2
A Content.separate_plugin_subelem() 0 29 3
A Content._modify_preview_dimension_slice() 0 19 2
A Content._change_value() 0 14 1
A Content.check_required_args() 0 11 5
A Content._separate_dimension_and_slice() 0 17 3
A Content.dim_str_to_int() 0 18 3
C Content.convert_pos() 0 42 9
A Content.size() 0 3 1
A Content.modify() 0 31 3
A Content.inc_positions() 0 11 2
A Content.on_and_off() 0 5 2
A Content.create_plugin_dict() 0 11 1
A Content.setup_modify() 0 22 2
A Content._set_empty_dimension_slice() 0 9 1
A Content.get() 0 2 1
A Content._set_empty_list() 0 12 2
A Content._check_command_valid() 0 25 5
A Content.modify_main() 0 41 4
A Content.get_positions() 0 7 2
A Content._check_command_str() 0 11 2
A Content.inc_letters() 0 4 2
A Content._split_subelem() 0 15 1
A Content.level() 0 7 2
A Content.insert() 0 7 2
A Content.modify_preview() 0 31 4
A Content.get_param_arg_str() 0 16 1
B Content.modify_dimensions() 0 34 6
A Content.preview_dimension_to_modify() 0 18 3
A Content._get_modified_slice() 0 7 2
A Content._get_start_stop() 0 5 2
A Content.check_param_exists() 0 10 2
B Content.value() 0 20 6
A Content._modified_slice_notation() 0 19 3
A Content._set_incomplete_slices() 0 11 2
A Content.get_split_positions() 0 9 3

How to fix   Complexity   

Complexity

Complex classes like scripts.config_generator.content often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
# Copyright 2014 Diamond Light Source Ltd.
2
#
3
# Licensed under the Apache License, Version 2.0 (the "License");
4
# you may not use this file except in compliance with the License.
5
# You may obtain a copy of the License at
6
#
7
#     http://www.apache.org/licenses/LICENSE-2.0
8
#
9
# Unless required by applicable law or agreed to in writing, software
10
# distributed under the License is distributed on an "AS IS" BASIS,
11
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
# See the License for the specific language governing permissions and
13
# limitations under the License.
14
15
"""
16
.. module:: content
17
   :platform: Unix
18
   :synopsis: Content class for the configurator
19
20
.. moduleauthor:: Nicola Wadeson <[email protected]>
21
22
"""
23
import re
24
import os
25
import inspect
26
27
from savu.plugins import utils as pu
28
from savu.data.plugin_list import PluginList
29
import scripts.config_generator.parameter_utils as param_u
30
31
from . import mutations
32
33
34
class Content(object):
35
    def __init__(self, filename=None, level="basic"):
36
        self.disp_level = level
37
        self.plugin_list = PluginList()
38
        self.plugin_mutations = mutations.plugin_mutations
39
        self.param_mutations = mutations.param_mutations
40
        self.filename = filename
41
        self._finished = False
42
        self.failed = {}
43
        self.expand_dim = None
44
45
    def set_finished(self, check="y"):
46
        self._finished = True if check.lower() == "y" else False
47
48
    def is_finished(self):
49
        return self._finished
50
51
    def fopen(self, infile, update=False, skip=False):
52
        if os.path.exists(infile):
53
            self.plugin_list._populate_plugin_list(infile, active_pass=True)
54
        else:
55
            raise Exception("INPUT ERROR: The file does not exist.")
56
        self.filename = infile
57
        if update:
58
            self.plugin_mutations = self.check_mutations(
59
                self.plugin_mutations
60
            )
61
            self.param_mutations = self.check_mutations(self.param_mutations)
62
            self._apply_plugin_updates(skip)
63
64
    def check_mutations(self, mut_dict: dict):
65
        plist_version = self._version_to_float(self.plugin_list.version)
66
        # deleting elements while iterating invalidates the iterator
67
        # which raises a RuntimeError in Python 3.
68
        # Instead a copy of the dict is mutated and returned
69
        mut_dict_copy = mut_dict.copy()
70
        for key, subdict in mut_dict.items():
71
            if "up_to_version" in subdict.keys():
72
                up_to_version = self._version_to_float(
73
                    subdict["up_to_version"]
74
                )
75
                if plist_version >= up_to_version:
76
                    del mut_dict_copy[key]
77
        return mut_dict_copy
78
79
    def _version_to_float(self, version):
80
        if version is None:
81
            return 0
82
        if isinstance(version, bytes):
83
            version = version.decode("ascii")
84
        split_vals = version.split(".")
85
        return float(".".join([split_vals[0], "".join(split_vals[1:])]))
86
87
    def display(self, formatter, **kwargs):
88
        # Set current level
89
        if "current_level" not in list(kwargs.keys()):
90
            kwargs["current_level"] = self.disp_level
91
        if (
92
            "disp_level" in list(kwargs.keys())
93
            and kwargs["disp_level"] is True
94
        ):
95
            # Display level
96
            print(f"Level is set at '{kwargs['current_level']}'")
97
        else:
98
            # Display parameter
99
            kwargs["expand_dim"] = self.expand_dim
100
            print("\n" + formatter._get_string(**kwargs) + "\n")
101
102
    def check_file(self, filename):
103
        if not filename:
104
            raise Exception(
105
                "INPUT ERROR: Please specify the output filepath."
106
            )
107
        path = os.path.dirname(filename)
108
        path = path if path else "."
109
        if not os.path.exists(path):
110
            file_error = "INPUT_ERROR: Incorrect filepath."
111
            raise Exception(file_error)
112
113
    def save(self, filename, check="y", template=False):
114
        self.check_plugin_list_exists()
115
        # Check if a loader and saver are present.
116
        self.plugin_list._check_loaders()
117
        if check.lower() == "y":
118
            print(f"Saving file {filename}")
119
            if template:
120
                self.plugin_list.add_template(create=True)
121
            self.plugin_list._save_plugin_list(filename)
122
        else:
123
            print("The process list has NOT been saved.")
124
125
    def clear(self, check="y"):
126
        if check.lower() == "y":
127
            self.plugin_list.plugin_list = []
128
129
    def check_plugin_list_exists(self):
130
        """ Check if plugin list is populated. """
131
        pos_list = self.get_positions()
132
        if not pos_list:
133
            print("There are no items to access in your list.")
134
            raise Exception("Please add an item to the process list.")
135
136
    def add(self, name, str_pos):
137
        if name not in list(pu.plugins.keys()):
138
            if (self.failed is not None) and (
139
                name in list(self.failed.keys())
140
            ):
141
                msg = (
142
                    "IMPORT ERROR: %s is unavailable due to the following"
143
                    " error:\n\t%s" % (name, self.failed[name])
144
                )
145
                raise Exception(msg)
146
            else:
147
                raise Exception("INPUT ERROR: Unknown plugin %s" % name)
148
        plugin = pu.plugins[name]()
149
        plugin.get_plugin_tools()._populate_default_parameters()
150
        pos, str_pos = self.convert_pos(str_pos)
151
        self.insert(plugin, pos, str_pos)
152
153
    def refresh(self, str_pos, defaults=False, change=False):
154
        pos = self.find_position(str_pos)
155
        plugin_entry = self.plugin_list.plugin_list[pos]
156
        name = change if change else plugin_entry["name"]
157
        active = plugin_entry["active"]
158
        plugin = pu.plugins[name]()
159
        plugin.get_plugin_tools()._populate_default_parameters()
160
        keep = self.get(pos)["data"] if not defaults else None
161
        self.insert(plugin, pos, str_pos, replace=True)
162
        self.plugin_list.plugin_list[pos]["active"] = active
163
        if keep:
164
            self._update_parameters(plugin, name, keep, str_pos)
165
166
    def check_preview_param(self, plugin_pos):
167
        """ Check that the plugin position number is valid and it contains
168
        a preview parameter
169
170
        :param plugin_pos:
171
        :return:
172
        """
173
        pos = self.find_position(plugin_pos)
174
        plugin_entry = self.plugin_list.plugin_list[pos]
175
        parameters = plugin_entry["data"]
176
        if "preview" not in parameters:
177
            raise Exception("You can only use this command with "
178
                            "plugins containing the preview parameter")
179
180
    def set_preview_display(self, expand_off, expand_dim, dim_view,
181
                            plugin_pos):
182
        """Set the dimensions_to_display value to "off" to prevent the
183
        preview parameter being shown in it's expanded form.
184
185
        If dimensions_to_display = "all", then all dimension slices are shown.
186
        If a number is provided to dim_view, that dimension is shown.
187
188
        :param expand_off: True if expand display should be turned off
189
        :param expand_dim: The dimension number to display, or "all"
190
        :param dim_view: True if only a certain dimension should be shown
191
        :param plugin_pos: Plugin position
192
        """
193
        if expand_off is True:
194
            self.expand_dim = None
195
            print(f"Expand diplay has been turned off")
196
        else:
197
            self.check_preview_param(plugin_pos)
198
            dims_to_display = expand_dim if dim_view else "all"
199
            self.expand_dim = dims_to_display
200
201
    def _update_parameters(self, plugin, name, keep, str_pos):
202
        union_params = set(keep).intersection(set(plugin.parameters))
203
        # Names of the parameter names present in both lists
204
        for param in union_params:
205
            self.modify(str_pos, param, keep[param], ref=True)
206
        # add any parameter mutations here
207
        classes = [c.__name__ for c in inspect.getmro(plugin.__class__)]
208
        m_dict = self.param_mutations
209
        keys = [k for k in list(m_dict.keys()) if k in classes]
210
211
        changes = False
212
        for k in keys:
213
            for entry in m_dict[k]["params"]:
214
                if entry["old"] in list(keep.keys()):
215
                    changes = True
216
                    val = keep[entry["old"]]
217
                    if "eval" in list(entry.keys()):
218
                        val = eval(entry["eval"])
219
                    self.modify(str_pos, entry["new"], val, ref=True)
220
        if changes:
221
            mutations.param_change_str(keep, plugin.parameters, name, keys)
222
223
    def _apply_plugin_updates(self, skip=False):
224
        # Update old process lists that start from 0
225
        the_list = self.plugin_list.plugin_list
226
        if "pos" in list(the_list[0].keys()) and the_list[0]["pos"] == "0":
227
            self.increment_positions()
228
229
        missing = []
230
        pos = len(the_list) - 1
231
        notices = mutations.plugin_notices
232
233
        for plugin in the_list[::-1]:
234
            # update old process lists to include 'active' flag
235
            if "active" not in list(plugin.keys()):
236
                plugin["active"] = True
237
238
            while True:
239
                name = the_list[pos]["name"]
240
                if name in notices.keys():
241
                    print(notices[name]["desc"])
242
243
                # if a plugin is missing from all available plugins
244
                # then look for mutations in the plugin name
245
                search = True if name not in pu.plugins.keys() else False
246
                found = self._mutate_plugins(name, pos, search=search)
247
                if search and not found:
248
                    str_pos = self.plugin_list.plugin_list[pos]["pos"]
249
                    missing.append([name, str_pos])
250
                    self.remove(pos)
251
                    pos -= 1
252
                if name == the_list[pos]["name"]:
253
                    break
254
            pos -= 1
255
256
        for name, pos in missing[::-1]:
257
            if skip:
258
                print(f"Skipping plugin {pos}: {name}")
259
            else:
260
                message = (
261
                    f"\nPLUGIN ERROR: The plugin {name} is "
262
                    f"unavailable in this version of Savu. \n Type open"
263
                    f" -s <process_list> to skip the broken plugin."
264
                )
265
                raise Exception(f"Incompatible process list. {message}")
266
267
    def _mutate_plugins(self, name, pos, search=False):
268
        """ Perform plugin mutations. """
269
        # check for case changes in plugin name
270
        if search:
271
            for key in pu.plugins.keys():
272
                if name.lower() == key.lower():
273
                    str_pos = self.plugin_list.plugin_list[pos]["pos"]
274
                    self.refresh(str_pos, change=key)
275
                    return True
276
277
        # check mutations dict
278
        m_dict = self.plugin_mutations
279
        if name in m_dict.keys():
280
            mutate = m_dict[name]
281
            if "replace" in mutate.keys():
282
                if mutate["replace"] in list(pu.plugins.keys()):
283
                    str_pos = self.plugin_list.plugin_list[pos]["pos"]
284
                    self.refresh(str_pos, change=mutate["replace"])
285
                    print(mutate["desc"])
286
                    return True
287
                raise Exception(
288
                    f"Replacement plugin {mutate['replace']} "
289
                    f"unavailable for {name}"
290
                )
291
            elif "remove" in mutate.keys():
292
                self.remove(pos)
293
                print(mutate["desc"])
294
            else:
295
                raise Exception("Unknown mutation type.")
296
        return False
297
298
    def move(self, old, new):
299
        old_pos = self.find_position(old)
300
        entry = self.plugin_list.plugin_list[old_pos]
301
        self.remove(old_pos)
302
        new_pos, new = self.convert_pos(new)
303
        name = entry["name"]
304
        self.insert(pu.plugins[name](), new_pos, new)
305
        self.plugin_list.plugin_list[new_pos] = entry
306
        self.plugin_list.plugin_list[new_pos]["pos"] = new
307
308
    def modify(self, pos_str, param_name, value, default=False, ref=False,
309
               dim=False):
310
        """Modify the plugin at pos_str and the parameter at param_name
311
        The new value will be set if it is valid.
312
313
        :param pos_str: The plugin position
314
        :param param_name: The parameter position/name
315
        :param value: The new parameter value
316
        :param default: True if value should be reverted to the default
317
        :param ref: boolean Refresh the plugin
318
        :param dim: The dimension to be modified
319
320
        returns: A boolean True if the value is a valid input for the
321
          selected parameter
322
        """
323
        pos = self.find_position(pos_str)
324
        plugin_entry = self.plugin_list.plugin_list[pos]
325
        tools = plugin_entry["tools"]
326
        parameters = plugin_entry["data"]
327
        params = plugin_entry["param"]
328
        param_name, value = self.setup_modify(params, param_name, value, ref)
329
        default_str = ["-d", "--default"]
330
        if default or value in default_str:
331
            value = tools.get_default_value(param_name)
332
            self._change_value(param_name, value, tools, parameters)
333
            valid_modification = True
334
        else:
335
            valid_modification = self.modify_main(
336
                param_name, value, tools, parameters, dim
337
            )
338
        return valid_modification
339
340
    def setup_modify(self, params, param_name, value, ref):
341
        """Get the parameter keys in the correct order and find
342
        the parameter name string
343
344
        :param params: The plugin parameters
345
        :param param_name: The parameter position/name
346
        :param value: The new parameter value
347
        :param ref: boolean Refresh the plugin
348
349
        return: param_name str to avoid discrepancy, value
350
        """
351
        if ref:
352
            # For a refresh, refresh all keys, including those with
353
            # dependencies (which have the display off)
354
            keys = params.keys()
355
        else:
356
            # Select the correct group and order of parameters according to that
357
            # on display to the user. This ensures correct parameter is modified.
358
            keys = pu.set_order_by_visibility(params)
359
            value = self.value(value)
360
        param_name = pu.param_to_str(param_name, keys)
361
        return param_name, value
362
363
    def modify_main(self, param_name, value, tools, parameters, dim):
364
        """Check the parameter is within the current parameter list.
365
        Check the new parameter value is valid, modify the parameter
366
        value, update defaults, check if dependent parameters should
367
        be made visible or hidden.
368
369
        :param param_name: The parameter position/name
370
        :param value: The new parameter value
371
        :param tools: The plugin tools
372
        :param parameters: The plugin parameters
373
        :param dim: The dimensions
374
375
        returns: A boolean True if the value is a valid input for the
376
          selected parameter
377
        """
378
        parameter_valid = False
379
        current_parameter_details = tools.param.get(param_name)
380
381
        # If dimensions are provided then alter preview param
382
        if self.preview_dimension_to_modify(dim, param_name):
383
            # Filter the dimension, dim1 or dim1.start
384
            dim, _slice = self._separate_dimension_and_slice(dim)
385
            value = self.modify_preview(
386
                parameters, param_name, value, dim, _slice
387
            )
388
389
        # If found, then the parameter is within the current parameter list
390
        # displayed to the user
391
        if current_parameter_details:
392
            value_check = pu._dumps(value)
393
            parameter_valid, error_str = param_u.is_valid(
394
                param_name, value_check, current_parameter_details
395
            )
396
            if parameter_valid:
397
                self._change_value(param_name, value, tools, parameters)
398
            else:
399
                print(error_str)
400
                print("ERROR: This value has not been saved.")
401
        else:
402
            print("Not in parameter keys.")
403
        return parameter_valid
404
405
    def _change_value(self, param_name, value, tools, parameters):
406
        """ Change the parameter "param_name" value inside the parrameters list
407
        Update feedback messages for various dependant parameters
408
409
        :param param_name: The parameter position/name
410
        :param value: The new parameter value
411
        :param tools: The plugin tools
412
        :param parameters: The plugin parameters
413
        """
414
        # Save the value
415
        parameters[param_name] = value
416
        tools.warn_dependents(param_name, value)
417
        # Update the list of parameters to hide those dependent on others
418
        tools.check_dependencies(parameters)
419
420
    def check_required_args(self, value, required):
421
        """Check required argument 'value' is present
422
423
        :param value: Argument value
424
        :param required: bool, True if the argument is required
425
        """
426
        if required and (not value):
427
            raise Exception("Please enter a value")
428
429
        if (not required) and value:
430
            raise Exception(f"Unrecognised argument: {value}")
431
432
    def preview_dimension_to_modify(self, dim, param_name):
433
        """Check that the dimension string is only present when the parameter
434
        to modify is the preview parameter
435
436
        :param dim: Dimension string
437
        :param param_name: The parameter name (of the parameter to be modified)
438
        :return: True if dimension string is provided and the parameter to modify
439
        the preview parameter
440
        """
441
        if dim:
442
            if param_name == "preview":
443
                return True
444
            else:
445
                raise Exception(
446
                    "Please only use the dimension syntax when "
447
                    "modifying the preview parameter."
448
                )
449
        return False
450
451
    def modify_dimensions(self, pos_str, dim):
452
        """Modify the plugin preview value. Remove or add dimensions
453
        to the preview parameter until the provided dimension number
454
        is reached.
455
456
        :param pos_str: The plugin position
457
        :param dim: The new number of dimensions
458
        :return True if preview is modified
459
        """
460
        pos = self.find_position(pos_str)
461
        plugin_entry = self.plugin_list.plugin_list[pos]
462
        parameters = plugin_entry["data"]
463
        self.check_param_exists(parameters, "preview")
464
        current_prev_list = pu._dumps(parameters["preview"])
465
        if not isinstance(current_prev_list, list):
466
            # Temporarily cover dict instance for preview
467
            print("This command is only possible for preview "
468
                  "values of the type list")
469
            return False
470
        pu.check_valid_dimension(dim, [])
471
        check_str = (
472
            f"Are you sure you want to alter the number of "
473
            f"dimensions to {dim}? [y/N]"
474
        )
475
        check = input(check_str) if current_prev_list else "y"
476
477
        if check.lower() == "y":
478
            while len(current_prev_list) > dim:
479
                current_prev_list.pop()
480
            while len(current_prev_list) < dim:
481
                current_prev_list.append(":")
482
            parameters["preview"] = current_prev_list
483
            return True
484
        return False
485
486
    def check_param_exists(self, parameters, pname):
487
        """Check the parameter is present in the current parameter list
488
489
        :param parameters: Dictionary of parameters
490
        :param pname: Parameter name
491
        :return:
492
        """
493
        if not parameters.get(pname):
494
            raise Exception(
495
                f"The {pname} parameter is not available" f" for this plugin."
496
            )
497
498
    def value(self, value):
499
        if not value.count(";"):
500
            try:
501
                value = eval(value)
502
            except (NameError, SyntaxError):
503
                try:
504
                    value = eval(f"'{value}'")
505
                    # if there is one quotation mark there will be an error
506
                except EOFError:
507
                    raise EOFError(
508
                        "There is an end of line error. Please check your"
509
                        ' input for the character "\'".'
510
                    )
511
                except SyntaxError:
512
                    raise SyntaxError(
513
                        "There is a syntax error. Please check your input."
514
                    )
515
                except:
516
                    raise Exception("Please check your input.")
517
        return value
518
519
    def convert_to_ascii(self, value):
520
        ascii_list = []
521
        for v in value:
522
            ascii_list.append(v.encode("ascii", "ignore"))
523
        return ascii_list
524
525
    def on_and_off(self, str_pos, index):
526
        print(("switching plugin %s %s" % (str_pos, index)))
527
        status = True if index == "ON" else False
528
        pos = self.find_position(str_pos)
529
        self.plugin_list.plugin_list[pos]["active"] = status
530
531
    def convert_pos(self, str_pos):
532
        """ Converts the display position (input) to the equivalent numerical
533
        position and updates the display position if required.
534
535
        :param str_pos: the plugin display position (input) string.
536
        :returns: the equivalent numerical position of str_pos and and updated\
537
            str_pos.
538
        :rtype: (pos, str_pos)
539
        """
540
        pos_list = self.get_split_positions()
541
        num = re.findall(r"\d+", str_pos)[0]
542
        letter = re.findall("[a-z]", str_pos)
543
        entry = [num, letter[0]] if letter else [num]
544
545
        # full value already exists in the list
546
        if entry in pos_list:
547
            index = pos_list.index(entry)
548
            return self.inc_positions(index, pos_list, entry, 1)
549
550
        # only the number exists in the list
551
        num_list = [pos_list[i][0] for i in range(len(pos_list))]
552
        if entry[0] in num_list:
553
            start = num_list.index(entry[0])
554
            if len(entry) == 2:
555
                if len(pos_list[start]) == 2:
556
                    idx = int([i for i in range(len(num_list)) if
557
                               (num_list[i] == entry[0])][-1]) + 1
558
                    entry = [entry[0], str(chr(ord(pos_list[idx - 1][1]) + 1))]
559
                    return idx, ''.join(entry)
560
                if entry[1] == 'a':
561
                    self.plugin_list.plugin_list[start]['pos'] = entry[0] + 'b'
562
                    return start, ''.join(entry)
563
                else:
564
                    self.plugin_list.plugin_list[start]['pos'] = entry[0] + 'a'
565
                    return start + 1, entry[0] + 'b'
566
            return self.inc_positions(start, pos_list, entry, 1)
567
568
        # number not in list
569
        entry[0] = str(int(num_list[-1]) + 1 if num_list else 1)
570
        if len(entry) == 2:
571
            entry[1] = "a"
572
        return len(self.plugin_list.plugin_list), "".join(entry)
573
574
    def increment_positions(self):
575
        """Update old process lists that start plugin numbering from 0 to
576
        start from 1."""
577
        for plugin in self.plugin_list.plugin_list:
578
            str_pos = plugin["pos"]
579
            num = str(int(re.findall(r"\d+", str_pos)[0]) + 1)
580
            letter = re.findall("[a-z]", str_pos)
581
            plugin["pos"] = "".join([num, letter[0]] if letter else [num])
582
583
    def get_positions(self):
584
        """ Get a list of all current plugin entry positions. """
585
        elems = self.plugin_list.plugin_list
586
        pos_list = []
587
        for e in elems:
588
            pos_list.append(e["pos"])
589
        return pos_list
590
591
    def get_param_arg_str(self, pos_str, subelem):
592
        """Get the name of the parameter so that the display lists the
593
        correct item when the parameter order has been updated
594
595
        :param pos_str: The plugin position
596
        :param subelem: The parameter
597
        :return: The plugin.parameter_name argument
598
        """
599
        pos = self.find_position(pos_str)
600
        current_parameter_list = self.plugin_list.plugin_list[pos]["param"]
601
        current_parameter_list_ordered = pu.set_order_by_visibility(
602
            current_parameter_list
603
        )
604
        param_name = pu.param_to_str(subelem, current_parameter_list_ordered)
605
        param_argument = pos_str + "." + param_name
606
        return param_argument
607
608
    def get_split_positions(self):
609
        """ Separate numbers and letters in positions. """
610
        positions = self.get_positions()
611
        split_pos = []
612
        for i in range(len(positions)):
613
            num = re.findall(r"\d+", positions[i])[0]
614
            letter = re.findall("[a-z]", positions[i])
615
            split_pos.append([num, letter[0]] if letter else [num])
616
        return split_pos
617
618
    def find_position(self, pos):
619
        """ Find the numerical index of a position (a string). """
620
        pos_list = self.get_positions()
621
        if not pos_list:
622
            print("There are no items to access in your list.")
623
            raise Exception("Please add an item to the process list.")
624
        else:
625
            if pos not in pos_list:
626
                raise ValueError("Incorrect plugin position.")
627
            return pos_list.index(pos)
628
629
    def inc_positions(self, start, pos_list, entry, inc):
630
        if len(entry) == 1:
631
            self.inc_numbers(start, pos_list, inc)
632
        else:
633
            idx = [
634
                i
635
                for i in range(start, len(pos_list))
636
                if pos_list[i][0] == entry[0]
637
            ]
638
            self.inc_letters(idx, pos_list, inc)
639
        return start, "".join(entry)
640
641
    def inc_numbers(self, start, pos_list, inc):
642
        for i in range(start, len(pos_list)):
643
            pos_list[i][0] = str(int(pos_list[i][0]) + inc)
644
            self.plugin_list.plugin_list[i]["pos"] = "".join(pos_list[i])
645
646
    def inc_letters(self, idx, pos_list, inc):
647
        for i in idx:
648
            pos_list[i][1] = str(chr(ord(pos_list[i][1]) + inc))
649
            self.plugin_list.plugin_list[i]["pos"] = "".join(pos_list[i])
650
651
    def split_plugin_string(self, start, stop, subelem_view=False):
652
        """Find the start and stop number for the plugin range selected.
653
654
        :param start: Plugin starting index (including a subelem value
655
          if permitted)
656
        :param stop: Plugin stopping index
657
        :param subelem_view: False if subelem value not permitted
658
        :return: range_dict containing start stop (and possible subelem)
659
        """
660
        range_dict = {}
661
        if start:
662
            if subelem_view and "." in start:
663
                start, stop, subelem = self._split_subelem(start)
664
                range_dict["subelem"] = subelem
665
            else:
666
                start, stop = self._get_start_stop(start, stop)
667
            range_dict["start"] = start
668
            range_dict["stop"] = stop
669
        return range_dict
670
671
    def _get_start_stop(self, start, stop):
672
        """Find the start and stop number for the plugin range selected """
673
        start = self.find_position(start)
674
        stop = self.find_position(stop) + 1 if stop else start + 1
675
        return start, stop
676
677
    def _split_subelem(self, start, config_disp=True):
678
        """Separate the start string containing the plugin number,
679
        parameter(subelement), dimension and command
680
681
        :param start: The plugin to start at
682
        :param config_disp: True if command and dimension arguments
683
          are not permitted
684
        :return: start plugin, range_dict containing a subelem
685
            if a parameter is specified
686
        """
687
        start, subelem, dim, command = self.separate_plugin_subelem(
688
            start, config_disp
689
        )
690
        start, stop = self._get_start_stop(start, "")
691
        return start, stop, subelem
692
693
    def _check_command_valid(self, plugin_param, config_disp):
694
        """Check the plugin_param string length
695
696
        :param plugin_param: The string containing plugin number, parameter,
697
         and command
698
        :param config_disp: bool, True if command and dimension arguments are
699
          not permitted
700
        """
701
        if config_disp:
702
            if not 1 < len(plugin_param) < 3:
703
                raise ValueError(
704
                    "Use either 'plugin_pos.param_name' or"
705
                    " 'plugin_pos.param_no'"
706
                )
707
        else:
708
            # The modify command is being used
709
            if len(plugin_param) <= 1:
710
                raise ValueError(
711
                    "Please enter the plugin parameter to modify"
712
                    ". Either 'plugin_pos.param_name' or"
713
                    " 'plugin_pos.param_no'"
714
                )
715
            if not 1 < len(plugin_param) < 5:
716
                raise ValueError(
717
                    "Enter 'plugin_pos.param_no.dimension'. "
718
                    "Following the dimension, use start/stop/step"
719
                    " eg. '1.1.dim1.start' "
720
                )
721
722
    def separate_plugin_subelem(self, plugin_param, config_disp):
723
        """Separate the plugin number,parameter (subelement) number
724
        and additional command if present.
725
726
        :param plugin_param: A string supplied by the user input which
727
         contains the plugin element to edit/display. eg "1.1.dim.command"
728
        :param config_disp: bool, True if command and dimension arguments are
729
          not permitted
730
731
        :returns plugin: The number of the plugin element
732
                 subelem: The number of the parameter
733
                 dim: The dimension
734
                 command: The supplied command, eg expand or a dimension
735
                          string
736
        """
737
        plugin_param = plugin_param.split(".")
738
        plugin = plugin_param[0]
739
        start = self.find_position(plugin)
740
        self._check_command_valid(plugin_param, config_disp)
741
        subelem = plugin_param[1]
742
        if len(plugin_param) > 2:
743
            dim = self.dim_str_to_int(plugin_param[2])
744
            command = str(dim)
745
            if len(plugin_param) == 4:
746
                self._check_command_str(plugin_param[3])
747
                command += "." + plugin_param[3]
748
        else:
749
            dim, command = "", ""
750
        return plugin, subelem, dim, command
751
752
    def _check_command_str(self, command_str):
753
        """Check the additional 1.1.dim.command for slice or 'expand'
754
        keywords
755
        """
756
        command_list = ["expand", "start", "stop", "step", "chunk"]
757
        if command_str not in command_list:
758
            raise ValueError(
759
                "Following the dimension, use start/stop/step eg.  "
760
                "'1.1.dim1.start' "
761
            )
762
        return command_str
763
764
    def insert(self, plugin, pos, str_pos, replace=False):
765
        plugin_dict = self.create_plugin_dict(plugin)
766
        plugin_dict["pos"] = str_pos
767
        if replace:
768
            self.plugin_list.plugin_list[pos] = plugin_dict
769
        else:
770
            self.plugin_list.plugin_list.insert(pos, plugin_dict)
771
772
    def create_plugin_dict(self, plugin):
773
        tools = plugin.get_plugin_tools()
774
        plugin_dict = {}
775
        plugin_dict["name"] = plugin.name
776
        plugin_dict["id"] = plugin.__module__
777
        plugin_dict["data"] = plugin.parameters
778
        plugin_dict["active"] = True
779
        plugin_dict["tools"] = tools
780
        plugin_dict["param"] = tools.get_param_definitions()
781
        plugin_dict["doc"] = tools.docstring_info
782
        return plugin_dict
783
784
    def get(self, pos):
785
        return self.plugin_list.plugin_list[pos]
786
787
    def _separate_dimension_and_slice(self, command_str):
788
        """Check the start stop step command
789
790
        param command_str: a string '1.1' containing the dimension
791
            and slice number seperated by a full stop
792
793
        :returns dim, slice
794
        """
795
        if isinstance(command_str, str) and "." in command_str:
796
            # If the slice value is included
797
            slice_dict = {"start": 0, "stop": 1, "step": 2, "chunk": 3}
798
            _slice = slice_dict[command_str.split(".")[1]]
799
            dim = int(command_str.split(".")[0])
800
        else:
801
            dim = int(command_str)
802
            _slice = ""
803
        return dim, _slice
804
805
    def dim_str_to_int(self, dim_str):
806
        """Check the additional 1.1.dim keyword
807
808
        :param dim: A string 'dim1' specifying the dimension
809
810
        :return: dim - An integer dimension value
811
        """
812
        number = "".join(l for l in dim_str if l.isdigit())
813
        letters = "".join(l for l in dim_str if l.isalpha())
814
815
        if letters == "dim" and number.strip():
816
            dim = int(number)
817
        else:
818
            raise ValueError(
819
                "Following the second decimal place, please "
820
                "specify a dimension '1.1.dim1/1.preview.dim1'"
821
            )
822
        return dim
823
824
    def modify_preview(self, parameters, param_name, value, dim, _slice):
825
        """ Check the entered value is valid and edit preview"""
826
        slice_list = [0, 1, 2, 3]
827
        type_check_value = pu._dumps(value)
828
        current_preview_value = pu._dumps(parameters[param_name])
829
        pu.check_valid_dimension(dim, current_preview_value)
830
        if _slice in slice_list:
831
            # Modify this dimension and slice only
832
            if param_u._preview_dimension_singular(type_check_value):
833
                value = self._modify_preview_dimension_slice(
834
                    value, current_preview_value, dim, _slice
835
                )
836
            else:
837
                raise ValueError(
838
                    "Invalid preview dimension slice value. Please "
839
                    "enter a float, an integer or a string including "
840
                    "only mid and end keywords."
841
                )
842
        else:
843
            # If the entered value is a valid dimension value
844
            if param_u._preview_dimension(type_check_value):
845
                # Modify the whole dimension
846
                value = self._modify_preview_dimension(
847
                    value, current_preview_value, dim
848
                )
849
            else:
850
                raise ValueError(
851
                    "Invalid preview dimension value. Please "
852
                    "enter a float, an integer or slice notation."
853
                )
854
        return value
855
856
    def _modify_preview_dimension_slice(self, value, current_val, dim, _slice):
857
        """Modify the preview dimension slice value at the dimension (dim)
858
        provided
859
860
        :param value: The new value
861
        :param current_value: The current preview parameter value
862
        :param dim: The dimension to modify
863
        :param _slice: The slice value to modify
864
        :return: The modified value
865
        """
866
        if not current_val:
867
            current_val = self._set_empty_list(
868
                dim, self._set_empty_dimension_slice(value, _slice)
869
            )
870
        else:
871
            current_val[dim - 1] = self._modified_slice_notation(
872
                current_val[dim - 1], value, _slice
873
            )
874
        return current_val
875
876
    def _modified_slice_notation(self, old_value, value, _slice):
877
        """Change the current value at the provided slice
878
879
        :param old_value: Previous slice notation
880
        :param value: New value to set
881
        :param _slice: Slice to modify
882
        :return: Changed value (str/int)
883
        """
884
        old_value = self._set_incomplete_slices(str(old_value), _slice)
885
        if pu.is_slice_notation(old_value):
886
            start_stop_split = old_value.split(":")
887
            return self._get_modified_slice(start_stop_split, value, _slice)
888
        elif _slice == 0:
889
            # If there is no slice notation, only allow first slice to
890
            # be modified
891
            return value
892
        else:
893
            raise Exception(
894
                "There is no existing slice notation to modify."
895
            )
896
897
    def _get_modified_slice(self, start_stop_split, value, _slice):
898
        if all(v == "" for v in start_stop_split):
899
            return self._set_empty_dimension_slice(value, _slice)
900
        else:
901
            start_stop_split[_slice] = str(value)
902
            start_stop_split = ":".join(start_stop_split)
903
            return start_stop_split
904
905
    def _modify_preview_dimension(self, value, current_preview, dim):
906
        """Modify the preview list value at the dimension provided (dim)
907
908
        :param value: The new value
909
        :param current_value: The current preview parameter list value
910
        :param dim: The dimension to modify
911
        :return: The modified value
912
        """
913
        if not current_preview:
914
            return self._set_empty_list(dim, value)
915
        current_preview[dim - 1] = value
916
        # Save the altered preview value
917
        return current_preview
918
919
    def _set_empty_dimension_slice(self, value, _slice):
920
        """Set the empty dimension and insert colons to indicate
921
        the correct slice notation.
922
923
        :param value: New value to set
924
        :param _slice: start/stop/step/chunk value
925
        :return: String for the new value
926
        """
927
        return _slice * ":" + str(value)
928
929
    def _set_incomplete_slices(self, old_value, _slice):
930
        """Append default slice values.to the current string value, in order
931
         to allow later slice values to be set
932
933
        :param old_value: Current string value with slice notation
934
        :param _slice: slice notation index to be edited
935
        :return: String with default slice notation entries set
936
        """
937
        while old_value.count(":") < _slice:
938
            old_value += ":"
939
        return old_value
940
941
    def _set_empty_list(self, dim, value):
942
        """If the dimension is 1 then allow the whole empty list
943
        to be set.
944
945
        :param dim: Dimension to be altered
946
        :param value: New value
947
        :return: List containing new value
948
        """
949
        if dim == 1:
950
            return [value]
951
        else:
952
            raise ValueError("You have not set earlier dimensions")
953
954
    def level(self, level):
955
        """ Set the visibility level of parameters """
956
        if level:
957
            self.disp_level = level
958
            print(f"Level set to '{level}'")
959
        else:
960
            print(f"Level is set at '{self.disp_level}'")
961
962
    def remove(self, pos):
963
        if pos >= self.size:
964
            raise Exception(
965
                "Cannot remove plugin %s as it does not exist."
966
                % self.plugin_list.plugin_list[pos]["name"]
967
            )
968
        pos_str = self.plugin_list.plugin_list[pos]["pos"]
969
        self.plugin_list.plugin_list.pop(pos)
970
        pos_list = self.get_split_positions()
971
        self.inc_positions(pos, pos_list, pos_str, -1)
972
973
    @property
974
    def size(self):
975
        return len(self.plugin_list.plugin_list)
976