From 039c2f4f21d82f62febe10cc1c3c0922ffdea42a Mon Sep 17 00:00:00 2001 From: Armel Thoni Date: Wed, 16 Apr 2025 15:36:03 +0200 Subject: [PATCH 1/3] Ware duplication tool * added internal structure for ware replication * changed create-ware CLI command to allow duplication (references OpenFLUID/openfluid#1140) --- cmake/OpenFLUIDTestScript.cmake | 9 ++ src/apps/openfluid/WareTasks.cpp | 49 ++++++++--- src/apps/openfluid/WareTasks.hpp | 2 + src/apps/openfluid/main.cpp | 5 +- src/apps/openfluid/tests/CMakeLists.txt | 18 ++++ src/openfluid/tools/StringHelpers.cpp | 29 ++++++- src/openfluid/tools/StringHelpers.hpp | 10 +++ src/openfluid/utils/GitProxy.cpp | 75 ++++++++++++++++ src/openfluid/utils/GitProxy.hpp | 2 + src/openfluid/waresdev/WareSrcFactory.cpp | 100 ++++++++++++++++++++++ src/openfluid/waresdev/WareSrcFactory.hpp | 2 + 11 files changed, 289 insertions(+), 12 deletions(-) diff --git a/cmake/OpenFLUIDTestScript.cmake b/cmake/OpenFLUIDTestScript.cmake index c888daa01..26ce219c2 100644 --- a/cmake/OpenFLUIDTestScript.cmake +++ b/cmake/OpenFLUIDTestScript.cmake @@ -54,6 +54,13 @@ FUNCTION(EXECUTE_COMMAND CMD) ENDIF() FILE(REMOVE_RECURSE ${TMPDIR}) + ELSEIF(${CMD} STREQUAL "CHECK_FILE_CONTAINS") + FILE(READ ${TMPDIR}/${ARGV1} TMPTXT) + STRING(FIND "${TMPTXT}" "${ARGV2}" ISMATCH) + IF(${ISMATCH} EQUAL -1) + MESSAGE(FATAL_ERROR "CHECK_FILE_CONTAINS: file ${ARGV1} does not contain string ${ARGV2}") + ENDIF() + ELSEIF(${CMD} STREQUAL "CHECK_FILE_IDENTICAL") # generates consistent eol between files (can be replaced by option ignore_eol in cmake 3.14) CONFIGURE_FILE(${ARGV1}${ARGV3} ${ARGV1}${ARGV3}_UNIX_EOL NEWLINE_STYLE UNIX) @@ -116,6 +123,7 @@ FUNCTION(PARSE_COMMANDS) IF(${CMD_EXPECTED}) IF(${ELEM} STREQUAL "CHECK_FILE_EXIST" OR ${ELEM} STREQUAL "CHECK_FILE_EXIST_IN_ARCHIVE" OR + ${ELEM} STREQUAL "CHECK_FILE_CONTAINS" OR ${ELEM} STREQUAL "CHECK_FILE_IDENTICAL" OR ${ELEM} STREQUAL "CHECK_DIRECTORY_EXIST" OR ${ELEM} STREQUAL "CHECK_DIRECTORY_NOT_EXISTING" OR @@ -141,6 +149,7 @@ FUNCTION(PARSE_COMMANDS) ELSE() IF(${CURRENT_CMD} STREQUAL "COMPARE_FILES" OR ${CURRENT_CMD} STREQUAL "CHECK_FILE_EXIST_IN_ARCHIVE" OR + ${CURRENT_CMD} STREQUAL "CHECK_FILE_CONTAINS" OR ${CURRENT_CMD} STREQUAL "COMPARE_DIRECTORIES" OR ${CURRENT_CMD} STREQUAL "COPY_FILE" OR ${CURRENT_CMD} STREQUAL "COPY_DIRECTORY") diff --git a/src/apps/openfluid/WareTasks.cpp b/src/apps/openfluid/WareTasks.cpp index 3494d4085..fcf07d326 100644 --- a/src/apps/openfluid/WareTasks.cpp +++ b/src/apps/openfluid/WareTasks.cpp @@ -52,6 +52,7 @@ #include #include #include +#include #include #include #include @@ -64,16 +65,28 @@ #include "DefaultDocalyzeListener.hpp" -int WareTasks::processCreate() const +void WareTasks::postWareCreation(const std::string& WarePath) const { - if (!m_Cmd.isOptionActive("id")) + if (m_Cmd.isOptionActive("set-remote")) { - return error("missing ware ID"); + if (!openfluid::utils::GitProxy::isAvailable() || openfluid::utils::GitProxy::setRemote(WarePath, + m_Cmd.getOptionValue("set-remote")) != 0) + { + openfluid::utils::log::warning("Ware creation", "set-remote failed"); + } } +} - if (!m_Cmd.isOptionActive("type")) + +// ===================================================================== +// ===================================================================== + + +int WareTasks::processCreate() const +{ + if (!m_Cmd.isOptionActive("id")) { - return error("missing ware type"); + return error("missing ware ID"); } const auto ID = m_Cmd.getOptionValue("id"); @@ -83,8 +96,6 @@ int WareTasks::processCreate() const return error("invalid ware ID"); } - const auto TypeStr = m_Cmd.getOptionValue("type"); - const auto ParentPath = (m_Cmd.getOptionValue("parent-path").empty() ? openfluid::tools::Filesystem::currentPath() : m_Cmd.getOptionValue("parent-path")); @@ -96,13 +107,29 @@ int WareTasks::processCreate() const Config.MainClassName = (m_Cmd.getOptionValue("main-class").empty() ? Config.MainClassName : m_Cmd.getOptionValue("main-class")); + if (m_Cmd.isOptionActive("from")) + { + const std::string WarePath = openfluid::waresdev::WareSrcFactory::duplicateWare(ID, ParentPath, + m_Cmd.getOptionValue("from"), + m_Cmd.isOptionActive("accept-all")); + postWareCreation(WarePath); + return 0; + } + + if (!m_Cmd.isOptionActive("type")) + { + return error("missing ware type"); + } + const auto TypeStr = m_Cmd.getOptionValue("type"); + if (TypeStr == "simulator") { openfluid::ware::SimulatorSignature Sign; Sign.ID = ID; try { - openfluid::waresdev::WareSrcFactory::createSimulator(Sign,Config,ParentPath); + const std::string WarePath = openfluid::waresdev::WareSrcFactory::createSimulator(Sign,Config,ParentPath); + postWareCreation(WarePath); return 0; } catch(const openfluid::base::FrameworkException& E) @@ -116,7 +143,8 @@ int WareTasks::processCreate() const Sign.ID = ID; try { - openfluid::waresdev::WareSrcFactory::createObserver(Sign,Config,ParentPath); + const std::string WarePath = openfluid::waresdev::WareSrcFactory::createObserver(Sign,Config,ParentPath); + postWareCreation(WarePath); return 0; } catch(const openfluid::base::FrameworkException& E) @@ -165,7 +193,8 @@ int WareTasks::processCreate() const try { - openfluid::waresdev::WareSrcFactory::createBuilderext(Sign,Config,ParentPath); + const std::string WarePath = openfluid::waresdev::WareSrcFactory::createBuilderext(Sign,Config,ParentPath); + postWareCreation(WarePath); return 0; } catch(const openfluid::base::FrameworkException& E) diff --git a/src/apps/openfluid/WareTasks.hpp b/src/apps/openfluid/WareTasks.hpp index 2cd9d2752..677ace5e3 100644 --- a/src/apps/openfluid/WareTasks.hpp +++ b/src/apps/openfluid/WareTasks.hpp @@ -49,6 +49,8 @@ class WareTasks : public TasksBase { private: + void postWareCreation(const std::string& WarePath) const; + int processCreate() const; int processConfigure() const; diff --git a/src/apps/openfluid/main.cpp b/src/apps/openfluid/main.cpp index 4dca77335..4b91caba5 100644 --- a/src/apps/openfluid/main.cpp +++ b/src/apps/openfluid/main.cpp @@ -197,6 +197,7 @@ int main(int argc, char **argv) {"id","i","ID of the ware sources to create (required)",true}, {"main-class","m","name to use for the main C++ class",true}, {"parent-path","p","parent path where to create the ware sources",true}, + {"from", "", "ware to use as reference"}, {"with-paramsui","w","generate the C++ class of the parameterization UI " "(simulators and observers only)"}, {"paramsui-class","u","name to use for the C++ class of the parameterization UI " @@ -205,7 +206,9 @@ int main(int argc, char **argv) {"bext-category","","category the Builder-Extension" "(spatial|model|results|other, default is other)",true}, {"bext-mode","","mode of Builder-Extension (modal|modeless|workspace, default is modal)", - true}}); + true}, + {"accept-all", "a", "say yes to any question (useful during ware duplication)"}, + {"set-remote", "", "define the URL of remote git repository"}}); Parser.addCommand(CreateWareCmd, &WareSection); // --- diff --git a/src/apps/openfluid/tests/CMakeLists.txt b/src/apps/openfluid/tests/CMakeLists.txt index b809e0de4..24f43791f 100644 --- a/src/apps/openfluid/tests/CMakeLists.txt +++ b/src/apps/openfluid/tests/CMakeLists.txt @@ -393,6 +393,24 @@ ENDIF() ########################################################################### +OPENFLUID_ADD_TEST(NAME cmdline-openfluid-CreateWareSimulatorFromExisting + COMMAND "${OFBUILD_DIST_BIN_DIR}/${OPENFLUID_CMD_APP}" + "create-ware" "--id=tests.cmdline.sim.ilar" + "-p" "${OFBUILD_TMP_DIR}/cmdline/create-ware/sim" + "--from=${OFBUILD_TESTS_OUTPUT_DATA_DIR}/cmdline/create-ware/sim/tests.cmdline.sim" + "--set-remote=https://hub.foo.org/git-service/wares/simulators/tests.cmdline.sim.ilar" + "-a" + PRE_TEST REMOVE_DIRECTORY "${OFBUILD_TMP_DIR}/cmdline/create-ware/sim/tests.cmdline.sim.ilar" + POST_TEST CHECK_FILE_EXIST "${OFBUILD_TMP_DIR}/cmdline/create-ware/sim/tests.cmdline.sim.ilar/CMakeLists.txt" + POST_TEST CHECK_FILE_CONTAINS "${OFBUILD_TMP_DIR}/cmdline/create-ware/sim/tests.cmdline.sim.ilar/openfluid-ware.json" "tests.cmdline.sim.ilar" + POST_TEST CHECK_FILE_CONTAINS "${OFBUILD_TMP_DIR}/cmdline/create-ware/sim/tests.cmdline.sim.ilar/.git/config" "hub.foo.org") + +SET_PROPERTY(TEST cmdline-openfluid-CreateWareSimulatorFromExisting APPEND PROPERTY DEPENDS cmdline-openfluid-CreateWareSimulator) + + +########################################################################### + + OPENFLUID_ADD_TEST(NAME cmdline-openfluid-CreateWareObserver COMMAND "${OFBUILD_DIST_BIN_DIR}/${OPENFLUID_CMD_APP}" "create-ware" "--type=observer" "--id=tests.cmdline.obs" diff --git a/src/openfluid/tools/StringHelpers.cpp b/src/openfluid/tools/StringHelpers.cpp index a0af6b730..792144503 100644 --- a/src/openfluid/tools/StringHelpers.cpp +++ b/src/openfluid/tools/StringHelpers.cpp @@ -39,10 +39,10 @@ #include #include - #include #include +#include #include @@ -136,6 +136,33 @@ bool contains(const std::string& Str,const std::string& SubStr, bool CaseSensiti // ===================================================================== +std::map> searchInFolder(std::string Folder, std::string String) +{ + std::map> OccurencesPosByFile; + for (const auto& E : std::filesystem::recursive_directory_iterator{Folder}) + { + auto FileObj = openfluid::tools::Path::fromStdPath(E.path()); + const std::string FileContent = openfluid::tools::Filesystem::readFile(FileObj); + auto it = FileContent.find(String); + + while (it != std::string::npos) + { + if (OccurencesPosByFile.find(E.path()) == OccurencesPosByFile.end()) + { + OccurencesPosByFile[E.path()] = {}; + } + OccurencesPosByFile[E.path()].insert(it); + it = FileContent.find(String, it+String.size()); + } + } + return OccurencesPosByFile; +} + + +// ===================================================================== +// ===================================================================== + + std::string replace(const std::string& Str,const std::string& SearchStr, const std::string& ReplaceStr) { return boost::replace_all_copy(Str,SearchStr,ReplaceStr); diff --git a/src/openfluid/tools/StringHelpers.hpp b/src/openfluid/tools/StringHelpers.hpp index 754c7e62c..1c8984b35 100644 --- a/src/openfluid/tools/StringHelpers.hpp +++ b/src/openfluid/tools/StringHelpers.hpp @@ -45,6 +45,7 @@ #include #include #include +#include #include #include @@ -181,6 +182,15 @@ bool OPENFLUID_API endsWith(const std::string& Str,const std::string& SubStr); bool OPENFLUID_API contains(const std::string& Str,const std::string& SubStr, bool CaseSensitive = true); +/** + Find all occurences of a substring in a folder, iterating on any subdirectory + @param[in] Folder the location to check + @param[in] String the string to look for + @return a map containing the file path associated with a set of string locations in this file +*/ +std::map> OPENFLUID_API searchInFolder(std::string Folder, std::string String); + + /** Replaces every occurrence in a string of a searched substring by a replacement substring @snippet misc/strings.cpp str_repl diff --git a/src/openfluid/utils/GitProxy.cpp b/src/openfluid/utils/GitProxy.cpp index d92eda32a..708dc95d6 100644 --- a/src/openfluid/utils/GitProxy.cpp +++ b/src/openfluid/utils/GitProxy.cpp @@ -166,4 +166,79 @@ const std::string GitProxy::getCurrentBranchName(const std::string& Path) } } + +// ===================================================================== +// ===================================================================== + + +int GitProxy::setRemote(const std::string RepoPath, const std::string RemoteUrl, bool Verbose) +{ + if(isPathGitRepo(RepoPath)) + { + // already versioned, use 'set-url' + openfluid::utils::Process::Command Cmd{ + .Program = m_ExecutablePath, + .Args = {"remote", "set-url", "origin", RemoteUrl}, + .WorkDir = RepoPath + }; + openfluid::utils::Process P(Cmd); + P.run(); + if (Verbose) + { + for (const auto& L : P.stdOutLines()) + { + std::cout << L << std::endl; + } + for (const auto& L : P.stdErrLines()) + { + std::cout << L << std::endl; + } + } + return P.getExitCode(); + } + else + { + // not versioned, use 'git init+git remote add origin' + openfluid::utils::Process::Command CmdInit{ + .Program = m_ExecutablePath, + .Args = {"init"}, + .WorkDir = RepoPath + }; + openfluid::utils::Process PreP(CmdInit); + PreP.run(); + if (Verbose) + { + for (const auto& L : PreP.stdOutLines()) + { + std::cout << L << std::endl; + } + for (const auto& L : PreP.stdErrLines()) + { + std::cout << L << std::endl; + } + } + openfluid::utils::Process::Command Cmd{ + .Program = m_ExecutablePath, + .Args = {"remote", "add", "origin", RemoteUrl}, + .WorkDir = RepoPath + }; + openfluid::utils::Process P(Cmd); + P.run(); + if (Verbose) + { + for (const auto& L : P.stdOutLines()) + { + std::cout << L << std::endl; + } + for (const auto& L : P.stdErrLines()) + { + std::cout << L << std::endl; + } + } + return P.getExitCode(); + } + +} + + } } // namespaces diff --git a/src/openfluid/utils/GitProxy.hpp b/src/openfluid/utils/GitProxy.hpp index 47a97ffad..b4346585b 100644 --- a/src/openfluid/utils/GitProxy.hpp +++ b/src/openfluid/utils/GitProxy.hpp @@ -82,6 +82,8 @@ class OPENFLUID_API GitProxy : public ProgramProxy static const std::string getCurrentBranchName(const std::string& Path); + static int setRemote(const std::string RepoPath, const std::string RemoteUrl, bool Verbose=false); + }; diff --git a/src/openfluid/waresdev/WareSrcFactory.cpp b/src/openfluid/waresdev/WareSrcFactory.cpp index e0d12bfd9..e8ed62ffe 100644 --- a/src/openfluid/waresdev/WareSrcFactory.cpp +++ b/src/openfluid/waresdev/WareSrcFactory.cpp @@ -45,10 +45,14 @@ #include #include #include +#include #include #include #include +#include +#include #include +#include "WareSrcFactory.hpp" namespace openfluid { namespace waresdev { @@ -372,6 +376,102 @@ std::string WareSrcFactory::createBuilderext(const openfluid::builderext::Builde // ===================================================================== +void replaceInFolder(std::string Folder, std::string PreviousWareId, std::string NewWareId, bool AcceptAll=false) +{ + std::string Strategy = (AcceptAll ? "always" : "ask"); + for (const auto& PosByFile : openfluid::tools::searchInFolder(Folder, PreviousWareId)) + { + const auto PathParts = openfluid::tools::FilesystemPath(PosByFile.first).split(); + + // ignore .git folder and '_' prefixed folders + const auto WareIdPos = std::find(PathParts.begin(), PathParts.end(), NewWareId); + bool IgnoredSubDir = false; + for(auto it = WareIdPos; it <= PathParts.end(); it++ ) + { + if ((*it)[0] == '_') + { + IgnoredSubDir = true; + } + } + + if (std::find(PathParts.begin(), PathParts.end(), ".git") == PathParts.end() && + !IgnoredSubDir) + { + std::cout << "In file " << PosByFile.first << std::endl; //TOIMPL better display management + const auto FileObj = openfluid::tools::Path::fromStdPath(PosByFile.first); + std::string FileContent = openfluid::tools::Filesystem::readFile(FileObj); + for (const auto& Pos : PosByFile.second) + { + //TOIMPL better line display: one line before and one line after + std::string DisplayedContext = FileContent.substr(std::max((std::size_t)0,(std::max(Pos, + (std::size_t)10)-10)), + std::min(PreviousWareId.size()+20, FileContent.size()-Pos)); + + std::cout << " Occurence found: " << std::endl << DisplayedContext << std::endl; + bool Apply = false; + if (Strategy == "ask") + { + std::cout << "Replace this occurence? (yes/no/Always/Never) "; + std::string Choice; + std::cin >> Choice; + if (Choice.size() < 1) + { + // no content <=> no + } + else if (Choice[0] == 'y') + { + Apply = true; + } + else if (Choice[0] == 'A') + { + Apply = true; + Strategy = "always"; + } + else if (Choice[0] == 'N') + { + Strategy = "never"; + } + } + else if (Strategy == "always") + { + Apply = true; + } + if (Apply) + { + FileContent = openfluid::tools::replace(FileContent, PreviousWareId, NewWareId); + openfluid::tools::Filesystem::writeFile(FileContent,FileObj); + } + } + } + } +} + + +// ===================================================================== +// ===================================================================== + + +std::string WareSrcFactory::duplicateWare(const std::string ID, const std::string& ParentPath, + const std::string& OriginDir, bool AcceptAll) +{ + std::string CleanedOriginDir = openfluid::tools::FilesystemPath::removeTrailingSeparators(OriginDir); + + auto WareSrcPathObj = getSrcPathObject(ID,ParentPath,true); + + // copy directory + std::string PreviousWareId = openfluid::tools::Path(CleanedOriginDir).filename(); + WareSrcPathObj.makeDirectory(); + openfluid::tools::Filesystem::copyDirectoryContent(CleanedOriginDir, WareSrcPathObj.toGeneric()); + + replaceInFolder(WareSrcPathObj.toGeneric(), PreviousWareId, ID, AcceptAll); + return WareSrcPathObj.toGeneric(); +} + + +// ===================================================================== +// ===================================================================== + + std::string getDeclarationStringFromVarType(openfluid::core::Value::Type VarType) { if (VarType == openfluid::core::Value::NONE) diff --git a/src/openfluid/waresdev/WareSrcFactory.hpp b/src/openfluid/waresdev/WareSrcFactory.hpp index 0017f5fca..90f4b5483 100644 --- a/src/openfluid/waresdev/WareSrcFactory.hpp +++ b/src/openfluid/waresdev/WareSrcFactory.hpp @@ -111,6 +111,8 @@ class OPENFLUID_API WareSrcFactory const std::string& ParentPath, bool WithIDSubDir = true); + static std::string duplicateWare(const std::string ID, const std::string& ParentPath, + const std::string& OriginDir, bool AcceptAll=false); }; From e8f9dc52b5c3bf173340b2c289f21b8cc9b534c7 Mon Sep 17 00:00:00 2001 From: Armel Thoni Date: Wed, 16 Apr 2025 17:42:45 +0200 Subject: [PATCH 2/3] Integration of ware duplication in DevStudio (closes OpenFLUID/openfluid#1140) --- CMake.in.cmake | 4 +- src/apps/openfluid-devstudio/MainWindow.cpp | 49 +++++++++++++++++++ src/apps/openfluid-devstudio/MainWindow.hpp | 2 + src/openfluid/ui/waresdev/WareSrcExplorer.cpp | 2 + src/openfluid/ui/waresdev/WareSrcExplorer.hpp | 2 + .../ui/waresdev/WareSrcWidgetCollection.cpp | 30 ++++++++++++ .../ui/waresdev/WareSrcWidgetCollection.hpp | 2 + src/openfluid/waresdev/WareSrcFactory.cpp | 8 +++ 8 files changed, 97 insertions(+), 2 deletions(-) diff --git a/CMake.in.cmake b/CMake.in.cmake index 90939b6da..297ee1a03 100644 --- a/CMake.in.cmake +++ b/CMake.in.cmake @@ -22,8 +22,8 @@ SET(OFBUILD_CUSTOM_CMAKE_VERSION "${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}. SET(OPENFLUID_VERSION_MAJOR 2) SET(OPENFLUID_VERSION_MINOR 2) -SET(OPENFLUID_VERSION_PATCH 0) -SET(OPENFLUID_VERSION_STATUS "") # example: SET(OPENFLUID_VERSION_STATUS "rc1") +SET(OPENFLUID_VERSION_PATCH 1) +SET(OPENFLUID_VERSION_STATUS "alpha1") # example: SET(OPENFLUID_VERSION_STATUS "rc1") SET(OPENFLUID_VERSION_FULL "${OPENFLUID_VERSION_MAJOR}.${OPENFLUID_VERSION_MINOR}.${OPENFLUID_VERSION_PATCH}") diff --git a/src/apps/openfluid-devstudio/MainWindow.cpp b/src/apps/openfluid-devstudio/MainWindow.cpp index 620210e7e..021ad16c5 100644 --- a/src/apps/openfluid-devstudio/MainWindow.cpp +++ b/src/apps/openfluid-devstudio/MainWindow.cpp @@ -277,6 +277,8 @@ QToolButton::menu-button:pressed, QToolButton::menu-button:hover { mp_WidgetsCollection, SLOT(openPath(const QString&))); connect(Explorer, SIGNAL(deleteWareAsked()), this, SLOT(onDeleteWareRequested())); + connect(Explorer, SIGNAL(duplicateWareAsked()), + this, SLOT(onDuplicateWareRequested())); connect(Explorer, SIGNAL(fileDeleted(const QString&)), mp_WidgetsCollection, SLOT(closeEditor(const QString&))); connect(Explorer, SIGNAL(folderDeleted(const QString&, const QString&, const bool)), @@ -724,6 +726,53 @@ void MainWindow::updateSaveButtonsStatus(bool FileModified, bool FileOpen, bool // ===================================================================== +void MainWindow::onDuplicateWareRequested() +{ + QWidget* CurrentWidget = ui->WaresTabWidget->currentWidget(); + QString SelectedPath = ""; + + if (CurrentWidget == ui->SimPage) + { + SelectedPath = ui->SimExplorer->getCurrentPath(); + } + else if (CurrentWidget == ui->ObsPage) + { + SelectedPath = ui->ObsExplorer->getCurrentPath(); + } + else if (CurrentWidget == ui->ExtPage) + { + SelectedPath = ui->ExtExplorer->getCurrentPath(); + } + + if (SelectedPath != "") + { + const auto WareInfo = openfluid::waresdev::WareSrcEnquirer::getWareInfoFromPath(SelectedPath.toStdString()); + QString WarePath = + QString::fromStdString( + WareInfo.AbsoluteWarePath); + if (WarePath != "") + { + // add dialog to know more information (new ware name) + bool OK; + QString NewWareName = QInputDialog::getText(this, "Ware name", + "Name of the new ware:", + QLineEdit::Normal, + QString::fromStdString(WareInfo.WareDirName+".duplicate"), + &OK); + // TOIMPL reuse here the filter checking if ware name OK (not existing + only accepted symbols) + if (OK && !NewWareName.isEmpty() && WareInfo.WareDirName != NewWareName.toStdString()) + { + mp_WidgetsCollection->duplicateWare(WarePath, NewWareName); + } + } + } +} + + +// ===================================================================== +// ===================================================================== + + void MainWindow::onDeleteWareRequested() { QWidget* CurrentWidget = ui->WaresTabWidget->currentWidget(); diff --git a/src/apps/openfluid-devstudio/MainWindow.hpp b/src/apps/openfluid-devstudio/MainWindow.hpp index 4e0945eec..f2af3b519 100644 --- a/src/apps/openfluid-devstudio/MainWindow.hpp +++ b/src/apps/openfluid-devstudio/MainWindow.hpp @@ -129,6 +129,8 @@ class OPENFLUID_API MainWindow: public openfluid::ui::common::AppMainWindow void updateSaveButtonsStatus(bool FileModified, bool FileOpen, bool WareModified); + void onDuplicateWareRequested(); + void onDeleteWareRequested(); void onCloseAllWaresRequested(); diff --git a/src/openfluid/ui/waresdev/WareSrcExplorer.cpp b/src/openfluid/ui/waresdev/WareSrcExplorer.cpp index 302deb045..f72fd447f 100644 --- a/src/openfluid/ui/waresdev/WareSrcExplorer.cpp +++ b/src/openfluid/ui/waresdev/WareSrcExplorer.cpp @@ -155,7 +155,9 @@ void WareSrcExplorer::onCustomContextMenuRequested(const QPoint& Point) if (getCurrentPath().toStdString() == openfluid::waresdev::WareSrcEnquirer::getWareInfoFromPath(getCurrentPath().toStdString()).AbsoluteWarePath) { + Menu.addAction(tr("Duplicate ware"), this, SIGNAL(duplicateWareAsked())); Menu.addAction(tr("Delete ware"), this, SIGNAL(deleteWareAsked())); + } else if (!IsRemoveFragment) // "delete folder" is hidden when "remove this fragment" already there { diff --git a/src/openfluid/ui/waresdev/WareSrcExplorer.hpp b/src/openfluid/ui/waresdev/WareSrcExplorer.hpp index 06b2afddb..d97711597 100644 --- a/src/openfluid/ui/waresdev/WareSrcExplorer.hpp +++ b/src/openfluid/ui/waresdev/WareSrcExplorer.hpp @@ -130,6 +130,8 @@ class OPENFLUID_API WareSrcExplorer: public QTreeView void openPathAsked(const QString& FilePath); + void duplicateWareAsked(); + void deleteWareAsked(); void fileDeleted(const QString& Path); diff --git a/src/openfluid/ui/waresdev/WareSrcWidgetCollection.cpp b/src/openfluid/ui/waresdev/WareSrcWidgetCollection.cpp index 72a23b899..3ac425308 100644 --- a/src/openfluid/ui/waresdev/WareSrcWidgetCollection.cpp +++ b/src/openfluid/ui/waresdev/WareSrcWidgetCollection.cpp @@ -1172,6 +1172,36 @@ void WareSrcWidgetCollection::openWare(openfluid::ware::WareType Type, const QSt // ===================================================================== +void WareSrcWidgetCollection::duplicateWare(const QString& RefWarePath, const QString& NewWareName) +{ + // check if ware ID acceptable + if (!openfluid::tools::isValidWareID(NewWareName.toStdString())) //TOIMPL do this as validator in custom dialog + { + QMessageBox::critical(nullptr, tr("Duplicate simulator"), + tr("Error duplicating simulator: invalid characters in %1").arg(NewWareName)); + return; + } + try + { + std::string WarePath = openfluid::waresdev::WareSrcFactory::duplicateWare(NewWareName.toStdString(), + openfluid::tools::FilesystemPath(RefWarePath.toStdString()).dirname(), + RefWarePath.toStdString(), true + ); + + openWarePath(WarePath, false); + } + catch(const openfluid::base::FrameworkException& E) + { + QMessageBox::critical(nullptr, tr("Duplicate simulator"), tr("Error duplicating simulator %1: %2") + .arg(RefWarePath).arg(E.what())); + } +} + + +// ===================================================================== +// ===================================================================== + + void WareSrcWidgetCollection::deleteWare(const QString& WarePath) { if (QMessageBox::warning(QApplication::activeWindow(), tr("Delete ware"), diff --git a/src/openfluid/ui/waresdev/WareSrcWidgetCollection.hpp b/src/openfluid/ui/waresdev/WareSrcWidgetCollection.hpp index bb5e881e7..b603d4b45 100644 --- a/src/openfluid/ui/waresdev/WareSrcWidgetCollection.hpp +++ b/src/openfluid/ui/waresdev/WareSrcWidgetCollection.hpp @@ -361,6 +361,8 @@ class OPENFLUID_API WareSrcWidgetCollection: public QObject bool isBuildNoInstallMode(); + void duplicateWare(const QString& WarePath, const QString& NewWareName); + void deleteWare(const QString& WarePath); }; diff --git a/src/openfluid/waresdev/WareSrcFactory.cpp b/src/openfluid/waresdev/WareSrcFactory.cpp index e8ed62ffe..230e7cdb8 100644 --- a/src/openfluid/waresdev/WareSrcFactory.cpp +++ b/src/openfluid/waresdev/WareSrcFactory.cpp @@ -44,6 +44,7 @@ #include #include #include +#include #include #include #include @@ -460,6 +461,13 @@ std::string WareSrcFactory::duplicateWare(const std::string ID, const std::strin // copy directory std::string PreviousWareId = openfluid::tools::Path(CleanedOriginDir).filename(); + + // Check if directory does not already exist + if (WareSrcPathObj.exists()) + { + throw openfluid::base::FrameworkException(OPENFLUID_CODE_LOCATION, + "Ware duplication aborted: target location already exists"); + } WareSrcPathObj.makeDirectory(); openfluid::tools::Filesystem::copyDirectoryContent(CleanedOriginDir, WareSrcPathObj.toGeneric()); From eb06732974f3bdc469fef8051f99f7bbf6e77052 Mon Sep 17 00:00:00 2001 From: Armel Thoni Date: Thu, 17 Apr 2025 16:08:10 +0200 Subject: [PATCH 3/3] wip ware replication pr improvements --- src/apps/openfluid-devstudio/MainWindow.cpp | 10 +++++++++- src/openfluid/tools/StringHelpers.cpp | 14 ++++++++++++++ src/openfluid/tools/StringHelpers.hpp | 3 +++ src/openfluid/waresdev/WareSrcFactory.cpp | 21 +++++++++++++++------ 4 files changed, 41 insertions(+), 7 deletions(-) diff --git a/src/apps/openfluid-devstudio/MainWindow.cpp b/src/apps/openfluid-devstudio/MainWindow.cpp index 021ad16c5..9eec9b58c 100644 --- a/src/apps/openfluid-devstudio/MainWindow.cpp +++ b/src/apps/openfluid-devstudio/MainWindow.cpp @@ -752,7 +752,7 @@ void MainWindow::onDuplicateWareRequested() WareInfo.AbsoluteWarePath); if (WarePath != "") { - // add dialog to know more information (new ware name) + // add dialog to know more information bool OK; QString NewWareName = QInputDialog::getText(this, "Ware name", "Name of the new ware:", @@ -762,7 +762,15 @@ void MainWindow::onDuplicateWareRequested() // TOIMPL reuse here the filter checking if ware name OK (not existing + only accepted symbols) if (OK && !NewWareName.isEmpty() && WareInfo.WareDirName != NewWareName.toStdString()) { + mp_WidgetsCollection->duplicateWare(WarePath, NewWareName); + QMessageBox::warning(QApplication::activeWindow(), + tr("Ware duplication warning"), + tr("If this ware is versioned, change now the remote repository " + "(using 'git remote set-url origin '). " + "It is currently the same than original ware and would " + "generate conflict or data loss risk."), + QMessageBox::Close); } } } diff --git a/src/openfluid/tools/StringHelpers.cpp b/src/openfluid/tools/StringHelpers.cpp index 792144503..abfde5c9e 100644 --- a/src/openfluid/tools/StringHelpers.cpp +++ b/src/openfluid/tools/StringHelpers.cpp @@ -173,6 +173,20 @@ std::string replace(const std::string& Str,const std::string& SearchStr, const s // ===================================================================== +std::string replace_once(const std::string& Str,const std::string& SearchStr, const std::string& ReplaceStr, + std::size_t Pos) +{ + std::string Prefix = Str.substr(0,Pos); + std::string SubStr = Str.substr(Pos, Str.size()); + std::string Res = boost::replace_first_copy(SubStr,SearchStr,ReplaceStr); + return Prefix+Res; +} + + +// ===================================================================== +// ===================================================================== + + std::vector split(const std::string& Str, const char Sep, bool KeepEmpty) { return split(Str,std::string(1,Sep),KeepEmpty); diff --git a/src/openfluid/tools/StringHelpers.hpp b/src/openfluid/tools/StringHelpers.hpp index 1c8984b35..5c4eef485 100644 --- a/src/openfluid/tools/StringHelpers.hpp +++ b/src/openfluid/tools/StringHelpers.hpp @@ -201,6 +201,9 @@ std::map> OPENFLUID_API searchInFolder(std::s */ std::string OPENFLUID_API replace(const std::string& Str,const std::string& SearchStr, const std::string& ReplaceStr); +std::string OPENFLUID_API replace_once(const std::string& Str,const std::string& SearchStr, + const std::string& ReplaceStr, + std::size_t Pos); /** Formats a string using a format and a variable number of arguments. diff --git a/src/openfluid/waresdev/WareSrcFactory.cpp b/src/openfluid/waresdev/WareSrcFactory.cpp index 230e7cdb8..a847701f3 100644 --- a/src/openfluid/waresdev/WareSrcFactory.cpp +++ b/src/openfluid/waresdev/WareSrcFactory.cpp @@ -387,9 +387,9 @@ void replaceInFolder(std::string Folder, std::string PreviousWareId, std::string // ignore .git folder and '_' prefixed folders const auto WareIdPos = std::find(PathParts.begin(), PathParts.end(), NewWareId); bool IgnoredSubDir = false; - for(auto it = WareIdPos; it <= PathParts.end(); it++ ) + for(auto It = WareIdPos; It <= PathParts.end(); It++ ) { - if ((*it)[0] == '_') + if ((*It)[0] == '_') { IgnoredSubDir = true; } @@ -401,13 +401,22 @@ void replaceInFolder(std::string Folder, std::string PreviousWareId, std::string std::cout << "In file " << PosByFile.first << std::endl; //TOIMPL better display management const auto FileObj = openfluid::tools::Path::fromStdPath(PosByFile.first); std::string FileContent = openfluid::tools::Filesystem::readFile(FileObj); - for (const auto& Pos : PosByFile.second) + // do from end to begin to avoid char pos shift + std::set::reverse_iterator Rit; + //for (const auto& Pos : PosByFile.second) + for (Rit = PosByFile.second.rbegin(); Rit != PosByFile.second.rend(); Rit++) { - //TOIMPL better line display: one line before and one line after + const auto& Pos = *Rit; + //TOIMPL better line display: one line before and one line after, use colors std::string DisplayedContext = FileContent.substr(std::max((std::size_t)0,(std::max(Pos, (std::size_t)10)-10)), - std::min(PreviousWareId.size()+20, FileContent.size()-Pos)); + std::min(PreviousWareId.size()+20, FileContent.size()-Pos)); //FIXME BUG IN THIS LINE, bad window + // WIP better context display + // auto Before = FileContent.substr(std::max((std::size_t)0,(std::max(Pos, + // (std::size_t)10)-10)), Pos); + // auto After = FileContent.substr(std::min(PreviousWareId.size()+20, FileContent.size()-Pos)); + // std::cout << Before << "\033[1;31m"<< FileContent.substr(Pos, PreviousWareId.size()) << "\033[0m" << After << std::endl; std::cout << " Occurence found: " << std::endl << DisplayedContext << std::endl; bool Apply = false; if (Strategy == "ask") @@ -439,7 +448,7 @@ void replaceInFolder(std::string Folder, std::string PreviousWareId, std::string } if (Apply) { - FileContent = openfluid::tools::replace(FileContent, PreviousWareId, NewWareId); + FileContent = openfluid::tools::replace_once(FileContent, PreviousWareId, NewWareId, Pos); openfluid::tools::Filesystem::writeFile(FileContent,FileObj); } }