Test Failed
Pull Request — master (#785)
by
unknown
03:24
created

Content.convert_to_ascii()   A

Complexity

Conditions 2

Size

Total Lines 5
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 5
nop 2
dl 0
loc 5
rs 10
c 0
b 0
f 0
1
# Copyright 2014 Diamond Light Source Ltd.
2
#
3
# Licensed under the Apache License, Version 2.0 (the "License");
4
# you may not use this file except in compliance with the License.
5
# You may obtain a copy of the License at
6
#
7
#     http://www.apache.org/licenses/LICENSE-2.0
8
#
9
# Unless required by applicable law or agreed to in writing, software
10
# distributed under the License is distributed on an "AS IS" BASIS,
11
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
# See the License for the specific language governing permissions and
13
# limitations under the License.
14
15
"""
16
.. module:: 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, check="y"):
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
        if check.lower() == "y":
472
            while len(current_prev_list) > dim:
473
                current_prev_list.pop()
474
            while len(current_prev_list) < dim:
475
                current_prev_list.append(":")
476
            parameters["preview"] = current_prev_list
477
            return True
478
        return False
479
480
    def check_param_exists(self, parameters, pname):
481
        """Check the parameter is present in the current parameter list
482
483
        :param parameters: Dictionary of parameters
484
        :param pname: Parameter name
485
        :return:
486
        """
487
        if not parameters.get(pname):
488
            raise Exception(
489
                f"The {pname} parameter is not available" f" for this plugin."
490
            )
491
492
    def value(self, value):
493
        if not value.count(";"):
494
            try:
495
                value = eval(value)
496
            except (NameError, SyntaxError):
497
                try:
498
                    value = eval(f"'{value}'")
499
                    # if there is one quotation mark there will be an error
500
                except EOFError:
501
                    raise EOFError(
502
                        "There is an end of line error. Please check your"
503
                        ' input for the character "\'".'
504
                    )
505
                except SyntaxError:
506
                    raise SyntaxError(
507
                        "There is a syntax error. Please check your input."
508
                    )
509
                except:
510
                    raise Exception("Please check your input.")
511
        return value
512
513
    def convert_to_ascii(self, value):
514
        ascii_list = []
515
        for v in value:
516
            ascii_list.append(v.encode("ascii", "ignore"))
517
        return ascii_list
518
519
    def on_and_off(self, str_pos, index):
520
        print(("switching plugin %s %s" % (str_pos, index)))
521
        status = True if index == "ON" else False
522
        pos = self.find_position(str_pos)
523
        self.plugin_list.plugin_list[pos]["active"] = status
524
525
    def convert_pos(self, str_pos):
526
        """ Converts the display position (input) to the equivalent numerical
527
        position and updates the display position if required.
528
529
        :param str_pos: the plugin display position (input) string.
530
        :returns: the equivalent numerical position of str_pos and and updated\
531
            str_pos.
532
        :rtype: (pos, str_pos)
533
        """
534
        pos_list = self.get_split_positions()
535
        num = re.findall(r"\d+", str_pos)[0]
536
        letter = re.findall("[a-z]", str_pos)
537
        entry = [num, letter[0]] if letter else [num]
538
539
        # full value already exists in the list
540
        if entry in pos_list:
541
            index = pos_list.index(entry)
542
            return self.inc_positions(index, pos_list, entry, 1)
543
544
        # only the number exists in the list
545
        num_list = [pos_list[i][0] for i in range(len(pos_list))]
546
        if entry[0] in num_list:
547
            start = num_list.index(entry[0])
548
            if len(entry) == 2:
549
                if len(pos_list[start]) == 2:
550
                    idx = int([i for i in range(len(num_list)) if
551
                               (num_list[i] == entry[0])][-1]) + 1
552
                    entry = [entry[0], str(chr(ord(pos_list[idx - 1][1]) + 1))]
553
                    return idx, ''.join(entry)
554
                if entry[1] == 'a':
555
                    self.plugin_list.plugin_list[start]['pos'] = entry[0] + 'b'
556
                    return start, ''.join(entry)
557
                else:
558
                    self.plugin_list.plugin_list[start]['pos'] = entry[0] + 'a'
559
                    return start + 1, entry[0] + 'b'
560
            return self.inc_positions(start, pos_list, entry, 1)
561
562
        # number not in list
563
        entry[0] = str(int(num_list[-1]) + 1 if num_list else 1)
564
        if len(entry) == 2:
565
            entry[1] = "a"
566
        return len(self.plugin_list.plugin_list), "".join(entry)
567
568
    def increment_positions(self):
569
        """Update old process lists that start plugin numbering from 0 to
570
        start from 1."""
571
        for plugin in self.plugin_list.plugin_list:
572
            str_pos = plugin["pos"]
573
            num = str(int(re.findall(r"\d+", str_pos)[0]) + 1)
574
            letter = re.findall("[a-z]", str_pos)
575
            plugin["pos"] = "".join([num, letter[0]] if letter else [num])
576
577
    def get_positions(self):
578
        """ Get a list of all current plugin entry positions. """
579
        elems = self.plugin_list.plugin_list
580
        pos_list = []
581
        for e in elems:
582
            pos_list.append(e["pos"])
583
        return pos_list
584
585
    def get_param_arg_str(self, pos_str, subelem):
586
        """Get the name of the parameter so that the display lists the
587
        correct item when the parameter order has been updated
588
589
        :param pos_str: The plugin position
590
        :param subelem: The parameter
591
        :return: The plugin.parameter_name argument
592
        """
593
        pos = self.find_position(pos_str)
594
        current_parameter_list = self.plugin_list.plugin_list[pos]["param"]
595
        current_parameter_list_ordered = pu.set_order_by_visibility(
596
            current_parameter_list
597
        )
598
        param_name = pu.param_to_str(subelem, current_parameter_list_ordered)
599
        param_argument = pos_str + "." + param_name
600
        return param_argument
601
602
    def get_split_positions(self):
603
        """ Separate numbers and letters in positions. """
604
        positions = self.get_positions()
605
        split_pos = []
606
        for i in range(len(positions)):
607
            num = re.findall(r"\d+", positions[i])[0]
608
            letter = re.findall("[a-z]", positions[i])
609
            split_pos.append([num, letter[0]] if letter else [num])
610
        return split_pos
611
612
    def find_position(self, pos):
613
        """ Find the numerical index of a position (a string). """
614
        pos_list = self.get_positions()
615
        if not pos_list:
616
            print("There are no items to access in your list.")
617
            raise Exception("Please add an item to the process list.")
618
        else:
619
            if pos not in pos_list:
620
                raise ValueError("Incorrect plugin position.")
621
            return pos_list.index(pos)
622
623
    def inc_positions(self, start, pos_list, entry, inc):
624
        if len(entry) == 1:
625
            self.inc_numbers(start, pos_list, inc)
626
        else:
627
            idx = [
628
                i
629
                for i in range(start, len(pos_list))
630
                if pos_list[i][0] == entry[0]
631
            ]
632
            self.inc_letters(idx, pos_list, inc)
633
        return start, "".join(entry)
634
635
    def inc_numbers(self, start, pos_list, inc):
636
        for i in range(start, len(pos_list)):
637
            pos_list[i][0] = str(int(pos_list[i][0]) + inc)
638
            self.plugin_list.plugin_list[i]["pos"] = "".join(pos_list[i])
639
640
    def inc_letters(self, idx, pos_list, inc):
641
        for i in idx:
642
            pos_list[i][1] = str(chr(ord(pos_list[i][1]) + inc))
643
            self.plugin_list.plugin_list[i]["pos"] = "".join(pos_list[i])
644
645
    def split_plugin_string(self, start, stop, subelem_view=False):
646
        """Find the start and stop number for the plugin range selected.
647
648
        :param start: Plugin starting index (including a subelem value
649
          if permitted)
650
        :param stop: Plugin stopping index
651
        :param subelem_view: False if subelem value not permitted
652
        :return: range_dict containing start stop (and possible subelem)
653
        """
654
        range_dict = {}
655
        if start:
656
            if subelem_view and "." in start:
657
                start, stop, subelem = self._split_subelem(start)
658
                range_dict["subelem"] = subelem
659
            else:
660
                start, stop = self._get_start_stop(start, stop)
661
            range_dict["start"] = start
662
            range_dict["stop"] = stop
663
        return range_dict
664
665
    def _get_start_stop(self, start, stop):
666
        """Find the start and stop number for the plugin range selected """
667
        start = self.find_position(start)
668
        stop = self.find_position(stop) + 1 if stop else start + 1
669
        return start, stop
670
671
    def _split_subelem(self, start, config_disp=True):
672
        """Separate the start string containing the plugin number,
673
        parameter(subelement), dimension and command
674
675
        :param start: The plugin to start at
676
        :param config_disp: True if command and dimension arguments
677
          are not permitted
678
        :return: start plugin, range_dict containing a subelem
679
            if a parameter is specified
680
        """
681
        start, subelem, dim, command = self.separate_plugin_subelem(
682
            start, config_disp
683
        )
684
        start, stop = self._get_start_stop(start, "")
685
        return start, stop, subelem
686
687
    def _check_command_valid(self, plugin_param, config_disp):
688
        """Check the plugin_param string length
689
690
        :param plugin_param: The string containing plugin number, parameter,
691
         and command
692
        :param config_disp: bool, True if command and dimension arguments are
693
          not permitted
694
        """
695
        if config_disp:
696
            if not 1 < len(plugin_param) < 3:
697
                raise ValueError(
698
                    "Use either 'plugin_pos.param_name' or"
699
                    " 'plugin_pos.param_no'"
700
                )
701
        else:
702
            # The modify command is being used
703
            if len(plugin_param) <= 1:
704
                raise ValueError(
705
                    "Please enter the plugin parameter to modify"
706
                    ". Either 'plugin_pos.param_name' or"
707
                    " 'plugin_pos.param_no'"
708
                )
709
            if not 1 < len(plugin_param) < 5:
710
                raise ValueError(
711
                    "Enter 'plugin_pos.param_no.dimension'. "
712
                    "Following the dimension, use start/stop/step"
713
                    " eg. '1.1.dim1.start' "
714
                )
715
716
    def separate_plugin_subelem(self, plugin_param, config_disp):
717
        """Separate the plugin number,parameter (subelement) number
718
        and additional command if present.
719
720
        :param plugin_param: A string supplied by the user input which
721
         contains the plugin element to edit/display. eg "1.1.dim.command"
722
        :param config_disp: bool, True if command and dimension arguments are
723
          not permitted
724
725
        :returns plugin: The number of the plugin element
726
                 subelem: The number of the parameter
727
                 dim: The dimension
728
                 command: The supplied command, eg expand or a dimension
729
                          string
730
        """
731
        plugin_param = plugin_param.split(".")
732
        plugin = plugin_param[0]
733
        start = self.find_position(plugin)
734
        self._check_command_valid(plugin_param, config_disp)
735
        subelem = plugin_param[1]
736
        if len(plugin_param) > 2:
737
            dim = self.dim_str_to_int(plugin_param[2])
738
            command = str(dim)
739
            if len(plugin_param) == 4:
740
                self._check_command_str(plugin_param[3])
741
                command += "." + plugin_param[3]
742
        else:
743
            dim, command = "", ""
744
        return plugin, subelem, dim, command
745
746
    def _check_command_str(self, command_str):
747
        """Check the additional 1.1.dim.command for slice or 'expand'
748
        keywords
749
        """
750
        command_list = ["expand", "start", "stop", "step", "chunk"]
751
        if command_str not in command_list:
752
            raise ValueError(
753
                "Following the dimension, use start/stop/step eg.  "
754
                "'1.1.dim1.start' "
755
            )
756
        return command_str
757
758
    def insert(self, plugin, pos, str_pos, replace=False):
759
        plugin_dict = self.create_plugin_dict(plugin)
760
        plugin_dict["pos"] = str_pos
761
        if replace:
762
            self.plugin_list.plugin_list[pos] = plugin_dict
763
        else:
764
            self.plugin_list.plugin_list.insert(pos, plugin_dict)
765
766
    def create_plugin_dict(self, plugin):
767
        tools = plugin.get_plugin_tools()
768
        plugin_dict = {}
769
        plugin_dict["name"] = plugin.name
770
        plugin_dict["id"] = plugin.__module__
771
        plugin_dict["data"] = plugin.parameters
772
        plugin_dict["active"] = True
773
        plugin_dict["tools"] = tools
774
        plugin_dict["param"] = tools.get_param_definitions()
775
        plugin_dict["doc"] = tools.docstring_info
776
        return plugin_dict
777
778
    def get(self, pos):
779
        return self.plugin_list.plugin_list[pos]
780
781
    def _separate_dimension_and_slice(self, command_str):
782
        """Check the start stop step command
783
784
        param command_str: a string '1.1' containing the dimension
785
            and slice number seperated by a full stop
786
787
        :returns dim, slice
788
        """
789
        if isinstance(command_str, str) and "." in command_str:
790
            # If the slice value is included
791
            slice_dict = {"start": 0, "stop": 1, "step": 2, "chunk": 3}
792
            _slice = slice_dict[command_str.split(".")[1]]
793
            dim = int(command_str.split(".")[0])
794
        else:
795
            dim = int(command_str)
796
            _slice = ""
797
        return dim, _slice
798
799
    def dim_str_to_int(self, dim_str):
800
        """Check the additional 1.1.dim keyword
801
802
        :param dim: A string 'dim1' specifying the dimension
803
804
        :return: dim - An integer dimension value
805
        """
806
        number = "".join(l for l in dim_str if l.isdigit())
807
        letters = "".join(l for l in dim_str if l.isalpha())
808
809
        if letters == "dim" and number.strip():
810
            dim = int(number)
811
        else:
812
            raise ValueError(
813
                "Following the second decimal place, please "
814
                "specify a dimension '1.1.dim1/1.preview.dim1'"
815
            )
816
        return dim
817
818
    def modify_preview(self, parameters, param_name, value, dim, _slice):
819
        """ Check the entered value is valid and edit preview"""
820
        slice_list = [0, 1, 2, 3]
821
        type_check_value = pu._dumps(value)
822
        current_preview_value = pu._dumps(parameters[param_name])
823
        pu.check_valid_dimension(dim, current_preview_value)
824
        if _slice in slice_list:
825
            # Modify this dimension and slice only
826
            if param_u._preview_dimension_singular(type_check_value):
827
                value = self._modify_preview_dimension_slice(
828
                    value, current_preview_value, dim, _slice
829
                )
830
            else:
831
                raise ValueError(
832
                    "Invalid preview dimension slice value. Please "
833
                    "enter a float, an integer or a string including "
834
                    "only mid and end keywords."
835
                )
836
        else:
837
            # If the entered value is a valid dimension value
838
            if param_u._preview_dimension(type_check_value):
839
                # Modify the whole dimension
840
                value = self._modify_preview_dimension(
841
                    value, current_preview_value, dim
842
                )
843
            else:
844
                raise ValueError(
845
                    "Invalid preview dimension value. Please "
846
                    "enter a float, an integer or slice notation."
847
                )
848
        return value
849
850
    def _modify_preview_dimension_slice(self, value, current_val, dim, _slice):
851
        """Modify the preview dimension slice value at the dimension (dim)
852
        provided
853
854
        :param value: The new value
855
        :param current_value: The current preview parameter value
856
        :param dim: The dimension to modify
857
        :param _slice: The slice value to modify
858
        :return: The modified value
859
        """
860
        if not current_val:
861
            current_val = self._set_empty_list(
862
                dim, self._set_empty_dimension_slice(value, _slice)
863
            )
864
        else:
865
            current_val[dim - 1] = self._modified_slice_notation(
866
                current_val[dim - 1], value, _slice
867
            )
868
        return current_val
869
870
    def _modified_slice_notation(self, old_value, value, _slice):
871
        """Change the current value at the provided slice
872
873
        :param old_value: Previous slice notation
874
        :param value: New value to set
875
        :param _slice: Slice to modify
876
        :return: Changed value (str/int)
877
        """
878
        old_value = self._set_incomplete_slices(str(old_value), _slice)
879
        if pu.is_slice_notation(old_value):
880
            start_stop_split = old_value.split(":")
881
            return self._get_modified_slice(start_stop_split, value, _slice)
882
        elif _slice == 0:
883
            # If there is no slice notation, only allow first slice to
884
            # be modified
885
            return value
886
        else:
887
            raise Exception(
888
                "There is no existing slice notation to modify."
889
            )
890
891
    def _get_modified_slice(self, start_stop_split, value, _slice):
892
        if all(v == "" for v in start_stop_split):
893
            return self._set_empty_dimension_slice(value, _slice)
894
        else:
895
            start_stop_split[_slice] = str(value)
896
            start_stop_split = ":".join(start_stop_split)
897
            return start_stop_split
898
899
    def _modify_preview_dimension(self, value, current_preview, dim):
900
        """Modify the preview list value at the dimension provided (dim)
901
902
        :param value: The new value
903
        :param current_value: The current preview parameter list value
904
        :param dim: The dimension to modify
905
        :return: The modified value
906
        """
907
        if not current_preview:
908
            return self._set_empty_list(dim, value)
909
        current_preview[dim - 1] = value
910
        # Save the altered preview value
911
        return current_preview
912
913
    def _set_empty_dimension_slice(self, value, _slice):
914
        """Set the empty dimension and insert colons to indicate
915
        the correct slice notation.
916
917
        :param value: New value to set
918
        :param _slice: start/stop/step/chunk value
919
        :return: String for the new value
920
        """
921
        return _slice * ":" + str(value)
922
923
    def _set_incomplete_slices(self, old_value, _slice):
924
        """Append default slice values.to the current string value, in order
925
         to allow later slice values to be set
926
927
        :param old_value: Current string value with slice notation
928
        :param _slice: slice notation index to be edited
929
        :return: String with default slice notation entries set
930
        """
931
        while old_value.count(":") < _slice:
932
            old_value += ":"
933
        return old_value
934
935
    def _set_empty_list(self, dim, value):
936
        """If the dimension is 1 then allow the whole empty list
937
        to be set.
938
939
        :param dim: Dimension to be altered
940
        :param value: New value
941
        :return: List containing new value
942
        """
943
        if dim == 1:
944
            return [value]
945
        else:
946
            raise ValueError("You have not set earlier dimensions")
947
948
    def level(self, level):
949
        """ Set the visibility level of parameters """
950
        if level:
951
            self.disp_level = level
952
            print(f"Level set to '{level}'")
953
        else:
954
            print(f"Level is set at '{self.disp_level}'")
955
956
    def remove(self, pos):
957
        if pos >= self.size:
958
            raise Exception(
959
                "Cannot remove plugin %s as it does not exist."
960
                % self.plugin_list.plugin_list[pos]["name"]
961
            )
962
        pos_str = self.plugin_list.plugin_list[pos]["pos"]
963
        self.plugin_list.plugin_list.pop(pos)
964
        pos_list = self.get_split_positions()
965
        self.inc_positions(pos, pos_list, pos_str, -1)
966
967
    @property
968
    def size(self):
969
        return len(self.plugin_list.plugin_list)
970