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

main()   B

Complexity

Conditions 5

Size

Total Lines 43
Code Lines 32

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 32
nop 0
dl 0
loc 43
rs 8.6453
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:: savu_plugin_generator
17
   :platform: Unix
18
   :synopsis: A command line tool for creating Savu plugins
19
20
.. moduleauthor:: Jessica Verschoyle <[email protected]>
21
22
"""
23
24
import os
25
import string
26
import argparse
27
28
import warnings
29
with warnings.catch_warnings():
30
    warnings.simplefilter("ignore")
31
    import scripts.config_generator.savu_config
32
    from savu.plugins import utils as pu
33
    from scripts.config_generator import config_utils as utils
34
35
36
def __option_parser(doc=True):
37
    """ Option parser for command line arguments. Use -d for file deletion
38
    and -q for quick template.
39
    """
40
    parser = argparse.ArgumentParser(prog='savu_plugin_generator')
41
    parser.add_argument('plugin_name',
42
                        help='Plugin name to create file',
43
                        type=str)
44
    delete_str = 'Delete the plugin file and its tools ' \
45
                 'and documentation files.'
46
    parser.add_argument('-q', '--quick',
47
                        action='store_true',
48
                        default='False',
49
                        help='Create a short template version')
50
    parser.add_argument('-d', '--delete',
51
                        action='store_true',
52
                        default='False',
53
                        help=delete_str)
54
    return parser if doc is True else parser.parse_args()
55
56
57
def get_plugin_class(plugin_name):
58
    """Return the class for the given plugin"""
59
    with warnings.catch_warnings():
60
        warnings.simplefilter("ignore")
61
        failed_plugins = utils.populate_plugins()
62
63
    if (failed_plugins is not None) and (
64
        plugin_name in failed_plugins.keys()
65
    ):
66
        print(
67
            f"IMPORT ERROR: {plugin_name} is unavailable due to the "
68
            f"following error:\n\t {failed_plugins[plugin_name]}"
69
        )
70
        # At the moment a new file is then created in the general folder.
71
        # A yes or no confirmation should be provided before that is created
72
        plugin_class = None
73
    elif plugin_name not in pu.plugins.keys():
74
        print(f"The plugin named {plugin_name} is not in the list "
75
              f"of registered plugins.\n")
76
        plugin_class = None
77
    else:
78
        plugin_class = pu.plugins[plugin_name]()
79
    return plugin_class
80
81
82
def append_file(f, additional_file):
83
    """ Append the additional_file on to main file f """
84
    with open(additional_file) as input:
85
        f.write(input.read())
86
87
88
def create_plugin_template(file_path, module, quick_arg, savu_base_path):
89
    """
90
    Find the file path for the selected plugin. Generate template files
91
    for those which are not present already.
92
93
    :param file_path: File path to the new file
94
    :param module: The module name of the new plugin
95
    :param quick_arg: bool True if the user wants a quick template
96
    :param savu_base_path: The base directory
97
98
    """
99
    plugin_folder = savu_base_path + file_path
100
    title = module.split(".")
101
    capital_title = convert_title(title[-1]).replace(" ", "")
102
    file_str = plugin_folder + ".py"
103
    generator_dir = savu_base_path + "scripts/plugin_generator/"
104
    copyright_template = generator_dir + "template_elements/copyright.py"
105
    detailed_template = (
106
        generator_dir
107
        + "template_elements/process_and_setup_detailed_notes.py"
108
    )
109
    quick_template = generator_dir + "template_elements/process_and_setup.py"
110
    if os.path.isfile(file_str):
111
        print('\nA plugin file exists at', file_str)
112
    else:
113
        with open(file_str, 'w+') as new_py_file:
114
            append_file(new_py_file, copyright_template)
115
            new_py_file.write(get_module_info(title[-1]).strip())
116
            new_py_file.write('\n')
117
            new_py_file.write('from savu.plugins.utils '
118
                                            'import register_plugin\n')
119
            new_py_file.write('from savu.plugins.plugin import Plugin\n')
120
            new_py_file.write('# Import any additional libraries or base '
121
                              'plugins here.\n')
122
            new_py_file.write('\n')
123
            new_py_file.write('# This decorator is required for the '
124
                              'configurator to recognise the plugin\n')
125
            new_py_file.write('@register_plugin')
126
            new_py_file.write('\nclass ' + capital_title + '(Plugin):\n')
127
            new_py_file.write('# Each class must inherit from the '
128
                              'Plugin class and a driver\n')
129
            new_py_file.write('\n    def __init__(self):')
130
            new_py_file.write('\n        super(' + capital_title)
131
            new_py_file.write(', self).__init__("' + capital_title + '")\n\n')
132
133
            if quick_arg is True:
134
                # Concise template for previous users
135
                append_file(new_py_file, quick_template)
136
            else:
137
                # Detailed template for new users
138
                append_file(new_py_file, detailed_template)
139
140
        print('A plugin file has been created at:\n', file_str)
141
142
143
def create_tools_template(file_path, module, savu_base_path):
144
    """Locate a tools file if it exists, otherwise create a new file.
145
    Include a brief guide for the parameter yaml layout and citation layout
146
147
    """
148
    plugin_folder = savu_base_path + file_path
149
    title = module.split(".")
150
    capital_title = convert_title(title[-1]).replace(" ", "")
151
    file_str = plugin_folder + "_tools.py"
152
    generator_dir = savu_base_path + "scripts/plugin_generator/"
153
    param_definition_template = (
154
        generator_dir + "template_elements/parameter_definition.py"
155
    )
156
157
    if os.path.isfile(file_str):
158
        print("\nA tools file exists at " + file_str)
159
    else:
160
        with open(file_str, "w+") as new_tools_file:
161
            new_tools_file.write(get_tools_info(capital_title))
162
            append_file(new_tools_file, param_definition_template)
163
164
        print("A tools file has been created at:\n", file_str)
165
166
167
def get_tools_info(title):
168
    tools_info =\
169
'''from savu.plugins.plugin_tools import PluginTools
170
171
class ''' + title + '''Tools(PluginTools):
172
    """(Change this) A short description of the plugin"""
173
    
174
'''
175
    return tools_info
176
177
178
def create_documentation_template(
179
    file_path, module, savu_base_path, plugin_guide_path):
180
    # Locate documentation file
181
    plugin_path = file_path.replace("savu/", "")
182
    doc_folder = (
183
        savu_base_path + "doc/source/" + plugin_guide_path + plugin_path
184
    )
185
    title = module.split(".")
186
    file_str = doc_folder + "_doc.rst"
187
    doc_image_folder = (
188
        savu_base_path
189
        + "doc/source/files_and_images/"
190
        + plugin_guide_path
191
        + plugin_path
192
        + ".png"
193
    )
194
195
    if os.path.isfile(file_str):
196
        print("\nA documentation file exists at " + file_str)
197
    else:
198
        # Create the file directory for the documentation if it doesn't exist
199
        pu.create_dir(file_str)
200
        # Create the file for the documentation images
201
        pu.create_dir(doc_image_folder)
202
        doc_image_folder_inline = doc_image_folder.split("files_and_images/")[
203
            1
204
        ]
205
        with open(file_str, "w+") as new_rst_file:
206
            new_rst_file.write(":orphan:\n\n")
207
            new_rst_file.write(
208
                convert_title(title[-1])
209
                + " Documentation"
210
                + "\n##########################################"
211
                "#######################\n"
212
            )
213
            new_rst_file.write(
214
                "\n(Change this) Include your plugin "
215
                "documentation here. Use a restructured "
216
                "text format.\n"
217
            )
218
            new_rst_file.write("\n..")
219
            new_rst_file.write(
220
                "\n    This is a comment. Include an image "
221
                'or file by using the following text \n    "'
222
                ".. figure:: ../files_and_images/"
223
                + doc_image_folder_inline
224
                + '"\n'
225
            )
226
        print("A documentation file has been created at:\n", file_str)
227
228
229
def get_module_info(title):
230
    module_info =\
231
    '''
232
"""
233
.. module:: ''' + title + \
234
'''
235
   :platform: Unix
236
   :synopsis: (Change this) A template to create a simple plugin that takes 
237
    one dataset as input and returns a similar dataset as output.
238
239
.. moduleauthor:: (Change this) Developer Name <[email protected]>
240
"""
241
    '''
242
    return module_info
243
244
245
def convert_title(original_title):
246
    # Capwords is used so that the first letter following a number is
247
    # not capitalised. This would affect plugin names including '3d'
248
    return string.capwords(original_title.replace("_", " "))
249
250
251
def valid_name(plugin_name):
252
    """Return false if the plugin name is not valid.
253
    Plugin names must begin with a lowercase letter.
254
    """
255
    letters = [l for l in plugin_name]
256
    if isinstance(letters[0], str) and letters[0].islower():
257
        return True
258
    return False
259
260
261
def remove_plugin_files(file_path, savu_base_path, plugin_guide_path):
262
    """Delete plugin file, tools file and documentation file"""
263
    plugin_folder = savu_base_path + file_path
264
    file_str = plugin_folder + ".py"
265
    plugin_error_str = "No plugin file exists for this plugin."
266
    remove_file(file_str, plugin_error_str)
267
268
    # Delete tools file
269
    file_str = plugin_folder + "_tools.py"
270
    tools_error_str = "No tools file was located for this plugin."
271
    remove_file(file_str, tools_error_str)
272
273
    # Delete documentation file
274
    doc_file_path = file_path.replace("savu/", "")
275
    doc_folder = (
276
        savu_base_path + "doc/source/" + plugin_guide_path + doc_file_path
277
    )
278
    doc_file_str = doc_folder + "_doc.rst"
279
    doc_error_str = \
280
        "No documentation file was located for this plugin."
281
    remove_file(doc_file_str, doc_error_str)
282
283
284
def check_decision(check):
285
    if check.lower() == "y":
286
        return True
287
    else:
288
        return False
289
290
291
def remove_file(file_str, error_str):
292
    """Remove the file at the provided file path
293
294
    :param file_str: The file path to the file to remove
295
    :param error_str: The error message to display
296
    """
297
    if os.path.isfile(file_str):
298
        os.remove(file_str)
299
        print("The file at:\n", file_str, "was removed.")
300
    else:
301
        print(error_str)
302
303
304
def check_plugin_exists(plugin_name):
305
    """Check if a plugin class is already registered inside Savu
306
307
    :param plugin_name:
308
    :return: True if found, module name for plugin
309
    """
310
    if not valid_name(plugin_name):
311
        raise ValueError(
312
            "Please write the plugin name in the format plugin_name with "
313
            "a lowercase letter as the first character and underscores in "
314
            "the place of spaces. For example, to create a plugin named "
315
            "Median Filter, type median_filter."
316
        )
317
    print("\nChecking if this plugin already exists..")
318
    plugin_title = convert_title(plugin_name).replace(" ", "")
319
    plugin = get_plugin_class(plugin_title)
320
    if plugin is None:
321
        plugin_module_name = plugin_name
322
        module = "savu.plugins." + plugin_module_name
323
        return False, module
324
    print("This plugin does exist inside Savu.\n")
325
    return True, plugin.__module__
326
327
328
def main():
329
    args = __option_parser(doc=False)
330
331
    print("\n*******************************************************")
332
    print(" Please only use this command when you are working on")
333
    print(" your own Savu directory. New plugin files cannot be ")
334
    print(" saved to the Diamond Light Source Savu directory")
335
    print("*******************************************************")
336
    print(" Three files will be created: ")
337
    print(" * A plugin file \n * A plugin tools file \n"
338
          " * A file where you can describe how to use your "
339
          "plugin")
340
    print("*******************************************************")
341
342
    plugin_exists, module = check_plugin_exists(args.plugin_name)
343
344
    savu_base_path = \
345
        os.path.dirname(os.path.realpath(__file__)).split("scripts")[0]
346
    file_path = module.replace(".", "/")
347
    plugin_guide_path = "plugin_guides/"
348
    if args.delete is True:
349
        question_str = "Are you sure you want to delete all files for " \
350
                       "this plugin? [y/n]"
351
        if check_decision(check=input(question_str)) is True:
352
            remove_plugin_files(file_path, savu_base_path, plugin_guide_path)
353
        else:
354
            print("Plugin files were not removed.")
355
    else:
356
        question_str_1 = f"Do you want the files to be saved inside " \
357
                       f"{savu_base_path}?"
358
        question_str = f"Do you want to view the paths to the current " \
359
                       f"plugin files?" if plugin_exists \
360
                       else question_str_1
361
        if check_decision(check=input(question_str)) is True:
362
            create_plugin_template(
363
                file_path, module, args.quick, savu_base_path)
364
            create_tools_template(file_path, module, savu_base_path)
365
            create_documentation_template(
366
                file_path, module, savu_base_path, plugin_guide_path)
367
            print(f"\nIf you want to remove all files created, use "
368
                  f"\n      savu_plugin_generator -r {args.plugin_name}\n")
369
        else:
370
            print("Exiting plugin generator.")
371
372
373
if __name__ == '__main__':
374
    main()
375