diff --git a/PluginSkeletonGenerator/PluginSkeletonGenerator.py b/PluginSkeletonGenerator/PluginSkeletonGenerator.py new file mode 100755 index 0000000..792bd41 --- /dev/null +++ b/PluginSkeletonGenerator/PluginSkeletonGenerator.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 +import os +from file_data import FileData, HeaderData, SourceData, CMakeData, JSONData, ConfData +from utils import Indenter, FileUtils +import menu +import global_variables + + +class PluginGenerator: + def __init__(self, blueprint_data) -> None: + self.blueprint_data = blueprint_data + self.directory = self.blueprint_data.plugin_name + os.makedirs(self.blueprint_data.plugin_name, exist_ok=False) + self.indenter = Indenter() + + def load_template(self, template_name): + return FileUtils.read_file(template_name) + + def replace_code(self, template): + code = FileUtils.replace_keywords(template, self.blueprint_data.keywords) + return code + + def generate_file(self, template_path, output_path): + template = self.load_template(template_path) + if template: + code = self.replace_code(template) + + indented_code = self.indenter.process_indent(code, output_path) + header_path = os.path.join(self.directory, output_path) + with open(header_path, "w") as f: + f.write(indented_code) + + def generate_source(self): + self.generate_file(global_variables.PLUGIN_SOURCE_PATH, f'{self.blueprint_data.plugin_name}.cpp') + self.generate_file(global_variables.MODULE_SOURCE_PATH, 'Module.cpp') + + def generate_headers(self): + self.generate_file(global_variables.PLUGIN_HEADER_PATH, f'{self.blueprint_data.plugin_name}.h') + self.generate_file(global_variables.MODULE_HEADER_PATH, 'Module.h') + + # Although this is a .cpp file, it's actually most like a .h + if self.blueprint_data.out_of_process: + self.blueprint_data.type = HeaderData.HeaderType.HEADER_IMPLEMENTATION + self.blueprint_data.populate_keywords() + self.generate_file(global_variables.PLUGIN_IMPLEMENTATION_PATH, f'{self.blueprint_data.plugin_name}Implementation.cpp') + + def generate_cmake(self): + self.generate_file(global_variables.CMAKE_PATH, 'CMakeLists.txt') + + def generate_json(self): + self.generate_file(global_variables.PLUGIN_JSON, f'{self.blueprint_data.plugin_name}Plugin.json') + + def generate_conf_in(self): + self.generate_file(global_variables.PLUGIN_CONF_PATH, f'{self.blueprint_data.plugin_name}.conf.in') + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~# +def main(): + print('[NOTE]: The output from this generator is a skeleton, therefore it uses example methods. Please correct the generated methods accordingly!') + menu.menu() +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~# +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/PluginSkeletonGenerator/file_data.py b/PluginSkeletonGenerator/file_data.py new file mode 100644 index 0000000..66caad3 --- /dev/null +++ b/PluginSkeletonGenerator/file_data.py @@ -0,0 +1,835 @@ +#!/usr/bin/env python3 + +from utils import FileUtils, Utils +from enum import Enum +import global_variables + +class FileData: + def __init__(self,plugin_name, comrpc_interfaces, jsonrpc_interfaces, out_of_process, jsonrpc, plugin_config, notification_interfaces) -> None: + self.plugin_name = plugin_name + self.comrpc_interfaces = comrpc_interfaces if comrpc_interfaces else [] + self.jsonrpc_interfaces = jsonrpc_interfaces if jsonrpc_interfaces else [] + self.out_of_process = out_of_process + self.jsonrpc = jsonrpc + self.plugin_config = plugin_config + self.notification_interfaces = notification_interfaces + + self.keywords = self.generate_keywords_map() + + def generate_keywords_map(self): + return { + "{{PLUGIN_NAME}}": self.plugin_name, + "{{PLUGIN_NAME_CAPS}}": self.plugin_name.upper() + } + +class HeaderData(FileData): + class HeaderType(Enum): + HEADER = 1 + HEADER_IMPLEMENTATION = 2 + + def __init__( + self, + plugin_name, + comrpc_interfaces, + jsonrpc_interfaces, + out_of_process, + jsonrpc, + plugin_config, + notification_interfaces + ) -> None: + super().__init__( + plugin_name, + comrpc_interfaces, + jsonrpc_interfaces, + out_of_process, + jsonrpc, + plugin_config, + notification_interfaces + ) + self.type = HeaderData.HeaderType.HEADER + self.keywords = self.keywords.copy() + + def populate_keywords(self): + self.keywords.update(self.generate_keyword_map()) + self.keywords.update(self.generate_nested_map()) + + def generate_comrpc_includes(self): + includes = [] + for comrpc in self.comrpc_interfaces: + if comrpc == 'IConfiguration' and self.type == HeaderData.HeaderType.HEADER: + break + includes.append(f'#include ') + if self.type == HeaderData.HeaderType.HEADER: + includes.append("#include ") + for interface in self.jsonrpc_interfaces: + if 'I' + interface[1:] in self.notification_interfaces: + includes.append(f'#include ') + return '\n'.join(includes) if includes else 'rm\*n' + + def generate_inherited_classes(self): + inheritance = ['PluginHost::IPlugin'] + if self.jsonrpc: + inheritance.append('public PluginHost::JSONRPC') + if not self.out_of_process: + inheritance.extend(f'public Exchange::{cls}' for cls in self.comrpc_interfaces) + return ', '.join(inheritance) + + def generate_oop_inherited_classes(self): + if not self.comrpc_interfaces: + return "" + inheritance = [f': public Exchange::{self.comrpc_interfaces[0]}'] + if self.out_of_process: + inheritance.extend(f', public Exchange::{cls}' for cls in self.comrpc_interfaces[1:]) + return "".join(inheritance) + + def notification_registers(self, interface): + text = [] + text.append('\n~INDENT_INCREASE~') + text.append('\nASSERT(notification != nullptr);') + text.append('\n_adminLock.Lock();') + text.append(f'''\nauto item = std::find(_{interface[1:].lower()}Notification.begin(), _{interface[1:].lower()}Notification.end(), notification); + ASSERT(item == _{interface[1:].lower()}Notification.end()); + if (item == _{interface[1:].lower()}Notification.end()) {{ + ~INDENT_INCREASE~ + notification->AddRef(); + _{interface[1:].lower()}Notification.push_back(notification); + ~INDENT_DECREASE~ + }} + + _adminLock.Unlock(); ''') + text.append('\n~INDENT_DECREASE~') + return ''.join(text) + + def notification_unregisters(self,interface): + text = [] + text.append(f'''\n~INDENT_INCREASE~ + ASSERT(notification != nullptr); + + _adminLock.Lock(); + auto item = std::find(_{interface[1:].lower()}Notification.begin(), _{interface[1:].lower()}Notification.end(), notification); + ASSERT(item != _{interface[1:].lower()}Notification.end()); + + if (item != _{interface[1:].lower()}Notification.end()) {{ + ~INDENT_INCREASE~ + (*item)->Release(); + _{interface[1:].lower()}Notification.erase(item); + ~INDENT_DECREASE~ + }} + _adminLock.Unlock(); + ~INDENT_DECREASE~''') + return ''.join(text) + + def generate_inherited_methods(self): + if (not self.out_of_process and self.type != HeaderData.HeaderType.HEADER) or \ + (self.out_of_process and self.type != HeaderData.HeaderType.HEADER_IMPLEMENTATION): + return "rm\*n" + + if not self.comrpc_interfaces: + return 'rm\*n' + + methods = [] + for inherited in self.comrpc_interfaces: + if (inherited == 'IConfiguration'): + continue + if self.type == HeaderData.HeaderType.HEADER: + methods.append(f"// {inherited} methods\n" f"uint32_t {inherited}Method1() override;") + if inherited in self.notification_interfaces: + methods.append(f'void Register(Exchange::{inherited}::INotification* notification) override;') + methods.append(f'void Unregister(const Exchange::{inherited}::INotification* notification) override;') + if f'J{inherited[1:]}' in self.jsonrpc_interfaces and self.out_of_process: + methods.append(f'void {inherited}NotificationExample();') + else: + methods.append(f'// {inherited} methods') + if inherited in self.notification_interfaces: + methods.append(f'''uint32_t {inherited}Method1() override {{\n + ~INDENT_INCREASE~ + // This function could be used to call a notify method + Notify{inherited}(); + return Core::ERROR_NONE; + ~INDENT_DECREASE~ + }}\n''') + else: + methods.append(f'uint32_t {inherited}Method1() override {{\n~INDENT_INCREASE~\nreturn Core::ERROR_NONE;\n~INDENT_DECREASE~\n}}\n') + if inherited in self.notification_interfaces and self.type == HeaderData.HeaderType.HEADER_IMPLEMENTATION: + methods.append(f'void Register(Exchange::{inherited}::INotification* notification) override {{') + methods.append(self.notification_registers(inherited)) + methods.append('}\n') + methods.append(f'void Unregister(const Exchange::{inherited}::INotification* notification) override {{') + methods.append(self.notification_unregisters(inherited)) + methods.append('}\n') + + if self.comrpc_interfaces: + if self.comrpc_interfaces[-1] == "IConfiguration": + template_name = global_variables.CONFIGURE_METHOD + template = FileUtils.read_file(template_name) + code = FileUtils.replace_keywords(template, self.keywords) + methods.append(code) + return ("\n").join(methods) + + def generate_plugin_methods(self): + method = [] + if self.type == HeaderData.HeaderType.HEADER: + method = [f'void {self.plugin_name}Method();'] + if self.out_of_process: + method.append(f'void Deactivated(RPC::IRemoteConnection* connection);') + return '\n'.join(method) + + def generate_interface_entry(self): + entries = [] + if self.type == HeaderData.HeaderType.HEADER: + entries.append(f"INTERFACE_ENTRY(PluginHost::IPlugin)") + if self.jsonrpc: + entries.append(f"INTERFACE_ENTRY(PluginHost::IDispatcher)") + + if (self.type == HeaderData.HeaderType.HEADER_IMPLEMENTATION) or \ + (not self.out_of_process) and (self.type == HeaderData.HeaderType.HEADER): + entries.extend(f'INTERFACE_ENTRY(Exchange::{comrpc})' for comrpc in self.comrpc_interfaces) + return '\n'.join(entries) if entries else 'rm\*n' + + def generate_interface_aggregate(self): + aggregates = [] + if self.out_of_process: + for comrpc in self.comrpc_interfaces: + if comrpc == "IConfiguration": + break + aggregates.append(f'INTERFACE_AGGREGATE(Exchange::{comrpc}, _impl{comrpc[1:]})') + return ('\n').join(aggregates) if aggregates else 'rm\*n' + + def generate_module_plugin_name(self): + return f'Plugin_{self.plugin_name}' + + def generate_base_constructor(self): + constructor = [f' PluginHost::IPlugin()'] + if self.jsonrpc: + constructor.append(f'\n, PluginHost::JSONRPC()') + return ''.join(constructor) + + def generate_interface_constructor(self): + constructor = [] + if not self.comrpc_interfaces: + return 'rm\*n' + + if self.type == HeaderData.HeaderType.HEADER_IMPLEMENTATION: + constructor = [f": Exchange::{self.comrpc_interfaces[0]}()"] + [f", Exchange::{comrpc}()" for comrpc in self.comrpc_interfaces[1:]] + constructor.append(', _test(0)') + if self.notification_interfaces: + constructor.append(', _adminLock()') + for interface in self.notification_interfaces: + constructor.append(f', _{interface[1:].lower()}Notification()') + + if (not self.out_of_process and self.type == HeaderData.HeaderType.HEADER): + if self.comrpc_interfaces: + constructor.append(f", Exchange::{self.comrpc_interfaces[0]}()") + for comrpc in self.comrpc_interfaces[1:]: + if comrpc == 'IConfiguration': + continue + constructor.append(f", Exchange::{comrpc}()") + return "\n".join(constructor) if constructor else "rm\*n" + + def generate_member_impl(self): + members = [] + if self.out_of_process: + members.append("PluginHost::IShell* _service;") + members.append("uint32_t _connectionId;") + if self.out_of_process: + for comrpc in self.comrpc_interfaces: + if comrpc == 'IConfiguration': + break + members.append(f"Exchange::{comrpc}* _impl{comrpc[1:]};") + members.append(f"Core::SinkType _notification;") + if self.notification_interfaces and not self.out_of_process: + members.append(f"mutable Core::CriticalSection _adminLock;") + for comrpc in self.notification_interfaces: + members.append(f'{comrpc[1:]}NotificationVector _{comrpc[1:].lower()}Notification;') + return "\n".join(members) if members else 'rm\*n' + + def generate_member_constructor(self): + members = [] + if self.out_of_process: + members.append(f", _service(nullptr)") + members.append(f", _connectionId(0)") + for comrpc in self.comrpc_interfaces: + if comrpc == "IConfiguration": + break + members.append(f", _impl{comrpc[1:]}(nullptr)") + members.append(f", _notification(*this)") + if self.notification_interfaces and not self.out_of_process: + members.append(f", _adminLock()") + for comrpc in self.notification_interfaces: + members.append(f", _{comrpc[1:].lower()}Notification()") + return "\n".join(members) if members else 'rm\*n' + + def generate_notification_class(self): + + classes = [] + classes.append(f"public RPC::IRemoteConnection::INotification") + for interface in self.notification_interfaces: + classes.append(f", public Exchange::{interface}::INotification") + return "".join(classes) + + def generate_notification_constructor(self): + members = [] + for notif in self.notification_interfaces: + members.append(f', Exchange::{notif}::INotification()') + return '\n'.join(members) if members else 'rm\*n' + + def generate_notification_entry(self): + entries = [] + for entry in self.notification_interfaces: + entries.append(f'INTERFACE_ENTRY(Exchange::{entry}::INotification)') + return '\n'.join(entries) if entries else 'rm\*n' + + def generate_notification_function(self): + methods = [] + for inherited in self.notification_interfaces: + if Utils.replace_comrpc_to_jsonrpc(inherited) in self.jsonrpc_interfaces: + methods.append(f'''void {inherited}Notification() override {{\n~INDENT_INCREASE~\nExchange::J{inherited[1:]}::Event::{inherited}Notification(_parent);\n~INDENT_DECREASE~\n}}\n''') + else: + methods.append(f'void {inherited}Notification() override {{\n\n}}') + return ("\n").join(methods) if methods else 'rm\*n' + + def generate_oop_members(self): + members = [] + if self.notification_interfaces: + members.append('mutable Core::CriticalSection _adminLock;') + for interface in self.notification_interfaces: + members.append(f'{interface[1:]}NotificationVector _{interface[1:].lower()}Notification;') + return '\n'.join(members) if members else 'rm\*n' + + def generate_notify_method(self): + methods = [] + if self.out_of_process and self.type == HeaderData.HeaderType.HEADER_IMPLEMENTATION or not self.out_of_process and HeaderData.HeaderType.HEADER: + for interface in self.notification_interfaces: + methods.append(f'void Notify{interface}() const {{') + methods.append('\n~INDENT_INCREASE~') + methods.append('\n_adminLock.Lock();') + methods.append(f'''\nfor (auto* notification : _{interface[1:].lower()}Notification) {{ + ~INDENT_INCREASE~ + notification->{interface}Notification(); + ~INDENT_DECREASE~ + }} + _adminLock.Unlock(); + ~INDENT_DECREASE~ + }}\n''') + + return ''.join(methods) if methods else 'rm\*n' + + def generate_using_container(self): + usings = [] + if (self.out_of_process and self.type == HeaderData.HeaderType.HEADER_IMPLEMENTATION) or \ + (not self.out_of_process and self.type == HeaderData.HeaderType.HEADER): + for comrpc in self.notification_interfaces: + usings.append(f'using {comrpc[1:]}NotificationVector = std::vector;') + return '\n'.join(usings) if usings else 'rm\*n' + + def generate_keyword_map(self): + return { + "{{COMRPC_INTERFACE_INCLUDES}}": self.generate_comrpc_includes(), + "{{INHERITED_CLASS}}": self.generate_inherited_classes(), + "{{INHERITED_METHOD}}": self.generate_inherited_methods(), + "{{PLUGIN_METHOD}}": self.generate_plugin_methods(), + "{{INTERFACE_ENTRY}}": self.generate_interface_entry(), + "{{INTERFACE_AGGREGATE}}": self.generate_interface_aggregate(), + "{{MODULE_PLUGIN_NAME}}": self.generate_module_plugin_name(), + "{{OOP_INHERITED_CLASS}}": self.generate_oop_inherited_classes(), + "{{BASE_CONSTRUCTOR}}": self.generate_base_constructor(), + "{{INTERFACE_CONSTRUCTOR}}": self.generate_interface_constructor(), + "{{MEMBER_IMPL}}": self.generate_member_impl(), + "{{MEMBER_CONSTRUCTOR}}": self.generate_member_constructor(), + "{{NOTIFICATION_CLASS}}": self.generate_notification_class(), + "{{NOTIFICATION_CONSTRUCTOR}}" : self.generate_notification_constructor(), + "{{NOTIFICATION_ENTRY}}" : self.generate_notification_entry(), + "{{NOTIFICATION_FUNCTION}}" : self.generate_notification_function(), + "{{OOP_MEMBERS}}" : self.generate_oop_members(), + "{{NOTIFY_METHOD}}" : self.generate_notify_method(), + "{{USING_CONTAINER}}" : self.generate_using_container() + } + + def generate_nested_map(self): + return { + "{{JSONRPC_EVENT}}": self.generate_jsonrpc_event(), + "{{CONFIG_CLASS}}": self.generate_config(), + } + + def generate_jsonrpc_event(self): + if (self.jsonrpc or self.notification_interfaces) and self.out_of_process: + template_name = global_variables.RPC_NOTIFICATION_CLASS_PATH + template = FileUtils.read_file(template_name) + code = FileUtils.replace_keywords(template, self.keywords) + return code + + def generate_config(self): + code = [] + if self.plugin_config: + if self.type == HeaderData.HeaderType.HEADER_IMPLEMENTATION: + code.append('~INDENT_DECREASE~\nprivate:\n~INDENT_INCREASE~\n') + print("Hello)") + if (not self.out_of_process) or (self.type == HeaderData.HeaderType.HEADER_IMPLEMENTATION): + template_name = global_variables.CONFIG_CLASS_PATH + template = FileUtils.read_file(template_name) + code.append(f'{FileUtils.replace_keywords(template, self.keywords)}') + return ''.join(code) if code else 'rm\*n' + +class SourceData(FileData): + def __init__( + self, + plugin_name, + comrpc_interfaces, + jsonrpc_interfaces, + out_of_process, + jsonrpc, + plugin_config, + notification_interfaces + ) -> None: + super().__init__( + plugin_name, + comrpc_interfaces, + jsonrpc_interfaces, + out_of_process, + jsonrpc, + plugin_config, + notification_interfaces + ) + self.keywords = self.keywords.copy() + # self.preconditions = preconditions if preconditions else [] + # self.terminations = terminations if terminations else [] + # self.controls = controls if controls else [] + + def populate_keywords(self): + self.keywords.update(self.generate_keyword_map()) + self.keywords.update(self.generate_nested_map()) + + def generate_include_statements(self): + return_string = f'#include "{self.plugin_name}.h"' + if self.plugin_config and self.out_of_process: + return_string += f'\n#include ' + return return_string + + def generate_initialize(self): + if self.out_of_process: + template_name = global_variables.INITIALIZE_OOP_PATH + else: + template_name = global_variables.INITIALIZE_IP_PATH + + template = FileUtils.read_file(template_name) + code = FileUtils.replace_keywords(template, self.keywords) + return code + + def generate_deinitialize(self): + if self.out_of_process: + template_name = global_variables.DENINITIALIZE_OOP_PATH + else: + template_name = global_variables.DENINITIALIZE_IP_PATH + + template = FileUtils.read_file(template_name) + code = FileUtils.replace_keywords(template, self.keywords) + return code + + def generate_variable_used(self): + if self.out_of_process: + return "" + return "VARIABLE_IS_NOT_USED " + + def generate_jsonrpc_register(self): + + if not self.jsonrpc_interfaces: + return 'rm\*n' + + registers = [] + ''' + _implementation->Register(&_volumeNotification); + Exchange::JVolumeControl::Register(*this, _implementation); + ''' + for jsonrpc in self.jsonrpc_interfaces: + registers.append(f"Exchange::{jsonrpc}::Register(*this, this);") + + registers = ("\n").join(registers) + return registers if registers else "rm\*n" + + def generate_jsonrpc_unregister(self): + registers = [] + if not self.out_of_process: + for jsonrpc in self.jsonrpc_interfaces: + registers.append(f"Exchange::{jsonrpc}::Unregister(*this);") + else: + for jsonrpc in self.jsonrpc_interfaces: + registers.append(f"Exchange::{jsonrpc}::Unregister(*this);") + + registers = ("\n").join(registers) + return registers if registers else "rm\*n" + + def generate_jsonrpc_includes(self): + includes = [] + for jsonrpc in self.jsonrpc_interfaces: + if 'I' + jsonrpc[1:] in self.notification_interfaces: + continue + else: + includes.append(f"#include ") + return "\n".join(includes) if includes else 'rm\*n' + + """ + def generate_preconditions(self): + return ', '.join([f'subsystem::{condition}'for condition in self.preconditions]) + + def generate_terminations(self): + return ', '.join([f'subsystem::{termination}'for termination in self.terminations]) + + def generate_controls(self): + return ', '.join([f'subsystem::{control}'for control in self.controls]) + """ + + def generate_plugin_method_impl(self): + if self.out_of_process and self.notification_interfaces: + method = [f'''\nvoid {self.plugin_name}::Deactivated(RPC::IRemoteConnection* connection) {{ + ~INDENT_INCREASE~ + if (connection->Id() == _connectionId) {{ + ~INDENT_INCREASE~ + ASSERT(_service != nullptr); + Core::IWorkerPool::Instance().Submit(PluginHost::IShell::Job::Create(_service, PluginHost::IShell::DEACTIVATED, PluginHost::IShell::FAILURE)); + ~INDENT_DECREASE~ + }} + ~INDENT_DECREASE~ + }}\n'''] + return "".join(method) + return "rm\*n" + + def generate_inherited_method_impl(self): + methods = [] + if not self.out_of_process: + for inherited in self.comrpc_interfaces: + methods.append(f'uint32_t {self.plugin_name}::{inherited}Method1() {{') + methods.append(f'~INDENT_INCREASE~') + if Utils.replace_comrpc_to_jsonrpc(inherited) in self.jsonrpc_interfaces: + methods.append(f'''// Note this an example of a notification method that can call the JSONRPC interface equivalent. + Exchange::{Utils.replace_comrpc_to_jsonrpc(inherited)}::Event::{inherited}Notification(*this);''') + methods.append(f'return Core::ERROR_NONE;') + methods.append(f'~INDENT_DECREASE~') + methods.append(f'}}') + if inherited in self.notification_interfaces: + methods.append(f'void {self.plugin_name}::Register(Exchange::{inherited}::INotification* notification) {{') + methods.append(HeaderData.notification_registers(None, inherited)) + methods.append('}\n') + methods.append(f'void {self.plugin_name}::Unregister(const Exchange::{inherited}::INotification* notification) {{') + methods.append(HeaderData.notification_unregisters(None, inherited)) + methods.append('}\n') + return ("\n").join(methods) + return 'rm\*n' + + def generate_nested_query(self): + nested_query = [] + closing_brackets = len(self.comrpc_interfaces) - 1 + if self.plugin_config: + closing_brackets -= 1 + iteration = 0 + + for comrpc in self.comrpc_interfaces[1:]: + if comrpc == 'IConfiguration': + break + nested_query.append("\nrm\*n \n~INDENT_INCREASE~\n") + if self.comrpc_interfaces[iteration] in self.notification_interfaces: + nested_query.append(f"_impl{self.comrpc_interfaces[iteration][1:]}->Register(¬ification);\n") + if any(Utils.replace_comrpc_to_jsonrpc(self.comrpc_interfaces[iteration]) in notif for notif in self.jsonrpc_interfaces): + nested_query.append(f'Exchange::{Utils.replace_comrpc_to_jsonrpc(self.comrpc_interfaces[iteration])}::Register(*this, _impl{self.comrpc_interfaces[iteration][1:]});\n') + nested_query.append(f"_impl{comrpc[1:]} = _impl{comrpc[0][1:]}->QueryInterface();") + nested_query.append(f'''\nif (_impl{comrpc[1:]} == nullptr) {{ + ~INDENT_INCREASE~ + message = _T("Couldn't create instance of _impl{comrpc}") + ~INDENT_DECREASE~ + }} else {{\n''') + iteration += 1 + + nested_query.append(self.generate_nested_initialize(iteration)) + + for i in range(closing_brackets): + nested_query.append("\n~INDENT_DECREASE~") + nested_query.append("\n}") + + return ''.join(nested_query) + + def generate_nested_initialize(self, iteration): + + text = [] + rootCOMRPC = self.comrpc_interfaces[0][1:] if self.comrpc_interfaces else 'IPlugin' + + if self.comrpc_interfaces: + text.append("\n~INDENT_INCREASE~\n") + if self.notification_interfaces: + text.append(f'_impl{self.comrpc_interfaces[iteration][1:]}->Register(&_notification);\n') + if self.jsonrpc_interfaces: + if self.jsonrpc_interfaces[-1] == Utils.replace_comrpc_to_jsonrpc(self.comrpc_interfaces[iteration]): + text.append(f'Exchange::{self.jsonrpc_interfaces[-1]}::Register(*this, _impl{self.comrpc_interfaces[iteration][1:]});\n') + if self.plugin_config: + #text.append("\n~INDENT_INCREASE~\n") + text.append(f'''\nExchange::IConfiguration* configuration = _impl{rootCOMRPC}->QueryInterface(); + ASSERT(configuration != nullptr); + if (configuration != nullptr) {{ + ~INDENT_INCREASE~ + if (configuration->Configure(service) != Core::ERROR_NONE) {{ + ~INDENT_INCREASE~ + message = _T("{self.plugin_name} could not be configured."); + ~INDENT_DECREASE~ + }} + ~INDENT_DECREASE~ + configuration->Release(); + }}''') + + return ''.join(text) if text else 'rm\*n' + + def generate_configure_implementation(self): + return 'Config config;\nconfig.FromString(service->ConfigLine());\nTRACE(Trace::Information, (_T("This is just an example: [%s])"), config.Example));' if self.plugin_config else 'rm\*n' + + def generate_nested_deinitialize(self): + text = [] + if not self.comrpc_interfaces: + return '' + text.append(f'if (_impl{self.comrpc_interfaces[0][1:]} != nullptr) {{') + text.append(f'\n~INDENT_INCREASE~') + if self.notification_interfaces: + if self.notification_interfaces[0] == self.comrpc_interfaces[0]: + text.append(f'\n_impl{self.comrpc_interfaces[0][1:]}->Unregister(&_notification);') + if Utils.replace_comrpc_to_jsonrpc(self.comrpc_interfaces[0]) in self.jsonrpc_interfaces: + text.append(f'\nExchange::{Utils.replace_comrpc_to_jsonrpc(self.comrpc_interfaces[0])}::Unregister(*this);') + + for comrpc in self.comrpc_interfaces[1:]: + if comrpc == 'IConfiguration': + break + text.append(f'''\nif (_impl{comrpc[1:]} != nullptr) {{ + \n~INDENT_INCREASE~\n''') + if Utils.replace_comrpc_to_jsonrpc(comrpc) in self.jsonrpc_interfaces: + text.append(f'\nExchange::{Utils.replace_comrpc_to_jsonrpc(comrpc)}::Unregister(*this);') + if comrpc in self.notification_interfaces: + text.append(f'\n_impl{comrpc[1:]}->Unregister(&_notification);') + text.append(f"\n_impl{comrpc[1:]}->Release();") + text.append(f'\n_impl{comrpc[1:]} = nullptr;') + text.append('\n~INDENT_DECREASE~') + text.append('\n}') + + text.append(f'''\nRPC::IRemoteConnection* connection(service->RemoteConnection(_connectionId)); + \nVARIABLE_IS_NOT_USED uint32_t result = _impl{self.comrpc_interfaces[0][1:]}->Release(); + \n_impl{self.comrpc_interfaces[0][1:]} = nullptr;''') + return ''.join(text) + + def generate_assert_implementation(self): + asserts = [] + for comrpc in self.comrpc_interfaces: + if comrpc == 'IConfiguration': + continue + asserts.append(f'ASSERT(_impl{comrpc[1:]} == nullptr);') + return '\n'.join(asserts) + + def generate_register_notification(self): + return "_service->Register(&_notification);" + + def generate_unregister_notification(self): + return "_service->Unregister(&_notification);" + + def generate_keyword_map(self): + return { + "{{INCLUDE}}": self.generate_include_statements(), + """ + '{{PRECONDITIONS}}' : self.generate_preconditions(), + '{{TERMINATIONS}}' : self.generate_terminations(), + '{{CONTROLS}}' : self.generate_controls(), + """ + "{{VARIABLE_NOT_USED}}": self.generate_variable_used(), + "{{REGISTER}}": self.generate_jsonrpc_register(), + "{{JSONRPC_UNREGISTER}}": self.generate_jsonrpc_unregister(), + "{{COMRPC}}": f'{self.comrpc_interfaces[0] if self.comrpc_interfaces else "Exchange::IPlugin"}', + "{{COMRPC[1:]}}" : f'{self.comrpc_interfaces[0][1:] if self.comrpc_interfaces else "Exchange::IPlugin"}', + "{{JSONRPC_INTERFACE_INCLUDES}}": self.generate_jsonrpc_includes(), + "{{PLUGIN_METHOD_IMPL}}": self.generate_plugin_method_impl(), + "{{INHERITED_METHOD_IMPL}}": self.generate_inherited_method_impl(), + "{{VARIABLE_NOT_USED}}" : self.generate_variable_used(), + "{{NESTED_QUERY}}" : self.generate_nested_query(), + "{{CONFIGURE_IP}}" : self.generate_configure_implementation(), + "{{DEINITIALIZE}}" : self.generate_nested_deinitialize(), + "{{ASSERT_IMPL}}" : self.generate_assert_implementation(), + "{{REGISTER_NOTIFICATION}}" : self.generate_register_notification(), + "{{UNREGISTER_NOTIFICATION}}" : self.generate_unregister_notification() + } + + def generate_nested_map(self): + return { + "{{INITIALIZE_IMPLEMENTATION}}": self.generate_initialize(), + "{{DEINITIALIZE_IMPLEMENTATION}}": self.generate_deinitialize(), + } + + +class CMakeData(FileData): + + def __init__( + self, + plugin_name, + comrpc_interfaces, + jsonrpc_interfaces, + out_of_process, + jsonrpc, + plugin_config, + notification_interfaces + ) -> None: + super().__init__( + plugin_name, + comrpc_interfaces, + jsonrpc_interfaces, + out_of_process, + jsonrpc, + plugin_config, + notification_interfaces + ) + self.keywords = self.keywords.copy() + + def populate_keywords(self): + self.keywords.update(self.generate_keyword_map()) + # self.keywords.update(self.generate_nested_map()) + + def generate_set_mode(self): + s = '' + if self.out_of_process: + s = (f'set(PLUGIN_{self.plugin_name.upper()}_MODE "Local" CACHE STRING "Controls if the plugin should run in its own process, in process or remote.")') + return s if s else 'rm\*n' + + def generate_set_config(self): + s = '' + if self.plugin_config: + s = (f'set(PLUGIN_{self.plugin_name.upper()}_EXAMPLE "Example Text" CACHE STRING "Example String")') + return s if s else 'rm\*n' + + def generate_keyword_map(self): + return { + "{{SOURCE_FILES}}": self.find_source_files(), + '{{SET_MODE}}' : self.generate_set_mode(), + "{{SET_CONFIG}}" : self.generate_set_config() + } + + def find_source_files(self): + if self.out_of_process: + return f"{self.plugin_name}.cpp \n{self.plugin_name}Implementation.cpp" + else: + return f"{self.plugin_name}.cpp" + + +class JSONData(FileData): + + def __init__( + self, + plugin_name, + comrpc_interfaces, + jsonrpc_interfaces, + out_of_process, + jsonrpc, + plugin_config, + notification_interfaces + ) -> None: + super().__init__( + plugin_name, + comrpc_interfaces, + jsonrpc_interfaces, + out_of_process, + jsonrpc, + plugin_config, + notification_interfaces + ) + self.keywords = self.keywords.copy() + + def populate_keywords(self): + self.keywords.update(self.generate_keyword_map()) + self.keywords.update(self.generate_nested_map()) + + def generate_cppref(self): + cppref = [] + for comrpc in self.comrpc_interfaces: + if comrpc == 'IConfiguration': + break + cppref.append(f'"$cppref": "{{cppinterfacedir}}/{comrpc}.h"') + return ",\n".join(cppref) if cppref else 'rm\*n' + + def generate_json_info(self): + template_name = global_variables.JSON_INFO + template = FileUtils.read_file(template_name) + code = FileUtils.replace_keywords(template, self.keywords) + return code + + def generate_json_configuration(self): + code = [] + if self.plugin_config: + template_name = global_variables.JSON_CONFIGURATION + template = FileUtils.read_file(template_name) + code = FileUtils.replace_keywords(template, self.keywords) + return code if code else "rm\*n" + + def generate_json_interface(self): + template_name = global_variables.JSON_INTERFACE + template = FileUtils.read_file(template_name) + code = FileUtils.replace_keywords(template, self.keywords) + return code + + def generate_keyword_map(self): + return { + "{{cppref}}": self.generate_cppref(), + } + + def generate_nested_map(self): + return { + "{{JSON_INFO}}": self.generate_json_info(), + "{{JSON_CONFIGURATION}}": self.generate_json_configuration(), + "{{JSON_INTERFACE}}": self.generate_json_interface(), + } + +class ConfData(FileData): + + def __init__( + self, + plugin_name, + comrpc_interfaces, + jsonrpc_interfaces, + out_of_process, + jsonrpc, + plugin_config, + notification_interfaces + ) -> None: + super().__init__( + plugin_name, + comrpc_interfaces, + jsonrpc_interfaces, + out_of_process, + jsonrpc, + plugin_config, + notification_interfaces + ) + self.keywords = self.keywords.copy() + + def populate_keywords(self): + self.keywords.update(self.generate_keyword_map()) + # self.keywords.update(self.generate_nested_map()) + + def generate_config(self): + configuration = [] + configuration.append("configuration = JSON()") + if self.plugin_config: + configuration.append(f'configuration.add("example", "@PLUGIN_{self.plugin_name.upper()}_EXAMPLE@")') + return '\n'.join(configuration) + + def generate_root(self): + root = [] + if self.out_of_process: + root.append(f'root = JSON() \n root.add("mode", "@PLUGIN_{self.plugin_name.upper()}_MODE@")') + root.append(f'configuration.add("root", root)') + return '\n'.join(root) if root else 'rm\*n' + + def generate_keyword_map(self): + return { + "{{PLUGIN_STARTMODE}}": f'"@PLUGIN_{self.plugin_name.upper()}_STARTMODE@"', + "{{OOP_ROOT}}" : self.generate_root(), + '{{CONFIG}}' : self.generate_config() + } + + +# Not in use currently, may use later on to track rather than hardcode +class PluginData: + # todo: store data such as class names, filenames etc + def __init__(self) -> None: + self.classes = [] + self.file_names = [] + + def add_class(self, class_name): + pass \ No newline at end of file diff --git a/PluginSkeletonGenerator/global_variables.py b/PluginSkeletonGenerator/global_variables.py new file mode 100644 index 0000000..3fa8dd8 --- /dev/null +++ b/PluginSkeletonGenerator/global_variables.py @@ -0,0 +1,44 @@ +import os + +BASE_DIR = os.path.dirname(os.path.abspath(__file__)) + +# Nested Directories: +TEMPLATE_DIR = os.path.join(BASE_DIR, 'templates') + +IPLUGIN_DIR = os.path.join(TEMPLATE_DIR, 'iplugin_methods') +MODULE_DIR = os.path.join(TEMPLATE_DIR, 'module') +NESTED_CLASS_DIR = os.path.join(TEMPLATE_DIR, 'nested_class') +NESTED_METHOD_DIR = os.path.join(TEMPLATE_DIR, 'nested_methods') +JSON_DIR = os.path.join(TEMPLATE_DIR, 'json') + +# Nested File Paths: +# /templates +CMAKE_PATH = os.path.join(TEMPLATE_DIR, '.cmake.txt') +PLUGIN_CONF_PATH = os.path.join(TEMPLATE_DIR, '.plugin_conf_in.txt') +PLUGIN_HEADER_PATH = os.path.join(TEMPLATE_DIR, '.plugin_header.txt') +PLUGIN_IMPLEMENTATION_PATH = os.path.join(TEMPLATE_DIR, '.plugin_implementation.txt') +PLUGIN_SOURCE_PATH = os.path.join(TEMPLATE_DIR, '.plugin_source.txt') + +# /templates/iplugin_methods +DENINITIALIZE_OOP_PATH = os.path.join(IPLUGIN_DIR, '.deinitialize_oop.txt') +DENINITIALIZE_IP_PATH = os.path.join(IPLUGIN_DIR, '.deinitialize_ip.txt') +INITIALIZE_OOP_PATH = os.path.join(IPLUGIN_DIR, '.initialize_oop.txt') +INITIALIZE_IP_PATH = os.path.join(IPLUGIN_DIR, '.initialize_ip.txt') +NESTED_OOP_PATH = os.path.join(IPLUGIN_DIR, '.nested_initialize_oop.txt') + +# /templates/module +MODULE_HEADER_PATH = os.path.join(MODULE_DIR, '.module_header.txt') +MODULE_SOURCE_PATH = os.path.join(MODULE_DIR, '.module_source.txt') + +# /templates/nested_class +CONFIG_CLASS_PATH = os.path.join(NESTED_CLASS_DIR, '.config_class.txt') +RPC_NOTIFICATION_CLASS_PATH = os.path.join(NESTED_CLASS_DIR, '.rpc_inotification.txt') + +# /templates/nested_methods +CONFIGURE_METHOD = os.path.join(NESTED_METHOD_DIR, '.configure.txt') + +# /templates/.json +PLUGIN_JSON = os.path.join(JSON_DIR, '.plugin_json.txt') +JSON_INFO = os.path.join(JSON_DIR, '.json_info.txt') +JSON_CONFIGURATION = os.path.join(JSON_DIR, '.json_configuration.txt') +JSON_INTERFACE = os.path.join(JSON_DIR, '.json_interface.txt') \ No newline at end of file diff --git a/PluginSkeletonGenerator/menu.py b/PluginSkeletonGenerator/menu.py new file mode 100644 index 0000000..8ad5920 --- /dev/null +++ b/PluginSkeletonGenerator/menu.py @@ -0,0 +1,220 @@ +import json +from os import wait +from file_data import FileData, HeaderData, SourceData, CMakeData, JSONData, ConfData +from PluginSkeletonGenerator import PluginGenerator +from utils import Utils +import time + +# Helpers for menu options + + +def generate_files(plugin_name, comrpc_interfaces, jsonrpc_interfaces, out_of_process, jsonrpc, plugin_config, notification_interfaces): + + data = FileData(plugin_name, comrpc_interfaces, jsonrpc_interfaces, out_of_process, jsonrpc, plugin_config, notification_interfaces) + plugin_generator = PluginGenerator(data) + + file_map = { + HeaderData: plugin_generator.generate_headers, + SourceData: plugin_generator.generate_source, + CMakeData: plugin_generator.generate_cmake, + ConfData: plugin_generator.generate_conf_in, + JSONData: plugin_generator.generate_json, + } + + for file_data, generate in file_map.items(): + instance = file_data(plugin_name, comrpc_interfaces, jsonrpc_interfaces, out_of_process, jsonrpc, plugin_config, notification_interfaces) + instance.populate_keywords() + plugin_generator.blueprint_data = instance + generate() + return True + + +def display_settings( + plugin_name, + comrpc_interface, + jsonrpc_interface, + out_of_process, + jsonrpc, + plugin_config, + notification_interfaces +): + print("=======================================") + print("You have chosen the following settings:") + print(f"Plugin Name: {plugin_name}") + print(f"COMRPC Interfaces: {comrpc_interface if comrpc_interface else 'None'}") + print(f"COMRPC Interfaces with events: {notification_interfaces if notification_interfaces else 'None'}") + print(f"JSONRPC Functionality: {jsonrpc}") + print(f"JSONRPC Interfaces: {jsonrpc_interface if jsonrpc else 'N/A'} ") + print(f"Out Of Process: {out_of_process}") + print(f"Plugin specific configuration: {plugin_config}") + + """ + print(f"Subsystem Support: {subsystems}") + print(f"Subsystem Preconditions: {preconditions if subsystems else 'N/A'} ") + print(f"Subsystem Terminations: {terminations if subsystems else 'N/A'}") + print(f"Subsystem Controls: {controls if subsystems else 'N/A'}") + """ + +# Menu options + +def menu(): + print("=======================================") + comrpc_interfaces = [] + jsonrpc_interfaces = [] + notification_interfaces = [] + + plugin_name = str(user_plugin_name()) + out_of_process = user_out_of_process() + jsonrpc = user_comrpc(out_of_process, comrpc_interfaces, jsonrpc_interfaces, notification_interfaces) + plugin_config = user_plugin_configuration(comrpc_interfaces, out_of_process) + + display_settings(plugin_name, comrpc_interfaces, jsonrpc_interfaces, out_of_process, jsonrpc, plugin_config, notification_interfaces) + + generate_files( + plugin_name, + comrpc_interfaces, + jsonrpc_interfaces, + out_of_process, + jsonrpc, + plugin_config, + notification_interfaces + ) + + +def get_boolean_input(option): + while True: + user_input = input(f"{option}") + if user_input.lower() == "y": + user_input = True + break + elif user_input.lower() == "n": + user_input = False + break + else: + print("Unknown character, try again.") + return user_input + + +def user_plugin_name(): + name = input("What will your plugin be called: ") + return name + + +def user_comrpc(out_of_process, comrpc_interfaces, jsonrpc_interfaces, notification_interfaces): + + myBool = True + iteration = 0 + jsonrpc = False + + print("\n=======================================") + print("[INFO]: The following section tracks the number of sucessfully defined interfaces!") + print("=======================================") + while myBool: + + print(f"\nNumber of succesfully defined interfaces: {iteration}") + tempName = input( + "What C++ IDL interface header file (COMRPC Interface) is your interface in?" + "\nExample: IHello.h" + "\n[Note]: Do not define: IPlugin, IConfiguration, JSONRPC. They are automatically configured accordingly!" + "\nTo quit defining interfaces, press ENTER \n" + ) + + if tempName: + correctInitial = Utils.check_correct_comrpc(tempName) + correctExtension = Utils.extract_interface(tempName) + if not correctInitial: + print("\n=======================================") + print("[Error]: Interface header file does not begin with 'I'") + print("=======================================") + if not correctExtension: + print("\n=======================================") + print("[Error]: Interface header file does not have the extension '.h'") + print("=======================================\n") + + if not correctInitial or not correctExtension: + time.sleep(2) + continue + correctName = correctExtension + # CorrectName at this point will always return the correct interface name without the '.h' + while True: + comrpc_confirm = input(f"The name of the interface in {tempName} is {correctName} (enter to confirm or type the correct interface name): ") + if comrpc_confirm: + correctInitial = Utils.check_correct_comrpc(comrpc_confirm) + if not correctInitial: + print("\n=======================================") + print("[Error]: Interface header file does not begin with 'I'") + print("=======================================") + else: + correctName = comrpc_confirm + break + else: + break + + comrpc_interfaces.append(correctName) + notification = get_boolean_input("\nDoes your interface contain a notification: (Enter Y or N)\n") + if notification: + notification_interfaces.append(correctName) + + json_tag = user_jsonrpc(correctName, jsonrpc_interfaces) + if json_tag: + jsonrpc = True + iteration += 1 + else: + if out_of_process and not comrpc_interfaces: + print("An out of process plugin requires a COMRPC interface.") + else: + myBool = False + return jsonrpc + + +def user_jsonrpc(comrpc_interface, jsonrpc_interfaces): + + json_tag = get_boolean_input("Does your plugin require JSON-RPC functionality (@json tag): (Enter Y or N)\n") + if json_tag: + jsonrpc_interface = Utils.replace_comrpc_to_jsonrpc(comrpc_interface) + jsonrpc_interfaces.append(jsonrpc_interface) + return True + return False + + +def user_out_of_process(): + out_of_process = get_boolean_input("\nIs your plugin expected to work out of process: (Enter Y or N)\n") + return out_of_process + +def user_plugin_configuration(comrpc_interfaces, out_of_process): + plugin_config = get_boolean_input("\nDoes the plugin require plugin specific configuration: (Enter Y or N) \n") + if plugin_config and out_of_process: + comrpc_interfaces.append("IConfiguration") + return plugin_config + + +# Future Thunder Versions.. +""" +def user_subsystems(preconditions, terminations, controls): + sub_systems = get_boolean_input("Does your plugin need subsystem support: (Enter Y or N)\n") + + if sub_systems: + while True: + precondition = input("Enter subsystem precondition: (Enter to quit) ") + if not precondition: + break + preconditions.append(precondition) + + while True: + termination = input("Enter subsystem termination: (Enter to quit) ") + if not termination: + break + terminations.append(termination) + + while True: + control = input("Enter subsystem control: (Enter to quit) ") + if not control: + break + controls.append(control) + return sub_systems + +def user_smart_interface(): + + smart_interface = get_boolean_input("Does your plugin require smart interface") + return smart_interface +""" diff --git a/PluginSkeletonGenerator/templates/.cmake.txt b/PluginSkeletonGenerator/templates/.cmake.txt new file mode 100644 index 0000000..eeb7c89 --- /dev/null +++ b/PluginSkeletonGenerator/templates/.cmake.txt @@ -0,0 +1,76 @@ +# If not stated otherwise in this file or this component's license file the +# following copyright and licenses apply: +# +# Copyright 2024 Metrological +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +project({{PLUGIN_NAME}}) + +cmake_minimum_required(VERSION 3.15) + +find_package(Thunder) + +project_version(1.0.0) + +set(MODULE_NAME ${NAMESPACE}${PROJECT_NAME}) + +message("Setup ${MODULE_NAME} v${PROJECT_VERSION}") + +set(PLUGIN_{{PLUGIN_NAME_CAPS}}_STARTMODE "Activated" CACHE STRING "Automatically start {{PLUGIN_NAME}} plugin") +{{SET_MODE}} +{{SET_CONFIG}} + +if(BUILD_REFERENCE) +~INDENT_INCREASE~ + add_definitions(-DBUILD_REFERENCE=${BUILD_REFERENCE}) +~INDENT_DECREASE~ +endif() + +find_package(${NAMESPACE}Plugins REQUIRED) +find_package(${NAMESPACE}Definitions REQUIRED) +find_package(CompileSettingsDebug CONFIG REQUIRED) + +add_library(${MODULE_NAME} SHARED +~INDENT_INCREASE~ + {{SOURCE_FILES}} + Module.cpp +~INDENT_DECREASE~ +) + +set_target_properties(${MODULE_NAME} PROPERTIES +~INDENT_INCREASE~ + CXX_STANDARD 11 + CXX_STANDARD_REQUIRED YES) +~INDENT_DECREASE~ + +target_link_libraries(${MODULE_NAME} +~INDENT_INCREASE~ + PRIVATE + CompileSettingsDebug::CompileSettingsDebug + ${NAMESPACE}Plugins::${NAMESPACE}Plugins + ${NAMESPACE}Definitions::${NAMESPACE}Definitions) +~INDENT_DECREASE~ + +target_include_directories(${MODULE_NAME} +~INDENT_INCREASE~ + PRIVATE + $) +~INDENT_DECREASE~ + +install(TARGETS ${MODULE_NAME} +~INDENT_INCREASE~ + DESTINATION ${CMAKE_INSTALL_LIBDIR}/${STORAGE_DIRECTORY}/plugins COMPONENT ${NAMESPACE}_Runtime) +~INDENT_DECREASE~ + +write_config() \ No newline at end of file diff --git a/PluginSkeletonGenerator/templates/.plugin_conf_in.txt b/PluginSkeletonGenerator/templates/.plugin_conf_in.txt new file mode 100644 index 0000000..f8f1012 --- /dev/null +++ b/PluginSkeletonGenerator/templates/.plugin_conf_in.txt @@ -0,0 +1,5 @@ +startmode = {{PLUGIN_STARTMODE}} + +{{CONFIG}} + +{{OOP_ROOT}} \ No newline at end of file diff --git a/PluginSkeletonGenerator/templates/.plugin_header.txt b/PluginSkeletonGenerator/templates/.plugin_header.txt new file mode 100644 index 0000000..0fa0cfa --- /dev/null +++ b/PluginSkeletonGenerator/templates/.plugin_header.txt @@ -0,0 +1,84 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2024 Metrological + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once +~INDENT_RESET~ +{{COMRPC_INTERFACE_INCLUDES}} + +namespace Thunder { +namespace Plugin { +~INDENT_INCREASE~ + + class {{PLUGIN_NAME}} : public {{INHERITED_CLASS}} { + public: +~INDENT_INCREASE~ + {{PLUGIN_NAME}}(const {{PLUGIN_NAME}}&) = delete; + {{PLUGIN_NAME}} &operator=(const {{PLUGIN_NAME}}&) = delete; + {{PLUGIN_NAME}}({{PLUGIN_NAME}}&&) = delete; + {{PLUGIN_NAME}} &operator=({{PLUGIN_NAME}}&&) = delete; + + {{PLUGIN_NAME}}() + ~INDENT_INCREASE~ + :{{BASE_CONSTRUCTOR}} + {{INTERFACE_CONSTRUCTOR}} + , _example(0) + {{MEMBER_CONSTRUCTOR}} + ~INDENT_DECREASE~ + { + } + + ~{{PLUGIN_NAME}}() override = default; +~INDENT_DECREASE~ + private: +~INDENT_INCREASE~ + {{CONFIG_CLASS}} + {{JSONRPC_EVENT}} +~INDENT_DECREASE~ + public: +~INDENT_INCREASE~ + // IPlugin Methods + const string Initialize(PluginHost::IShell* service) override; + void Deinitialize(PluginHost::IShell* service) override; + string Information() const override; + + {{INHERITED_METHOD}} + // Plugin Methods + {{PLUGIN_METHOD}} + + BEGIN_INTERFACE_MAP({{PLUGIN_NAME}}) + ~INDENT_INCREASE~ + {{INTERFACE_ENTRY}} + {{INTERFACE_AGGREGATE}} + ~INDENT_DECREASE~ + END_INTERFACE_MAP + +~INDENT_DECREASE~ + private: +~INDENT_INCREASE~ + {{USING_CONTAINER}} + // Include the correct member variables for your plugin: + // Note this is only an example, you are responsible for adding the correct members: + uint32_t _example; + + {{MEMBER_IMPL}} +~INDENT_DECREASE~ + }; +~INDENT_DECREASE~ +} // Plugin +} // Thunder \ No newline at end of file diff --git a/PluginSkeletonGenerator/templates/.plugin_implementation.txt b/PluginSkeletonGenerator/templates/.plugin_implementation.txt new file mode 100644 index 0000000..cc7c5b4 --- /dev/null +++ b/PluginSkeletonGenerator/templates/.plugin_implementation.txt @@ -0,0 +1,71 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2024 Metrological + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Module.h" +{{COMRPC_INTERFACE_INCLUDES}} + +namespace Thunder { +namespace Plugin { +~INDENT_INCREASE~ + + class {{PLUGIN_NAME}}Implementation {{OOP_INHERITED_CLASS}} { + public: +~INDENT_INCREASE~ + {{PLUGIN_NAME}}Implementation(const {{PLUGIN_NAME}}Implementation&) = delete; + {{PLUGIN_NAME}}Implementation& operator=(const {{PLUGIN_NAME}}Implementation&) = delete; + {{PLUGIN_NAME}}Implementation({{PLUGIN_NAME}}Implementation&&) = delete; + {{PLUGIN_NAME}}Implementation& operator=({{PLUGIN_NAME}}Implementation&&) = delete; + + {{PLUGIN_NAME}}Implementation() + ~INDENT_INCREASE~ + {{INTERFACE_CONSTRUCTOR}} + ~INDENT_DECREASE~ + { + } + ~{{PLUGIN_NAME}}Implementation() override = default; + + {{CONFIG_CLASS}} +~INDENT_DECREASE~ + public: +~INDENT_INCREASE~ + + BEGIN_INTERFACE_MAP({{PLUGIN_NAME}}Implementation) + ~INDENT_INCREASE~ + {{INTERFACE_ENTRY}} + ~INDENT_DECREASE~ + END_INTERFACE_MAP + + // Implement methods from the interface + {{INHERITED_METHOD}} +~INDENT_DECREASE~ + private: +~INDENT_INCREASE~ +{{USING_CONTAINER}} + + {{NOTIFY_METHOD}} + // Note: test is just an example... + uint32_t _test; + {{OOP_MEMBERS}} +~INDENT_DECREASE~ + }; + + SERVICE_REGISTRATION({{PLUGIN_NAME}}Implementation, 1, 0) +~INDENT_DECREASE~ +} // Plugin +} // Thunder \ No newline at end of file diff --git a/PluginSkeletonGenerator/templates/.plugin_source.txt b/PluginSkeletonGenerator/templates/.plugin_source.txt new file mode 100644 index 0000000..bce04cb --- /dev/null +++ b/PluginSkeletonGenerator/templates/.plugin_source.txt @@ -0,0 +1,72 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2024 Metrological + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +{{INCLUDE}} +{{JSONRPC_INTERFACE_INCLUDES}} + +namespace Thunder { +namespace Plugin { +~INDENT_INCREASE~ + namespace { +~INDENT_INCREASE~ + static Metadata<{{PLUGIN_NAME}}>metadata( +~INDENT_INCREASE~ + // Version + 1, 0, 0, + // Preconditions + {}, + // Terminations + {}, + // Controls + {} +~INDENT_DECREASE~ + ); +~INDENT_DECREASE~ + } + +// Implement all methods from {{PLUGIN_NAME}}.h + +const string {{PLUGIN_NAME}}::Initialize(PluginHost::IShell* service) { +~INDENT_INCREASE~ + {{INITIALIZE_IMPLEMENTATION}} +~INDENT_DECREASE~ +} + +void {{PLUGIN_NAME}}::Deinitialize({{VARIABLE_NOT_USED}}PluginHost::IShell* service) { +~INDENT_INCREASE~ + {{DEINITIALIZE_IMPLEMENTATION}} +~INDENT_DECREASE~ +} + +string {{PLUGIN_NAME}}::Information() const { +~INDENT_INCREASE~ + return string(); +~INDENT_DECREASE~ +} + +void {{PLUGIN_NAME}}::{{PLUGIN_NAME}}Method() { + +} + +{{PLUGIN_METHOD_IMPL}} +{{INHERITED_METHOD_IMPL}} + +~INDENT_DECREASE~ +} // Plugin +} // Thunder \ No newline at end of file diff --git a/PluginSkeletonGenerator/templates/iplugin_methods/.deinitialize_ip.txt b/PluginSkeletonGenerator/templates/iplugin_methods/.deinitialize_ip.txt new file mode 100644 index 0000000..699c17b --- /dev/null +++ b/PluginSkeletonGenerator/templates/iplugin_methods/.deinitialize_ip.txt @@ -0,0 +1 @@ +{{JSONRPC_UNREGISTER}} \ No newline at end of file diff --git a/PluginSkeletonGenerator/templates/iplugin_methods/.deinitialize_oop.txt b/PluginSkeletonGenerator/templates/iplugin_methods/.deinitialize_oop.txt new file mode 100644 index 0000000..b4ce8e9 --- /dev/null +++ b/PluginSkeletonGenerator/templates/iplugin_methods/.deinitialize_oop.txt @@ -0,0 +1,22 @@ + +ASSERT(_service == service); + +{{UNREGISTER_NOTIFICATION}} + +{{DEINITIALIZE}} + + ASSERT(result == Core::ERROR_DESTRUCTION_SUCCEEDED); + + // The process can disappear in the meantime... + if (connection != nullptr) { +~INDENT_INCREASE~ + // But if it did not disappear in the meantime, forcefully terminate it. Shoot to kill + connection->Terminate(); + connection->Release(); +~INDENT_DECREASE~ + } +~INDENT_DECREASE~ +} +_service->Release(); +_service = nullptr; +_connectionId = 0; \ No newline at end of file diff --git a/PluginSkeletonGenerator/templates/iplugin_methods/.initialize_ip.txt b/PluginSkeletonGenerator/templates/iplugin_methods/.initialize_ip.txt new file mode 100644 index 0000000..8d472da --- /dev/null +++ b/PluginSkeletonGenerator/templates/iplugin_methods/.initialize_ip.txt @@ -0,0 +1,6 @@ +string message; + +ASSERT(service != nullptr); +{{CONFIGURE_IP}} +{{REGISTER}} +return (message); \ No newline at end of file diff --git a/PluginSkeletonGenerator/templates/iplugin_methods/.initialize_oop.txt b/PluginSkeletonGenerator/templates/iplugin_methods/.initialize_oop.txt new file mode 100644 index 0000000..de7d95a --- /dev/null +++ b/PluginSkeletonGenerator/templates/iplugin_methods/.initialize_oop.txt @@ -0,0 +1,23 @@ +string message; + +ASSERT(_service == nullptr); +ASSERT(service != nullptr); +{{ASSERT_IMPL}} +ASSERT(_connectionId == 0); + +_service = service; +_service->AddRef(); +{{REGISTER_NOTIFICATION}} + +// Example +_impl{{COMRPC[1:]}} = service->Root(_connectionId, 2000, _T("{{PLUGIN_NAME}}Implementation")); +if (_impl{{COMRPC[1:]}} == nullptr) { +~INDENT_INCREASE~ + message = _T("Couldn't create instance of impl{{COMRPC[1:]}}"); +~INDENT_DECREASE~ +} else { + {{NESTED_QUERY}} +~INDENT_DECREASE~ +} + +return (message); \ No newline at end of file diff --git a/PluginSkeletonGenerator/templates/iplugin_methods/.nested_initialize_oop.txt b/PluginSkeletonGenerator/templates/iplugin_methods/.nested_initialize_oop.txt new file mode 100644 index 0000000..5686044 --- /dev/null +++ b/PluginSkeletonGenerator/templates/iplugin_methods/.nested_initialize_oop.txt @@ -0,0 +1,15 @@ +~INDENT_INCREASE~ +Exchange::IConfiguration* config{{COMRPC}}= _impl{self.comrpc_interfaces[0]}->QueryInterface(); +ASSERT (config{{COMRPC}}!= nullptr); + +if (config{{COMRPC}} != nullptr) { + ~INDENT_INCREASE~ + if (config{{COMRPC}}>Configure(service) != Core::ERROR_NONE) { + ~INDENT_INCREASE~ + message = _T("{{PLUGIN_NAME}} could not be configured."); + ~INDENT_DECREASE~ + } +~INDENT_DECREASE~ +config{{COMRPC}}->Release(); +} +~INDENT_DECREASE~ \ No newline at end of file diff --git a/PluginSkeletonGenerator/templates/json/.json_configuration.txt b/PluginSkeletonGenerator/templates/json/.json_configuration.txt new file mode 100644 index 0000000..8d8a0d7 --- /dev/null +++ b/PluginSkeletonGenerator/templates/json/.json_configuration.txt @@ -0,0 +1,25 @@ +"configuration": { + ~INDENT_INCREASE~ + "type": "object", + "properties": { + ~INDENT_INCREASE~ + "configuration": { + ~INDENT_INCREASE~ + "type": "object", + "required": [], + "properties": { + ~INDENT_INCREASE~ + "example": { + ~INDENT_INCREASE~ + "type": "string", + "description": "Note: This is an example!" + ~INDENT_DECREASE~ + } + ~INDENT_DECREASE~ + } + ~INDENT_DECREASE~ + } + ~INDENT_DECREASE~ + } + ~INDENT_DECREASE~ +}, \ No newline at end of file diff --git a/PluginSkeletonGenerator/templates/json/.json_info.txt b/PluginSkeletonGenerator/templates/json/.json_info.txt new file mode 100644 index 0000000..4640930 --- /dev/null +++ b/PluginSkeletonGenerator/templates/json/.json_info.txt @@ -0,0 +1,12 @@ +"info": { + ~INDENT_INCREASE~ + "title": "{{PLUGIN_NAME}} Plugin", + "callsign": "{{PLUGIN_NAME}}", + "locator": "libThunder{{PLUGIN_NAME}}.so", + "status": "development", + "description": [ + "" + ], + "version": "1.0" + ~INDENT_DECREASE~ + } \ No newline at end of file diff --git a/PluginSkeletonGenerator/templates/json/.json_interface.txt b/PluginSkeletonGenerator/templates/json/.json_interface.txt new file mode 100644 index 0000000..6385984 --- /dev/null +++ b/PluginSkeletonGenerator/templates/json/.json_interface.txt @@ -0,0 +1,5 @@ +"interface": { + ~INDENT_INCREASE~ + {{cppref}} + ~INDENT_DECREASE~ + } \ No newline at end of file diff --git a/PluginSkeletonGenerator/templates/json/.plugin_json.txt b/PluginSkeletonGenerator/templates/json/.plugin_json.txt new file mode 100644 index 0000000..c19341b --- /dev/null +++ b/PluginSkeletonGenerator/templates/json/.plugin_json.txt @@ -0,0 +1,8 @@ +{ +~INDENT_INCREASE~ + "$schema": "plugin.schema.json", + {{JSON_INFO}}, + {{JSON_CONFIGURATION}} + {{JSON_INTERFACE}} +~INDENT_DECREASE~ +} \ No newline at end of file diff --git a/PluginSkeletonGenerator/templates/module/.module_header.txt b/PluginSkeletonGenerator/templates/module/.module_header.txt new file mode 100644 index 0000000..fffe31c --- /dev/null +++ b/PluginSkeletonGenerator/templates/module/.module_header.txt @@ -0,0 +1,31 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2024 Metrological + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#ifndef MODULE_NAME +#define MODULE_NAME {{MODULE_PLUGIN_NAME}} +#endif + +#include +#include +#include + +#undef EXTERNAL +#define EXTERNAL \ No newline at end of file diff --git a/PluginSkeletonGenerator/templates/module/.module_source.txt b/PluginSkeletonGenerator/templates/module/.module_source.txt new file mode 100644 index 0000000..1a6d827 --- /dev/null +++ b/PluginSkeletonGenerator/templates/module/.module_source.txt @@ -0,0 +1,22 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2024 Metrological + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Module.h" + +MODULE_NAME_DECLARATION(BUILD_REFERENCE) \ No newline at end of file diff --git a/PluginSkeletonGenerator/templates/nested_class/.config_class.txt b/PluginSkeletonGenerator/templates/nested_class/.config_class.txt new file mode 100644 index 0000000..a13dd47 --- /dev/null +++ b/PluginSkeletonGenerator/templates/nested_class/.config_class.txt @@ -0,0 +1,25 @@ +class Config : public Core::JSON::Container { +public: +~INDENT_INCREASE~ + Config(const Config&) = delete; + Config& operator=(const Config&) = delete; + Config(Config&&) = delete; + Config& operator=(Config&&) = delete; + + Config() +~INDENT_INCREASE~ + : Core::JSON::Container() + , Example() +~INDENT_DECREASE~ + { +~INDENT_INCREASE~ + Add(_T("example"), &Example); +~INDENT_DECREASE~ + } + ~Config() override = default; +~INDENT_DECREASE~ +public: +~INDENT_INCREASE~ +Core::JSON::String Example; +~INDENT_DECREASE~ +}; \ No newline at end of file diff --git a/PluginSkeletonGenerator/templates/nested_class/.rpc_inotification.txt b/PluginSkeletonGenerator/templates/nested_class/.rpc_inotification.txt new file mode 100644 index 0000000..8363553 --- /dev/null +++ b/PluginSkeletonGenerator/templates/nested_class/.rpc_inotification.txt @@ -0,0 +1,43 @@ +class Notification : {{NOTIFICATION_CLASS}} { +public: +~INDENT_INCREASE~ + explicit Notification({{PLUGIN_NAME}}& parent) +~INDENT_INCREASE~ + : RPC::IRemoteConnection::INotification() + {{NOTIFICATION_CONSTRUCTOR}} + , _parent(parent) +~INDENT_DECREASE~ + { + } + + ~Notification() override = default; + Notification() = delete; + Notification(const Notification&) = delete; + Notification& operator=(const Notification&) = delete; + Notification(Notification&&) = delete; + Notification& operator=(Notification&&) = delete; + + void Activated(RPC::IRemoteConnection*) override + { + } + + void Deactivated(RPC::IRemoteConnection* connection) override + { +~INDENT_INCREASE~ + _parent.Deactivated(connection); +~INDENT_DECREASE~ + } + + {{NOTIFICATION_FUNCTION}} + BEGIN_INTERFACE_MAP(Notification) +~INDENT_INCREASE~ + INTERFACE_ENTRY(RPC::IRemoteConnection::INotification) + {{NOTIFICATION_ENTRY}} +~INDENT_DECREASE~ + END_INTERFACE_MAP +~INDENT_DECREASE~ +private: +~INDENT_INCREASE~ + {{PLUGIN_NAME}}& _parent; +~INDENT_DECREASE~ +}; \ No newline at end of file diff --git a/PluginSkeletonGenerator/templates/nested_methods/.configure.txt b/PluginSkeletonGenerator/templates/nested_methods/.configure.txt new file mode 100644 index 0000000..b478cc3 --- /dev/null +++ b/PluginSkeletonGenerator/templates/nested_methods/.configure.txt @@ -0,0 +1,12 @@ +uint32_t Configure(PluginHost::IShell* service) override { + ~INDENT_INCREASE~ + if (service) { + ~INDENT_INCREASE~ + Config config; + config.FromString(service->ConfigLine()); + TRACE(Trace::Information, (_T("This is just an example: [%s]\n)"), config.Example)); + ~INDENT_DECREASE~ + } + return Core::ERROR_NONE; + ~INDENT_DECREASE~ +} \ No newline at end of file diff --git a/PluginSkeletonGenerator/templates/nested_methods/.notification_register.txt b/PluginSkeletonGenerator/templates/nested_methods/.notification_register.txt new file mode 100644 index 0000000..d36e0fb --- /dev/null +++ b/PluginSkeletonGenerator/templates/nested_methods/.notification_register.txt @@ -0,0 +1,15 @@ +~INDENT_INCREASE~ +ASSERT(notification); + +_adminLock.Lock(); +auto item = std::find(_notifications.begin(), _notifications.end(), notification); +ASSERT(item == _notifications.end()); + +if (item == _notifications.end()) { +~INDENT_INCREASE~ +notification->AddRef(); +_notifications.push_back(notification); +~INDENT_DECREASE~ +} +_adminLock.Unlock(); +~INDENT_DECREASE~ \ No newline at end of file diff --git a/PluginSkeletonGenerator/templates/nested_methods/.notification_unregister.txt b/PluginSkeletonGenerator/templates/nested_methods/.notification_unregister.txt new file mode 100644 index 0000000..d1d3da2 --- /dev/null +++ b/PluginSkeletonGenerator/templates/nested_methods/.notification_unregister.txt @@ -0,0 +1,15 @@ +~INDENT_INCREASE~ +ASSERT(notification); + +_adminLock.Lock(); +auto item = std::find(_notifications.begin(), _notifications.end(), notification); +ASSERT(item != _notifications.end()); + +if (item != _notifications.end()) { + ~INDENT_INCREASE~ +_notifications.erase(item); +(*item)->Release(); +~INDENT_DECREASE~ +} +_adminLock.Unlock(); +~INDENT_DECREASE~ \ No newline at end of file diff --git a/PluginSkeletonGenerator/tests/IHello.h b/PluginSkeletonGenerator/tests/IHello.h new file mode 100644 index 0000000..fdcfdb6 --- /dev/null +++ b/PluginSkeletonGenerator/tests/IHello.h @@ -0,0 +1,28 @@ +#pragma once + +#include "Module.h" + +namespace Thunder { +namespace Exchange { + + // @json 1.0.0 @uncompliant:extended + struct EXTERNAL IHello: virtual public Core::IUnknown { + enum { ID = ID_VOLUMECONTROL }; + + // @event @uncompliant:extended // NOTE: extended format is deprecated!! Do not just copy this line! + struct EXTERNAL INotification : virtual public Core::IUnknown { + enum { ID = ID_VOLUMECONTROL_NOTIFICATION }; + + ~INotification() override = default; + virtual void IHelloNotification() {}; + }; + + ~IHello() override = default; + + virtual void Register(IHello::INotification* sink) {}; + virtual void Unregister(const IHello::INotification* sink) {}; + + virtual uint32_t IHelloMethod1() = 0; + }; +} +} diff --git a/PluginSkeletonGenerator/tests/test.py b/PluginSkeletonGenerator/tests/test.py new file mode 100644 index 0000000..8a9558b --- /dev/null +++ b/PluginSkeletonGenerator/tests/test.py @@ -0,0 +1,169 @@ +#!/usr/bin/env python3 + +import unittest +import sys +import os + +sys.path.insert(1, os.path.join(sys.path[0], '..')) +from menu import generate_files + +def test_plugin_name(): + return 'TestPluginName' + +def test_comrpc_interfaces(): + return ['IHello', 'IWorld'] + +def test_no_comrpc_interfaces(): + return [] + +def test_jsonrpc_functionality(): + return True + +def test_no_jsonrpc_functionality(): + return False + +def test_jsonrpc_interfaces(): + return ['JHello', 'JWorld'] + +def test_no_jsonrpc_interfaces(): + return [] + +def test_out_of_process(): + return True + +def test_no_out_of_process(): + return False + +def test_subsystems(): + return True + +def test_no_subsystems(): + return False + +def test_preconditions(): + return ["PRE1", "PRE2"] + +def test_no_preconditions(): + return [] + +def test_terminations(): + return ["PRE1", "PRE2"] + +def test_no_terminations(): + return [] + +def test_controls(): + return ["PRE1", "PRE2"] + +def test_no_controls(): + return [] + + +test_cases = { + 'InProcessComrpcJsonrpc': { + 'test_plugin_name' : 'InProcessComrpcJsonrpc', + 'test_comrpc_interfaces' : test_comrpc_interfaces(), + 'test_jsonrpc_functionality' : test_jsonrpc_functionality(), + 'test_jsonrpc_interfaces' : test_jsonrpc_interfaces(), + 'test_out_of_process' : test_no_out_of_process(), + 'test_subsystems' : test_no_subsystems(), + 'test_preconditions' : test_no_preconditions(), + 'test_terminations' : test_no_terminations(), + 'test_controls' : test_no_controls() + }, + 'InProcessComrpc': { + 'test_plugin_name' : 'InProcessComrpc', + 'test_comrpc_interfaces' : test_comrpc_interfaces(), + 'test_jsonrpc_functionality' : test_no_jsonrpc_functionality(), + 'test_jsonrpc_interfaces' : test_no_jsonrpc_interfaces(), + 'test_out_of_process' : test_no_out_of_process(), + 'test_subsystems' : test_no_subsystems(), + 'test_preconditions' : test_no_preconditions(), + 'test_terminations' : test_no_terminations(), + 'test_controls' : test_no_controls() + }, + 'InProcessJsonrpc': { + 'test_plugin_name' : 'InProcessJsonrpc', + 'test_comrpc_interfaces' : test_no_comrpc_interfaces(), + 'test_jsonrpc_functionality' : test_jsonrpc_functionality(), + 'test_jsonrpc_interfaces' : test_jsonrpc_interfaces(), + 'test_out_of_process' : test_no_out_of_process(), + 'test_subsystems' : test_no_subsystems(), + 'test_preconditions' : test_no_preconditions(), + 'test_terminations' : test_no_terminations(), + 'test_controls' : test_no_controls() + }, + 'InProcess': { + 'test_plugin_name' : 'InProcess', + 'test_comrpc_interfaces' : test_no_comrpc_interfaces(), + 'test_jsonrpc_functionality' : test_no_jsonrpc_functionality(), + 'test_jsonrpc_interfaces' : test_no_jsonrpc_interfaces(), + 'test_out_of_process' : test_no_out_of_process(), + 'test_subsystems' : test_no_subsystems(), + 'test_preconditions' : test_no_preconditions(), + 'test_terminations' : test_no_terminations(), + 'test_controls' : test_no_controls() + }, + 'OutProcessComrpcJsonrpc': { + 'test_plugin_name' : 'OutProcessComrpcJsonrpc', + 'test_comrpc_interfaces' : test_comrpc_interfaces(), + 'test_jsonrpc_functionality' : test_jsonrpc_functionality(), + 'test_jsonrpc_interfaces' : test_jsonrpc_interfaces(), + 'test_out_of_process' : test_out_of_process(), + 'test_subsystems' : test_no_subsystems(), + 'test_preconditions' : test_no_preconditions(), + 'test_terminations' : test_no_terminations(), + 'test_controls' : test_no_controls() + }, + 'OutProcessComrpc': { + 'test_plugin_name' : 'OutProcessComrpc', + 'test_comrpc_interfaces' : test_comrpc_interfaces(), + 'test_jsonrpc_functionality' : test_no_jsonrpc_functionality(), + 'test_jsonrpc_interfaces' : test_no_jsonrpc_interfaces(), + 'test_out_of_process' : test_out_of_process(), + 'test_subsystems' : test_no_subsystems(), + 'test_preconditions' : test_no_preconditions(), + 'test_terminations' : test_no_terminations(), + 'test_controls' : test_no_controls() + }, + 'OutProcessJsonrpc': { + 'test_plugin_name' : 'OutProcessJsonrpc', + 'test_comrpc_interfaces' : test_no_comrpc_interfaces(), + 'test_jsonrpc_functionality' : test_jsonrpc_functionality(), + 'test_jsonrpc_interfaces' : test_jsonrpc_interfaces(), + 'test_out_of_process' : test_out_of_process(), + 'test_subsystems' : test_no_subsystems(), + 'test_preconditions' : test_no_preconditions(), + 'test_terminations' : test_no_terminations(), + 'test_controls' : test_no_controls() + }, + 'OutProcess': { + 'test_plugin_name' : 'OutProcess', + 'test_comrpc_interfaces' : test_no_comrpc_interfaces(), + 'test_jsonrpc_functionality' : test_no_jsonrpc_functionality(), + 'test_jsonrpc_interfaces' : test_no_jsonrpc_interfaces(), + 'test_out_of_process' : test_out_of_process(), + 'test_subsystems' : test_no_subsystems(), + 'test_preconditions' : test_no_preconditions(), + 'test_terminations' : test_no_terminations(), + 'test_controls' : test_no_controls() + }, +} +#generate_files(plugin_name, comrpc_interfaces, jsonrpc_interfaces, out_of_process, jsonrpc, plugin_config, notification_interfaces): +class TestSkeletonGenerator(unittest.TestCase): + def test(self): + for key,value in test_cases.items(): + result = generate_files( + value['test_plugin_name'], + value['test_comrpc_interfaces'], + value['test_jsonrpc_interfaces'], + value['test_out_of_process'], + value['test_jsonrpc_functionality'], + True, + value['test_comrpc_interfaces'] + ) + self.assertEqual(result, True) + print(f'Success for case: {value["test_plugin_name"]}') + +if __name__ == "__main__": + unittest.main() \ No newline at end of file diff --git a/PluginSkeletonGenerator/utils.py b/PluginSkeletonGenerator/utils.py new file mode 100644 index 0000000..e7f5c43 --- /dev/null +++ b/PluginSkeletonGenerator/utils.py @@ -0,0 +1,106 @@ +from enum import Enum +import os +import re + +class FileUtils(): + def __init__(self) -> None: + pass + + @staticmethod + def replace_keywords(template, keywords): + pattern = re.compile('|'.join(re.escape(k) for k in keywords.keys())) + return pattern.sub(lambda m: keywords[m.group(0)], template) + + @staticmethod + def read_file(template_name): + try: + with open(template_name, 'r') as template_file: + return template_file.read() + except FileNotFoundError as e: + raise (f'Could not load template: {template_name}') from e + except PermissionError as e: + raise (f'Permissions denied: {template_name}') from e + except OSError as e: + raise OSError( + f'OS error occurred trying to open: {template_name}, Error details: {e.strerror}') + + +class Indenter(): + def __init__(self) -> None: + self.indent_size = 0 + self.indent = "" + self.indented_code = [] + self.indent_map = self.create_indent_map() + + self.INDENT_RESET = '~INDENT_RESET~' + self.INDENT_INCREASE = '~INDENT_INCREASE~' + self.INDENT_DECREASE = '~INDENT_DECREASE~' + self.REMOVE_LINE = 'rm\*n' + + def create_indent_map(self): + return { + '.json': ' ' * 2, + '.py': ' ' * 4, + '.h': ' ' * 4, + '.cpp': ' ' * 4 + } + + def indent_type(self, path): + extension = os.path.splitext(path)[1] + self.indent = self.indent_map.get(extension, ' ' * 4) # Default case: 4 spaces + + def process_indent(self, code, path): + if not code: + return '' + + self.indented_code.clear() + self.indent_type(path) + lines = code.splitlines() + self.indent_size = 0 + + actions = { + self.INDENT_RESET: lambda: setattr(self, 'indent_size', 0), + self.INDENT_INCREASE: lambda: setattr(self, 'indent_size', self.indent_size + 1), + self.INDENT_DECREASE: lambda: setattr(self, 'indent_size', self.indent_size - 1), + self.REMOVE_LINE: lambda: None + } + + for line in lines: + newline = line.strip() + + action = actions.get(newline, None) + if action: + action() + continue + + indented_line = self.indent * self.indent_size + newline + self.indented_code.append(indented_line) + return '\n'.join(self.indented_code) + + +class Utils(): + def __init__(self) -> None: + pass + + class Error(Enum): + EXTENSION = 1 + INITIAL = 2 + + # Replaces COMRPC interface to JSONRPC interface + # IHello -> JHello + # Under the assumption, an interface is correctly named as I + @staticmethod + def replace_comrpc_to_jsonrpc(comrpc_interface): + return f'J{comrpc_interface[1:]}' + + @staticmethod + def extract_interface(interface): + extension = interface[-2:] + if extension != ".h": + return False + return interface[:-2] + + # Currently check to see if interface name begins with I + @staticmethod + def check_correct_comrpc(interface): + return True if interface[0] == "I" else False diff --git a/ThunderDevTools/ThunderDevTools.py b/ThunderDevTools/ThunderDevTools.py new file mode 100755 index 0000000..c9384dc --- /dev/null +++ b/ThunderDevTools/ThunderDevTools.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python3 + +import os +import subprocess +import argparse +import sys + +CURRENT_DIR = os.path.dirname(os.path.abspath(__file__)) +THUNDER_TOOLS_DIR = os.path.dirname(CURRENT_DIR) + +def python_distribution(): + if os.name == "posix": + return "python3" + else: + return "python" + +# add future scripts +scripts = { +"plugin_skeleton" : os.path.join("PluginSkeletonGenerator","PluginSkeletonGenerator.py") +} + +def script_menu(): + print("== Thunder Development Tools ==") + print("Choose one of the following tools: (To quit, type q) ") + print("1. Plugin Skeleton Generator") + while True: + option = input("You have picked: ") + + if option == '1': + script = "plugin_skeleton" + break + elif option == "q": + return + else: + print("Incorrect input, try again...") + + run_script(script) + +def run_script(script): + try: + script_path = scripts[script] + full_path = os.path.join(THUNDER_TOOLS_DIR, script_path) + subprocess.run([python_distribution(), full_path], check=True, text=True, stderr=sys.stderr, stdout=sys.stdout) + print("Success") + except subprocess.CalledProcessError as e: + print(f"Error running the script: {e.stderr}") + +def main(): + parser = argparse.ArgumentParser(description="Run any ThunderDevTool script") + parser.add_argument("--s", help="Choose a script to run: ", choices=scripts.keys()) + arg = parser.parse_args() + + if arg.s: + run_script(arg.s) + else: + script_menu() + +if __name__ == "__main__": + main() \ No newline at end of file