diff --git a/src/Core/ConsoleApplication/CMakeLists.txt b/src/Core/ConsoleApplication/CMakeLists.txt index 44f46d796d..8eb939a85d 100644 --- a/src/Core/ConsoleApplication/CMakeLists.txt +++ b/src/Core/ConsoleApplication/CMakeLists.txt @@ -47,6 +47,7 @@ SCIRUN_ADD_LIBRARY(Core_ConsoleApplication TARGET_LINK_LIBRARIES(Core_ConsoleApplication Core_Application Core_Command + Core_Serialization_Network_Importer ) IF(BUILD_WITH_PYTHON) diff --git a/src/Core/ConsoleApplication/ConsoleCommandFactory.cc b/src/Core/ConsoleApplication/ConsoleCommandFactory.cc index 6fa935754c..7dcd861e77 100644 --- a/src/Core/ConsoleApplication/ConsoleCommandFactory.cc +++ b/src/Core/ConsoleApplication/ConsoleCommandFactory.cc @@ -54,6 +54,8 @@ CommandHandle ConsoleGlobalCommandFactory::create(GlobalCommands type) const return makeShared(); case GlobalCommands::LoadNetworkFile: return makeShared(); + case GlobalCommands::ImportNetworkFile: + return makeShared(); case GlobalCommands::PrintModules: return makeShared(); case GlobalCommands::SaveNetworkFile: diff --git a/src/Core/ConsoleApplication/ConsoleCommands.cc b/src/Core/ConsoleApplication/ConsoleCommands.cc index 895df66f96..858e28cee8 100644 --- a/src/Core/ConsoleApplication/ConsoleCommands.cc +++ b/src/Core/ConsoleApplication/ConsoleCommands.cc @@ -26,12 +26,14 @@ */ +#include #include #include #include #include #include #include +#include #include #include #include @@ -63,20 +65,19 @@ namespace /// @todo: real logger #define LOG_CONSOLE(x) std::cout << "[SCIRun] " << x << std::endl; -bool LoadFileCommandConsole::execute() +std::string NetworkFileProcessCommandConsole::resolveFilename() const +{ + return get(Variables::Filename).toFilename().string(); +} + +bool NetworkFileProcessCommandConsole::execute() { quietModulesIfNotVerbose(); - auto inputFiles = Application::Instance().parameters()->inputFiles(); - std::string filename; - if (!inputFiles.empty()) - filename = inputFiles[0]; - else - { - filename = get(Variables::Filename).toFilename().string(); - } + auto filename = resolveFilename(); + const auto verb = actionVerb(); - LOG_CONSOLE("Attempting load of " << filename); + LOG_CONSOLE("Attempting " << verb << " of " << filename); if (!boost::filesystem::exists(filename)) { LOG_CONSOLE("File does not exist: " << filename); @@ -84,24 +85,61 @@ bool LoadFileCommandConsole::execute() } try { - auto openedFile = XMLSerializer::load_xml(filename); + auto file = processFile(filename); - if (openedFile) + if (file) { Application::Instance().controller()->clear(); - Application::Instance().controller()->loadNetwork(openedFile); - LOG_CONSOLE("File load done: " << filename); + Application::Instance().controller()->loadNetwork(file); + LOG_CONSOLE("File " << verb << " done: " << filename); return true; } - LOG_CONSOLE("File load failed: " << filename); + LOG_CONSOLE("File " << verb << " failed: " << filename); + } + catch (std::exception& e) + { + LOG_CONSOLE("File " << verb << " failed: " << filename << ", exception: " << e.what()); } catch (...) { - LOG_CONSOLE("File load failed: " << filename); + LOG_CONSOLE("File " << verb << " failed: " << filename); + } + + // Mirrors FileImportCommand/NetworkFileProcessCommand (GUI): a failure that + // matters in regression mode must exit non-zero so ctest reports it, + // instead of silently continuing. + if (failTestOnErrorInRegressionMode() && Application::Instance().parameters()->isRegressionMode()) + { + LOG_CONSOLE("Regression " << verb << " failed, exiting non-zero: " << filename); + // std:: qualifier avoided: libc++'s using-declaration for quick_exit fails + // to resolve on Xcode 15.4 (macOS-14 CI runners); the global C symbol works fine. + quick_exit(1); } return false; } +std::string LoadFileCommandConsole::resolveFilename() const +{ + auto inputFiles = Application::Instance().parameters()->inputFiles(); + if (!inputFiles.empty()) + return inputFiles[0]; + return NetworkFileProcessCommandConsole::resolveFilename(); +} + +NetworkFileHandle LoadFileCommandConsole::processFile(const std::string& filename) const +{ + return XMLSerializer::load_xml(filename); +} + +NetworkFileHandle ImportFileCommandConsole::processFile(const std::string& filename) const +{ + auto dtdPath = Core::Application::Instance().executablePath(); + const auto& modFactory = Core::Application::Instance().controller()->moduleFactory(); + std::ostringstream legacyImportLog; + LegacyNetworkIO lnio(dtdPath.string(), modFactory, legacyImportLog); + return lnio.load_net(filename); +} + bool SaveFileCommandConsole::execute() { return !saveImpl(get(Variables::Filename).toFilename().string()).empty(); diff --git a/src/Core/ConsoleApplication/ConsoleCommands.h b/src/Core/ConsoleApplication/ConsoleCommands.h index 8f3743cd72..0ce69cc9c6 100644 --- a/src/Core/ConsoleApplication/ConsoleCommands.h +++ b/src/Core/ConsoleApplication/ConsoleCommands.h @@ -31,19 +31,45 @@ #include #include +#include #include namespace SCIRun { namespace Core { namespace Console { - class SCISHARE LoadFileCommandConsole : public Commands::FileCommand + // Shared logic for the two commands that turn a file on disk into a loaded + // network: resolve a filename, hand it to a format-specific parser, and + // load the result into the controller. Load (.srn5) and Import (legacy + // .srn/.net) differ only in how the filename is resolved, how the file is + // parsed, and whether a parse failure should fail a regression test. + class SCISHARE NetworkFileProcessCommandConsole : public Commands::FileCommand + { + public: + bool execute() final; + protected: + virtual std::string resolveFilename() const; + virtual Dataflow::Networks::NetworkFileHandle processFile(const std::string& filename) const = 0; + virtual std::string actionVerb() const = 0; + virtual bool failTestOnErrorInRegressionMode() const { return false; } + }; + + class SCISHARE LoadFileCommandConsole : public NetworkFileProcessCommandConsole { public: LoadFileCommandConsole(); - bool execute() override; - // private: - // int index_ = 0; + protected: + std::string resolveFilename() const override; + Dataflow::Networks::NetworkFileHandle processFile(const std::string& filename) const override; + std::string actionVerb() const override { return "load"; } + }; + + class SCISHARE ImportFileCommandConsole : public NetworkFileProcessCommandConsole + { + protected: + Dataflow::Networks::NetworkFileHandle processFile(const std::string& filename) const override; + std::string actionVerb() const override { return "import"; } + bool failTestOnErrorInRegressionMode() const override { return true; } }; class SCISHARE SaveFileCommandConsole : public Commands::FileCommand, public Commands::SaveFileCommandHelper