Test Failed
Pull Request — master (#777)
by
unknown
03:28
created

scripts.config_generator.content   F

Complexity

Total Complexity 181

Size/Duplication

Total Lines 959
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 540
dl 0
loc 959
rs 2
c 0
b 0
f 0
wmc 181

60 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.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.check_preview_param() 0 12 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.check_required_args() 0 11 5
A Content._separate_dimension_and_slice() 0 17 3
A Content.dim_str_to_int() 0 18 3
D Content._apply_plugin_updates() 0 43 13
C Content.convert_pos() 0 42 9
A Content.size() 0 3 1
A Content.modify() 0 23 1
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.move() 0 9 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 47 4
A Content.get_positions() 0 7 2
A Content._check_command_str() 0 11 2
A Content.inc_letters() 0 4 2
B Content._update_parameters() 0 21 7
A Content._split_subelem() 0 15 1
A Content.set_preview_display() 0 20 3
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, ref=False, dim=False):
309
        """Modify the plugin at pos_str and the parameter at param_name
310
        The new value will be set if it is valid.
311
312
        :param pos_str: The plugin position
313
        :param param_name: The parameter position/name
314
        :param value: The new parameter value
315
        :param ref: boolean Refresh the plugin
316
        :param dim: The dimension to be modified
317
318
        returns: A boolean True if the value is a valid input for the
319
          selected parameter
320
        """
321
        pos = self.find_position(pos_str)
322
        plugin_entry = self.plugin_list.plugin_list[pos]
323
        tools = plugin_entry["tools"]
324
        parameters = plugin_entry["data"]
325
        params = plugin_entry["param"]
326
        param_name, value = self.setup_modify(params, param_name, value, ref)
327
        valid_modification = self.modify_main(
328
            param_name, value, tools, parameters, dim
329
        )
330
        return valid_modification
331
332
    def setup_modify(self, params, param_name, value, ref):
333
        """Get the parameter keys in the correct order and find
334
        the parameter name string
335
336
        :param params: The plugin parameters
337
        :param param_name: The parameter position/name
338
        :param value: The new parameter value
339
        :param ref: boolean Refresh the plugin
340
341
        return: param_name str to avoid discrepancy, value
342
        """
343
        if ref:
344
            # For a refresh, refresh all keys, including those with
345
            # dependencies (which have the display off)
346
            keys = params.keys()
347
        else:
348
            # Select the correct group and order of parameters according to that
349
            # on display to the user. This ensures correct parameter is modified.
350
            keys = pu.set_order_by_visibility(params)
351
            value = self.value(value)
352
        param_name = pu.param_to_str(param_name, keys)
353
        return param_name, value
354
355
    def modify_main(self, param_name, value, tools, parameters, dim):
356
        """Check the parameter is within the current parameter list.
357
        Check the new parameter value is valid, modify the parameter
358
        value, update defaults, check if dependent parameters should
359
        be made visible or hidden.
360
361
        :param param_name: The parameter position/name
362
        :param value: The new parameter value
363
        :param tools: The plugin tools
364
        :param parameters: The plugin parameters
365
        :param dim: The dimensions
366
367
        returns: A boolean True if the value is a valid input for the
368
          selected parameter
369
        """
370
        parameter_valid = False
371
        current_parameter_details = tools.param.get(param_name)
372
373
        # If dimensions are provided then alter preview param
374
        if self.preview_dimension_to_modify(dim, param_name):
375
            # Filter the dimension, dim1 or dim1.start
376
            dim, _slice = self._separate_dimension_and_slice(dim)
377
            value = self.modify_preview(
378
                parameters, param_name, value, dim, _slice
379
            )
380
381
        # If found, then the parameter is within the current parameter list
382
        # displayed to the user
383
        if current_parameter_details:
384
            value_check = pu._dumps(value)
385
            parameter_valid, error_str = param_u.is_valid(
386
                param_name, value_check, current_parameter_details
387
            )
388
            if parameter_valid:
389
                # If default choice is requested, then find the default value and set it
390
                value = tools.check_for_default(param_name, value)
391
                # Save the value
392
                parameters[param_name] = value
393
                tools.warn_dependents(param_name, value)
394
                # Update the list of parameters to hide those dependent on others
395
                tools.check_dependencies(parameters)
396
            else:
397
                print(error_str)
398
                print("ERROR: This value has not been saved.")
399
        else:
400
            print("Not in parameter keys.")
401
        return parameter_valid
402
403
    def check_required_args(self, value, required):
404
        """Check required argument 'value' is present
405
406
        :param value: Argument value
407
        :param required: bool, True if the argument is required
408
        """
409
        if required and (not value):
410
            raise Exception("Please enter a value")
411
412
        if (not required) and value:
413
            raise Exception(f"Unrecognised argument: {value}")
414
415
    def preview_dimension_to_modify(self, dim, param_name):
416
        """Check that the dimension string is only present when the parameter
417
        to modify is the preview parameter
418
419
        :param dim: Dimension string
420
        :param param_name: The parameter name (of the parameter to be modified)
421
        :return: True if dimension string is provided and the parameter to modify
422
        the preview parameter
423
        """
424
        if dim:
425
            if param_name == "preview":
426
                return True
427
            else:
428
                raise Exception(
429
                    "Please only use the dimension syntax when "
430
                    "modifying the preview parameter."
431
                )
432
        return False
433
434
    def modify_dimensions(self, pos_str, dim):
435
        """Modify the plugin preview value. Remove or add dimensions
436
        to the preview parameter until the provided dimension number
437
        is reached.
438
439
        :param pos_str: The plugin position
440
        :param dim: The new number of dimensions
441
        :return True if preview is modified
442
        """
443
        pos = self.find_position(pos_str)
444
        plugin_entry = self.plugin_list.plugin_list[pos]
445
        parameters = plugin_entry["data"]
446
        self.check_param_exists(parameters, "preview")
447
        current_prev_list = pu._dumps(parameters["preview"])
448
        if not isinstance(current_prev_list, list):
449
            # Temporarily cover dict instance for preview
450
            print("This command is only possible for preview "
451
                  "values of the type list")
452
            return False
453
        pu.check_valid_dimension(dim, [])
454
        check_str = (
455
            f"Are you sure you want to alter the number of "
456
            f"dimensions to {dim}? [y/N]"
457
        )
458
        check = input(check_str) if current_prev_list else "y"
459
460
        if check.lower() == "y":
461
            while len(current_prev_list) > dim:
462
                current_prev_list.pop()
463
            while len(current_prev_list) < dim:
464
                current_prev_list.append(":")
465
            parameters["preview"] = current_prev_list
466
            return True
467
        return False
468
469
    def check_param_exists(self, parameters, pname):
470
        """Check the parameter is present in the current parameter list
471
472
        :param parameters: Dictionary of parameters
473
        :param pname: Parameter name
474
        :return:
475
        """
476
        if not parameters.get(pname):
477
            raise Exception(
478
                f"The {pname} parameter is not available" f" for this plugin."
479
            )
480
481
    def value(self, value):
482
        if not value.count(";"):
483
            try:
484
                value = eval(value)
485
            except (NameError, SyntaxError):
486
                try:
487
                    value = eval(f"'{value}'")
488
                    # if there is one quotation mark there will be an error
489
                except EOFError:
490
                    raise EOFError(
491
                        "There is an end of line error. Please check your"
492
                        ' input for the character "\'".'
493
                    )
494
                except SyntaxError:
495
                    raise SyntaxError(
496
                        "There is a syntax error. Please check your input."
497
                    )
498
                except:
499
                    raise Exception("Please check your input.")
500
        return value
501
502
    def convert_to_ascii(self, value):
503
        ascii_list = []
504
        for v in value:
505
            ascii_list.append(v.encode("ascii", "ignore"))
506
        return ascii_list
507
508
    def on_and_off(self, str_pos, index):
509
        print(("switching plugin %s %s" % (str_pos, index)))
510
        status = True if index == "ON" else False
511
        pos = self.find_position(str_pos)
512
        self.plugin_list.plugin_list[pos]["active"] = status
513
514
    def convert_pos(self, str_pos):
515
        """ Converts the display position (input) to the equivalent numerical
516
        position and updates the display position if required.
517
518
        :param str_pos: the plugin display position (input) string.
519
        :returns: the equivalent numerical position of str_pos and and updated\
520
            str_pos.
521
        :rtype: (pos, str_pos)
522
        """
523
        pos_list = self.get_split_positions()
524
        num = re.findall(r"\d+", str_pos)[0]
525
        letter = re.findall("[a-z]", str_pos)
526
        entry = [num, letter[0]] if letter else [num]
527
528
        # full value already exists in the list
529
        if entry in pos_list:
530
            index = pos_list.index(entry)
531
            return self.inc_positions(index, pos_list, entry, 1)
532
533
        # only the number exists in the list
534
        num_list = [pos_list[i][0] for i in range(len(pos_list))]
535
        if entry[0] in num_list:
536
            start = num_list.index(entry[0])
537
            if len(entry) == 2:
538
                if len(pos_list[start]) == 2:
539
                    idx = int([i for i in range(len(num_list)) if
540
                               (num_list[i] == entry[0])][-1]) + 1
541
                    entry = [entry[0], str(chr(ord(pos_list[idx - 1][1]) + 1))]
542
                    return idx, ''.join(entry)
543
                if entry[1] == 'a':
544
                    self.plugin_list.plugin_list[start]['pos'] = entry[0] + 'b'
545
                    return start, ''.join(entry)
546
                else:
547
                    self.plugin_list.plugin_list[start]['pos'] = entry[0] + 'a'
548
                    return start + 1, entry[0] + 'b'
549
            return self.inc_positions(start, pos_list, entry, 1)
550
551
        # number not in list
552
        entry[0] = str(int(num_list[-1]) + 1 if num_list else 1)
553
        if len(entry) == 2:
554
            entry[1] = "a"
555
        return len(self.plugin_list.plugin_list), "".join(entry)
556
557
    def increment_positions(self):
558
        """Update old process lists that start plugin numbering from 0 to
559
        start from 1."""
560
        for plugin in self.plugin_list.plugin_list:
561
            str_pos = plugin["pos"]
562
            num = str(int(re.findall(r"\d+", str_pos)[0]) + 1)
563
            letter = re.findall("[a-z]", str_pos)
564
            plugin["pos"] = "".join([num, letter[0]] if letter else [num])
565
566
    def get_positions(self):
567
        """ Get a list of all current plugin entry positions. """
568
        elems = self.plugin_list.plugin_list
569
        pos_list = []
570
        for e in elems:
571
            pos_list.append(e["pos"])
572
        return pos_list
573
574
    def get_param_arg_str(self, pos_str, subelem):
575
        """Get the name of the parameter so that the display lists the
576
        correct item when the parameter order has been updated
577
578
        :param pos_str: The plugin position
579
        :param subelem: The parameter
580
        :return: The plugin.parameter_name argument
581
        """
582
        pos = self.find_position(pos_str)
583
        current_parameter_list = self.plugin_list.plugin_list[pos]["param"]
584
        current_parameter_list_ordered = pu.set_order_by_visibility(
585
            current_parameter_list
586
        )
587
        param_name = pu.param_to_str(subelem, current_parameter_list_ordered)
588
        param_argument = pos_str + "." + param_name
589
        return param_argument
590
591
    def get_split_positions(self):
592
        """ Separate numbers and letters in positions. """
593
        positions = self.get_positions()
594
        split_pos = []
595
        for i in range(len(positions)):
596
            num = re.findall(r"\d+", positions[i])[0]
597
            letter = re.findall("[a-z]", positions[i])
598
            split_pos.append([num, letter[0]] if letter else [num])
599
        return split_pos
600
601
    def find_position(self, pos):
602
        """ Find the numerical index of a position (a string). """
603
        pos_list = self.get_positions()
604
        if not pos_list:
605
            print("There are no items to access in your list.")
606
            raise Exception("Please add an item to the process list.")
607
        else:
608
            if pos not in pos_list:
609
                raise ValueError("Incorrect plugin position.")
610
            return pos_list.index(pos)
611
612
    def inc_positions(self, start, pos_list, entry, inc):
613
        if len(entry) == 1:
614
            self.inc_numbers(start, pos_list, inc)
615
        else:
616
            idx = [
617
                i
618
                for i in range(start, len(pos_list))
619
                if pos_list[i][0] == entry[0]
620
            ]
621
            self.inc_letters(idx, pos_list, inc)
622
        return start, "".join(entry)
623
624
    def inc_numbers(self, start, pos_list, inc):
625
        for i in range(start, len(pos_list)):
626
            pos_list[i][0] = str(int(pos_list[i][0]) + inc)
627
            self.plugin_list.plugin_list[i]["pos"] = "".join(pos_list[i])
628
629
    def inc_letters(self, idx, pos_list, inc):
630
        for i in idx:
631
            pos_list[i][1] = str(chr(ord(pos_list[i][1]) + inc))
632
            self.plugin_list.plugin_list[i]["pos"] = "".join(pos_list[i])
633
634
    def split_plugin_string(self, start, stop, subelem_view=False):
635
        """Find the start and stop number for the plugin range selected.
636
637
        :param start: Plugin starting index (including a subelem value
638
          if permitted)
639
        :param stop: Plugin stopping index
640
        :param subelem_view: False if subelem value not permitted
641
        :return: range_dict containing start stop (and possible subelem)
642
        """
643
        range_dict = {}
644
        if start:
645
            if subelem_view and "." in start:
646
                start, stop, subelem = self._split_subelem(start)
647
                range_dict["subelem"] = subelem
648
            else:
649
                start, stop = self._get_start_stop(start, stop)
650
            range_dict["start"] = start
651
            range_dict["stop"] = stop
652
        return range_dict
653
654
    def _get_start_stop(self, start, stop):
655
        """Find the start and stop number for the plugin range selected """
656
        start = self.find_position(start)
657
        stop = self.find_position(stop) + 1 if stop else start + 1
658
        return start, stop
659
660
    def _split_subelem(self, start, config_disp=True):
661
        """Separate the start string containing the plugin number,
662
        parameter(subelement), dimension and command
663
664
        :param start: The plugin to start at
665
        :param config_disp: True if command and dimension arguments
666
          are not permitted
667
        :return: start plugin, range_dict containing a subelem
668
            if a parameter is specified
669
        """
670
        start, subelem, dim, command = self.separate_plugin_subelem(
671
            start, config_disp
672
        )
673
        start, stop = self._get_start_stop(start, "")
674
        return start, stop, subelem
675
676
    def _check_command_valid(self, plugin_param, config_disp):
677
        """Check the plugin_param string length
678
679
        :param plugin_param: The string containing plugin number, parameter,
680
         and command
681
        :param config_disp: bool, True if command and dimension arguments are
682
          not permitted
683
        """
684
        if config_disp:
685
            if not 1 < len(plugin_param) < 3:
686
                raise ValueError(
687
                    "Use either 'plugin_pos.param_name' or"
688
                    " 'plugin_pos.param_no'"
689
                )
690
        else:
691
            # The modify command is being used
692
            if len(plugin_param) <= 1:
693
                raise ValueError(
694
                    "Please enter the plugin parameter to modify"
695
                    ". Either 'plugin_pos.param_name' or"
696
                    " 'plugin_pos.param_no'"
697
                )
698
            if not 1 < len(plugin_param) < 5:
699
                raise ValueError(
700
                    "Enter 'plugin_pos.param_no.dimension'. "
701
                    "Following the dimension, use start/stop/step"
702
                    " eg. '1.1.dim1.start' "
703
                )
704
705
    def separate_plugin_subelem(self, plugin_param, config_disp):
706
        """Separate the plugin number,parameter (subelement) number
707
        and additional command if present.
708
709
        :param plugin_param: A string supplied by the user input which
710
         contains the plugin element to edit/display. eg "1.1.dim.command"
711
        :param config_disp: bool, True if command and dimension arguments are
712
          not permitted
713
714
        :returns plugin: The number of the plugin element
715
                 subelem: The number of the parameter
716
                 dim: The dimension
717
                 command: The supplied command, eg expand or a dimension
718
                          string
719
        """
720
        plugin_param = plugin_param.split(".")
721
        plugin = plugin_param[0]
722
        start = self.find_position(plugin)
723
        self._check_command_valid(plugin_param, config_disp)
724
        subelem = plugin_param[1]
725
        if len(plugin_param) > 2:
726
            dim = self.dim_str_to_int(plugin_param[2])
727
            command = str(dim)
728
            if len(plugin_param) == 4:
729
                self._check_command_str(plugin_param[3])
730
                command += "." + plugin_param[3]
731
        else:
732
            dim, command = "", ""
733
        return plugin, subelem, dim, command
734
735
    def _check_command_str(self, command_str):
736
        """Check the additional 1.1.dim.command for slice or 'expand'
737
        keywords
738
        """
739
        command_list = ["expand", "start", "stop", "step", "chunk"]
740
        if command_str not in command_list:
741
            raise ValueError(
742
                "Following the dimension, use start/stop/step eg.  "
743
                "'1.1.dim1.start' "
744
            )
745
        return command_str
746
747
    def insert(self, plugin, pos, str_pos, replace=False):
748
        plugin_dict = self.create_plugin_dict(plugin)
749
        plugin_dict["pos"] = str_pos
750
        if replace:
751
            self.plugin_list.plugin_list[pos] = plugin_dict
752
        else:
753
            self.plugin_list.plugin_list.insert(pos, plugin_dict)
754
755
    def create_plugin_dict(self, plugin):
756
        tools = plugin.get_plugin_tools()
757
        plugin_dict = {}
758
        plugin_dict["name"] = plugin.name
759
        plugin_dict["id"] = plugin.__module__
760
        plugin_dict["data"] = plugin.parameters
761
        plugin_dict["active"] = True
762
        plugin_dict["tools"] = tools
763
        plugin_dict["param"] = tools.get_param_definitions()
764
        plugin_dict["doc"] = tools.docstring_info
765
        return plugin_dict
766
767
    def get(self, pos):
768
        return self.plugin_list.plugin_list[pos]
769
770
    def _separate_dimension_and_slice(self, command_str):
771
        """Check the start stop step command
772
773
        param command_str: a string '1.1' containing the dimension
774
            and slice number seperated by a full stop
775
776
        :returns dim, slice
777
        """
778
        if isinstance(command_str, str) and "." in command_str:
779
            # If the slice value is included
780
            slice_dict = {"start": 0, "stop": 1, "step": 2, "chunk": 3}
781
            _slice = slice_dict[command_str.split(".")[1]]
782
            dim = int(command_str.split(".")[0])
783
        else:
784
            dim = int(command_str)
785
            _slice = ""
786
        return dim, _slice
787
788
    def dim_str_to_int(self, dim_str):
789
        """Check the additional 1.1.dim keyword
790
791
        :param dim: A string 'dim1' specifying the dimension
792
793
        :return: dim - An integer dimension value
794
        """
795
        number = "".join(l for l in dim_str if l.isdigit())
796
        letters = "".join(l for l in dim_str if l.isalpha())
797
798
        if letters == "dim" and number.strip():
799
            dim = int(number)
800
        else:
801
            raise ValueError(
802
                "Following the second decimal place, please "
803
                "specify a dimension '1.1.dim1/1.preview.dim1'"
804
            )
805
        return dim
806
807
    def modify_preview(self, parameters, param_name, value, dim, _slice):
808
        """ Check the entered value is valid and edit preview"""
809
        slice_list = [0, 1, 2, 3]
810
        type_check_value = pu._dumps(value)
811
        current_preview_value = pu._dumps(parameters[param_name])
812
        pu.check_valid_dimension(dim, current_preview_value)
813
        if _slice in slice_list:
814
            # Modify this dimension and slice only
815
            if param_u._preview_dimension_singular(type_check_value):
816
                value = self._modify_preview_dimension_slice(
817
                    value, current_preview_value, dim, _slice
818
                )
819
            else:
820
                raise ValueError(
821
                    "Invalid preview dimension slice value. Please "
822
                    "enter a float, an integer or a string including "
823
                    "only mid and end keywords."
824
                )
825
        else:
826
            # If the entered value is a valid dimension value
827
            if param_u._preview_dimension(type_check_value):
828
                # Modify the whole dimension
829
                value = self._modify_preview_dimension(
830
                    value, current_preview_value, dim
831
                )
832
            else:
833
                raise ValueError(
834
                    "Invalid preview dimension value. Please "
835
                    "enter a float, an integer or slice notation."
836
                )
837
        return value
838
839
    def _modify_preview_dimension_slice(self, value, current_val, dim, _slice):
840
        """Modify the preview dimension slice value at the dimension (dim)
841
        provided
842
843
        :param value: The new value
844
        :param current_value: The current preview parameter value
845
        :param dim: The dimension to modify
846
        :param _slice: The slice value to modify
847
        :return: The modified value
848
        """
849
        if not current_val:
850
            current_val = self._set_empty_list(
851
                dim, self._set_empty_dimension_slice(value, _slice)
852
            )
853
        else:
854
            current_val[dim - 1] = self._modified_slice_notation(
855
                current_val[dim - 1], value, _slice
856
            )
857
        return current_val
858
859
    def _modified_slice_notation(self, old_value, value, _slice):
860
        """Change the current value at the provided slice
861
862
        :param old_value: Previous slice notation
863
        :param value: New value to set
864
        :param _slice: Slice to modify
865
        :return: Changed value (str/int)
866
        """
867
        old_value = self._set_incomplete_slices(str(old_value), _slice)
868
        if pu.is_slice_notation(old_value):
869
            start_stop_split = old_value.split(":")
870
            return self._get_modified_slice(start_stop_split, value, _slice)
871
        elif _slice == 0:
872
            # If there is no slice notation, only allow first slice to
873
            # be modified
874
            return value
875
        else:
876
            raise Exception(
877
                "There is no existing slice notation to modify."
878
            )
879
880
    def _get_modified_slice(self, start_stop_split, value, _slice):
881
        if all(v == "" for v in start_stop_split):
882
            return self._set_empty_dimension_slice(value, _slice)
883
        else:
884
            start_stop_split[_slice] = str(value)
885
            start_stop_split = ":".join(start_stop_split)
886
            return start_stop_split
887
888
    def _modify_preview_dimension(self, value, current_preview, dim):
889
        """Modify the preview list value at the dimension provided (dim)
890
891
        :param value: The new value
892
        :param current_value: The current preview parameter list value
893
        :param dim: The dimension to modify
894
        :return: The modified value
895
        """
896
        if not current_preview:
897
            return self._set_empty_list(dim, value)
898
        current_preview[dim - 1] = value
899
        # Save the altered preview value
900
        return current_preview
901
902
    def _set_empty_dimension_slice(self, value, _slice):
903
        """Set the empty dimension and insert colons to indicate
904
        the correct slice notation.
905
906
        :param value: New value to set
907
        :param _slice: start/stop/step/chunk value
908
        :return: String for the new value
909
        """
910
        return _slice * ":" + str(value)
911
912
    def _set_incomplete_slices(self, old_value, _slice):
913
        """Append default slice values.to the current string value, in order
914
         to allow later slice values to be set
915
916
        :param old_value: Current string value with slice notation
917
        :param _slice: slice notation index to be edited
918
        :return: String with default slice notation entries set
919
        """
920
        while old_value.count(":") < _slice:
921
            old_value += ":"
922
        return old_value
923
924
    def _set_empty_list(self, dim, value):
925
        """If the dimension is 1 then allow the whole empty list
926
        to be set.
927
928
        :param dim: Dimension to be altered
929
        :param value: New value
930
        :return: List containing new value
931
        """
932
        if dim == 1:
933
            return [value]
934
        else:
935
            raise ValueError("You have not set earlier dimensions")
936
937
    def level(self, level):
938
        """ Set the visibility level of parameters """
939
        if level:
940
            self.disp_level = level
941
            print(f"Level set to '{level}'")
942
        else:
943
            print(f"Level is set at '{self.disp_level}'")
944
945
    def remove(self, pos):
946
        if pos >= self.size:
947
            raise Exception(
948
                "Cannot remove plugin %s as it does not exist."
949
                % self.plugin_list.plugin_list[pos]["name"]
950
            )
951
        pos_str = self.plugin_list.plugin_list[pos]["pos"]
952
        self.plugin_list.plugin_list.pop(pos)
953
        pos_list = self.get_split_positions()
954
        self.inc_positions(pos, pos_list, pos_str, -1)
955
956
    @property
957
    def size(self):
958
        return len(self.plugin_list.plugin_list)
959