From 60a81213effb9cb95aa155169ded536850aeaa59 Mon Sep 17 00:00:00 2001 From: Michael Jackson Date: Tue, 12 Mar 2024 14:19:24 -0400 Subject: [PATCH] BUG: Fix python plugin generation codes (#881) Signed-off-by: Michael Jackson --- .../Algorithms/GeneratePythonSkeleton.cpp | 3 - .../Filters/GeneratePythonSkeletonFilter.cpp | 28 ++++- .../utils/PythonPluginInitTemplate.py | 3 +- .../SimplnxCore/utils/PythonPluginTemplate.py | 3 +- .../utils/PythonPluginTemplateFile.hpp | 107 ++++++++++++------ src/simplnx/Utilities/StringUtilities.hpp | 43 +++++-- 6 files changed, 132 insertions(+), 55 deletions(-) diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/GeneratePythonSkeleton.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/GeneratePythonSkeleton.cpp index 8f3eedee78..3aabb40867 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/GeneratePythonSkeleton.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/GeneratePythonSkeleton.cpp @@ -1,13 +1,10 @@ #include "GeneratePythonSkeleton.hpp" -#include "simplnx/Common/Constants.hpp" #include "simplnx/Common/RgbColor.hpp" #include "simplnx/DataStructure/DataArray.hpp" #include "simplnx/DataStructure/DataGroup.hpp" #include "simplnx/Utilities/DataArrayUtilities.hpp" -#include - using namespace nx::core; // ----------------------------------------------------------------------------- diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/GeneratePythonSkeletonFilter.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/GeneratePythonSkeletonFilter.cpp index 9ac9ef6be9..2a0dffd49d 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/GeneratePythonSkeletonFilter.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/GeneratePythonSkeletonFilter.cpp @@ -87,15 +87,39 @@ IFilter::PreflightResult GeneratePythonSkeletonFilter::preflightImpl(const DataS auto useExistingPlugin = filterArgs.value(k_UseExistingPlugin_Key); auto pluginOutputDir = filterArgs.value(k_PluginOutputDirectory_Key); auto pluginName = filterArgs.value(k_PluginName_Key); + auto pluginInputDir = filterArgs.value(k_PluginInputDirectory_Key); + auto filterNames = filterArgs.value(k_PluginFilterNames); nx::core::Result resultOutputActions; std::vector preflightUpdatedValues; - if(!useExistingPlugin) + auto filterList = StringUtilities::split(filterNames, ','); + + std::string pluginPath = fmt::format("{}{}{}", pluginOutputDir.string(), std::string{fs::path::preferred_separator}, pluginName); + if(useExistingPlugin) + { + pluginPath = pluginInputDir.string(); + } + + std::stringstream preflightUpdatedValue; + + for(const auto& filterName : filterList) { - preflightUpdatedValues.push_back({"Generated Plugin Directory", fmt::format("", pluginOutputDir.string(), std::string{fs::path::preferred_separator}, pluginName)}); + std::string fullPath = fmt::format("{}{}{}.py", pluginPath, std::string{fs::path::preferred_separator}, filterName); + if(std::filesystem::exists({fullPath})) + { + fullPath = "[EXISTS]: " + fullPath; + } + else + { + fullPath = "[New]: " + fullPath; + } + preflightUpdatedValue << fullPath << '\n'; } + preflightUpdatedValues.push_back({"Generated Plugin File(s):", preflightUpdatedValue.str()}); + preflightUpdatedValues.push_back({"Warning:", "Any Existing Files Will Be Overwritten"}); + return {std::move(resultOutputActions), std::move(preflightUpdatedValues)}; } diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/PythonPluginInitTemplate.py b/src/Plugins/SimplnxCore/src/SimplnxCore/utils/PythonPluginInitTemplate.py index 283ec00e91..3cd33c9511 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/PythonPluginInitTemplate.py +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/utils/PythonPluginInitTemplate.py @@ -1,7 +1,6 @@ from #PLUGIN_NAME#.Plugin import #PLUGIN_NAME# -#PLUGIN_IMPORT_CODE# -# FILTER_INCLUDE_INSERT +#PLUGIN_IMPORT_CODE## FILTER_INCLUDE_INSERT def get_plugin(): return #PLUGIN_NAME#() diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/PythonPluginTemplate.py b/src/Plugins/SimplnxCore/src/SimplnxCore/utils/PythonPluginTemplate.py index 8082978817..9d443d32e2 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/PythonPluginTemplate.py +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/utils/PythonPluginTemplate.py @@ -2,8 +2,7 @@ Insert documentation here. """ -#PLUGIN_IMPORT_CODE# -# FILTER_INCLUDE_INSERT +#PLUGIN_IMPORT_CODE## FILTER_INCLUDE_INSERT import simplnx as nx diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/PythonPluginTemplateFile.hpp b/src/Plugins/SimplnxCore/src/SimplnxCore/utils/PythonPluginTemplateFile.hpp index 271a26ae38..f63900d429 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/PythonPluginTemplateFile.hpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/utils/PythonPluginTemplateFile.hpp @@ -50,9 +50,9 @@ inline Result<> InsertFilterNameInPluginFiles(const std::filesystem::path& plugi if(StringUtilities::ends_with(pluginName, "/")) { pluginName.pop_back(); - std::filesystem::path plugPath(pluginName); - pluginName = plugPath.stem().string(); } + std::filesystem::path plugPath(pluginName); + pluginName = plugPath.stem().string(); fs::path pluginPyPath = pluginPath / "Plugin.py"; if(!fs::exists(pluginPyPath)) @@ -66,60 +66,93 @@ inline Result<> InsertFilterNameInPluginFiles(const std::filesystem::path& plugi return MakeErrorResult(-2001, fmt::format("Non-existent plugin file at path: {}", initPyPath.string())); } - std::vector pluginFilePaths = {pluginPyPath, initPyPath}; - for(const auto& pluginFilePath : pluginFilePaths) + // Update the __init__.py file { - std::ifstream file(pluginFilePath.string()); + std::ifstream file(initPyPath.string()); std::stringstream buffer; buffer << file.rdbuf(); std::string content = buffer.str(); file.close(); + std::vector lines = nx::core::StringUtilities::split_2(content, "\n", true); + if(lines.back().empty()) + { + lines.pop_back(); + } + // Create the output file by opening the same file for OVER WRITE. + std::ofstream outFile = std::ofstream(initPyPath.string(), std::ios_base::binary | std::ios_base::trunc); - // Insert filter import statement + std::string filterImportToken = fmt::format("from {0}.{1} import {1}", pluginName, filterName); + bool insertToken = true; + for(auto& line : lines) { - std::size_t filterInsertPos = content.find(k_FilterIncludeInsertToken); - if(filterInsertPos == std::string::npos) + + // If the line is the exact same as the generated import statement mark false + if(line == filterImportToken) { - result.warnings().push_back( - {-2002, fmt::format("Plugin file ('{0}') does not contain the filter insert token ('{1}'), so the filter import statement could not be automatically inserted into the plugin " - "file. This plugin file must be manually updated to include the filter import statement.", - pluginFilePath.string(), k_FilterNameInsertToken)}); + insertToken = false; } - else + // If we hit the include marker comment, then possibly write the newly generated token + if(nx::core::StringUtilities::starts_with(line, k_FilterIncludeInsertToken)) { - filterInsertPos--; // Go back one character to insert before the newline - - std::string filterImportToken = fmt::format("from {0}.{1} import {1}\n", pluginName, filterName); - content.insert(filterInsertPos, filterImportToken); + if(insertToken) + { + outFile << filterImportToken << '\n'; + } } + // Do we need to append the filter to the list of filters + if(nx::core::StringUtilities::contains(line, k_FilterNameInsertToken) && insertToken) + { + line = nx::core::StringUtilities::replace(line, "__all__ = [", fmt::format("__all__ = ['{}', ", filterName)); + } + + outFile << line << '\n'; // Write the line out to the file + } + outFile.close(); + } + + // Update the Plugin.py file + { + std::ifstream file(pluginPyPath.string()); + std::stringstream buffer; + buffer << file.rdbuf(); + std::string content = buffer.str(); + file.close(); + std::vector lines = nx::core::StringUtilities::split_2(content, "\n", true); + if(lines.back().empty()) + { + lines.pop_back(); } + // Create the output file by opening the same file for OVER WRITE. + std::ofstream outFile = std::ofstream(pluginPyPath.string(), std::ios_base::binary | std::ios_base::trunc); - // Insert filter name + std::string filterImportToken = fmt::format("from {0}.{1} import {1}", pluginName, filterName); + std::string filterInsertToken = fmt::format("'{}'", filterName); + + bool insertToken = true; + for(auto& line : lines) { - std::size_t filterInsertPos = content.find(k_FilterNameInsertToken); - if(filterInsertPos == std::string::npos) + // If the line is the exact same as the generated import statement mark false + if(line == filterImportToken) { - result.warnings().push_back( - {-2002, fmt::format("Plugin file ('{0}') does not contain the filter insert token ('{1}'), so the filter name ('{2}') could not be automatically inserted into the plugin " - "file. This plugin file must be manually updated to include the filter name ('{2}').", - pluginFilePath.string(), k_FilterNameInsertToken, filterName)}); + insertToken = false; } - else + // If we hit the include marker comment, then possibly write the newly generated token + if(nx::core::StringUtilities::starts_with(line, k_FilterIncludeInsertToken)) { - filterInsertPos -= 2; // Go back two characters to insert before the closing brace - - std::string filterNameToken = filterName; - if(pluginFilePath.filename() == "__init__.py") + if(insertToken) { - filterNameToken = fmt::format("'{}'", filterNameToken); + outFile << filterImportToken << '\n'; } - content.insert(filterInsertPos, "," + filterNameToken); } - } + // Do we need to append the filter to the list of filters + if(nx::core::StringUtilities::contains(line, k_FilterNameInsertToken) && insertToken) + { + line = nx::core::StringUtilities::replace(line, "return [", fmt::format("return [{}, ", filterName)); + } - std::ofstream out_file(pluginFilePath.string()); - out_file << content; - out_file.close(); + outFile << line << '\n'; // Write the line out to the file + } + outFile.close(); } return result; @@ -224,7 +257,7 @@ inline std::string GeneratePythonPlugin(const std::string& pluginName, const std content = StringUtilities::replace(content, "#PLUGIN_DESCRIPTION#", pluginDescription); auto filterList = StringUtilities::split(pluginFilterList, ','); - content = StringUtilities::replace(content, "#PLUGIN_FILTER_LIST#", fmt::format("{}", fmt::join(filterList, ","))); + content = StringUtilities::replace(content, "#PLUGIN_FILTER_LIST#", fmt::format("{}", fmt::join(filterList, ", "))); std::string importStatements; for(const auto& name : filterList) @@ -294,7 +327,7 @@ inline Result<> WritePythonPluginFiles(const std::filesystem::path& outputDirect auto filterList = StringUtilities::split(pluginFilterList, ','); - std::string aList(fmt::format("'{}',", pluginName)); + std::string aList(fmt::format("'{}', ", pluginName)); for(const auto& name : filterList) { aList.append(fmt::format("'{}', ", name)); diff --git a/src/simplnx/Utilities/StringUtilities.hpp b/src/simplnx/Utilities/StringUtilities.hpp index 78c2df6df7..3b4f923bf6 100644 --- a/src/simplnx/Utilities/StringUtilities.hpp +++ b/src/simplnx/Utilities/StringUtilities.hpp @@ -196,18 +196,43 @@ inline std::vector split(std::string_view str, char delim) return split(str, delims, false); } -inline std::vector split_2(const std::string& line, char delimiter) +/** + * + * @param input + * @param delimiter + * @param consecutiveDelimiters + * @return + */ +inline std::vector split_2(std::string_view input, nonstd::span delimiter, bool consecutiveDelimiters) { - std::stringstream ss(line); - - std::vector tokens; - std::string tempStr; - - while(std::getline(ss, tempStr, delimiter)) + std::vector result; + std::string current; + for(char ch : input) { - tokens.push_back(tempStr); + if(ch == delimiter[0]) + { + // If consecutive delimiters should lead to empty strings, or if the current string is not empty, + // we add the current string (which could be empty) to the result. + if(consecutiveDelimiters || !current.empty()) + { + result.push_back(current); + current.clear(); // Reset current for the next word. + } + // If consecutiveDelimiters is false, we simply skip this part, + // which avoids adding an empty string for consecutive delimiters. + } + else + { + current += ch; + } } - return tokens; + // Add the last word to the result if it's not empty, or if the last character was a delimiter + // and consecutiveDelimiters is true. + if(!current.empty() || (consecutiveDelimiters && !input.empty() && input.back() == delimiter[0])) + { + result.push_back(current); + } + return result; } inline std::string join(nonstd::span vec, std::string_view delim)