Test Failed
Pull Request — master (#772)
by
unknown
03:45
created

scripts.config_generator.content   F

Complexity

Total Complexity 179

Size/Duplication

Total Lines 938
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 530
dl 0
loc 938
rs 2
c 0
b 0
f 0
wmc 179

59 Methods

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