From 9066a14231e0e89d2b0eac93e03130af3b8adce5 Mon Sep 17 00:00:00 2001 From: Marcos Bento Date: Thu, 26 Mar 2026 13:08:24 +0000 Subject: [PATCH 01/90] refactor: Remove unnecessary reserve_* functions --- libs/core/src/ecflow/core/Str.hpp | 6 ------ libs/node/src/ecflow/node/Node.cpp | 18 +++++------------- 2 files changed, 5 insertions(+), 19 deletions(-) diff --git a/libs/core/src/ecflow/core/Str.hpp b/libs/core/src/ecflow/core/Str.hpp index c9460b4c0..a54e6003d 100644 --- a/libs/core/src/ecflow/core/Str.hpp +++ b/libs/core/src/ecflow/core/Str.hpp @@ -78,12 +78,6 @@ class Str { // Disable default construction Str() = delete; - static int reserve_4() { return 4; } - static int reserve_8() { return 8; } - static int reserve_16() { return 17; } - static int reserve_32() { return 32; } - static int reserve_64() { return 64; } - // remove any quotes on the string, else does nothing // "fred" -> fred // fred -> fred diff --git a/libs/node/src/ecflow/node/Node.cpp b/libs/node/src/ecflow/node/Node.cpp index 67bc6c1ec..e0ef0dabe 100644 --- a/libs/node/src/ecflow/node/Node.cpp +++ b/libs/node/src/ecflow/node/Node.cpp @@ -2435,8 +2435,11 @@ bool Node::checkInvariants(std::string& errorMsg) const { } std::string Node::absNodePath() const { + const auto NR_OF_PATH_ELEMENTS = 16; + const auto BASE_PATH_LENGTH = 64; + std::vector vec; - vec.reserve(Str::reserve_16()); + vec.reserve(NR_OF_PATH_ELEMENTS); vec.push_back(name()); Node* theParent = parent(); while (theParent) { @@ -2444,24 +2447,13 @@ std::string Node::absNodePath() const { theParent = theParent->parent(); } std::string ret; - ret.reserve(Str::reserve_64()); + ret.reserve(BASE_PATH_LENGTH); auto r_end = vec.rend(); for (auto r = vec.rbegin(); r != r_end; ++r) { ret += '/'; ret += *r; } - // // Another algorithm broadly similar results - // std::string ret; ret.reserve(Str::reserve_64()); - // ret += '/'; - // ret += name(); - // Node* theParent = parent(); - // while (theParent) { - // ret.insert(0,"/"); - // ret.insert(1,theParent->name()); - // theParent = theParent->parent(); - // } - return ret; } From a821bc12bfce7d0539147166caa39bb4a3a244f4 Mon Sep 17 00:00:00 2001 From: Marcos Bento Date: Thu, 26 Mar 2026 13:33:42 +0000 Subject: [PATCH 02/90] refactor: Make 'empty' a constant instead of a function --- .clang-tidy | 1 + .../attribute/src/ecflow/attribute/RepeatAttr.cpp | 2 +- libs/base/src/ecflow/base/cts/user/UserCmd.cpp | 2 +- .../src/ecflow/base/stc/ServerToClientCmd.cpp | 4 ---- .../src/ecflow/base/stc/ServerToClientCmd.hpp | 8 ++++---- .../src/ecflow/client/ClientEnvironment.cpp | 2 +- libs/core/src/ecflow/core/Log.cpp | 2 +- libs/core/src/ecflow/core/Str.cpp | 4 ---- libs/core/src/ecflow/core/Str.hpp | 7 ++++++- libs/core/src/ecflow/core/WhiteListFile.cpp | 4 ++-- libs/node/src/ecflow/node/AutoRestoreAttr.cpp | 2 +- libs/node/src/ecflow/node/InLimitMgr.cpp | 2 +- libs/node/src/ecflow/node/Node.cpp | 15 ++++++--------- libs/node/src/ecflow/node/NodeFind.cpp | 4 ++-- libs/node/src/ecflow/node/ServerState.cpp | 3 +-- libs/node/src/ecflow/node/Submittable.cpp | 2 +- .../ecflow/node/parser/DefsStructureParser.cpp | 8 +++++++- .../ecflow/node/parser/DefsStructureParser.hpp | 1 + 18 files changed, 37 insertions(+), 36 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index eca959dae..2a5f169e5 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -12,6 +12,7 @@ Checks: > -bugprone-misplaced-widening-cast, -bugprone-narrowing-conversions, -bugprone-parent-virtual-call, + -bugprone-throwing-static-initialization, cppcoreguidelines-*, -cppcoreguidelines-avoid-const-or-ref-data-members, -cppcoreguidelines-avoid-do-while, diff --git a/libs/attribute/src/ecflow/attribute/RepeatAttr.cpp b/libs/attribute/src/ecflow/attribute/RepeatAttr.cpp index c6e09828a..d589a38c0 100644 --- a/libs/attribute/src/ecflow/attribute/RepeatAttr.cpp +++ b/libs/attribute/src/ecflow/attribute/RepeatAttr.cpp @@ -119,7 +119,7 @@ bool Repeat::operator==(const Repeat& rhs) const { } const std::string& Repeat::name() const { - return (type_.get()) ? type_->name() : Str::EMPTY(); + return (type_.get()) ? type_->name() : ecf::string_constants::empty; } // ========================================================================= diff --git a/libs/base/src/ecflow/base/cts/user/UserCmd.cpp b/libs/base/src/ecflow/base/cts/user/UserCmd.cpp index 903ede9c2..5af1cc898 100644 --- a/libs/base/src/ecflow/base/cts/user/UserCmd.cpp +++ b/libs/base/src/ecflow/base/cts/user/UserCmd.cpp @@ -173,7 +173,7 @@ bool UserCmd::setup_user_authentification(AbstractClientEnv& clientEnv) { void UserCmd::setup_user_authentification() { if (user_.empty()) { - setup_user_authentification(get_login_name(), Str::EMPTY()); + setup_user_authentification(get_login_name(), ecf::string_constants::empty); } } diff --git a/libs/base/src/ecflow/base/stc/ServerToClientCmd.cpp b/libs/base/src/ecflow/base/stc/ServerToClientCmd.cpp index c473d66a6..1c76a51f6 100644 --- a/libs/base/src/ecflow/base/stc/ServerToClientCmd.cpp +++ b/libs/base/src/ecflow/base/stc/ServerToClientCmd.cpp @@ -15,7 +15,3 @@ using namespace ecf; ServerToClientCmd::~ServerToClientCmd() = default; - -const std::string& ServerToClientCmd::get_string() const { - return Str::EMPTY(); -} diff --git a/libs/base/src/ecflow/base/stc/ServerToClientCmd.hpp b/libs/base/src/ecflow/base/stc/ServerToClientCmd.hpp index 6353c4881..3a3a0a5e9 100644 --- a/libs/base/src/ecflow/base/stc/ServerToClientCmd.hpp +++ b/libs/base/src/ecflow/base/stc/ServerToClientCmd.hpp @@ -14,6 +14,7 @@ #include "ecflow/base/Cmd.hpp" #include "ecflow/base/ServerReply.hpp" #include "ecflow/core/Serialization.hpp" +#include "ecflow/core/Str.hpp" #include "ecflow/node/NodeFwd.hpp" //================================================================================ @@ -28,10 +29,9 @@ class ServerToClientCmd { virtual std::string print() const = 0; virtual bool equals(ServerToClientCmd*) const { return true; } - virtual const std::string& - get_string() const; /// Used by group command, can return any string, including file contents - virtual bool ok() const { return true; } /// Used by group command - virtual bool is_returnable_in_group_cmd() const { return true; } /// used by group command + virtual const std::string& get_string() const { return ecf::string_constants::empty; }; + virtual bool ok() const { return true; } + virtual bool is_returnable_in_group_cmd() const { return true; } virtual std::string error() const { return std::string{}; } /// Used by test diff --git a/libs/client/src/ecflow/client/ClientEnvironment.cpp b/libs/client/src/ecflow/client/ClientEnvironment.cpp index 9d506644a..db6ea42cb 100644 --- a/libs/client/src/ecflow/client/ClientEnvironment.cpp +++ b/libs/client/src/ecflow/client/ClientEnvironment.cpp @@ -388,7 +388,7 @@ const std::string& ClientEnvironment::get_password(const char* env, const std::s } } - return ecf::Str::EMPTY(); + return ecf::string_constants::empty; } struct TokenFile diff --git a/libs/core/src/ecflow/core/Log.cpp b/libs/core/src/ecflow/core/Log.cpp index 0caa11060..b699cd231 100644 --- a/libs/core/src/ecflow/core/Log.cpp +++ b/libs/core/src/ecflow/core/Log.cpp @@ -110,7 +110,7 @@ void Log::cache_time_stamp() { const std::string& Log::get_cached_time_stamp() const { std::scoped_lock lock(mx_); - return (logImpl_) ? logImpl_->get_cached_time_stamp() : Str::EMPTY(); + return (logImpl_) ? logImpl_->get_cached_time_stamp() : ecf::string_constants::empty; } void Log::flush() { diff --git a/libs/core/src/ecflow/core/Str.cpp b/libs/core/src/ecflow/core/Str.cpp index c9427b6b6..b1b52218c 100644 --- a/libs/core/src/ecflow/core/Str.cpp +++ b/libs/core/src/ecflow/core/Str.cpp @@ -29,10 +29,6 @@ const char* Str::SVR_CMD() { return SVR_CMD; } // Only for automatic check_pt -const std::string& Str::EMPTY() { - static std::string empty = std::string{}; - return empty; -} const std::string& Str::ROOT_PATH() { static std::string root_path = "/"; return root_path; diff --git a/libs/core/src/ecflow/core/Str.hpp b/libs/core/src/ecflow/core/Str.hpp index a54e6003d..94c8db396 100644 --- a/libs/core/src/ecflow/core/Str.hpp +++ b/libs/core/src/ecflow/core/Str.hpp @@ -203,7 +203,6 @@ class Str { static const char* SVR_CMD(); // Only for automatic check_pt // Allows string to be returned by reference - static const std::string& EMPTY(); static const std::string& ROOT_PATH(); // "/" static const std::string& PATH_SEPARATOR(); // "/" static const std::string& COLON(); // ":" @@ -221,6 +220,12 @@ class Str { static const std::string& WHITE_LIST_FILE(); }; +namespace string_constants { + +inline const std::string empty = ""; + +} // namespace string_constants + } // namespace ecf #endif /* ecflow_core_Str_HPP */ diff --git a/libs/core/src/ecflow/core/WhiteListFile.cpp b/libs/core/src/ecflow/core/WhiteListFile.cpp index 66225bbe9..c241674fc 100644 --- a/libs/core/src/ecflow/core/WhiteListFile.cpp +++ b/libs/core/src/ecflow/core/WhiteListFile.cpp @@ -62,10 +62,10 @@ bool WhiteListFile::verify_write_access(const std::string& user) const { return true; } - if (verify_path_access(user, Str::EMPTY(), users_with_write_access_)) { + if (verify_path_access(user, ecf::string_constants::empty, users_with_write_access_)) { return true; } - if (verify_path_access("*", Str::EMPTY(), users_with_write_access_)) { + if (verify_path_access("*", ecf::string_constants::empty, users_with_write_access_)) { return true; } return false; diff --git a/libs/node/src/ecflow/node/AutoRestoreAttr.cpp b/libs/node/src/ecflow/node/AutoRestoreAttr.cpp index bbf797b8f..4b979ff8f 100644 --- a/libs/node/src/ecflow/node/AutoRestoreAttr.cpp +++ b/libs/node/src/ecflow/node/AutoRestoreAttr.cpp @@ -98,7 +98,7 @@ void AutoRestoreAttr::check(std::string& errorMsg) const { // OK a little bit of duplication, since findReferencedNode, will also look for externs // See if the Path:name is defined as an extern, in which case *DONT* error: // This is client side specific, since server does not have externs. - if (node_->defs()->find_extern(i, Str::EMPTY())) { + if (node_->defs()->find_extern(i, ecf::string_constants::empty)) { continue; } diff --git a/libs/node/src/ecflow/node/InLimitMgr.cpp b/libs/node/src/ecflow/node/InLimitMgr.cpp index 045db9365..d2262bc88 100644 --- a/libs/node/src/ecflow/node/InLimitMgr.cpp +++ b/libs/node/src/ecflow/node/InLimitMgr.cpp @@ -422,7 +422,7 @@ limit_ptr InLimitMgr::find_limit(const InLimit& inLimit, // See if the name is defined, as an extern, in which case *DONT* warn: // This is client side specific, since server does not have externs. - if (node_->defs()->find_extern(inLimit.name(), Str::EMPTY())) { + if (node_->defs()->find_extern(inLimit.name(), ecf::string_constants::empty)) { return referencedLimit; // this is empty/NULL } diff --git a/libs/node/src/ecflow/node/Node.cpp b/libs/node/src/ecflow/node/Node.cpp index e0ef0dabe..03cfabc14 100644 --- a/libs/node/src/ecflow/node/Node.cpp +++ b/libs/node/src/ecflow/node/Node.cpp @@ -279,7 +279,7 @@ void Node::begin() { if (!mirrors_.empty()) { // In case mirror attributes are available, the node state becomes UNKNOWN - setStateOnly(NState::State::UNKNOWN, true /*force*/, Str::EMPTY() /* additional info to log */, false); + setStateOnly(NState::State::UNKNOWN, true, ecf::string_constants::empty, false); } clearTrigger(); @@ -352,7 +352,7 @@ void Node::requeue(Requeue_args& args) { if (!mirrors_.empty()) { // In case mirror attributes are available, the node state becomes UNKNOWN - setStateOnly(NState::State::UNKNOWN, true /*force*/, Str::EMPTY() /* additional info to log */, false); + setStateOnly(NState::State::UNKNOWN, true, ecf::string_constants::empty, false); } // Set the state without causing any side effects @@ -458,7 +458,7 @@ void Node::reset() { if (!mirrors_.empty()) { // In case of mirror attributes, the node state becomes UNKNOWN - setStateOnly(NState::State::UNKNOWN, true /*force*/, Str::EMPTY() /* additional info to log */, false); + setStateOnly(NState::State::UNKNOWN, true, ecf::string_constants::empty, false); } clearTrigger(); @@ -614,7 +614,7 @@ void Node::initState(int clear_suspended_in_child_nodes, bool log_state_changes) /// Note: DState::SUSPENDED is not a real state, its really a user interaction /// Replace with suspend, and set underlying state as queued suspend(); - setStateOnly(NState::QUEUED, false /*force*/, Str::EMPTY() /* additional info to log */, log_state_changes); + setStateOnly(NState::QUEUED, false, ecf::string_constants::empty, log_state_changes); } else { @@ -624,10 +624,7 @@ void Node::initState(int clear_suspended_in_child_nodes, bool log_state_changes) // convert DState --> NState. // NOTE:: NState does *NOT* have SUSPENDED - setStateOnly(DState::convert(d_st_.state()), - false /*force*/, - Str::EMPTY() /* additional info to log */, - log_state_changes); + setStateOnly(DState::convert(d_st_.state()), false, ecf::string_constants::empty, log_state_changes); } } @@ -985,7 +982,7 @@ bool Node::evaluateTrigger() const { } const std::string& Node::abortedReason() const { - return Str::EMPTY(); + return ecf::string_constants::empty; } void Node::set_state(NState::State newState, bool force, const std::string& additional_info_to_log) { diff --git a/libs/node/src/ecflow/node/NodeFind.cpp b/libs/node/src/ecflow/node/NodeFind.cpp index 9d065d546..25a137433 100644 --- a/libs/node/src/ecflow/node/NodeFind.cpp +++ b/libs/node/src/ecflow/node/NodeFind.cpp @@ -148,7 +148,7 @@ const std::string& Node::find_parent_user_variable_value(const std::string& name // The node can be detached from the defs. return the_defs->server_state().find_variable(name); } - return Str::EMPTY(); + return ecf::string_constants::empty; } bool Node::user_variable_exists(const std::string& name) const { @@ -712,7 +712,7 @@ node_ptr Node::non_const_this() const { } node_ptr Node::findReferencedNode(const std::string& nodePath, std::string& errorMsg) const { - return findReferencedNode(nodePath, Str::EMPTY(), errorMsg); + return findReferencedNode(nodePath, ecf::string_constants::empty, errorMsg); } // #define DEBUG_FIND_REFERENCED_NODE 1 diff --git a/libs/node/src/ecflow/node/ServerState.cpp b/libs/node/src/ecflow/node/ServerState.cpp index 30b4586fb..5a4e2130d 100644 --- a/libs/node/src/ecflow/node/ServerState.cpp +++ b/libs/node/src/ecflow/node/ServerState.cpp @@ -251,8 +251,7 @@ const std::string& ServerState::find_variable(const std::string& theVarName) con } } - // cerr << "FAILED to FIND '" << theVarName << "'\n"; - return Str::EMPTY(); + return ecf::string_constants::empty; } const Variable& ServerState::findVariable(const std::string& name) const { diff --git a/libs/node/src/ecflow/node/Submittable.cpp b/libs/node/src/ecflow/node/Submittable.cpp index 2d2fdf23e..15228cb18 100644 --- a/libs/node/src/ecflow/node/Submittable.cpp +++ b/libs/node/src/ecflow/node/Submittable.cpp @@ -674,7 +674,7 @@ bool Submittable::non_script_based_job_submission(JobsParam& jobsParam) { // ecflow_client --hcomplete if (createChildProcess(jobsParam)) { - set_state(NState::SUBMITTED, false, Str::EMPTY() /*job size*/); + set_state(NState::SUBMITTED, false, ecf::string_constants::empty); return true; } diff --git a/libs/node/src/ecflow/node/parser/DefsStructureParser.cpp b/libs/node/src/ecflow/node/parser/DefsStructureParser.cpp index 8d7a044ca..d72b71f41 100644 --- a/libs/node/src/ecflow/node/parser/DefsStructureParser.cpp +++ b/libs/node/src/ecflow/node/parser/DefsStructureParser.cpp @@ -30,7 +30,7 @@ DefsStructureParser::DefsStructureParser(Defs* defsfile, const std::string& file defsParser_(this), lineNumber_(0), file_type_(PrintStyle::DEFS), - defs_as_string_(Str::EMPTY()) { + defs_as_string_() { if (!infile_.ok()) { error_ = MESSAGE("DefsStructureParser::DefsStructureParser: Unable to open file! " << infile_.file_name() << "\n\n" @@ -271,6 +271,12 @@ bool DefsStructureParser::semiColonInEditVariable() { } //////////////////////////////////////////////////////////////////////////////////////////////// +DefsString::DefsString() + : lines_{}, + line_pos_{0}, + empty_(true) { +} + DefsString::DefsString(const std::string& defs_as_string) : empty_(defs_as_string.empty()) { if (!empty_) { diff --git a/libs/node/src/ecflow/node/parser/DefsStructureParser.hpp b/libs/node/src/ecflow/node/parser/DefsStructureParser.hpp index ddab5e5ee..18a99413a 100644 --- a/libs/node/src/ecflow/node/parser/DefsStructureParser.hpp +++ b/libs/node/src/ecflow/node/parser/DefsStructureParser.hpp @@ -26,6 +26,7 @@ class Parser; // This class is used get a line of defs format from a defs string class DefsString { public: + DefsString(); explicit DefsString(const std::string& defs_as_string); // Disable copy (and move) semantics DefsString(const DefsString&) = delete; From a4c6c9a686938bc43cd96c7c3392c7dd8d09f1ed Mon Sep 17 00:00:00 2001 From: Marcos Bento Date: Thu, 26 Mar 2026 14:31:21 +0000 Subject: [PATCH 03/90] refactor: Make 'colon' a constant instead of a function --- libs/attribute/src/ecflow/attribute/CronAttr.cpp | 2 +- libs/attribute/src/ecflow/attribute/VerifyAttr.cpp | 5 +++-- libs/attribute/src/ecflow/attribute/ZombieAttr.cpp | 6 +++--- .../client/src/ecflow/client/ClientEnvironment.cpp | 2 +- libs/client/src/ecflow/client/ClientInvoker.cpp | 2 +- libs/client/test/harness/InvokeServer.hpp | 4 +--- libs/core/src/ecflow/core/LogVerification.cpp | 5 ++--- libs/core/src/ecflow/core/Str.cpp | 4 ---- libs/core/src/ecflow/core/Str.hpp | 3 ++- libs/core/src/ecflow/core/TimeSlot.cpp | 2 +- libs/core/test/TestCalendar.cpp | 4 ++-- libs/node/src/ecflow/node/Defs.cpp | 2 +- libs/node/src/ecflow/node/ExprAst.cpp | 14 +++++++------- libs/node/src/ecflow/node/InLimit.cpp | 2 +- libs/node/src/ecflow/node/InLimitMgr.cpp | 5 +++-- libs/node/src/ecflow/node/Node.cpp | 2 +- libs/node/src/ecflow/node/NodeFind.cpp | 2 +- .../node/src/ecflow/node/ResolveExternsVisitor.cpp | 2 +- libs/node/src/ecflow/node/formatter/DefsWriter.hpp | 4 ++-- libs/node/src/ecflow/node/parser/ClockParser.cpp | 2 +- libs/test/overall/TestTrigger.cpp | 3 ++- libs/test/overall/harness/TestFixture.cpp | 5 +++-- .../src/ecflow/simulator/FlatAnalyserVisitor.cpp | 2 +- 23 files changed, 41 insertions(+), 43 deletions(-) diff --git a/libs/attribute/src/ecflow/attribute/CronAttr.cpp b/libs/attribute/src/ecflow/attribute/CronAttr.cpp index e6f9bf30f..16a10b5d9 100644 --- a/libs/attribute/src/ecflow/attribute/CronAttr.cpp +++ b/libs/attribute/src/ecflow/attribute/CronAttr.cpp @@ -602,7 +602,7 @@ static bool isComment(const std::string& token) { } static bool isTimeSpec(const std::string& token) { - if (token.find(Str::COLON()) == std::string::npos) { + if (token.find(ecf::string_constants::colon) == std::string::npos) { return false; } return true; diff --git a/libs/attribute/src/ecflow/attribute/VerifyAttr.cpp b/libs/attribute/src/ecflow/attribute/VerifyAttr.cpp index 4a4f69660..5f0908ef0 100644 --- a/libs/attribute/src/ecflow/attribute/VerifyAttr.cpp +++ b/libs/attribute/src/ecflow/attribute/VerifyAttr.cpp @@ -50,11 +50,12 @@ void VerifyAttr::reset() { } std::string VerifyAttr::toString() const { - return MESSAGE("verify " << NState::toString(state_) << Str::COLON() << expected_); + return MESSAGE("verify " << NState::toString(state_) << ecf::string_constants::colon << expected_); } std::string VerifyAttr::dump() const { - return MESSAGE("verify " << NState::toString(state_) << Str::COLON() << expected_ << " actual(" << actual_ << ")"); + return MESSAGE("verify " << NState::toString(state_) << ecf::string_constants::colon << expected_ << " actual(" + << actual_ << ")"); } template diff --git a/libs/attribute/src/ecflow/attribute/ZombieAttr.cpp b/libs/attribute/src/ecflow/attribute/ZombieAttr.cpp index 5e44e525f..d399edb02 100644 --- a/libs/attribute/src/ecflow/attribute/ZombieAttr.cpp +++ b/libs/attribute/src/ecflow/attribute/ZombieAttr.cpp @@ -95,11 +95,11 @@ void ZombieAttr::write(std::string& ret) const { /// format is zombie_type : child_cmds(optional) : action : zombie_lifetime_(optional) ret += "zombie "; ret += Child::to_string(zombie_type_); - ret += Str::COLON(); + ret += ecf::string_constants::colon; ret += ecf::to_string(action_); - ret += Str::COLON(); + ret += ecf::string_constants::colon; ret += Child::to_string(child_cmds_); - ret += Str::COLON(); + ret += ecf::string_constants::colon; ret += ecf::convert_to(zombie_lifetime_); } diff --git a/libs/client/src/ecflow/client/ClientEnvironment.cpp b/libs/client/src/ecflow/client/ClientEnvironment.cpp index db6ea42cb..421caf366 100644 --- a/libs/client/src/ecflow/client/ClientEnvironment.cpp +++ b/libs/client/src/ecflow/client/ClientEnvironment.cpp @@ -215,7 +215,7 @@ std::string ClientEnvironment::toString() const { << "\n"; if (!host_vec_.empty()) { for (std::pair i : host_vec_) { - ss << " " << i.first << ecf::Str::COLON() << i.second << "\n"; + ss << " " << i.first << ecf::string_constants::colon << i.second << "\n"; } } ss << " " << ecf::environment::ECF_NAME << " = " << task_path_ << "\n"; diff --git a/libs/client/src/ecflow/client/ClientInvoker.cpp b/libs/client/src/ecflow/client/ClientInvoker.cpp index 43cfe0f96..67ae2e591 100644 --- a/libs/client/src/ecflow/client/ClientInvoker.cpp +++ b/libs/client/src/ecflow/client/ClientInvoker.cpp @@ -1724,7 +1724,7 @@ int ClientInvoker::edit_script_submit(const std::string& path_to_task, std::string ClientInvoker::client_env_host_port() const { std::string host_port = clientEnv_.host(); - host_port += Str::COLON(); + host_port += ecf::string_constants::colon; host_port += clientEnv_.port(); return host_port; } diff --git a/libs/client/test/harness/InvokeServer.hpp b/libs/client/test/harness/InvokeServer.hpp index 01ee96885..375848869 100644 --- a/libs/client/test/harness/InvokeServer.hpp +++ b/libs/client/test/harness/InvokeServer.hpp @@ -58,7 +58,7 @@ class InvokeServer { std::string test_name = msg; test_name += " on "; test_name += host_; - test_name += ecf::Str::COLON(); + test_name += ecf::string_constants::colon; test_name += port_; std::cout << test_name << std::endl; @@ -162,8 +162,6 @@ class InvokeServer { const std::string& port, bool remove_checkpt_file_after_server_exit, bool remove_log_file_after_server_exit) { - // std::cout << "*****InvokeServer::doEnd Closing server on " << host << ecf::Str::COLON() << port << - // "\n"; { ClientInvoker theClient(host, port); BOOST_REQUIRE_NO_THROW(theClient.terminateServer()); diff --git a/libs/core/src/ecflow/core/LogVerification.cpp b/libs/core/src/ecflow/core/LogVerification.cpp index b7effc0a3..ee4328934 100644 --- a/libs/core/src/ecflow/core/LogVerification.cpp +++ b/libs/core/src/ecflow/core/LogVerification.cpp @@ -52,7 +52,6 @@ bool LogVerification::extractNodePathAndState(const std::string& logfile, if (!NState::isValid(theState)) { continue; } - // cout << line_number << Str::COLON() << *i << "\n"; pathStateVec.emplace_back(lineTokens[3], theState); } @@ -94,10 +93,10 @@ bool LogVerification::compareNodeStates(const std::string& logfile, std::string theLine, theGoldenLine; if (i < lines.size()) { - theLine = lines[i].second + Str::COLON() + lines[i].first; + theLine = lines[i].second + ecf::string_constants::colon + lines[i].first; } if (i < goldenLines.size()) { - theGoldenLine = goldenLines[i].second + Str::COLON() + goldenLines[i].first; + theGoldenLine = goldenLines[i].second + ecf::string_constants::colon + goldenLines[i].first; } if (i < lines.size() && i < goldenLines.size()) { diff --git a/libs/core/src/ecflow/core/Str.cpp b/libs/core/src/ecflow/core/Str.cpp index b1b52218c..a76096e71 100644 --- a/libs/core/src/ecflow/core/Str.cpp +++ b/libs/core/src/ecflow/core/Str.cpp @@ -37,10 +37,6 @@ const std::string& Str::PATH_SEPARATOR() { static std::string path_sep = "/"; return path_sep; } -const std::string& Str::COLON() { - static std::string colon = ":"; - return colon; -} const std::string& Str::STATE_CHANGE() { static std::string state_change = " state change "; diff --git a/libs/core/src/ecflow/core/Str.hpp b/libs/core/src/ecflow/core/Str.hpp index 94c8db396..179a6e2f9 100644 --- a/libs/core/src/ecflow/core/Str.hpp +++ b/libs/core/src/ecflow/core/Str.hpp @@ -205,7 +205,6 @@ class Str { // Allows string to be returned by reference static const std::string& ROOT_PATH(); // "/" static const std::string& PATH_SEPARATOR(); // "/" - static const std::string& COLON(); // ":" static const std::string& STATE_CHANGE(); @@ -224,6 +223,8 @@ namespace string_constants { inline const std::string empty = ""; +inline const std::string colon = ":"; + } // namespace string_constants } // namespace ecf diff --git a/libs/core/src/ecflow/core/TimeSlot.cpp b/libs/core/src/ecflow/core/TimeSlot.cpp index a20c4510f..2568d07d6 100644 --- a/libs/core/src/ecflow/core/TimeSlot.cpp +++ b/libs/core/src/ecflow/core/TimeSlot.cpp @@ -71,7 +71,7 @@ void TimeSlot::write(std::string& ret) const { } ret += ecf::convert_to(h_); - ret += Str::COLON(); + ret += ecf::string_constants::colon; if (m_ < 10) { ret += "0"; } diff --git a/libs/core/test/TestCalendar.cpp b/libs/core/test/TestCalendar.cpp index 5a645d761..c222e7eec 100644 --- a/libs/core/test/TestCalendar.cpp +++ b/libs/core/test/TestCalendar.cpp @@ -499,7 +499,7 @@ BOOST_AUTO_TEST_CASE(test_calendar_time_series_relative_complex) { if (intersects && boundaryOk) { BOOST_CHECK_MESSAGE(matches, "Calendar should match relative time series at " - << suiteTm.tm_hour << Str::COLON() << suiteTm.tm_min + << suiteTm.tm_hour << ecf::string_constants::colon << suiteTm.tm_min << " suite time = " << to_simple_string(calendar.suiteTime())); if (!matches) { ECF_TEST_ERR(<< "suiteTm.tm_hour =" << suiteTm.tm_hour << " suiteTm.tm_min = " << suiteTm.tm_min @@ -513,7 +513,7 @@ BOOST_AUTO_TEST_CASE(test_calendar_time_series_relative_complex) { else { BOOST_CHECK_MESSAGE(!matches, "Calendar should NOT match relative time series at " - << suiteTm.tm_hour << Str::COLON() << suiteTm.tm_min + << suiteTm.tm_hour << ecf::string_constants::colon << suiteTm.tm_min << " suite time = " << to_simple_string(calendar.suiteTime())); if (matches) { diff --git a/libs/node/src/ecflow/node/Defs.cpp b/libs/node/src/ecflow/node/Defs.cpp index 48b0ea6a9..e2e5226db 100644 --- a/libs/node/src/ecflow/node/Defs.cpp +++ b/libs/node/src/ecflow/node/Defs.cpp @@ -971,7 +971,7 @@ bool Defs::find_extern(const std::string& pathToNode, const std::string& node_at } std::string extern_path = pathToNode; - extern_path += Str::COLON(); + extern_path += ecf::string_constants::colon; extern_path += node_attr_name; if (externs_.find(extern_path) != externs_.end()) { diff --git a/libs/node/src/ecflow/node/ExprAst.cpp b/libs/node/src/ecflow/node/ExprAst.cpp index f59150692..536588032 100644 --- a/libs/node/src/ecflow/node/ExprAst.cpp +++ b/libs/node/src/ecflow/node/ExprAst.cpp @@ -1557,11 +1557,11 @@ int AstVariable::plus(Ast* right) const { } void AstVariable::print_flat(std::ostream& os, bool /*add_bracket*/) const { - os << nodePath_ << Str::COLON() << name_; + os << nodePath_ << ecf::string_constants::colon << name_; } std::string AstVariable::expression() const { - return nodePath_ + Str::COLON() + name_; + return nodePath_ + ecf::string_constants::colon + name_; } std::string AstVariable::why_expression(bool html) const { @@ -1601,7 +1601,7 @@ std::string AstVariable::why_expression(bool html) const { if (!ref_node) { ret += "(?)"; } - ret += Str::COLON(); + ret += ecf::string_constants::colon; ret += name_; ret += "("; std::ostringstream ss; @@ -1683,11 +1683,11 @@ int AstParentVariable::plus(Ast* right) const { } void AstParentVariable::print_flat(std::ostream& os, bool /*add_bracket*/) const { - os << Str::COLON() << name_; + os << ecf::string_constants::colon << name_; } std::string AstParentVariable::expression() const { - return Str::COLON() + name_; + return ecf::string_constants::colon + name_; } std::string AstParentVariable::why_expression(bool html) const { @@ -1727,7 +1727,7 @@ std::string AstParentVariable::why_expression(bool html) const { if (!ref_node) { ret += "(?)"; } - ret += Str::COLON(); + ret += ecf::string_constants::colon; ret += name_; ret += "("; std::ostringstream ss; @@ -1790,7 +1790,7 @@ VariableHelper::VariableHelper(const AstVariable* astVariable, std::string& erro } errorMsg += MESSAGE("From expression Variable " - << astVariable_->nodePath() << Str::COLON() << astVariable_->name() + << astVariable_->nodePath() << ecf::string_constants::colon << astVariable_->name() << " the referenced node is " << theReferenceNode_->debugNodePath() << "\n" << "Could not find event, meter, variable, repeat, generated variable, limit or queue of name('" << astVariable_->name() << "') on node " << theReferenceNode_->debugNodePath() << "\n"); diff --git a/libs/node/src/ecflow/node/InLimit.cpp b/libs/node/src/ecflow/node/InLimit.cpp index d68449173..4e4c9ac2f 100644 --- a/libs/node/src/ecflow/node/InLimit.cpp +++ b/libs/node/src/ecflow/node/InLimit.cpp @@ -121,7 +121,7 @@ void InLimit::write(std::string& ret) const { } else { ret += path_; - ret += Str::COLON(); + ret += ecf::string_constants::colon; ret += n_; } if (tokens_ != 1) { diff --git a/libs/node/src/ecflow/node/InLimitMgr.cpp b/libs/node/src/ecflow/node/InLimitMgr.cpp index d2262bc88..85f9c1887 100644 --- a/libs/node/src/ecflow/node/InLimitMgr.cpp +++ b/libs/node/src/ecflow/node/InLimitMgr.cpp @@ -342,11 +342,12 @@ bool InLimitMgr::why(std::vector& vec, bool html) const { } else { if (html) { - auto ref = MESSAGE("[limit]" << i.pathToNode() << Str::COLON() << limit->name()); + auto ref = + MESSAGE("[limit]" << i.pathToNode() << ecf::string_constants::colon << limit->name()); ss << Node::path_href_attribute(ref) << " is full"; } else { - ss << "limit " << i.pathToNode() << Str::COLON() << limit->name() << " is full"; + ss << "limit " << i.pathToNode() << ecf::string_constants::colon << limit->name() << " is full"; } } diff --git a/libs/node/src/ecflow/node/Node.cpp b/libs/node/src/ecflow/node/Node.cpp index 03cfabc14..35c2be450 100644 --- a/libs/node/src/ecflow/node/Node.cpp +++ b/libs/node/src/ecflow/node/Node.cpp @@ -2456,7 +2456,7 @@ std::string Node::absNodePath() const { std::string Node::debugNodePath() const { std::string ret = debugType(); - ret += Str::COLON(); + ret += ecf::string_constants::colon; ret += absNodePath(); return ret; } diff --git a/libs/node/src/ecflow/node/NodeFind.cpp b/libs/node/src/ecflow/node/NodeFind.cpp index 25a137433..8796d0b48 100644 --- a/libs/node/src/ecflow/node/NodeFind.cpp +++ b/libs/node/src/ecflow/node/NodeFind.cpp @@ -741,7 +741,7 @@ Node::findReferencedNode(const std::string& nodePath, const std::string& extern_ #ifdef DEBUG_FIND_REFERENCED_NODE std::string debug_path = - "Searching for path " + nodePath + " from " + debugType() + Str::COLON() + absNodePath() + "\n"; + "Searching for path " + nodePath + " from " + debugType() + ecf::string_constants::colon + absNodePath() + "\n"; #endif // if an absolute path cut in early diff --git a/libs/node/src/ecflow/node/ResolveExternsVisitor.cpp b/libs/node/src/ecflow/node/ResolveExternsVisitor.cpp index 2b4c6d1f3..d82c4e2ec 100644 --- a/libs/node/src/ecflow/node/ResolveExternsVisitor.cpp +++ b/libs/node/src/ecflow/node/ResolveExternsVisitor.cpp @@ -142,7 +142,7 @@ void AstResolveExternVisitor::visitFlag(AstFlag* astVar) { void AstResolveExternVisitor::addExtern(const std::string& absNodePath, const std::string& var) { std::string ext = absNodePath; if (!var.empty()) { - ext += Str::COLON(); + ext += ecf::string_constants::colon; ext += var; } // cout << " AstResolveExternVisitor::addExtern " << ext << "\n"; diff --git a/libs/node/src/ecflow/node/formatter/DefsWriter.hpp b/libs/node/src/ecflow/node/formatter/DefsWriter.hpp index 45c0d37cb..8e4100914 100644 --- a/libs/node/src/ecflow/node/formatter/DefsWriter.hpp +++ b/libs/node/src/ecflow/node/formatter/DefsWriter.hpp @@ -836,7 +836,7 @@ struct Writer static void writeln(Stream& output, const AstVariable& item) { output << "# "; output << item.nodePath(); - output << Str::COLON(); + output << ecf::string_constants::colon; output << item.name(); std::string error; @@ -873,7 +873,7 @@ struct Writer static void writeln(Stream& output, const AstParentVariable& item) { output << "# "; - output << Str::COLON(); + output << ecf::string_constants::colon; output << item.name(); if (const auto* ref_node = item.find_node_which_references_variable(); ref_node) { diff --git a/libs/node/src/ecflow/node/parser/ClockParser.cpp b/libs/node/src/ecflow/node/parser/ClockParser.cpp index 7d1427dcb..1bccf6b2e 100644 --- a/libs/node/src/ecflow/node/parser/ClockParser.cpp +++ b/libs/node/src/ecflow/node/parser/ClockParser.cpp @@ -20,7 +20,7 @@ using namespace ecf; static void extractTheGain(const std::string& theGainToken, ClockAttr& clockAttr) { - if (theGainToken.find(Str::COLON()) != std::string::npos) { + if (theGainToken.find(ecf::string_constants::colon) != std::string::npos) { // clock real +01:00 // clock real 01:36 int hour, min; diff --git a/libs/test/overall/TestTrigger.cpp b/libs/test/overall/TestTrigger.cpp index 4c904ed95..9540e0042 100644 --- a/libs/test/overall/TestTrigger.cpp +++ b/libs/test/overall/TestTrigger.cpp @@ -73,7 +73,8 @@ BOOST_AUTO_TEST_CASE(test_triggers_and_meters) { for (int i = 0; i < taskSize; i++) { task_ptr task = fam->add_task("t" + ecf::convert_to(i * 10 + 10)); task->addVerify(VerifyAttr(NState::COMPLETE, 1)); - task->add_trigger(taskName + Str::COLON() + meterName + " ge " + ecf::convert_to(i * 10 + 10)); + task->add_trigger(taskName + ecf::string_constants::colon + meterName + " ge " + + ecf::convert_to(i * 10 + 10)); } } diff --git a/libs/test/overall/harness/TestFixture.cpp b/libs/test/overall/harness/TestFixture.cpp index e1e2ec25a..6e65c1008 100644 --- a/libs/test/overall/harness/TestFixture.cpp +++ b/libs/test/overall/harness/TestFixture.cpp @@ -235,11 +235,12 @@ void TestFixture::init(const std::string& project_test_dir) { /// Either way, we wait for 60 seconds for server, for it to respond to pings /// This is important when server is started locally. We must wait for it to come alive. if (!client().wait_for_server_reply()) { - std::cout << " Ping server on " << client().host() << Str::COLON() << client().port() + std::cout << " Ping server on " << client().host() << ecf::string_constants::colon << client().port() << " failed. Is the server running ? " << client().errorMsg() << "\n"; assert(false); } - std::cout << " Ping OK: server running on: " << client().host() << Str::COLON() << client().port() << "\n"; + std::cout << " Ping OK: server running on: " << client().host() << ecf::string_constants::colon + << client().port() << "\n"; // Log file must exist, otherwise test will not work. Log file required for comparison if (!fs::exists(TestFixture::pathToLogFile())) { diff --git a/libs/test/simulator/src/ecflow/simulator/FlatAnalyserVisitor.cpp b/libs/test/simulator/src/ecflow/simulator/FlatAnalyserVisitor.cpp index 8e4f7ce6c..260d9ccea 100644 --- a/libs/test/simulator/src/ecflow/simulator/FlatAnalyserVisitor.cpp +++ b/libs/test/simulator/src/ecflow/simulator/FlatAnalyserVisitor.cpp @@ -65,7 +65,7 @@ bool FlatAnalyserVisitor::analyse(Node* node) { ss_ << l1; ss_ << node->debugType(); - ss_ << Str::COLON(); + ss_ << ecf::string_constants::colon; ss_ << node->name(); ss_ << " state("; ss_ << NState::toString(node->state()); From 0f177f190a546b7130051c56a7b1a1d2ab9b1bdf Mon Sep 17 00:00:00 2001 From: Marcos Bento Date: Thu, 26 Mar 2026 14:37:53 +0000 Subject: [PATCH 04/90] refactor: Make 'path_separator' a constant instead of a function --- libs/core/src/ecflow/core/File.cpp | 4 ++-- libs/core/src/ecflow/core/NodePath.cpp | 4 ++-- libs/core/src/ecflow/core/Str.cpp | 4 ---- libs/core/src/ecflow/core/Str.hpp | 2 +- libs/node/src/ecflow/node/Defs.cpp | 2 +- libs/node/src/ecflow/node/NodeFind.cpp | 4 ++-- libs/server/src/ecflow/server/ServerEnvironment.cpp | 4 ++-- 7 files changed, 10 insertions(+), 14 deletions(-) diff --git a/libs/core/src/ecflow/core/File.cpp b/libs/core/src/ecflow/core/File.cpp index ec38dea21..7334ecb94 100644 --- a/libs/core/src/ecflow/core/File.cpp +++ b/libs/core/src/ecflow/core/File.cpp @@ -495,7 +495,7 @@ bool File::createMissingDirectories(const std::string& pathToFileOrDir) { // if original path had leading slash then add it here, to preserve path if (pathToFileOrDir[0] == '/') { - pathToCreate += Str::PATH_SEPARATOR(); + pathToCreate += ecf::string_constants::path_separator; } for (const auto& i : thePath) { @@ -506,7 +506,7 @@ bool File::createMissingDirectories(const std::string& pathToFileOrDir) { #endif fs::create_directory(pathToCreate); } - pathToCreate += Str::PATH_SEPARATOR(); + pathToCreate += ecf::string_constants::path_separator; } } else { diff --git a/libs/core/src/ecflow/core/NodePath.cpp b/libs/core/src/ecflow/core/NodePath.cpp index 37d58869b..8f126a876 100644 --- a/libs/core/src/ecflow/core/NodePath.cpp +++ b/libs/core/src/ecflow/core/NodePath.cpp @@ -16,7 +16,7 @@ using namespace ecf; void NodePath::split(const std::string& path, std::vector& thePath) { /// The path is of the form "/suite/family/task" - Str::split(path, thePath, Str::PATH_SEPARATOR()); + Str::split(path, thePath, ecf::string_constants::path_separator); } bool NodePath::extractHostPort(const std::string& path, std::string& host, std::string& port) { @@ -61,7 +61,7 @@ std::string NodePath::createPath(const std::vector& vec) { std::string ret; size_t size = vec.size(); for (size_t i = 0; i < size; i++) { - ret += Str::PATH_SEPARATOR(); + ret += ecf::string_constants::path_separator; ret += vec[i]; } return ret; diff --git a/libs/core/src/ecflow/core/Str.cpp b/libs/core/src/ecflow/core/Str.cpp index a76096e71..630ca608f 100644 --- a/libs/core/src/ecflow/core/Str.cpp +++ b/libs/core/src/ecflow/core/Str.cpp @@ -33,10 +33,6 @@ const std::string& Str::ROOT_PATH() { static std::string root_path = "/"; return root_path; } -const std::string& Str::PATH_SEPARATOR() { - static std::string path_sep = "/"; - return path_sep; -} const std::string& Str::STATE_CHANGE() { static std::string state_change = " state change "; diff --git a/libs/core/src/ecflow/core/Str.hpp b/libs/core/src/ecflow/core/Str.hpp index 179a6e2f9..9322b5b30 100644 --- a/libs/core/src/ecflow/core/Str.hpp +++ b/libs/core/src/ecflow/core/Str.hpp @@ -204,7 +204,6 @@ class Str { // Allows string to be returned by reference static const std::string& ROOT_PATH(); // "/" - static const std::string& PATH_SEPARATOR(); // "/" static const std::string& STATE_CHANGE(); @@ -223,6 +222,7 @@ namespace string_constants { inline const std::string empty = ""; +inline const std::string path_separator = "/"; inline const std::string colon = ":"; } // namespace string_constants diff --git a/libs/node/src/ecflow/node/Defs.cpp b/libs/node/src/ecflow/node/Defs.cpp index e2e5226db..e1d451ef7 100644 --- a/libs/node/src/ecflow/node/Defs.cpp +++ b/libs/node/src/ecflow/node/Defs.cpp @@ -907,7 +907,7 @@ node_ptr Defs::findAbsNode(const std::string& pathToNode) const { // This is 14% quicker than the previous algorithm, that split 'pathToNode' into a vector of strings first. node_ptr ret; bool first = false; - StringSplitter string_splitter(pathToNode, Str::PATH_SEPARATOR()); + StringSplitter string_splitter(pathToNode, ecf::string_constants::path_separator); while (!string_splitter.finished()) { std::string_view path_token = string_splitter.next(); if (!first) { diff --git a/libs/node/src/ecflow/node/NodeFind.cpp b/libs/node/src/ecflow/node/NodeFind.cpp index 8796d0b48..18dc1a8d2 100644 --- a/libs/node/src/ecflow/node/NodeFind.cpp +++ b/libs/node/src/ecflow/node/NodeFind.cpp @@ -693,7 +693,7 @@ findRelativeNode(const std::vector& theExtractedPath, node_ptr trig else { for (const std::string& s : theExtractedPath) { errorMsg += s; - errorMsg += Str::PATH_SEPARATOR(); + errorMsg += ecf::string_constants::path_separator; } } errorMsg += "' from node "; @@ -910,7 +910,7 @@ Node::findReferencedNode(const std::string& nodePath, const std::string& extern_ // search suites, with the remaining path in theExtractedPath std::string path; for (const auto& i : theExtractedPath) { - path += Str::PATH_SEPARATOR(); + path += ecf::string_constants::path_separator; path += i; } node_ptr fndNode = theDefs->findAbsNode(path); diff --git a/libs/server/src/ecflow/server/ServerEnvironment.cpp b/libs/server/src/ecflow/server/ServerEnvironment.cpp index 4f348917f..54b019ff6 100644 --- a/libs/server/src/ecflow/server/ServerEnvironment.cpp +++ b/libs/server/src/ecflow/server/ServerEnvironment.cpp @@ -131,7 +131,7 @@ void ServerEnvironment::init(const CommandLine& cl, const std::string& path_to_c if (ecf_checkpt_file_[0] != '/') { // Prepend with ECF_HOME std::string check_pt = ecf_home(); - check_pt += Str::PATH_SEPARATOR(); + check_pt += ecf::string_constants::path_separator; check_pt += ecf_checkpt_file_; ecf_checkpt_file_ = check_pt; } @@ -142,7 +142,7 @@ void ServerEnvironment::init(const CommandLine& cl, const std::string& path_to_c } if (ecf_backup_checkpt_file_[0] != '/') { std::string check_pt = ecf_home(); - check_pt += Str::PATH_SEPARATOR(); + check_pt += ecf::string_constants::path_separator; check_pt += ecf_backup_checkpt_file_; ecf_backup_checkpt_file_ = check_pt; } From adc23c196c6587afbcdcd804a6cf55774ae93424 Mon Sep 17 00:00:00 2001 From: Marcos Bento Date: Thu, 26 Mar 2026 15:24:31 +0000 Subject: [PATCH 05/90] refactor: Make node names a constant instead of a function --- libs/core/src/ecflow/core/Str.cpp | 17 ----------------- libs/core/src/ecflow/core/Str.hpp | 13 +++++++------ libs/node/src/ecflow/node/Alias.cpp | 2 +- libs/node/src/ecflow/node/EcfFile.cpp | 8 ++++---- libs/node/src/ecflow/node/Family.cpp | 2 +- libs/node/src/ecflow/node/Suite.cpp | 2 +- libs/node/src/ecflow/node/Task.cpp | 2 +- 7 files changed, 15 insertions(+), 31 deletions(-) diff --git a/libs/core/src/ecflow/core/Str.cpp b/libs/core/src/ecflow/core/Str.cpp index 630ca608f..8030c906f 100644 --- a/libs/core/src/ecflow/core/Str.cpp +++ b/libs/core/src/ecflow/core/Str.cpp @@ -39,23 +39,6 @@ const std::string& Str::STATE_CHANGE() { return state_change; } -const std::string& Str::TASK() { - static std::string task = "TASK"; - return task; -} -const std::string& Str::FAMILY() { - static std::string family = "FAMILY"; - return family; -} -const std::string& Str::SUITE() { - static std::string suite = "SUITE"; - return suite; -} -const std::string& Str::ALIAS() { - static std::string alias = "ALIAS"; - return alias; -} - const std::string& Str::DEFAULT_PORT_NUMBER() { static std::string port_number = "3141"; return port_number; diff --git a/libs/core/src/ecflow/core/Str.hpp b/libs/core/src/ecflow/core/Str.hpp index 9322b5b30..f732d2a48 100644 --- a/libs/core/src/ecflow/core/Str.hpp +++ b/libs/core/src/ecflow/core/Str.hpp @@ -203,15 +203,10 @@ class Str { static const char* SVR_CMD(); // Only for automatic check_pt // Allows string to be returned by reference - static const std::string& ROOT_PATH(); // "/" + static const std::string& ROOT_PATH(); // "/" static const std::string& STATE_CHANGE(); - static const std::string& TASK(); - static const std::string& FAMILY(); - static const std::string& SUITE(); - static const std::string& ALIAS(); - static const std::string& DEFAULT_PORT_NUMBER(); // "3141" static const std::string& LOCALHOST(); @@ -225,6 +220,12 @@ inline const std::string empty = ""; inline const std::string path_separator = "/"; inline const std::string colon = ":"; +inline const std::string task = "TASK"; +inline const std::string family = "FAMILY"; +inline const std::string family1 = "FAMILY1"; +inline const std::string suite = "SUITE"; +inline const std::string alias = "ALIAS"; + } // namespace string_constants } // namespace ecf diff --git a/libs/node/src/ecflow/node/Alias.cpp b/libs/node/src/ecflow/node/Alias.cpp index c9ef022d6..632318a5f 100644 --- a/libs/node/src/ecflow/node/Alias.cpp +++ b/libs/node/src/ecflow/node/Alias.cpp @@ -70,7 +70,7 @@ void Alias::requeue(Requeue_args& args) { } const std::string& Alias::debugType() const { - return ecf::Str::ALIAS(); + return ecf::string_constants::alias; } node_ptr Alias::removeChild(Node*) { diff --git a/libs/node/src/ecflow/node/EcfFile.cpp b/libs/node/src/ecflow/node/EcfFile.cpp index 39227327f..b45d0bf90 100644 --- a/libs/node/src/ecflow/node/EcfFile.cpp +++ b/libs/node/src/ecflow/node/EcfFile.cpp @@ -911,16 +911,16 @@ void EcfFile::get_used_variables(std::string& used_variables) const { } // We must use exact match, to avoid user variables like ESUITE,EFAMILY,ETASK - if (item.first == Str::TASK()) { + if (item.first == ecf::string_constants::task) { continue; } - if (item.first == Str::FAMILY()) { + if (item.first == ecf::string_constants::family) { continue; } - if (item.first == "FAMILY1") { + if (item.first == ecf::string_constants::family1) { continue; } - if (item.first == Str::SUITE()) { + if (item.first == ecf::string_constants::suite) { continue; } used_variables += item.first; diff --git a/libs/node/src/ecflow/node/Family.cpp b/libs/node/src/ecflow/node/Family.cpp index ed051c98a..44d2af239 100644 --- a/libs/node/src/ecflow/node/Family.cpp +++ b/libs/node/src/ecflow/node/Family.cpp @@ -102,7 +102,7 @@ void Family::read_state(const std::string& line, const std::vector& } const std::string& Family::debugType() const { - return ecf::Str::FAMILY(); + return ecf::string_constants::family; } void Family::collateChanges(DefsDelta& changes) const { diff --git a/libs/node/src/ecflow/node/Suite.cpp b/libs/node/src/ecflow/node/Suite.cpp index 25fb1f29c..b30eb0d57 100644 --- a/libs/node/src/ecflow/node/Suite.cpp +++ b/libs/node/src/ecflow/node/Suite.cpp @@ -338,7 +338,7 @@ void Suite::read_state(const std::string& line, const std::vector& } const std::string& Suite::debugType() const { - return ecf::Str::SUITE(); + return ecf::string_constants::suite; } void Suite::addClock(const ClockAttr& c, bool initialize_calendar) { diff --git a/libs/node/src/ecflow/node/Task.cpp b/libs/node/src/ecflow/node/Task.cpp index 6f31684cc..461f514c1 100644 --- a/libs/node/src/ecflow/node/Task.cpp +++ b/libs/node/src/ecflow/node/Task.cpp @@ -343,7 +343,7 @@ void Task::acceptVisitTraversor(ecf::NodeTreeVisitor& v) { } const std::string& Task::debugType() const { - return ecf::Str::TASK(); + return ecf::string_constants::task; } void Task::immediateChildren(std::vector& vec) const { From f74b57679674e14165356a564b2f3523b343acd0 Mon Sep 17 00:00:00 2001 From: Marcos Bento Date: Thu, 26 Mar 2026 15:31:40 +0000 Subject: [PATCH 06/90] refactor: Make white_list_file a constant instead of a function --- libs/core/src/ecflow/core/Host.cpp | 2 +- libs/core/src/ecflow/core/Str.cpp | 5 ----- libs/core/src/ecflow/core/Str.hpp | 2 ++ libs/server/src/ecflow/server/ServerEnvironment.cpp | 4 ++-- 4 files changed, 5 insertions(+), 8 deletions(-) diff --git a/libs/core/src/ecflow/core/Host.cpp b/libs/core/src/ecflow/core/Host.cpp index d8e8de078..e33a71492 100644 --- a/libs/core/src/ecflow/core/Host.cpp +++ b/libs/core/src/ecflow/core/Host.cpp @@ -64,7 +64,7 @@ std::string Host::ecf_backup_checkpt_file(const std::string& port) const { } std::string Host::ecf_lists_file(const std::string& port) const { - return prefix_host_and_port(port, Str::WHITE_LIST_FILE()); + return prefix_host_and_port(port, ecf::string_constants::white_list_file); } std::string Host::ecf_passwd_file(const std::string& port) const { diff --git a/libs/core/src/ecflow/core/Str.cpp b/libs/core/src/ecflow/core/Str.cpp index 8030c906f..efa8d7aa4 100644 --- a/libs/core/src/ecflow/core/Str.cpp +++ b/libs/core/src/ecflow/core/Str.cpp @@ -48,11 +48,6 @@ const std::string& Str::LOCALHOST() { return localhost; } -const std::string& Str::WHITE_LIST_FILE() { - static std::string WHITE_LIST_FILE = "ecf.lists"; - return WHITE_LIST_FILE; -} - const std::string& Str::ALPHANUMERIC_UNDERSCORE() { static std::string ALPHANUMERIC_UNDERSCORE = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"; return ALPHANUMERIC_UNDERSCORE; diff --git a/libs/core/src/ecflow/core/Str.hpp b/libs/core/src/ecflow/core/Str.hpp index f732d2a48..1032d0934 100644 --- a/libs/core/src/ecflow/core/Str.hpp +++ b/libs/core/src/ecflow/core/Str.hpp @@ -226,6 +226,8 @@ inline const std::string family1 = "FAMILY1"; inline const std::string suite = "SUITE"; inline const std::string alias = "ALIAS"; +inline const std::string white_list_file = "ecf.lists"; + } // namespace string_constants } // namespace ecf diff --git a/libs/server/src/ecflow/server/ServerEnvironment.cpp b/libs/server/src/ecflow/server/ServerEnvironment.cpp index 54b019ff6..f103517fa 100644 --- a/libs/server/src/ecflow/server/ServerEnvironment.cpp +++ b/libs/server/src/ecflow/server/ServerEnvironment.cpp @@ -147,7 +147,7 @@ void ServerEnvironment::init(const CommandLine& cl, const std::string& path_to_c ecf_backup_checkpt_file_ = check_pt; } - if (ecf_white_list_file_ == Str::WHITE_LIST_FILE()) { + if (ecf_white_list_file_ == ecf::string_constants::white_list_file) { ecf_white_list_file_ = host_name_.prefix_host_and_port(port, ecf_white_list_file_); } @@ -523,7 +523,7 @@ void ServerEnvironment::read_config_file(std::string& log_file_name, const std:: ("ECF_URL_BASE", po::value(&urlBase_)->default_value(Ecf::URL_BASE()), "Defines url base.") ("ECF_URL", po::value(&url_)->default_value(Ecf::URL()), "The default url.") ("ECF_MICRODEF", po::value(&ecf_micro_)->default_value(Ecf::MICRO()), "Preprocessor character for variable substitution and including files") - ("ECF_LISTS", po::value(&ecf_white_list_file_)->default_value(Str::WHITE_LIST_FILE()), "Path name to file the list valid users and their access rights") + ("ECF_LISTS", po::value(&ecf_white_list_file_)->default_value(ecf::string_constants::white_list_file), "Path name to file the list valid users and their access rights") ("ECF_PASSWD", po::value(&passwd_file)->default_value(std::string{AuthenticationService::default_passwd_file()}), "Path name to passwd file") ("ECF_CUSTOM_PASSWD", po::value(&custom_passwd_file)->default_value(std::string{AuthenticationService::default_custom_passwd_file()}), "Path name to custom passwd file, for user who don't use login name") ("ECF_TASK_THRESHOLD", po::value(&the_task_threshold)->default_value(JobProfiler::task_threshold_default()), "The defaults thresholds when profiling job generation") From 9d43a20eba34741ec80d5bc8ca1c0de6c524ccc7 Mon Sep 17 00:00:00 2001 From: Marcos Bento Date: Thu, 26 Mar 2026 15:47:07 +0000 Subject: [PATCH 07/90] refactor: Make localhost and default_port a constant instead of a function --- libs/base/src/ecflow/base/Openssl.cpp | 6 +++--- libs/client/src/ecflow/client/ClientEnvironment.cpp | 10 +++++----- libs/client/src/ecflow/client/ClientOptions.cpp | 4 ++-- libs/client/test/TestClientEnvironment.cpp | 9 +++++---- libs/client/test/TestPlugCmd.cpp | 6 ++++-- libs/client/test/harness/InvokeServer.hpp | 8 ++++---- libs/client/test/harness/SCPort.cpp | 12 +++++++----- libs/core/src/ecflow/core/Host.cpp | 2 +- libs/core/src/ecflow/core/PasswdFile.cpp | 2 +- libs/core/src/ecflow/core/Str.cpp | 9 --------- libs/core/src/ecflow/core/Str.hpp | 3 +++ libs/node/src/ecflow/node/NodeContainer.cpp | 4 ++-- libs/node/src/ecflow/node/ServerState.cpp | 6 +++--- libs/node/test/MyDefsFixture.hpp | 4 ++-- libs/server/test/TestServerEnvironment.cpp | 9 +++++---- libs/test/overall/harness/LocalServerLauncher.hpp | 6 ++---- libs/test/overall/harness/TestFixture.cpp | 8 ++++---- libs/udp/test/TestSupport.hpp | 6 +++--- 18 files changed, 56 insertions(+), 58 deletions(-) diff --git a/libs/base/src/ecflow/base/Openssl.cpp b/libs/base/src/ecflow/base/Openssl.cpp index e70662559..c52e9debd 100644 --- a/libs/base/src/ecflow/base/Openssl.cpp +++ b/libs/base/src/ecflow/base/Openssl.cpp @@ -36,7 +36,7 @@ std::string Openssl::selected_crt() const { } bool Openssl::enable_no_throw(std::string host, const std::string& port, const std::string& ecf_ssl_env) { - if (host == Str::LOCALHOST()) { + if (host == ecf::string_constants::localhost) { host = Host().name(); } @@ -73,7 +73,7 @@ bool Openssl::enable_no_throw(std::string host, const std::string& port, const s } void Openssl::enable(std::string host, const std::string& port) { - if (host == Str::LOCALHOST()) { + if (host == ecf::string_constants::localhost) { host = Host().name(); } @@ -87,7 +87,7 @@ void Openssl::enable_if_defined(std::string host, const std::string& port) { if (auto ecf_ssl = ecf::environment::fetch(ecf::environment::ECF_SSL); ecf_ssl) { std::string ecf_ssl_env = ecf_ssl.value(); - if (host == Str::LOCALHOST()) { + if (host == ecf::string_constants::localhost) { host = Host().name(); } diff --git a/libs/client/src/ecflow/client/ClientEnvironment.cpp b/libs/client/src/ecflow/client/ClientEnvironment.cpp index 421caf366..b835dc5f0 100644 --- a/libs/client/src/ecflow/client/ClientEnvironment.cpp +++ b/libs/client/src/ecflow/client/ClientEnvironment.cpp @@ -102,7 +102,7 @@ void ClientEnvironment::init() { // If no host specified default to local host and default port number if (host_vec_.empty()) { - host_vec_.emplace_back(ecf::Str::LOCALHOST(), ecf::Str::DEFAULT_PORT_NUMBER()); + host_vec_.emplace_back(ecf::string_constants::localhost, ecf::string_constants::default_port_number); } // Program options are read in last, and will override any previous setting @@ -250,7 +250,7 @@ std::string ClientEnvironment::hostSpecified() { } std::string ClientEnvironment::portSpecified() { - std::string specified_port = ecf::Str::DEFAULT_PORT_NUMBER(); + std::string specified_port = ecf::string_constants::default_port_number; ecf::environment::get(ecf::environment::ECF_PORT, specified_port); return specified_port; } @@ -310,8 +310,8 @@ void ClientEnvironment::read_environment_variables() { } /// Override the config settings *IF* any - std::string port = ecf::Str::DEFAULT_PORT_NUMBER(); - std::string host = ecf::Str::LOCALHOST(); + std::string port = ecf::string_constants::default_port_number; + std::string host = ecf::string_constants::localhost; if (!host_vec_.empty()) { const auto& selected = host_vec_[0]; // the first entry holds the selected host/port host = selected.first; @@ -336,7 +336,7 @@ void ClientEnvironment::read_environment_variables() { bool ClientEnvironment::parseHostsFile(std::string& errorMsg) { - std::string configured_port = host_vec_.empty() ? ecf::Str::DEFAULT_PORT_NUMBER() : host_vec_[0].second; + std::string configured_port = host_vec_.empty() ? ecf::string_constants::default_port_number : host_vec_[0].second; int port = ecf::convert_to(configured_port); try { diff --git a/libs/client/src/ecflow/client/ClientOptions.cpp b/libs/client/src/ecflow/client/ClientOptions.cpp index 7c6212795..2bc5c91dc 100644 --- a/libs/client/src/ecflow/client/ClientOptions.cpp +++ b/libs/client/src/ecflow/client/ClientOptions.cpp @@ -166,10 +166,10 @@ Cmd_ptr ClientOptions::parse(const CommandLine& cl, ClientEnvironment* env) cons port = env->portSpecified(); // get the environment variable ECF_PORT || Str::DEFAULT_PORT_NUMBER() } if (host.empty()) { - host = Str::LOCALHOST(); // if ECF_HOST not specified default to localhost + host = ecf::string_constants::localhost; // if ECF_HOST not specified default to localhost } if (port.empty()) { - port = Str::DEFAULT_PORT_NUMBER(); // if ECF_PORT not specified use default + port = ecf::string_constants::default_port_number; // if ECF_PORT not specified use default } env->set_host_port(host, port); } diff --git a/libs/client/test/TestClientEnvironment.cpp b/libs/client/test/TestClientEnvironment.cpp index 43bbcf562..18c9b7c02 100644 --- a/libs/client/test/TestClientEnvironment.cpp +++ b/libs/client/test/TestClientEnvironment.cpp @@ -34,7 +34,7 @@ BOOST_AUTO_TEST_CASE(test_client_environment_host_file_parsing) { // local host should be implicitly added to internal host list std::string the_host = ClientEnvironment::hostSpecified(); if (the_host.empty()) { - the_host = Str::LOCALHOST(); + the_host = ecf::string_constants::localhost; } std::vector expectedHost; @@ -85,7 +85,7 @@ BOOST_AUTO_TEST_CASE(test_client_environment_host_file_defaults) { // local host should be implicitly added to internal host list std::vector> expectedHost; - expectedHost.emplace_back(Str::LOCALHOST(), std::string("5111")); // here 5111 is job supplied port + expectedHost.emplace_back(ecf::string_constants::localhost, std::string("5111")); // here 5111 is job supplied port expectedHost.emplace_back(std::string("host1"), std::string("3142")); expectedHost.emplace_back(std::string("host2"), std::string("3141")); expectedHost.emplace_back(std::string("host3"), @@ -96,10 +96,11 @@ BOOST_AUTO_TEST_CASE(test_client_environment_host_file_defaults) { expectedHost.emplace_back(std::string("host6"), std::string("4081")); // Create the ClientEnvironment overriding the config & environment. To specify host and port - ClientEnvironment client_env(good_host_file, Str::LOCALHOST(), "5111"); + ClientEnvironment client_env(good_host_file, ecf::string_constants::localhost, "5111"); std::string home_host = client_env.host(); std::string home_port = client_env.port(); - BOOST_CHECK_MESSAGE(Str::LOCALHOST() == home_host && "5111" == home_port, "host host & port not as expected"); + BOOST_CHECK_MESSAGE(ecf::string_constants::localhost == home_host && "5111" == home_port, + "host host & port not as expected"); std::string host; size_t count = 0; diff --git a/libs/client/test/TestPlugCmd.cpp b/libs/client/test/TestPlugCmd.cpp index bfcd193c6..b86a8fbd1 100644 --- a/libs/client/test/TestPlugCmd.cpp +++ b/libs/client/test/TestPlugCmd.cpp @@ -382,8 +382,10 @@ BOOST_AUTO_TEST_CASE(test_server_plug_cmd) { BOOST_REQUIRE_MESSAGE(invokeServer2.server_started(), "Server failed to start on " << invokeServer2.host() << ":" << invokeServer2.port()); - test_plug_on_multiple_server( - ClientEnvironment::hostSpecified(), ClientEnvironment::portSpecified(), Str::LOCALHOST(), port2); + test_plug_on_multiple_server(ClientEnvironment::hostSpecified(), + ClientEnvironment::portSpecified(), + ecf::string_constants::localhost, + port2); } } diff --git a/libs/client/test/harness/InvokeServer.hpp b/libs/client/test/harness/InvokeServer.hpp index 375848869..5736104a0 100644 --- a/libs/client/test/harness/InvokeServer.hpp +++ b/libs/client/test/harness/InvokeServer.hpp @@ -32,7 +32,7 @@ class InvokeServer { InvokeServer& operator=(InvokeServer&&) = delete; explicit InvokeServer(const std::string& msg, - const std::string& port = ecf::Str::DEFAULT_PORT_NUMBER(), + const std::string& port = ecf::string_constants::default_port_number, bool disable_job_generation = false, bool remove_checkpt_file_before_server_start = true, bool remove_checkpt_file_after_server_exit = true) @@ -88,7 +88,7 @@ class InvokeServer { // This will also remove the generated files. // Will only terminate local server, host_ is *EMPTY* for local server, using two constructors above if (host_.empty()) { - doEnd(ecf::Str::LOCALHOST(), + doEnd(ecf::string_constants::localhost, port_, remove_checkpt_file_after_server_exit_, remove_log_file_after_server_exit_); @@ -98,7 +98,7 @@ class InvokeServer { const std::string& port() const { return port_; } const std::string& host() const { if (host_.empty()) { - return ecf::Str::LOCALHOST(); + return ecf::string_constants::localhost; } return host_; } @@ -146,7 +146,7 @@ class InvokeServer { (void)system(theServerInvokePath.c_str()); // Allow time for server process to kick in. - ClientInvoker theClient(ecf::Str::LOCALHOST(), port); + ClientInvoker theClient(ecf::string_constants::localhost, port); if (theClient.wait_for_server_reply()) { server_started = true; if (!msg.empty()) { diff --git a/libs/client/test/harness/SCPort.cpp b/libs/client/test/harness/SCPort.cpp index 56a967216..b8f7c4d2d 100644 --- a/libs/client/test/harness/SCPort.cpp +++ b/libs/client/test/harness/SCPort.cpp @@ -59,7 +59,7 @@ std::string SCPort::next() { std::cout << " ECF_HOST('" << host << "')"; } - if (host == Str::LOCALHOST()) { + if (host == ecf::string_constants::localhost) { if (auto port = ecf::environment::fetch(ecf::environment::ECF_PORT); port) { if (!port.value().empty()) { if (debug) { @@ -117,9 +117,10 @@ bool SCPort::is_free_port(int port, bool debug) { const auto the_port = ecf::convert_to(port); try { if (debug) { - std::cout << " Trying to connect to server on '" << Str::LOCALHOST() << ":" << the_port << "'\n"; + std::cout << " Trying to connect to server on '" << ecf::string_constants::localhost << ":" << the_port + << "'\n"; } - client.set_host_port(Str::LOCALHOST(), the_port); + client.set_host_port(ecf::string_constants::localhost, the_port); client.pingServer(); if (debug) { std::cout << " Connected to server on port " << the_port << ". Returning FALSE\n"; @@ -172,9 +173,10 @@ std::string SCPort::find_free_port(int seed_port_number, bool debug) { free_port = ecf::convert_to(the_port); try { if (debug) { - std::cout << " Trying to connect to server on '" << Str::LOCALHOST() << ":" << free_port << "'\n"; + std::cout << " Trying to connect to server on '" << ecf::string_constants::localhost << ":" + << free_port << "'\n"; } - client.set_host_port(Str::LOCALHOST(), free_port); + client.set_host_port(ecf::string_constants::localhost, free_port); client.pingServer(); if (debug) { std::cout << " Connected to server on port " << free_port << " trying next port\n"; diff --git a/libs/core/src/ecflow/core/Host.cpp b/libs/core/src/ecflow/core/Host.cpp index e33a71492..ba2565c0d 100644 --- a/libs/core/src/ecflow/core/Host.cpp +++ b/libs/core/src/ecflow/core/Host.cpp @@ -27,7 +27,7 @@ Host::Host() { Host::Host(const std::string& host) : the_host_name_(host) { - if (the_host_name_ == Str::LOCALHOST()) { + if (the_host_name_ == ecf::string_constants::localhost) { get_host_name(); } } diff --git a/libs/core/src/ecflow/core/PasswdFile.cpp b/libs/core/src/ecflow/core/PasswdFile.cpp index 47009620a..9d5f1ea82 100644 --- a/libs/core/src/ecflow/core/PasswdFile.cpp +++ b/libs/core/src/ecflow/core/PasswdFile.cpp @@ -309,7 +309,7 @@ bool PasswdFile::createWithAccess(const std::string& pathToFile, line.clear(); line += username; line += " "; - line += Str::LOCALHOST(); + line += ecf::string_constants::localhost; line += " "; line += port; line += " "; diff --git a/libs/core/src/ecflow/core/Str.cpp b/libs/core/src/ecflow/core/Str.cpp index efa8d7aa4..4c9ea08c1 100644 --- a/libs/core/src/ecflow/core/Str.cpp +++ b/libs/core/src/ecflow/core/Str.cpp @@ -39,15 +39,6 @@ const std::string& Str::STATE_CHANGE() { return state_change; } -const std::string& Str::DEFAULT_PORT_NUMBER() { - static std::string port_number = "3141"; - return port_number; -} -const std::string& Str::LOCALHOST() { - static std::string localhost = "localhost"; - return localhost; -} - const std::string& Str::ALPHANUMERIC_UNDERSCORE() { static std::string ALPHANUMERIC_UNDERSCORE = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"; return ALPHANUMERIC_UNDERSCORE; diff --git a/libs/core/src/ecflow/core/Str.hpp b/libs/core/src/ecflow/core/Str.hpp index 1032d0934..425464c6e 100644 --- a/libs/core/src/ecflow/core/Str.hpp +++ b/libs/core/src/ecflow/core/Str.hpp @@ -226,6 +226,9 @@ inline const std::string family1 = "FAMILY1"; inline const std::string suite = "SUITE"; inline const std::string alias = "ALIAS"; +inline const std::string default_port_number = "3141"; +inline const std::string localhost = "localhost"; + inline const std::string white_list_file = "ecf.lists"; } // namespace string_constants diff --git a/libs/node/src/ecflow/node/NodeContainer.cpp b/libs/node/src/ecflow/node/NodeContainer.cpp index 575e6e7fd..10202ddb4 100644 --- a/libs/node/src/ecflow/node/NodeContainer.cpp +++ b/libs/node/src/ecflow/node/NodeContainer.cpp @@ -1090,12 +1090,12 @@ std::string NodeContainer::archive_path() const { Str::replaceall(the_archive_file_name, "/", ":"); // we use ':' since it is not allowed in the node names the_archive_file_name += ".check"; - std::string port = Str::DEFAULT_PORT_NUMBER(); + std::string port = ecf::string_constants::default_port_number; Defs* the_defs = defs(); if (the_defs) { port = the_defs->server_state().find_variable(ecf::environment::ECF_PORT); if (port.empty()) { - port = Str::DEFAULT_PORT_NUMBER(); + port = ecf::string_constants::default_port_number; } } Host host; diff --git a/libs/node/src/ecflow/node/ServerState.cpp b/libs/node/src/ecflow/node/ServerState.cpp index 5a4e2130d..9e7f9e08d 100644 --- a/libs/node/src/ecflow/node/ServerState.cpp +++ b/libs/node/src/ecflow/node/ServerState.cpp @@ -25,7 +25,7 @@ using namespace ecf; // o the jobGeneration_ is set ServerState::ServerState() : server_state_(default_state()) { - setup_default_env(Str::DEFAULT_PORT_NUMBER()); + setup_default_env(ecf::string_constants::default_port_number); } ServerState::ServerState(const std::string& port) @@ -425,7 +425,7 @@ void ServerState::set_state(SState::State s) { void ServerState::setup_default_env(const std::string& port) { // This environment is required for testing in the absence of the server. // When the defs file is begun in the server this environment get *overridden* - hostPort_ = std::make_pair(Str::LOCALHOST(), port); + hostPort_ = std::make_pair(ecf::string_constants::localhost, port); setup_default_server_variables(server_variables_, port); } @@ -468,7 +468,7 @@ void ServerState::setup_default_server_variables(std::vector& server_v // The server sets these variable for use by the client. i.e when creating the jobs // The clients then uses them to communicate with the server. server_variables.emplace_back(ecf::environment::ECF_PORT, port); - server_variables.emplace_back(ecf::environment::ECF_HOST, Str::LOCALHOST()); + server_variables.emplace_back(ecf::environment::ECF_HOST, ecf::string_constants::localhost); } /// determines why the node is not running. diff --git a/libs/node/test/MyDefsFixture.hpp b/libs/node/test/MyDefsFixture.hpp index a04742b35..71847b260 100644 --- a/libs/node/test/MyDefsFixture.hpp +++ b/libs/node/test/MyDefsFixture.hpp @@ -43,7 +43,7 @@ struct MyDefsFixture { - explicit MyDefsFixture(const std::string& port = ecf::Str::DEFAULT_PORT_NUMBER()) + explicit MyDefsFixture(const std::string& port = ecf::string_constants::default_port_number) : defsfile_(port) { suite_ptr suite = create_suite(); @@ -68,7 +68,7 @@ struct MyDefsFixture const Defs& fixtureDefsFile() const { return defsfile_; } - defs_ptr create_defs(const std::string& port = ecf::Str::DEFAULT_PORT_NUMBER()) const { + defs_ptr create_defs(const std::string& port = ecf::string_constants::default_port_number) const { defs_ptr defs = Defs::create(port); diff --git a/libs/server/test/TestServerEnvironment.cpp b/libs/server/test/TestServerEnvironment.cpp index f6d45f84e..e1d2151f4 100644 --- a/libs/server/test/TestServerEnvironment.cpp +++ b/libs/server/test/TestServerEnvironment.cpp @@ -37,7 +37,7 @@ BOOST_AUTO_TEST_CASE(test_server_environment_ecfinterval) { ECF_NAME_THIS_TEST(); // ecflow server interval is valid for range [1-60] - std::string port = Str::DEFAULT_PORT_NUMBER(); + std::string port = ecf::string_constants::default_port_number; for (int i = -10; i < 70; ++i) { std::string errorMsg; std::string argument = "--ecfinterval=" + ecf::convert_to(i); @@ -193,8 +193,9 @@ BOOST_AUTO_TEST_CASE(test_server_config_file) { continue; } if (std::string("ECF_PORT") == p.first && !ecf::environment::has("ECF_PORT")) { - BOOST_CHECK_MESSAGE(p.second == Str::DEFAULT_PORT_NUMBER(), - "for ECF_PORT expected " << Str::DEFAULT_PORT_NUMBER() << " but found " << p.second); + BOOST_CHECK_MESSAGE(p.second == ecf::string_constants::default_port_number, + "for ECF_PORT expected " << ecf::string_constants::default_port_number << " but found " + << p.second); continue; } if (std::string("ECF_CHECKINTERVAL") == p.first) { @@ -261,7 +262,7 @@ BOOST_AUTO_TEST_CASE(test_server_config_file) { if (std::string("ECF_PASSWD") == p.first) { Host host; - std::string port = Str::DEFAULT_PORT_NUMBER(); + std::string port = ecf::string_constants::default_port_number; if (ecf::environment::has("ECF_PORT")) { port = ecf::environment::get("ECF_PORT"); } diff --git a/libs/test/overall/harness/LocalServerLauncher.hpp b/libs/test/overall/harness/LocalServerLauncher.hpp index 727631391..2b4926cae 100644 --- a/libs/test/overall/harness/LocalServerLauncher.hpp +++ b/libs/test/overall/harness/LocalServerLauncher.hpp @@ -57,12 +57,10 @@ class LocalServerLauncher { } private: - std::string host_ = ecf::Str::LOCALHOST(); - std::string port_ = default_port(); + std::string host_ = ecf::string_constants::localhost; + std::string port_ = ecf::string_constants::default_port_number; bool use_http_ = false; int submission_interval_ = job_submission_interval(); - - static std::string default_port() { return "3141"; } }; #endif /* ecflow_test_harness_LocalServerLauncher_HPP */ diff --git a/libs/test/overall/harness/TestFixture.cpp b/libs/test/overall/harness/TestFixture.cpp index 6e65c1008..0843fafaf 100644 --- a/libs/test/overall/harness/TestFixture.cpp +++ b/libs/test/overall/harness/TestFixture.cpp @@ -44,15 +44,15 @@ using namespace ecf; namespace /* anonymous */ { bool is_external_server_running_remotelly(std::string_view host) { - return !host.empty() && host != Str::LOCALHOST(); + return !host.empty() && host != ecf::string_constants::localhost; } bool is_external_server_running_locally(std::string_view host) { - return !host.empty() && host == Str::LOCALHOST(); + return !host.empty() && host == ecf::string_constants::localhost; } bool is_local_server(std::string_view host) { - return host.empty() || host == Str::LOCALHOST(); + return host.empty() || host == ecf::string_constants::localhost; } } // namespace @@ -207,7 +207,7 @@ void TestFixture::init(const std::string& project_test_dir) { // Going to perform the tests using a server launched as part of the test setup. // Update ClientInvoker with local host and port - host_ = Str::LOCALHOST(); + host_ = ecf::string_constants::localhost; port_ = ecf::SCPort::find_available_port(port_); client().set_host_port(host_, port_); diff --git a/libs/udp/test/TestSupport.hpp b/libs/udp/test/TestSupport.hpp index 33bc75484..466d1b3e0 100644 --- a/libs/udp/test/TestSupport.hpp +++ b/libs/udp/test/TestSupport.hpp @@ -79,7 +79,7 @@ class MockServer : public BaseMockServer { : BaseMockServer(ecf::Host{}.name(), port) {} void load_definition(const std::string& defs) const { - ClientInvoker client(ecf::Str::LOCALHOST(), port()); + ClientInvoker client(ecf::string_constants::localhost, port()); try { BOOST_REQUIRE_MESSAGE(fs::exists(defs), "definitions file doesn't exist at: " + defs); auto error = client.loadDefs(defs); @@ -111,7 +111,7 @@ class MockServer : public BaseMockServer { private: node_ptr get_node_at(const std::string& path) const { ECF_TEST_DBG(<< " Creating ecflow_client connected to " << host() << ":" << port()); - ClientInvoker client(ecf::Str::LOCALHOST(), port()); + ClientInvoker client(ecf::string_constants::localhost, port()); // load all definitions std::shared_ptr defs = nullptr; @@ -148,7 +148,7 @@ class MockServer : public BaseMockServer { auto server = Process(invoke_command, {"--port", std::to_string(port), "-d"}); - ClientInvoker client(ecf::Str::LOCALHOST(), port); + ClientInvoker client(ecf::string_constants::localhost, port); if (!client.wait_for_server_reply(5)) { BOOST_REQUIRE_MESSAGE(false, "could not launch ecflow server"); } From d0176a6bfe174bdab77f56393ba8fc14b4cfa67f Mon Sep 17 00:00:00 2001 From: Marcos Bento Date: Thu, 26 Mar 2026 15:52:53 +0000 Subject: [PATCH 08/90] refactor: Remove unused 'state_change' function --- libs/core/src/ecflow/core/Str.cpp | 5 ----- libs/core/src/ecflow/core/Str.hpp | 7 ------- 2 files changed, 12 deletions(-) diff --git a/libs/core/src/ecflow/core/Str.cpp b/libs/core/src/ecflow/core/Str.cpp index 4c9ea08c1..d6628122d 100644 --- a/libs/core/src/ecflow/core/Str.cpp +++ b/libs/core/src/ecflow/core/Str.cpp @@ -34,11 +34,6 @@ const std::string& Str::ROOT_PATH() { return root_path; } -const std::string& Str::STATE_CHANGE() { - static std::string state_change = " state change "; - return state_change; -} - const std::string& Str::ALPHANUMERIC_UNDERSCORE() { static std::string ALPHANUMERIC_UNDERSCORE = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"; return ALPHANUMERIC_UNDERSCORE; diff --git a/libs/core/src/ecflow/core/Str.hpp b/libs/core/src/ecflow/core/Str.hpp index 425464c6e..bb08d591a 100644 --- a/libs/core/src/ecflow/core/Str.hpp +++ b/libs/core/src/ecflow/core/Str.hpp @@ -204,13 +204,6 @@ class Str { // Allows string to be returned by reference static const std::string& ROOT_PATH(); // "/" - - static const std::string& STATE_CHANGE(); - - static const std::string& DEFAULT_PORT_NUMBER(); // "3141" - static const std::string& LOCALHOST(); - - static const std::string& WHITE_LIST_FILE(); }; namespace string_constants { From fd0625efd45d8eaf4257704c74080f37a04b5d2b Mon Sep 17 00:00:00 2001 From: Marcos Bento Date: Thu, 26 Mar 2026 16:05:37 +0000 Subject: [PATCH 09/90] refactor: Make commands a constant instead of a function --- Viewer/libViewer/src/LogLoadData.cpp | 12 ++++++------ libs/base/src/ecflow/base/Gnuplot.cpp | 4 ++-- libs/base/src/ecflow/base/cts/task/AbortCmd.cpp | 2 +- libs/base/src/ecflow/base/cts/task/CompleteCmd.cpp | 2 +- libs/base/src/ecflow/base/cts/task/CtsWaitCmd.cpp | 2 +- libs/base/src/ecflow/base/cts/task/EventCmd.cpp | 2 +- libs/base/src/ecflow/base/cts/task/InitCmd.cpp | 2 +- libs/base/src/ecflow/base/cts/task/LabelCmd.cpp | 2 +- libs/base/src/ecflow/base/cts/task/MeterCmd.cpp | 2 +- libs/base/src/ecflow/base/cts/task/QueueCmd.cpp | 2 +- libs/core/src/ecflow/core/Str.cpp | 13 ------------- libs/core/src/ecflow/core/Str.hpp | 8 ++++---- libs/server/src/ecflow/server/CheckPtSaver.cpp | 2 +- .../simulator/src/ecflow/simulator/Simulator.cpp | 6 +++--- 14 files changed, 24 insertions(+), 37 deletions(-) diff --git a/Viewer/libViewer/src/LogLoadData.cpp b/Viewer/libViewer/src/LogLoadData.cpp index 83f6d2818..0b8539a8b 100644 --- a/Viewer/libViewer/src/LogLoadData.cpp +++ b/Viewer/libViewer/src/LogLoadData.cpp @@ -1083,10 +1083,10 @@ void LogLoadData::loadLogFileCore(const std::string& logFile, bool child_cmd = false; bool user_cmd = false; - if (line.find(ecf::Str::CHILD_CMD()) != std::string::npos) { + if (line.find(ecf::string_constants::child_cmd) != std::string::npos) { child_cmd = true; } - else if (line.find(ecf::Str::USER_CMD()) != std::string::npos) { + else if (line.find(ecf::string_constants::user_cmd) != std::string::npos) { user_cmd = true; } @@ -1151,10 +1151,10 @@ void LogLoadData::loadLogFileCore(const std::string& logFile, line = items[i]; child_cmd = false; user_cmd = false; - if (line.find(ecf::Str::CHILD_CMD()) != std::string::npos) { + if (line.find(ecf::string_constants::child_cmd) != std::string::npos) { child_cmd = true; } - else if (line.find(ecf::Str::USER_CMD()) != std::string::npos) { + else if (line.find(ecf::string_constants::user_cmd) != std::string::npos) { user_cmd = true; if (i > 0 && line.find("--sync") != std::string::npos) { continue; @@ -1243,10 +1243,10 @@ std::streamoff LogLoadData::getStartPos(const std::string& logFile, int numOfRow bool child_cmd = false; bool user_cmd = false; - if (line.find(ecf::Str::CHILD_CMD()) != std::string::npos) { + if (line.find(ecf::string_constants::child_cmd) != std::string::npos) { child_cmd = true; } - else if (line.find(ecf::Str::USER_CMD()) != std::string::npos) { + else if (line.find(ecf::string_constants::user_cmd) != std::string::npos) { user_cmd = true; } diff --git a/libs/base/src/ecflow/base/Gnuplot.cpp b/libs/base/src/ecflow/base/Gnuplot.cpp index a85415bc8..c72472dbc 100644 --- a/libs/base/src/ecflow/base/Gnuplot.cpp +++ b/libs/base/src/ecflow/base/Gnuplot.cpp @@ -125,10 +125,10 @@ std::string Gnuplot::create_gnuplot_file(std::vector& suite_vec, cons bool child_cmd = false; bool user_cmd = false; - if (line.find(Str::CHILD_CMD()) != std::string::npos) { + if (line.find(ecf::string_constants::child_cmd) != std::string::npos) { child_cmd = true; } - else if (line.find(Str::USER_CMD()) != std::string::npos) { + else if (line.find(ecf::string_constants::user_cmd) != std::string::npos) { user_cmd = true; } if (!child_cmd && !user_cmd) { diff --git a/libs/base/src/ecflow/base/cts/task/AbortCmd.cpp b/libs/base/src/ecflow/base/cts/task/AbortCmd.cpp index 1e8b1676e..72c3cff03 100644 --- a/libs/base/src/ecflow/base/cts/task/AbortCmd.cpp +++ b/libs/base/src/ecflow/base/cts/task/AbortCmd.cpp @@ -43,7 +43,7 @@ AbortCmd::AbortCmd(const std::string& pathToTask, } void AbortCmd::print(std::string& os) const { - os += Str::CHILD_CMD(); + os += ecf::string_constants::child_cmd; os += "abort "; os += path_to_node(); os += " "; diff --git a/libs/base/src/ecflow/base/cts/task/CompleteCmd.cpp b/libs/base/src/ecflow/base/cts/task/CompleteCmd.cpp index 1227d725a..cb0adb666 100644 --- a/libs/base/src/ecflow/base/cts/task/CompleteCmd.cpp +++ b/libs/base/src/ecflow/base/cts/task/CompleteCmd.cpp @@ -25,7 +25,7 @@ using namespace ecf; void CompleteCmd::print(std::string& os) const { - os += Str::CHILD_CMD(); + os += ecf::string_constants::child_cmd; os += "complete "; os += path_to_node(); if (!var_to_del_.empty()) { diff --git a/libs/base/src/ecflow/base/cts/task/CtsWaitCmd.cpp b/libs/base/src/ecflow/base/cts/task/CtsWaitCmd.cpp index ab79a4956..9a9e46121 100644 --- a/libs/base/src/ecflow/base/cts/task/CtsWaitCmd.cpp +++ b/libs/base/src/ecflow/base/cts/task/CtsWaitCmd.cpp @@ -44,7 +44,7 @@ CtsWaitCmd::CtsWaitCmd(const std::string& pathToTask, } void CtsWaitCmd::print(std::string& os) const { - os += Str::CHILD_CMD(); + os += ecf::string_constants::child_cmd; os += "wait "; os += expression_; os += " "; diff --git a/libs/base/src/ecflow/base/cts/task/EventCmd.cpp b/libs/base/src/ecflow/base/cts/task/EventCmd.cpp index 81714af45..dce9c21f9 100644 --- a/libs/base/src/ecflow/base/cts/task/EventCmd.cpp +++ b/libs/base/src/ecflow/base/cts/task/EventCmd.cpp @@ -48,7 +48,7 @@ ecf::authorisation_t EventCmd::authorise(AbstractServer& server) const { } void EventCmd::print(std::string& os) const { - os += Str::CHILD_CMD(); + os += ecf::string_constants::child_cmd; os += "event "; os += name_; os += " "; diff --git a/libs/base/src/ecflow/base/cts/task/InitCmd.cpp b/libs/base/src/ecflow/base/cts/task/InitCmd.cpp index 162cd08c0..ae4589043 100644 --- a/libs/base/src/ecflow/base/cts/task/InitCmd.cpp +++ b/libs/base/src/ecflow/base/cts/task/InitCmd.cpp @@ -27,7 +27,7 @@ using namespace ecf; void InitCmd::print(std::string& os) const { - os += Str::CHILD_CMD(); + os += ecf::string_constants::child_cmd; os += "init "; os += path_to_node(); if (!var_to_add_.empty()) { diff --git a/libs/base/src/ecflow/base/cts/task/LabelCmd.cpp b/libs/base/src/ecflow/base/cts/task/LabelCmd.cpp index 50024db28..bc0a34849 100644 --- a/libs/base/src/ecflow/base/cts/task/LabelCmd.cpp +++ b/libs/base/src/ecflow/base/cts/task/LabelCmd.cpp @@ -48,7 +48,7 @@ ecf::authorisation_t LabelCmd::authorise(AbstractServer& server) const { } void LabelCmd::print(std::string& os) const { - os += Str::CHILD_CMD(); + os += ecf::string_constants::child_cmd; os += "label "; os += name_; os += " '"; diff --git a/libs/base/src/ecflow/base/cts/task/MeterCmd.cpp b/libs/base/src/ecflow/base/cts/task/MeterCmd.cpp index 69337e1d5..7d8fff5cc 100644 --- a/libs/base/src/ecflow/base/cts/task/MeterCmd.cpp +++ b/libs/base/src/ecflow/base/cts/task/MeterCmd.cpp @@ -49,7 +49,7 @@ ecf::authorisation_t MeterCmd::authorise(AbstractServer& server) const { } void MeterCmd::print(std::string& os) const { - os += Str::CHILD_CMD(); + os += ecf::string_constants::child_cmd; os += "meter "; os += name_; os += " "; diff --git a/libs/base/src/ecflow/base/cts/task/QueueCmd.cpp b/libs/base/src/ecflow/base/cts/task/QueueCmd.cpp index 7f09f86f3..3eafd67d0 100644 --- a/libs/base/src/ecflow/base/cts/task/QueueCmd.cpp +++ b/libs/base/src/ecflow/base/cts/task/QueueCmd.cpp @@ -56,7 +56,7 @@ ecf::authorisation_t QueueCmd::authorise(AbstractServer& server) const { } void QueueCmd::print(std::string& os) const { - os += Str::CHILD_CMD(); + os += ecf::string_constants::child_cmd; os += TaskApi::queue_arg(); os += " "; os += name_; diff --git a/libs/core/src/ecflow/core/Str.cpp b/libs/core/src/ecflow/core/Str.cpp index d6628122d..05f34bd06 100644 --- a/libs/core/src/ecflow/core/Str.cpp +++ b/libs/core/src/ecflow/core/Str.cpp @@ -16,19 +16,6 @@ namespace ecf { const char* VALID_NODE_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_."; -const char* Str::CHILD_CMD() { - static const char* CHILD_CMD = "chd:"; - return CHILD_CMD; -} -const char* Str::USER_CMD() { - static const char* USER_CMD = "--"; - return USER_CMD; -} -const char* Str::SVR_CMD() { - static const char* SVR_CMD = "svr:"; - return SVR_CMD; -} // Only for automatic check_pt - const std::string& Str::ROOT_PATH() { static std::string root_path = "/"; return root_path; diff --git a/libs/core/src/ecflow/core/Str.hpp b/libs/core/src/ecflow/core/Str.hpp index bb08d591a..671b83301 100644 --- a/libs/core/src/ecflow/core/Str.hpp +++ b/libs/core/src/ecflow/core/Str.hpp @@ -198,16 +198,16 @@ class Str { // returns a static string of numerics chars static const std::string& NUMERIC(); - static const char* CHILD_CMD(); - static const char* USER_CMD(); - static const char* SVR_CMD(); // Only for automatic check_pt - // Allows string to be returned by reference static const std::string& ROOT_PATH(); // "/" }; namespace string_constants { +inline const std::string child_cmd = "chd:"; +inline const std::string user_cmd = "--"; +inline const std::string server_cmd = "svr:"; // Only for automatic check_pt + inline const std::string empty = ""; inline const std::string path_separator = "/"; diff --git a/libs/server/src/ecflow/server/CheckPtSaver.cpp b/libs/server/src/ecflow/server/CheckPtSaver.cpp index fba9b48c4..7afa2ca44 100644 --- a/libs/server/src/ecflow/server/CheckPtSaver.cpp +++ b/libs/server/src/ecflow/server/CheckPtSaver.cpp @@ -109,7 +109,7 @@ bool CheckPtSaver::explicitSave(bool from_server) const { if (from_server) { // Create new time stamp otherwise we end up using the time stamp from the last command Log::instance()->cache_time_stamp(); - std::string msg = Str::SVR_CMD(); + std::string msg = ecf::string_constants::server_cmd; msg += CtsApi::checkPtDefsArg(); log(Log::MSG, MESSAGE(msg << " in " << timer.duration() << " seconds")); } diff --git a/libs/test/simulator/src/ecflow/simulator/Simulator.cpp b/libs/test/simulator/src/ecflow/simulator/Simulator.cpp index 01610abc4..0d40eb020 100644 --- a/libs/test/simulator/src/ecflow/simulator/Simulator.cpp +++ b/libs/test/simulator/src/ecflow/simulator/Simulator.cpp @@ -288,7 +288,7 @@ bool Simulator::doJobSubmission(Defs& theDefs, std::string& errorMsg) const { } msg.clear(); - msg += Str::CHILD_CMD(); + msg += ecf::string_constants::child_cmd; msg += "event "; msg += event.name_or_number(); msg += " "; @@ -314,7 +314,7 @@ bool Simulator::doJobSubmission(Defs& theDefs, std::string& errorMsg) const { meter.set_value(meter.value() + 1); msg.clear(); - msg += Str::CHILD_CMD(); + msg += ecf::string_constants::child_cmd; msg += "meter "; msg += meter.name(); msg += " "; @@ -366,7 +366,7 @@ bool Simulator::update_for_queues(Submittable* t, } if (queue.used_in_trigger()) { msg.clear(); - msg += Str::CHILD_CMD(); + msg += ecf::string_constants::child_cmd; msg += "queue "; msg += queue.name(); msg += " complete"; From 77d333a3bb4227e06ec7e8f086d2dd2853eded44 Mon Sep 17 00:00:00 2001 From: Marcos Bento Date: Thu, 26 Mar 2026 16:15:48 +0000 Subject: [PATCH 10/90] refactor: Make char classes a constants instead of a function --- libs/attribute/src/ecflow/attribute/NodeAttr.cpp | 2 +- libs/core/src/ecflow/core/PasswdFile.cpp | 2 +- libs/core/src/ecflow/core/Str.cpp | 16 +++------------- libs/core/src/ecflow/core/Str.hpp | 11 +++++------ libs/core/src/ecflow/core/WhiteListFile.cpp | 2 +- libs/core/test/TestStr.cpp | 4 ++-- libs/node/src/ecflow/node/Node.cpp | 2 +- libs/node/src/ecflow/node/NodeChange.cpp | 4 ++-- libs/node/src/ecflow/node/NodeFind.cpp | 2 +- libs/node/src/ecflow/node/parser/EventParser.cpp | 2 +- 10 files changed, 18 insertions(+), 29 deletions(-) diff --git a/libs/attribute/src/ecflow/attribute/NodeAttr.cpp b/libs/attribute/src/ecflow/attribute/NodeAttr.cpp index f995e341f..6fc070ad9 100644 --- a/libs/attribute/src/ecflow/attribute/NodeAttr.cpp +++ b/libs/attribute/src/ecflow/attribute/NodeAttr.cpp @@ -75,7 +75,7 @@ Event::Event(const std::string& eventName, bool iv) // which then did *not* load. // // Test for numeric, and then casting, is ****faster***** than relying on exception alone - if (eventName.find_first_of(Str::NUMERIC()) == 0) { + if (eventName.find_first_of(ecf::string_constants::numeric_chars) == 0) { try { number_ = ecf::convert_to(eventName); n_.clear(); diff --git a/libs/core/src/ecflow/core/PasswdFile.cpp b/libs/core/src/ecflow/core/PasswdFile.cpp index 9d5f1ea82..d69b1053a 100644 --- a/libs/core/src/ecflow/core/PasswdFile.cpp +++ b/libs/core/src/ecflow/core/PasswdFile.cpp @@ -210,7 +210,7 @@ bool PasswdFile::authenticate(const std::string& user, const std::string& passwd bool PasswdFile::validateVersionNumber(const std::string& line, std::string& errorMsg) const { // Expect 4.5.0 // If first character is NUMERIC and we have dots - bool firstCharIsNumeric = Str::NUMERIC().find(line[0], 0) != std::string::npos; + bool firstCharIsNumeric = ecf::string_constants::numeric_chars.find(line[0], 0) != std::string::npos; if (firstCharIsNumeric && line.find(".") != std::string::npos) { std::vector versionNumberTokens; diff --git a/libs/core/src/ecflow/core/Str.cpp b/libs/core/src/ecflow/core/Str.cpp index 05f34bd06..80d29b33a 100644 --- a/libs/core/src/ecflow/core/Str.cpp +++ b/libs/core/src/ecflow/core/Str.cpp @@ -21,16 +21,6 @@ const std::string& Str::ROOT_PATH() { return root_path; } -const std::string& Str::ALPHANUMERIC_UNDERSCORE() { - static std::string ALPHANUMERIC_UNDERSCORE = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"; - return ALPHANUMERIC_UNDERSCORE; -} - -const std::string& Str::NUMERIC() { - static std::string NUMERIC = "0123456789"; - return NUMERIC; -} - void Str::removeQuotes(std::string& s) { if (!s.empty()) { if (s[0] == '"' && s[s.size() - 1] == '"') { @@ -349,7 +339,7 @@ bool Str::valid_name(const std::string& name, std::string& msg) { } // verify that the first character is alphanumeric or is an underscore - bool result = Str::ALPHANUMERIC_UNDERSCORE().find(name[0], 0) != std::string::npos; + bool result = ecf::string_constants::alphanumeric_underscore_chars.find(name[0], 0) != std::string::npos; if (!result) { msg = "Valid names can only consist of alphanumeric characters, " "underscores and dots (The first character cannot be a dot). " @@ -386,7 +376,7 @@ bool Str::valid_name(const std::string& name) { } // verify that the first character is alphabetic or has underscore - bool result = Str::ALPHANUMERIC_UNDERSCORE().find(name[0], 0) != std::string::npos; + bool result = ecf::string_constants::alphanumeric_underscore_chars.find(name[0], 0) != std::string::npos; if (!result) { return false; } @@ -400,7 +390,7 @@ bool Str::valid_name(const std::string& name) { } int Str::to_int(const std::string& the_str, int error_return) { - if (the_str.find_first_of(Str::NUMERIC(), 0) != std::string::npos) { + if (the_str.find_first_of(ecf::string_constants::numeric_chars, 0) != std::string::npos) { try { return ecf::convert_to(the_str); } diff --git a/libs/core/src/ecflow/core/Str.hpp b/libs/core/src/ecflow/core/Str.hpp index 671b83301..00b65cfbf 100644 --- a/libs/core/src/ecflow/core/Str.hpp +++ b/libs/core/src/ecflow/core/Str.hpp @@ -192,12 +192,6 @@ class Str { /// Only use strcmp if the first characters are the same static int local_strcmp(const char* s, const char* t) { return (*s != *t ? *s - *t : strcmp(s, t)); } - // returns a static string of alphanumerics and underscore - static const std::string& ALPHANUMERIC_UNDERSCORE(); - - // returns a static string of numerics chars - static const std::string& NUMERIC(); - // Allows string to be returned by reference static const std::string& ROOT_PATH(); // "/" }; @@ -224,6 +218,11 @@ inline const std::string localhost = "localhost"; inline const std::string white_list_file = "ecf.lists"; +inline const std::string alphanumeric_underscore_chars = + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"; + +inline const std::string numeric_chars = "0123456789"; + } // namespace string_constants } // namespace ecf diff --git a/libs/core/src/ecflow/core/WhiteListFile.cpp b/libs/core/src/ecflow/core/WhiteListFile.cpp index c241674fc..7f36f93c9 100644 --- a/libs/core/src/ecflow/core/WhiteListFile.cpp +++ b/libs/core/src/ecflow/core/WhiteListFile.cpp @@ -417,7 +417,7 @@ std::string WhiteListFile::dump_valid_users() const { bool WhiteListFile::validateVersionNumber(const std::string& line, std::string& errorMsg) const { // Expect 4.4.14, Current syntax in force after 4.4.5 // If first character is NUMERIC and we have dots - bool firstCharIsNumeric = Str::NUMERIC().find(line[0], 0) != std::string::npos; + bool firstCharIsNumeric = ecf::string_constants::numeric_chars.find(line[0], 0) != std::string::npos; if (firstCharIsNumeric && line.find(".") != std::string::npos) { std::vector versionNumberTokens; diff --git a/libs/core/test/TestStr.cpp b/libs/core/test/TestStr.cpp index f9e101fa6..89fd3072e 100644 --- a/libs/core/test/TestStr.cpp +++ b/libs/core/test/TestStr.cpp @@ -724,7 +724,7 @@ BOOST_AUTO_TEST_CASE(test_loop, *boost::unit_test::disabled()) { static void methodX(const std::string& str, std::vector& stringRes, std::vector& numberRes) { // 0.81 // for bad conversion istringstream seems to return 0, hence add guard - if (str.find_first_of(Str::NUMERIC(), 0) == 0) { + if (str.find_first_of(ecf::string_constants::numeric_chars, 0) == 0) { int number = 0; std::istringstream(str) >> number; numberRes.push_back(number); @@ -747,7 +747,7 @@ static void method1(const std::string& str, std::vector& stringRes, static void method2(const std::string& str, std::vector& stringRes, std::vector& numberRes) { // 0.6 - if (str.find_first_of(Str::NUMERIC(), 0) == 0) { + if (str.find_first_of(ecf::string_constants::numeric_chars, 0) == 0) { try { int number = ecf::convert_to(str); numberRes.push_back(number); diff --git a/libs/node/src/ecflow/node/Node.cpp b/libs/node/src/ecflow/node/Node.cpp index 35c2be450..30959a066 100644 --- a/libs/node/src/ecflow/node/Node.cpp +++ b/libs/node/src/ecflow/node/Node.cpp @@ -1568,7 +1568,7 @@ bool Node::variable_dollar_substitution(std::string& cmd) const { break; } - size_t secondPos = cmd.find_first_not_of(Str::ALPHANUMERIC_UNDERSCORE(), firstPos + 1); + size_t secondPos = cmd.find_first_not_of(ecf::string_constants::alphanumeric_underscore_chars, firstPos + 1); if (secondPos == std::string::npos) { secondPos = cmd.size(); } diff --git a/libs/node/src/ecflow/node/NodeChange.cpp b/libs/node/src/ecflow/node/NodeChange.cpp index 2d3cef079..a66b58b59 100644 --- a/libs/node/src/ecflow/node/NodeChange.cpp +++ b/libs/node/src/ecflow/node/NodeChange.cpp @@ -56,7 +56,7 @@ bool Node::set_event(const std::string& event_name_or_number, bool value) { } // Test for numeric, and then casting, is ****faster***** than relying on exception alone - if (event_name_or_number.find_first_of(Str::NUMERIC()) == 0) { + if (event_name_or_number.find_first_of(ecf::string_constants::numeric_chars) == 0) { try { auto number = ecf::convert_to(event_name_or_number); auto found = ecf::algorithm::find_by_number(events_, number); @@ -86,7 +86,7 @@ bool Node::set_event_used_in_trigger(const std::string& event_name_or_number) { } // Test for numeric, and then casting, is ****faster***** than relying on exception alone - if (event_name_or_number.find_first_of(Str::NUMERIC()) == 0) { + if (event_name_or_number.find_first_of(ecf::string_constants::numeric_chars) == 0) { try { auto number = ecf::convert_to(event_name_or_number); auto found = ecf::algorithm::find_by_number(events_, number); diff --git a/libs/node/src/ecflow/node/NodeFind.cpp b/libs/node/src/ecflow/node/NodeFind.cpp index 18dc1a8d2..052dd34e5 100644 --- a/libs/node/src/ecflow/node/NodeFind.cpp +++ b/libs/node/src/ecflow/node/NodeFind.cpp @@ -308,7 +308,7 @@ const Event& Node::findEventByNameOrNumber(const std::string& theName) const { } // Test for numeric, and then casting, is ****faster***** than relying on exception alone - if (theName.find_first_of(Str::NUMERIC(), 0) == 0) { + if (theName.find_first_of(ecf::string_constants::numeric_chars, 0) == 0) { try { auto eventNumber = ecf::convert_to(theName); return findEventByNumber(eventNumber); diff --git a/libs/node/src/ecflow/node/parser/EventParser.cpp b/libs/node/src/ecflow/node/parser/EventParser.cpp index 045455875..40d6bbe13 100644 --- a/libs/node/src/ecflow/node/parser/EventParser.cpp +++ b/libs/node/src/ecflow/node/parser/EventParser.cpp @@ -41,7 +41,7 @@ bool EventParser::doParse(const std::string& line, std::vector& lin int number = std::numeric_limits::max(); // Test for numeric, and then casting, is ****faster***** than relying on exception alone - if (lineTokens[1].find_first_of(Str::NUMERIC()) == 0) { + if (lineTokens[1].find_first_of(ecf::string_constants::numeric_chars) == 0) { // event 0 // event 1 eventName // event 2 eventNamea From 6f7c9a36b5074dc02a6650dc054b5898833585a6 Mon Sep 17 00:00:00 2001 From: Marcos Bento Date: Thu, 26 Mar 2026 16:21:45 +0000 Subject: [PATCH 11/90] refactor: Make 'root_path' a constant instead of a function --- .../src/ecflow/base/cts/ClientToServerCmd.cpp | 6 +-- libs/base/test/TestDeleteNodeCmd.cpp | 46 +++++++++++-------- libs/client/test/TestCheckPtDefsCmd.cpp | 22 +++++---- libs/client/test/TestServer.cpp | 29 ++++++------ libs/core/src/ecflow/core/Str.cpp | 5 -- libs/core/src/ecflow/core/Str.hpp | 4 +- libs/node/src/ecflow/node/Defs.cpp | 12 ++--- libs/node/src/ecflow/node/Memento.cpp | 2 +- 8 files changed, 67 insertions(+), 59 deletions(-) diff --git a/libs/base/src/ecflow/base/cts/ClientToServerCmd.cpp b/libs/base/src/ecflow/base/cts/ClientToServerCmd.cpp index 0a86587ed..ed751e8a1 100644 --- a/libs/base/src/ecflow/base/cts/ClientToServerCmd.cpp +++ b/libs/base/src/ecflow/base/cts/ClientToServerCmd.cpp @@ -173,7 +173,7 @@ void ClientToServerCmd::add_edit_history(Defs* defs) const { if (edit_history_nodes_.empty() && edit_history_node_paths_.empty()) { defs->flag().set(ecf::Flag::MESSAGE); - add_edit_history(defs, Str::ROOT_PATH()); + add_edit_history(defs, ecf::string_constants::root_path); } else { // edit_history_node_paths_ is only populated by the delete command @@ -220,10 +220,10 @@ void ClientToServerCmd::add_edit_history(Defs* defs, const std::string& path) co } void ClientToServerCmd::add_delete_edit_history(Defs* defs, const std::string& path) const { - // History is added to Str::ROOT_PATH(), but the path must show deleted node path + // History is added to ecf::string_constants::root_path, but the path must show deleted node path std::string ss("MSG:"); ss += Log::instance()->get_cached_time_stamp(); print(ss, path); // custom print - defs->add_edit_history(Str::ROOT_PATH(), ss); + defs->add_edit_history(ecf::string_constants::root_path, ss); } diff --git a/libs/base/test/TestDeleteNodeCmd.cpp b/libs/base/test/TestDeleteNodeCmd.cpp index b467ba513..b43afbc99 100644 --- a/libs/base/test/TestDeleteNodeCmd.cpp +++ b/libs/base/test/TestDeleteNodeCmd.cpp @@ -49,7 +49,8 @@ BOOST_AUTO_TEST_CASE(test_delete_node_cmd) { paths.push_back(alias->absNodePath()); } - size_t edit_history_size_before = fixtureDef.defsfile_.get_edit_history(Str::ROOT_PATH()).size(); + size_t edit_history_size_before = + fixtureDef.defsfile_.get_edit_history(ecf::string_constants::root_path).size(); BOOST_CHECK_MESSAGE(!paths.empty(), "Expected paths to be specified, *OTHERWISE* we delete all nodes"); DeleteCmd cmd(paths); cmd.setup_user_authentification(); @@ -57,8 +58,9 @@ BOOST_AUTO_TEST_CASE(test_delete_node_cmd) { BOOST_CHECK_MESSAGE(returnCmd->ok(), "Failed to delete aliases"); { // ECFLOW-434 - const std::vector& edit_history = fixtureDef.defsfile_.get_edit_history(Str::ROOT_PATH()); - size_t edit_history_size_after = edit_history_size_before + paths.size(); + const std::vector& edit_history = + fixtureDef.defsfile_.get_edit_history(ecf::string_constants::root_path); + size_t edit_history_size_after = edit_history_size_before + paths.size(); if (edit_history_size_after > Defs::max_edit_history_size_per_node()) { edit_history_size_after = Defs::max_edit_history_size_per_node(); } @@ -84,14 +86,16 @@ BOOST_AUTO_TEST_CASE(test_delete_node_cmd) { } BOOST_CHECK_MESSAGE(!paths.empty(), "Expected paths to be specified, *OTHERWISE* we delete all nodes"); - size_t edit_history_size_before = fixtureDef.defsfile_.get_edit_history(Str::ROOT_PATH()).size(); + size_t edit_history_size_before = + fixtureDef.defsfile_.get_edit_history(ecf::string_constants::root_path).size(); DeleteCmd cmd(paths); cmd.setup_user_authentification(); STC_Cmd_ptr returnCmd = cmd.handleRequest(&mockServer); BOOST_CHECK_MESSAGE(returnCmd->ok(), "Failed to delete tasks"); { - const std::vector& edit_history = fixtureDef.defsfile_.get_edit_history(Str::ROOT_PATH()); - size_t edit_history_size_after = edit_history_size_before + paths.size(); + const std::vector& edit_history = + fixtureDef.defsfile_.get_edit_history(ecf::string_constants::root_path); + size_t edit_history_size_after = edit_history_size_before + paths.size(); if (edit_history_size_after > Defs::max_edit_history_size_per_node()) { edit_history_size_after = Defs::max_edit_history_size_per_node(); } @@ -135,7 +139,8 @@ BOOST_AUTO_TEST_CASE(test_delete_node_cmd) { } if (!paths.empty()) { - size_t edit_history_size_before = fixtureDef.defsfile_.get_edit_history(Str::ROOT_PATH()).size(); + size_t edit_history_size_before = + fixtureDef.defsfile_.get_edit_history(ecf::string_constants::root_path).size(); DeleteCmd cmd(paths); cmd.setup_user_authentification(); STC_Cmd_ptr returnCmd = cmd.handleRequest(&mockServer); @@ -144,7 +149,7 @@ BOOST_AUTO_TEST_CASE(test_delete_node_cmd) { "Expected all Families to be deleted but found " << s->familyVec().size()); { const std::vector& edit_history = - fixtureDef.defsfile_.get_edit_history(Str::ROOT_PATH()); + fixtureDef.defsfile_.get_edit_history(ecf::string_constants::root_path); size_t edit_history_size_after = edit_history_size_before + paths.size(); if (edit_history_size_after > Defs::max_edit_history_size_per_node()) { edit_history_size_after = Defs::max_edit_history_size_per_node(); @@ -157,15 +162,17 @@ BOOST_AUTO_TEST_CASE(test_delete_node_cmd) { } // delete the suite - size_t edit_history_size_before = fixtureDef.defsfile_.get_edit_history(Str::ROOT_PATH()).size(); - std::string absNodePath = s->absNodePath(); + size_t edit_history_size_before = + fixtureDef.defsfile_.get_edit_history(ecf::string_constants::root_path).size(); + std::string absNodePath = s->absNodePath(); DeleteCmd cmd(absNodePath); cmd.setup_user_authentification(); STC_Cmd_ptr returnCmd = cmd.handleRequest(&mockServer); BOOST_CHECK_MESSAGE(returnCmd->ok(), "Failed to delete suite at path " << absNodePath); { - const std::vector& edit_history = fixtureDef.defsfile_.get_edit_history(Str::ROOT_PATH()); - size_t edit_history_size_after = edit_history_size_before + 1; + const std::vector& edit_history = + fixtureDef.defsfile_.get_edit_history(ecf::string_constants::root_path); + size_t edit_history_size_after = edit_history_size_before + 1; if (edit_history_size_after > Defs::max_edit_history_size_per_node()) { edit_history_size_after = Defs::max_edit_history_size_per_node(); } @@ -200,8 +207,9 @@ BOOST_AUTO_TEST_CASE(test_delete_node_edit_history_ECFLOW_1684) { // suspend all suites and tasks, then check we have some edit history { - size_t edit_history_root_size_before = fixtureDef.defsfile_.get_edit_history(Str::ROOT_PATH()).size(); - size_t edit_history_before = fixtureDef.defsfile_.get_edit_history().size(); + size_t edit_history_root_size_before = + fixtureDef.defsfile_.get_edit_history(ecf::string_constants::root_path).size(); + size_t edit_history_before = fixtureDef.defsfile_.get_edit_history().size(); BOOST_CHECK_MESSAGE(edit_history_root_size_before == 0, "Expected edit_history_root_size_before == 0 but found " << edit_history_root_size_before); BOOST_CHECK_MESSAGE(edit_history_before == 0, @@ -226,8 +234,9 @@ BOOST_AUTO_TEST_CASE(test_delete_node_edit_history_ECFLOW_1684) { STC_Cmd_ptr returnCmd = cmd.handleRequest(&mockServer); BOOST_CHECK_MESSAGE(returnCmd->ok(), "Failed to suspend tasks"); { - size_t edit_history_root_size_after = fixtureDef.defsfile_.get_edit_history(Str::ROOT_PATH()).size(); - size_t edit_history_after = fixtureDef.defsfile_.get_edit_history().size(); + size_t edit_history_root_size_after = + fixtureDef.defsfile_.get_edit_history(ecf::string_constants::root_path).size(); + size_t edit_history_after = fixtureDef.defsfile_.get_edit_history().size(); BOOST_CHECK_MESSAGE(edit_history_root_size_after == 0, "Expected edit_history_root_size_after == 0 but found " @@ -252,8 +261,9 @@ BOOST_AUTO_TEST_CASE(test_delete_node_edit_history_ECFLOW_1684) { BOOST_REQUIRE_MESSAGE(fixtureDef.defsfile_.suiteVec().empty(), "Expected all Suites to be deleted but found " << fixtureDef.defsfile_.suiteVec().size()); - size_t edit_history_root_size_after = fixtureDef.defsfile_.get_edit_history(Str::ROOT_PATH()).size(); - size_t edit_history_after = fixtureDef.defsfile_.get_edit_history().size(); + size_t edit_history_root_size_after = + fixtureDef.defsfile_.get_edit_history(ecf::string_constants::root_path).size(); + size_t edit_history_after = fixtureDef.defsfile_.get_edit_history().size(); BOOST_CHECK_MESSAGE(edit_history_root_size_after == vec.size(), "Expected edit_history size of root node to be same as number of deleted suites(" diff --git a/libs/client/test/TestCheckPtDefsCmd.cpp b/libs/client/test/TestCheckPtDefsCmd.cpp index b3ee1d0e4..5fbdd2eb2 100644 --- a/libs/client/test/TestCheckPtDefsCmd.cpp +++ b/libs/client/test/TestCheckPtDefsCmd.cpp @@ -320,9 +320,10 @@ BOOST_AUTO_TEST_CASE(test_check_pt_edit_history) { "Server failed to start on " << invokeServer.host() << ":" << invokeServer.port()); ClientInvoker theClient(invokeServer.host(), invokeServer.port()); - BOOST_REQUIRE_MESSAGE(theClient.edit_history(Str::ROOT_PATH()) == 0, - CtsApi::to_string(CtsApi::edit_history(Str::ROOT_PATH())) << " should return 0\n" - << theClient.errorMsg()); + BOOST_REQUIRE_MESSAGE(theClient.edit_history(ecf::string_constants::root_path) == 0, + CtsApi::to_string(CtsApi::edit_history(ecf::string_constants::root_path)) + << " should return 0\n" + << theClient.errorMsg()); BOOST_REQUIRE_MESSAGE(theClient.server_reply().get_string_vec().size() == 0, "Expected edit history of size 0 after server start, but found " << theClient.server_reply().get_string_vec().size()); @@ -344,9 +345,10 @@ BOOST_AUTO_TEST_CASE(test_check_pt_edit_history) { << theClient.errorMsg()); // make sure edit history updated - BOOST_REQUIRE_MESSAGE(theClient.edit_history(Str::ROOT_PATH()) == 0, - CtsApi::to_string(CtsApi::edit_history(Str::ROOT_PATH())) << " should return 0\n" - << theClient.errorMsg()); + BOOST_REQUIRE_MESSAGE(theClient.edit_history(ecf::string_constants::root_path) == 0, + CtsApi::to_string(CtsApi::edit_history(ecf::string_constants::root_path)) + << " should return 0\n" + << theClient.errorMsg()); BOOST_REQUIRE_MESSAGE(theClient.server_reply().get_string_vec().size() == 5, "Expected edit history of size 5, but found " << theClient.server_reply().get_string_vec().size()); @@ -355,9 +357,9 @@ BOOST_AUTO_TEST_CASE(test_check_pt_edit_history) { BOOST_REQUIRE_MESSAGE(theClient.getDefs() == 0, CtsApi::get() << " failed should return 0\n" << theClient.errorMsg()); - BOOST_REQUIRE_MESSAGE(theClient.defs()->get_edit_history(Str::ROOT_PATH()).size() == 0, + BOOST_REQUIRE_MESSAGE(theClient.defs()->get_edit_history(ecf::string_constants::root_path).size() == 0, "Expected edit history of size 0, but found " - << theClient.defs()->get_edit_history(Str::ROOT_PATH()).size()); + << theClient.defs()->get_edit_history(ecf::string_constants::root_path).size()); // This should write the edit history BOOST_REQUIRE_MESSAGE(theClient.checkPtDefs() == 0, @@ -374,9 +376,9 @@ BOOST_AUTO_TEST_CASE(test_check_pt_edit_history) { { Defs defs; defs.restore(invokeServer.ecf_checkpt_file()); // restore defs from checkpoint - BOOST_REQUIRE_MESSAGE(defs.get_edit_history(Str::ROOT_PATH()).size() == 5, + BOOST_REQUIRE_MESSAGE(defs.get_edit_history(ecf::string_constants::root_path).size() == 5, "Expected edit history of size 5, but found " - << defs.get_edit_history(Str::ROOT_PATH()).size()); + << defs.get_edit_history(ecf::string_constants::root_path).size()); } { diff --git a/libs/client/test/TestServer.cpp b/libs/client/test/TestServer.cpp index 3906ecaf8..d064b1923 100644 --- a/libs/client/test/TestServer.cpp +++ b/libs/client/test/TestServer.cpp @@ -146,9 +146,10 @@ BOOST_AUTO_TEST_CASE(test_server_state_changes) { // This check only valid if server was invoked locally. Ignore for remote servers // make sure edit history updated - BOOST_REQUIRE_MESSAGE(theClient.edit_history(Str::ROOT_PATH()) == 0, - CtsApi::to_string(CtsApi::edit_history(Str::ROOT_PATH())) << " should return 0\n" - << theClient.errorMsg()); + BOOST_REQUIRE_MESSAGE(theClient.edit_history(ecf::string_constants::root_path) == 0, + CtsApi::to_string(CtsApi::edit_history(ecf::string_constants::root_path)) + << " should return 0\n" + << theClient.errorMsg()); BOOST_REQUIRE_MESSAGE(theClient.server_reply().get_string_vec().size() == 7, "Expected edit history of size 7, but found " << theClient.server_reply().get_string_vec().size()); @@ -157,17 +158,18 @@ BOOST_AUTO_TEST_CASE(test_server_state_changes) { BOOST_REQUIRE_MESSAGE(theClient.getDefs() == 0, CtsApi::get() << " failed should return 0\n" << theClient.errorMsg()); - BOOST_REQUIRE_MESSAGE(theClient.defs()->get_edit_history(Str::ROOT_PATH()).size() == 0, + BOOST_REQUIRE_MESSAGE(theClient.defs()->get_edit_history(ecf::string_constants::root_path).size() == 0, "Expected edit history of size 0, but found " - << theClient.defs()->get_edit_history(Str::ROOT_PATH()).size()); + << theClient.defs()->get_edit_history(ecf::string_constants::root_path).size()); // clear edit history BOOST_REQUIRE_MESSAGE(theClient.edit_history("clear") == 0, CtsApi::to_string(CtsApi::edit_history("clear")) << " should return 0\n" << theClient.errorMsg()); - BOOST_REQUIRE_MESSAGE(theClient.edit_history(Str::ROOT_PATH()) == 0, - CtsApi::to_string(CtsApi::edit_history(Str::ROOT_PATH())) << " should return 0\n" - << theClient.errorMsg()); + BOOST_REQUIRE_MESSAGE(theClient.edit_history(ecf::string_constants::root_path) == 0, + CtsApi::to_string(CtsApi::edit_history(ecf::string_constants::root_path)) + << " should return 0\n" + << theClient.errorMsg()); BOOST_REQUIRE_MESSAGE(theClient.server_reply().get_string_vec().size() == 0, "Expected no edit history " << theClient.server_reply().get_string_vec().size()); } @@ -219,9 +221,10 @@ BOOST_AUTO_TEST_CASE(test_server_state_changes_with_auto_sync) { // This check only valid if server was invoked locally. Ignore for remote servers // make sure edit history updated - BOOST_REQUIRE_MESSAGE(theClient.edit_history(Str::ROOT_PATH()) == 0, - CtsApi::to_string(CtsApi::edit_history(Str::ROOT_PATH())) << " should return 0\n" - << theClient.errorMsg()); + BOOST_REQUIRE_MESSAGE(theClient.edit_history(ecf::string_constants::root_path) == 0, + CtsApi::to_string(CtsApi::edit_history(ecf::string_constants::root_path)) + << " should return 0\n" + << theClient.errorMsg()); BOOST_REQUIRE_MESSAGE(theClient.server_reply().get_string_vec().size() == 8 && theClient.server_reply().get_string_vec().size() <= Defs::max_edit_history_size_per_node(), @@ -233,9 +236,9 @@ BOOST_AUTO_TEST_CASE(test_server_state_changes_with_auto_sync) { BOOST_REQUIRE_MESSAGE(theClient.getDefs() == 0, CtsApi::get() << " failed should return 0\n" << theClient.errorMsg()); - BOOST_REQUIRE_MESSAGE(theClient.defs()->get_edit_history(Str::ROOT_PATH()).size() == 0, + BOOST_REQUIRE_MESSAGE(theClient.defs()->get_edit_history(ecf::string_constants::root_path).size() == 0, "Expected edit history of size 0, but found " - << theClient.defs()->get_edit_history(Str::ROOT_PATH()).size()); + << theClient.defs()->get_edit_history(ecf::string_constants::root_path).size()); } } diff --git a/libs/core/src/ecflow/core/Str.cpp b/libs/core/src/ecflow/core/Str.cpp index 80d29b33a..be6050ad7 100644 --- a/libs/core/src/ecflow/core/Str.cpp +++ b/libs/core/src/ecflow/core/Str.cpp @@ -16,11 +16,6 @@ namespace ecf { const char* VALID_NODE_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_."; -const std::string& Str::ROOT_PATH() { - static std::string root_path = "/"; - return root_path; -} - void Str::removeQuotes(std::string& s) { if (!s.empty()) { if (s[0] == '"' && s[s.size() - 1] == '"') { diff --git a/libs/core/src/ecflow/core/Str.hpp b/libs/core/src/ecflow/core/Str.hpp index 00b65cfbf..86504676c 100644 --- a/libs/core/src/ecflow/core/Str.hpp +++ b/libs/core/src/ecflow/core/Str.hpp @@ -191,9 +191,6 @@ class Str { /// Only use strcmp if the first characters are the same static int local_strcmp(const char* s, const char* t) { return (*s != *t ? *s - *t : strcmp(s, t)); } - - // Allows string to be returned by reference - static const std::string& ROOT_PATH(); // "/" }; namespace string_constants { @@ -204,6 +201,7 @@ inline const std::string server_cmd = "svr:"; // Only for automatic check_pt inline const std::string empty = ""; +inline const std::string root_path = "/"; inline const std::string path_separator = "/"; inline const std::string colon = ":"; diff --git a/libs/node/src/ecflow/node/Defs.cpp b/libs/node/src/ecflow/node/Defs.cpp index e1d451ef7..9d80c9328 100644 --- a/libs/node/src/ecflow/node/Defs.cpp +++ b/libs/node/src/ecflow/node/Defs.cpp @@ -133,7 +133,7 @@ void Defs::handle_migration() { auto it = edit_history_.begin(); while (it != edit_history_.end()) { - if ((*it).first == Str::ROOT_PATH()) { + if ((*it).first == ecf::string_constants::root_path) { it++; continue; // root path is defs, which is not a node, hence ignore } @@ -1640,7 +1640,7 @@ void Defs::collate_defs_changes_only(DefsDelta& incremental_changes) const { // Create StateMemento to signal a change in state change of the Defs if (state_.state_change_no() > incremental_changes.client_state_change_no()) { if (!comp.get()) { - comp = std::make_shared(Str::ROOT_PATH()); + comp = std::make_shared(ecf::string_constants::root_path); } comp->add(std::make_shared(state_.state())); } @@ -1648,7 +1648,7 @@ void Defs::collate_defs_changes_only(DefsDelta& incremental_changes) const { // Create OrderMemento to signal a change in Suite order if (order_state_change_no_ > incremental_changes.client_state_change_no()) { if (!comp.get()) { - comp = std::make_shared(Str::ROOT_PATH()); + comp = std::make_shared(ecf::string_constants::root_path); } std::vector order; order.reserve(suiteVec_.size()); @@ -1661,7 +1661,7 @@ void Defs::collate_defs_changes_only(DefsDelta& incremental_changes) const { // Create FlagMemento to signal a change in the flag value if (flag_.state_change_no() > incremental_changes.client_state_change_no()) { if (!comp.get()) { - comp = std::make_shared(Str::ROOT_PATH()); + comp = std::make_shared(ecf::string_constants::root_path); } comp->add(std::make_shared(flag_)); } @@ -1670,7 +1670,7 @@ void Defs::collate_defs_changes_only(DefsDelta& incremental_changes) const { // Currently only watching server state i.e., HALTED, SHUTDOWN, RUNNING. if (server_.state_change_no() > incremental_changes.client_state_change_no()) { if (!comp.get()) { - comp = std::make_shared(Str::ROOT_PATH()); + comp = std::make_shared(ecf::string_constants::root_path); } comp->add(std::make_shared(server_.get_state())); } @@ -1678,7 +1678,7 @@ void Defs::collate_defs_changes_only(DefsDelta& incremental_changes) const { // Create a ServerVariableMemento to signal a change in the list of server variables if (server_.variable_state_change_no() > incremental_changes.client_state_change_no()) { if (!comp.get()) { - comp = std::make_shared(Str::ROOT_PATH()); + comp = std::make_shared(ecf::string_constants::root_path); } comp->add(std::make_shared(server_.user_variables())); } diff --git a/libs/node/src/ecflow/node/Memento.cpp b/libs/node/src/ecflow/node/Memento.cpp index 58bd5c48c..1a724ad1d 100644 --- a/libs/node/src/ecflow/node/Memento.cpp +++ b/libs/node/src/ecflow/node/Memento.cpp @@ -32,7 +32,7 @@ void CompoundMemento::incremental_sync(defs_ptr client_def) const { node_ptr node = client_def->findAbsNode(absNodePath_); if (!node.get()) { - if (absNodePath_ != Str::ROOT_PATH()) { + if (absNodePath_ != ecf::string_constants::root_path) { // add more context, about what's suites are in the client defs. std::string error_msg = "CompoundMemento::incremental_sync: could not find path "; From 87c6f8811d68c4e5bdee7ae92dfc0671f977497a Mon Sep 17 00:00:00 2001 From: Marcos Bento Date: Fri, 27 Mar 2026 07:58:03 +0000 Subject: [PATCH 12/90] Move ecf::string_constants location This allows the use of these constants by the inline functions defined below. --- libs/core/src/ecflow/core/Str.hpp | 60 +++++++++++++++---------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/libs/core/src/ecflow/core/Str.hpp b/libs/core/src/ecflow/core/Str.hpp index 86504676c..a0b61219d 100644 --- a/libs/core/src/ecflow/core/Str.hpp +++ b/libs/core/src/ecflow/core/Str.hpp @@ -21,6 +21,36 @@ namespace ecf { +namespace string_constants { + +inline const std::string child_cmd = "chd:"; +inline const std::string user_cmd = "--"; +inline const std::string server_cmd = "svr:"; // Only for automatic check_pt + +inline const std::string empty = ""; + +inline const std::string root_path = "/"; +inline const std::string path_separator = "/"; +inline const std::string colon = ":"; + +inline const std::string task = "TASK"; +inline const std::string family = "FAMILY"; +inline const std::string family1 = "FAMILY1"; +inline const std::string suite = "SUITE"; +inline const std::string alias = "ALIAS"; + +inline const std::string default_port_number = "3141"; +inline const std::string localhost = "localhost"; + +inline const std::string white_list_file = "ecf.lists"; + +inline const std::string alphanumeric_underscore_chars = + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"; + +inline const std::string numeric_chars = "0123456789"; + +} // namespace string_constants + namespace algorithm { template @@ -193,36 +223,6 @@ class Str { static int local_strcmp(const char* s, const char* t) { return (*s != *t ? *s - *t : strcmp(s, t)); } }; -namespace string_constants { - -inline const std::string child_cmd = "chd:"; -inline const std::string user_cmd = "--"; -inline const std::string server_cmd = "svr:"; // Only for automatic check_pt - -inline const std::string empty = ""; - -inline const std::string root_path = "/"; -inline const std::string path_separator = "/"; -inline const std::string colon = ":"; - -inline const std::string task = "TASK"; -inline const std::string family = "FAMILY"; -inline const std::string family1 = "FAMILY1"; -inline const std::string suite = "SUITE"; -inline const std::string alias = "ALIAS"; - -inline const std::string default_port_number = "3141"; -inline const std::string localhost = "localhost"; - -inline const std::string white_list_file = "ecf.lists"; - -inline const std::string alphanumeric_underscore_chars = - "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"; - -inline const std::string numeric_chars = "0123456789"; - -} // namespace string_constants - } // namespace ecf #endif /* ecflow_core_Str_HPP */ From d45a560eef696a3b996e6762057d3d08da15b3af Mon Sep 17 00:00:00 2001 From: Marcos Bento Date: Fri, 27 Mar 2026 08:08:19 +0000 Subject: [PATCH 13/90] Remove unnecessary "optimisation" --- libs/core/src/ecflow/core/Str.hpp | 3 -- .../src/ecflow/node/parser/DefsParser.cpp | 28 +++++++++---------- libs/node/src/ecflow/node/parser/Parser.cpp | 10 +++---- 3 files changed, 18 insertions(+), 23 deletions(-) diff --git a/libs/core/src/ecflow/core/Str.hpp b/libs/core/src/ecflow/core/Str.hpp index a0b61219d..78a419acc 100644 --- a/libs/core/src/ecflow/core/Str.hpp +++ b/libs/core/src/ecflow/core/Str.hpp @@ -218,9 +218,6 @@ class Str { static bool truncate_at_end(std::string& fileContents, size_t max_lines); static std::string dump_string_vec(const std::vector& vec); - - /// Only use strcmp if the first characters are the same - static int local_strcmp(const char* s, const char* t) { return (*s != *t ? *s - *t : strcmp(s, t)); } }; } // namespace ecf diff --git a/libs/node/src/ecflow/node/parser/DefsParser.cpp b/libs/node/src/ecflow/node/parser/DefsParser.cpp index 09b3a051a..f0a0f1303 100644 --- a/libs/node/src/ecflow/node/parser/DefsParser.cpp +++ b/libs/node/src/ecflow/node/parser/DefsParser.cpp @@ -90,8 +90,8 @@ class AliasParser : public Parser { bool doParse(const std::string& line, std::vector& lineTokens) override { - const char* first_token = lineTokens[0].c_str(); - if (Str::local_strcmp(first_token, keyword()) == 0) { + const auto& first_token = lineTokens.front(); + if (first_token == keyword()) { if (lineTokens.size() < 2) { throw std::runtime_error("Alias name missing."); @@ -101,7 +101,7 @@ class AliasParser : public Parser { return true; } - else if (Str::local_strcmp(first_token, "endalias") == 0) { // required at the end + else if (first_token == "endalias") { // required at the end popNode(); return true; } @@ -187,20 +187,20 @@ class TaskParser : public Parser { bool doParse(const std::string& line, std::vector& lineTokens) override { - const char* first_token = lineTokens[0].c_str(); - if (Str::local_strcmp(first_token, keyword()) == 0) { + const auto& first_token = lineTokens.front(); + if (first_token == keyword()) { if (lineTokens.size() < 2) { throw std::runtime_error("Task name missing."); } addTask(line, lineTokens); return true; } - else if (Str::local_strcmp(first_token, "endfamily") == 0) { + else if (first_token == "endfamily") { if (parent()) { return parent()->doParse(line, lineTokens); } } - else if (Str::local_strcmp(first_token, "endtask") == 0) { // optional + else if (first_token == "endtask") { // optional popToContainerNode(); return true; } @@ -292,8 +292,8 @@ class FamilyParser : public Parser { bool doParse(const std::string& line, std::vector& lineTokens) override { - const char* first_token = lineTokens[0].c_str(); - if (Str::local_strcmp(first_token, keyword()) == 0) { + const auto& first_token = lineTokens.front(); + if (first_token == keyword()) { if (lineTokens.size() < 2) { throw std::runtime_error("Family name missing."); @@ -303,12 +303,12 @@ class FamilyParser : public Parser { return true; } - else if (Str::local_strcmp(first_token, "endfamily") == 0) { + else if (first_token == "endfamily") { popFamily(); return true; } - else if (Str::local_strcmp(first_token, "endtask") == 0) { // optional + else if (first_token == "endtask") { // optional popNode(); return true; } @@ -416,8 +416,8 @@ class SuiteParser : public Parser { bool doParse(const std::string& line, std::vector& lineTokens) override { - const char* first_token = lineTokens[0].c_str(); - if (Str::local_strcmp(first_token, keyword()) == 0) { + const auto& first_token = lineTokens.front(); + if (first_token == keyword()) { if (started_) { throw std::runtime_error("Can't have hierarchical suites."); @@ -431,7 +431,7 @@ class SuiteParser : public Parser { return true; } - else if (Str::local_strcmp(first_token, "endsuite") == 0) { + else if (first_token == "endsuite") { if (!started_) { throw std::runtime_error("Misplaced endsuite.."); diff --git a/libs/node/src/ecflow/node/parser/Parser.cpp b/libs/node/src/ecflow/node/parser/Parser.cpp index 71df7c7d5..48976809d 100644 --- a/libs/node/src/ecflow/node/parser/Parser.cpp +++ b/libs/node/src/ecflow/node/parser/Parser.cpp @@ -44,12 +44,12 @@ Parser::~Parser() { } bool Parser::doParse(const std::string& line, std::vector& lineTokens) { - const char* first_token = lineTokens[0].c_str(); + const auto& first_token = lineTokens.front(); size_t theSize = expectedParsers_.size(); for (size_t i = 0; i < theSize; ++i) { Parser* p = expectedParsers_[i]; - if (Str::local_strcmp(first_token, p->keyword()) == 0) { + if (first_token == p->keyword()) { #ifdef SHOW_PARSER_STATS p->incrementParserCount(); // used for stats @@ -72,15 +72,13 @@ bool Parser::doParse(const std::string& line, std::vector& lineToke #endif // Parent should handle "endfamily", "family" and "endsuite" for hierarchical families - if (parent() && - ((Str::local_strcmp(first_token, "endfamily") == 0) || (Str::local_strcmp(first_token, "family") == 0) || - (Str::local_strcmp(first_token, "endsuite") == 0))) { + if (parent() && ((first_token == "endfamily") || (first_token == "family") || (first_token == "endsuite"))) { return parent()->doParse(line, lineTokens); } // Check if first token is '#' comment character // very first non space character is # comment, hence ignore this line - if (*first_token == '#') { + if (first_token.front() == '#') { // std::cout << "Ignoring line with leading comment : " << line << "\n"; return true; } From e77eaee8a0362fabf4e8312472d844782a3fbe87 Mon Sep 17 00:00:00 2001 From: Marcos Bento Date: Fri, 27 Mar 2026 09:29:00 +0000 Subject: [PATCH 14/90] Rely on default data member initialisation --- libs/node/src/ecflow/node/parser/DefsStructureParser.cpp | 6 ------ libs/node/src/ecflow/node/parser/DefsStructureParser.hpp | 6 +++--- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/libs/node/src/ecflow/node/parser/DefsStructureParser.cpp b/libs/node/src/ecflow/node/parser/DefsStructureParser.cpp index d72b71f41..884c24ddd 100644 --- a/libs/node/src/ecflow/node/parser/DefsStructureParser.cpp +++ b/libs/node/src/ecflow/node/parser/DefsStructureParser.cpp @@ -271,12 +271,6 @@ bool DefsStructureParser::semiColonInEditVariable() { } //////////////////////////////////////////////////////////////////////////////////////////////// -DefsString::DefsString() - : lines_{}, - line_pos_{0}, - empty_(true) { -} - DefsString::DefsString(const std::string& defs_as_string) : empty_(defs_as_string.empty()) { if (!empty_) { diff --git a/libs/node/src/ecflow/node/parser/DefsStructureParser.hpp b/libs/node/src/ecflow/node/parser/DefsStructureParser.hpp index 18a99413a..2d91de55c 100644 --- a/libs/node/src/ecflow/node/parser/DefsStructureParser.hpp +++ b/libs/node/src/ecflow/node/parser/DefsStructureParser.hpp @@ -26,7 +26,7 @@ class Parser; // This class is used get a line of defs format from a defs string class DefsString { public: - DefsString(); + DefsString() = default; explicit DefsString(const std::string& defs_as_string); // Disable copy (and move) semantics DefsString(const DefsString&) = delete; @@ -37,9 +37,9 @@ class DefsString { bool empty() const { return empty_; } private: - std::vector lines_; + std::vector lines_{}; size_t line_pos_{0}; - bool empty_; + bool empty_{true}; }; // This class is used to parse the DEFS file. From 3d813a1962631c54bfcac6f6aa87c1c2de186627 Mon Sep 17 00:00:00 2001 From: Marcos Bento Date: Fri, 27 Mar 2026 09:29:43 +0000 Subject: [PATCH 15/90] Move implementation to source file to avoid include dependency --- libs/base/src/ecflow/base/stc/ServerToClientCmd.cpp | 4 ++++ libs/base/src/ecflow/base/stc/ServerToClientCmd.hpp | 3 +-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/libs/base/src/ecflow/base/stc/ServerToClientCmd.cpp b/libs/base/src/ecflow/base/stc/ServerToClientCmd.cpp index 1c76a51f6..fee3411d6 100644 --- a/libs/base/src/ecflow/base/stc/ServerToClientCmd.cpp +++ b/libs/base/src/ecflow/base/stc/ServerToClientCmd.cpp @@ -15,3 +15,7 @@ using namespace ecf; ServerToClientCmd::~ServerToClientCmd() = default; + +const std::string& ServerToClientCmd::get_string() const { + return ecf::string_constants::empty; +} diff --git a/libs/base/src/ecflow/base/stc/ServerToClientCmd.hpp b/libs/base/src/ecflow/base/stc/ServerToClientCmd.hpp index 3a3a0a5e9..8f9cdc41a 100644 --- a/libs/base/src/ecflow/base/stc/ServerToClientCmd.hpp +++ b/libs/base/src/ecflow/base/stc/ServerToClientCmd.hpp @@ -14,7 +14,6 @@ #include "ecflow/base/Cmd.hpp" #include "ecflow/base/ServerReply.hpp" #include "ecflow/core/Serialization.hpp" -#include "ecflow/core/Str.hpp" #include "ecflow/node/NodeFwd.hpp" //================================================================================ @@ -29,7 +28,7 @@ class ServerToClientCmd { virtual std::string print() const = 0; virtual bool equals(ServerToClientCmd*) const { return true; } - virtual const std::string& get_string() const { return ecf::string_constants::empty; }; + virtual const std::string& get_string() const; virtual bool ok() const { return true; } virtual bool is_returnable_in_group_cmd() const { return true; } From c72473de352e249f24d67081707ac0e77524158f Mon Sep 17 00:00:00 2001 From: Marcos Bento Date: Sat, 28 Mar 2026 07:25:47 +0000 Subject: [PATCH 16/90] refactor: check node name validity --- .../src/ecflow/attribute/GenericAttr.cpp | 4 +- .../src/ecflow/attribute/NodeAttr.cpp | 8 +- .../src/ecflow/attribute/QueueAttr.cpp | 4 +- .../src/ecflow/attribute/RepeatAttr.cpp | 12 +- .../src/ecflow/attribute/Variable.cpp | 4 +- .../src/ecflow/base/cts/task/QueueCmd.cpp | 2 +- libs/core/CMakeLists.txt | 1 + libs/core/src/ecflow/core/Str.cpp | 110 +++--- libs/core/src/ecflow/core/Str.hpp | 28 +- libs/core/test/TestNodeNameValidity.cpp | 323 ++++++++++++++++++ libs/core/test/TestStr.cpp | 58 ---- libs/node/src/ecflow/node/AvisoAttr.cpp | 4 +- libs/node/src/ecflow/node/InLimit.cpp | 2 +- libs/node/src/ecflow/node/Limit.cpp | 4 +- libs/node/src/ecflow/node/MirrorAttr.cpp | 2 +- libs/node/src/ecflow/node/Node.cpp | 2 +- libs/pyext/src/ecflow/python/Trigger.cpp | 2 +- 17 files changed, 421 insertions(+), 149 deletions(-) create mode 100644 libs/core/test/TestNodeNameValidity.cpp diff --git a/libs/attribute/src/ecflow/attribute/GenericAttr.cpp b/libs/attribute/src/ecflow/attribute/GenericAttr.cpp index 5a85d7ae9..ddbb75252 100644 --- a/libs/attribute/src/ecflow/attribute/GenericAttr.cpp +++ b/libs/attribute/src/ecflow/attribute/GenericAttr.cpp @@ -26,7 +26,7 @@ GenericAttr::GenericAttr(const std::string& name, const std::vector : name_(name), values_(values) { std::string msg; - if (!Str::valid_name(name, msg)) { + if (!ecf::algorithm::is_valid_name(name, msg)) { throw std::runtime_error("GenericAttr::GenericAttr : Invalid generic name : " + msg); } } @@ -34,7 +34,7 @@ GenericAttr::GenericAttr(const std::string& name, const std::vector GenericAttr::GenericAttr(const std::string& name) : name_(name) { std::string msg; - if (!Str::valid_name(name, msg)) { + if (!ecf::algorithm::is_valid_name(name, msg)) { throw std::runtime_error("GenericAttr::GenericAttr : Invalid generic name : " + msg); } } diff --git a/libs/attribute/src/ecflow/attribute/NodeAttr.cpp b/libs/attribute/src/ecflow/attribute/NodeAttr.cpp index 6fc070ad9..c7745698e 100644 --- a/libs/attribute/src/ecflow/attribute/NodeAttr.cpp +++ b/libs/attribute/src/ecflow/attribute/NodeAttr.cpp @@ -51,7 +51,7 @@ Event::Event(int number, const std::string& eventName, bool iv, bool check_name) iv_(iv) { if (!eventName.empty() && check_name) { std::string msg; - if (!Str::valid_name(eventName, msg)) { + if (!ecf::algorithm::is_valid_name(eventName, msg)) { throw std::runtime_error("Event::Event: Invalid event name : " + msg); } } @@ -87,7 +87,7 @@ Event::Event(const std::string& eventName, bool iv) } std::string msg; - if (!Str::valid_name(eventName, msg)) { + if (!ecf::algorithm::is_valid_name(eventName, msg)) { throw std::runtime_error("Event::Event: Invalid event name : " + msg); } } @@ -214,7 +214,7 @@ Meter::Meter(const std::string& name, int min, int max, int colorChange, int val cc_(colorChange), n_(name) { if (check) { - if (!Str::valid_name(name)) { + if (!ecf::algorithm::is_valid_name(name)) { throw std::runtime_error("Meter::Meter: Invalid Meter name: " + name); } } @@ -323,7 +323,7 @@ Label::Label(const std::string& name, const std::string& value, const std::strin : n_(name), v_(value), new_v_(new_value) { - if (check_name && !Str::valid_name(n_)) { + if (check_name && !ecf::algorithm::is_valid_name(n_)) { throw std::runtime_error(MESSAGE("Label::Label: Invalid Label name :" << n_)); } } diff --git a/libs/attribute/src/ecflow/attribute/QueueAttr.cpp b/libs/attribute/src/ecflow/attribute/QueueAttr.cpp index 0e78f53c9..60684a8b8 100644 --- a/libs/attribute/src/ecflow/attribute/QueueAttr.cpp +++ b/libs/attribute/src/ecflow/attribute/QueueAttr.cpp @@ -35,7 +35,7 @@ QueueAttr::QueueAttr(const std::string& name, const std::vector& th : theQueue_(theQueue), name_(name) { std::string msg; - if (!Str::valid_name(name, msg)) { + if (!ecf::algorithm::is_valid_name(name, msg)) { throw std::runtime_error("QueueAttr::QueueAttr: Invalid queue name : " + msg); } if (theQueue.empty()) { @@ -273,7 +273,7 @@ void QueueAttr::set_state_vec(const std::vector& state_vec) { void QueueAttr::set_name(const std::string& name) { std::string msg; - if (!Str::valid_name(name, msg)) { + if (!ecf::algorithm::is_valid_name(name, msg)) { throw std::runtime_error("QueueAttr::set_name: Invalid queue name : " + msg); } name_ = name; diff --git a/libs/attribute/src/ecflow/attribute/RepeatAttr.cpp b/libs/attribute/src/ecflow/attribute/RepeatAttr.cpp index d589a38c0..ddc8aefce 100644 --- a/libs/attribute/src/ecflow/attribute/RepeatAttr.cpp +++ b/libs/attribute/src/ecflow/attribute/RepeatAttr.cpp @@ -156,7 +156,7 @@ RepeatDate::RepeatDate(const std::string& variable, int start, int end, int delt end_(end), delta_(delta), value_(start) { - if (!Str::valid_name(variable)) { + if (!ecf::algorithm::is_valid_name(variable)) { throw std::runtime_error("RepeatDate::RepeatDate: Invalid name: " + variable); } @@ -500,7 +500,7 @@ RepeatDateTime::RepeatDateTime(const std::string& variable, Instant start, Insta end_(end), delta_(delta), value_(start) { - if (!Str::valid_name(variable)) { + if (!ecf::algorithm::is_valid_name(variable)) { throw std::runtime_error("RepeatDateTime::RepeatDateTime: Invalid name: " + variable); } @@ -806,7 +806,7 @@ void RepeatDateTime::set_value(long the_new_date) { RepeatDateList::RepeatDateList(const std::string& variable, const std::vector& l) : RepeatBase(variable), list_(l) { - if (!Str::valid_name(variable)) { + if (!ecf::algorithm::is_valid_name(variable)) { throw std::runtime_error("RepeatDateList: Invalid name: " + variable); } if (list_.empty()) { @@ -1131,7 +1131,7 @@ RepeatInteger::RepeatInteger(const std::string& variable, int start, int end, in delta_(delta), value_(start) { // cout << toString() << "\n"; - if (!Str::valid_name(variable)) { + if (!ecf::algorithm::is_valid_name(variable)) { throw std::runtime_error("RepeatInteger: Invalid name: " + variable); } } @@ -1325,7 +1325,7 @@ std::string RepeatInteger::prev_value_as_string() const { RepeatEnumerated::RepeatEnumerated(const std::string& variable, const std::vector& theEnums) : RepeatBase(variable), theEnums_(theEnums) { - if (!Str::valid_name(variable)) { + if (!ecf::algorithm::is_valid_name(variable)) { throw std::runtime_error("RepeatEnumerated: Invalid name: " + variable); } if (theEnums.empty()) { @@ -1543,7 +1543,7 @@ bool RepeatEnumerated::operator==(const RepeatEnumerated& rhs) const { RepeatString::RepeatString(const std::string& variable, const std::vector& theEnums) : RepeatBase(variable), theStrings_(theEnums) { - if (!Str::valid_name(variable)) { + if (!ecf::algorithm::is_valid_name(variable)) { throw std::runtime_error("RepeatString:: Invalid name: " + variable); } if (theEnums.empty()) { diff --git a/libs/attribute/src/ecflow/attribute/Variable.cpp b/libs/attribute/src/ecflow/attribute/Variable.cpp index ccabc98ac..efba46626 100644 --- a/libs/attribute/src/ecflow/attribute/Variable.cpp +++ b/libs/attribute/src/ecflow/attribute/Variable.cpp @@ -31,14 +31,14 @@ Variable::Variable(const std::string& name, const std::string& value) : n_(name), v_(value) { std::string msg; - if (!Str::valid_name(name, msg)) { + if (!ecf::algorithm::is_valid_name(name, msg)) { throw std::runtime_error("Variable::Variable: Invalid Variable name: " + msg); } } void Variable::set_name(const std::string& v) { std::string msg; - if (!Str::valid_name(v, msg)) { + if (!ecf::algorithm::is_valid_name(v, msg)) { throw std::runtime_error("Variable::set_name: Invalid Variable name: " + msg); } n_ = v; diff --git a/libs/base/src/ecflow/base/cts/task/QueueCmd.cpp b/libs/base/src/ecflow/base/cts/task/QueueCmd.cpp index 3eafd67d0..ec5674adc 100644 --- a/libs/base/src/ecflow/base/cts/task/QueueCmd.cpp +++ b/libs/base/src/ecflow/base/cts/task/QueueCmd.cpp @@ -277,7 +277,7 @@ void QueueCmd::create(Cmd_ptr& cmd, boost::program_options::variables_map& vm, A } std::string msg; - if (!Str::valid_name(queue_name, msg)) { + if (!ecf::algorithm::is_valid_name(queue_name, msg)) { throw std::runtime_error("QueueCmd: Invalid queue name : " + msg); } diff --git a/libs/core/CMakeLists.txt b/libs/core/CMakeLists.txt index 4d9e5f887..db61c07ca 100644 --- a/libs/core/CMakeLists.txt +++ b/libs/core/CMakeLists.txt @@ -47,6 +47,7 @@ set(test_srcs test/TestLog.cpp test/TestMessage.cpp test/TestMigration.cpp + test/TestNodeNameValidity.cpp test/TestNodePath.cpp test/TestPasswdFile.cpp test/TestPasswordEncryption.cpp diff --git a/libs/core/src/ecflow/core/Str.cpp b/libs/core/src/ecflow/core/Str.cpp index be6050ad7..df112238d 100644 --- a/libs/core/src/ecflow/core/Str.cpp +++ b/libs/core/src/ecflow/core/Str.cpp @@ -14,7 +14,54 @@ namespace ecf { -const char* VALID_NODE_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_."; +namespace algorithm { + +inline bool is_valid_node_name_first_character(char c) { + return std::isalnum(c) || c == '_'; +} + +inline bool is_valid_node_name_following_character(char c) { + return std::isalnum(c) || c == '_' || c == '.'; +} + +bool is_valid_name(const std::string& name, std::string& error) { + + if (name.empty()) { + error = "Invalid name. Empty string."; + return false; + } + + if (!is_valid_node_name_first_character(name.front())) { + error = "Valid names can only consist of alphanumeric characters, " + "underscores and dots (The first character cannot be a dot). " + "The first character is not valid (only alphanumeric or an underscore is allowed): "; + error += "'"; + error += name; + error += "'"; + return false; + } + + if (!std::all_of(name.begin() + 1, name.end(), is_valid_node_name_following_character)) { + error = "Valid names can only consist of alphanumeric characters, " + "underscores and dots (The first character cannot be a dot). "; + if (name.find('\r') != std::string::npos) { + error += "Windows line ending ? "; + } + error += "'"; + error += name; + error += "'"; + return false; + } + + return true; +} + +bool is_valid_name(const std::string& name) { + return !name.empty() && is_valid_node_name_first_character(name.front()) && + std::all_of(name.begin() + 1, name.end(), is_valid_node_name_following_character); +} + +} // namespace algorithm void Str::removeQuotes(std::string& s) { if (!s.empty()) { @@ -323,67 +370,6 @@ bool Str::caseInsGreater(const std::string& a, const std::string& b) { }); } -bool Str::valid_name(const std::string& name, std::string& msg) { - // valid names are alphabetic (alphanumeric | underscore | .) - // however we can't have a leading '.' as that can interfere with trigger expressions - - // verify that the string is not empty - if (name.empty()) { - msg = "Invalid name. Empty string."; - return false; - } - - // verify that the first character is alphanumeric or is an underscore - bool result = ecf::string_constants::alphanumeric_underscore_chars.find(name[0], 0) != std::string::npos; - if (!result) { - msg = "Valid names can only consist of alphanumeric characters, " - "underscores and dots (The first character cannot be a dot). " - "The first character is not valid (only alphanumeric or an underscore is allowed): "; - msg += name; - return false; - } - - // verify that any other characters are alphanumeric or underscore - if (name.size() > 1) { - result = name.find_first_not_of(VALID_NODE_CHARS, 1) == std::string::npos; - if (!result) { - msg = "Valid names can only consist of alphanumeric characters, " - "underscores and dots (The first character cannot be a dot). "; - if (name.find('\r') != std::string::npos) { - msg += "Windows line ending ? "; - } - msg += "'"; - msg += name; - msg += "'"; // use '' to show if PC format, i.e. carriage return - } - } - - return result; -} - -bool Str::valid_name(const std::string& name) { - // valid names are alphabetic (alphanumeric | underscore | .) - // however we can't have a leading '.' as that can interfere with trigger expressions - - // verify that the string is not empty - if (name.empty()) { - return false; - } - - // verify that the first character is alphabetic or has underscore - bool result = ecf::string_constants::alphanumeric_underscore_chars.find(name[0], 0) != std::string::npos; - if (!result) { - return false; - } - - // verify that any other characters are alphanumeric or underscore - if (name.size() > 1) { - result = name.find_first_not_of(VALID_NODE_CHARS, 1) == std::string::npos; - } - - return result; -} - int Str::to_int(const std::string& the_str, int error_return) { if (the_str.find_first_of(ecf::string_constants::numeric_chars, 0) != std::string::npos) { try { diff --git a/libs/core/src/ecflow/core/Str.hpp b/libs/core/src/ecflow/core/Str.hpp index 78a419acc..b1f733a00 100644 --- a/libs/core/src/ecflow/core/Str.hpp +++ b/libs/core/src/ecflow/core/Str.hpp @@ -101,6 +101,30 @@ inline std::string tolower(std::string s) { return s; } +/// +/// @brief Check if the \param name is valid according to the rules for node/attributes names in ecFlow. +/// +/// This is used to verify the validity of nodes and attributes (Variable, Label, Event, ...) names in ecFlow. +/// +/// If the name is valid, the error buffer will not be updated (e.g. cleared), otherwise the error buffer will contain a +/// description of the validation failure. +/// +/// @param name The name to be validated. +/// @param error A buffer to hold the error description, if validation fails (i.e. when function returns false). +/// @return true if the name is valid, false otherwise. +/// +bool is_valid_name(const std::string& name, std::string& error); + +/// +/// @brief Check if the \param name is valid according to the rules for node names in ecFlow. +/// +/// This is used to verify the validity of nodes and attributes (Variable, Label, Event, ...) names in ecFlow. +/// +/// @param name The name to be validated. +/// @return true if the name is valid, false otherwise. +/// +bool is_valid_name(const std::string& name); + } // namespace algorithm class Str { @@ -188,10 +212,6 @@ class Str { /// case-insensitive Greater static bool caseInsGreater(const std::string&, const std::string&); - /// Used for checking node names - static bool valid_name(const std::string& name, std::string& msg); - static bool valid_name(const std::string& name); - /** * Convert a given string to an integer. * diff --git a/libs/core/test/TestNodeNameValidity.cpp b/libs/core/test/TestNodeNameValidity.cpp new file mode 100644 index 000000000..4a0ca742c --- /dev/null +++ b/libs/core/test/TestNodeNameValidity.cpp @@ -0,0 +1,323 @@ +/* + * Copyright 2009- ECMWF. + * + * This software is licensed under the terms of the Apache Licence version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation + * nor does it submit to any jurisdiction. + */ + +#include // std::ostream_iterator +#include +#include + +#include + +#include "ecflow/core/NodePath.hpp" +#include "ecflow/core/Stl.hpp" +#include "ecflow/core/Str.hpp" +#include "ecflow/core/Timer.hpp" +#include "ecflow/test/scaffold/Naming.hpp" + +// +// Important: +// +// The following implementation was the one used by ecFlow to check if a node name is valid. +// It is now replaced by the implementation in Str.hpp, but it was kept in the scope of this test +// to check that the new implementation behaves as expected. +// + +namespace ecf { +namespace prototype { + +const char* VALID_NODE_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_."; + +bool Str_valid_name(const std::string& name, std::string& msg) { + // valid names are alphabetic (alphanumeric | underscore | .) + // however we can't have a leading '.' as that can interfere with trigger expressions + + // verify that the string is not empty + if (name.empty()) { + msg = "Invalid name. Empty string."; + return false; + } + + // verify that the first character is alphanumeric or is an underscore + bool result = ecf::string_constants::alphanumeric_underscore_chars.find(name[0], 0) != std::string::npos; + if (!result) { + msg = "Valid names can only consist of alphanumeric characters, " + "underscores and dots (The first character cannot be a dot). " + "The first character is not valid (only alphanumeric or an underscore is allowed): "; + msg += name; + return false; + } + + // verify that any other characters are alphanumeric or underscore + if (name.size() > 1) { + result = name.find_first_not_of(VALID_NODE_CHARS, 1) == std::string::npos; + if (!result) { + msg = "Valid names can only consist of alphanumeric characters, " + "underscores and dots (The first character cannot be a dot). "; + if (name.find('\r') != std::string::npos) { + msg += "Windows line ending ? "; + } + msg += "'"; + msg += name; + msg += "'"; // use '' to show if PC format, i.e. carriage return + } + } + + return result; +} + +bool Str_valid_name(const std::string& name) { + // valid names are alphabetic (alphanumeric | underscore | .) + // however we can't have a leading '.' as that can interfere with trigger expressions + + // verify that the string is not empty + if (name.empty()) { + return false; + } + + // verify that the first character is alphabetic or has underscore + bool result = ecf::string_constants::alphanumeric_underscore_chars.find(name[0], 0) != std::string::npos; + if (!result) { + return false; + } + + // verify that any other characters are alphanumeric or underscore + if (name.size() > 1) { + result = name.find_first_not_of(VALID_NODE_CHARS, 1) == std::string::npos; + } + + return result; +} + +} // namespace prototype +} // namespace ecf + +BOOST_AUTO_TEST_SUITE(U_Core) + +BOOST_AUTO_TEST_SUITE(T_NodeNameValidity) + +BOOST_AUTO_TEST_CASE(test_node_name_validity) { + ECF_NAME_THIS_TEST(); + + struct tc + { + std::string name; + bool expected; + }; + + std::vector test_cases = { + {"", false}, + {".", false}, + {".a.", false}, + {".A", false}, + {".1", false}, + {"_", true}, + {"_a.", true}, + {"_A", true}, + {"_1", true}, + {"a.", true}, + {"A", true}, + {"1", true}, + {"aa.", true}, + {"AA", true}, + {"11", true}, + {"a.", true}, + {"ab.", true}, + {"a.b", true}, + {"a.b.", true}, + {"a._", true}, + {"ab._", true}, + {"a.b_", true}, + {"a.b._", true}, + {"a1", true}, + {"1a", true}, + {"a1.", true}, + {"1a.", true}, + {"a1_", true}, + {"1a_", true}, + {"a", true}, + {"a122345", true}, + {"_a122345", true}, + {"0", true}, + {"1", true}, + {"2", true}, + {"3", true}, + {"4", true}, + {"5", true}, + {"6", true}, + {"7", true}, + {"8", true}, + {"9", true}, + {"11", true}, + {"111", true}, + {"abcdefghABCDEFGH", true}, + {"1234567890abcdefghABCDEFGH", true}, + {"abcdefgh1234567890ABCDEFGH", true}, + {"abcdefghABCDEFGH1234567890", true}, + {"a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z", true}, + {"a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z.", true}, + {"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_.", true}, + {"_abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_.", true}, + {".abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_.", false}, + {"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_.\r", false}, + {"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_.\n", false}, + {"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_.\t", false}, + {"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_. ", false}, + {"\t", false}, + {"\n", false}, + {"\r\n", false}, + {"?", false}, + {"!", false}, + {"\"", false}, + {"$", false}, + {"%", false}, + {"^", false}, + {"*", false}, + {"(", false}, + {")", false}, + {"-", false}, + {"+", false}, + {":", false}, + {";", false}, + {"@", false}, + {"~", false}, + {"<", false}, + {">", false}, + {"!", false}, + {" invalid", false}, + {"\tinvalid", false}, + {"\ninvalid", false}, + {"invalid space", false}, + {"invalid ", false}, + {"inva lid", false}, + {"inva\tlid", false}, + {"inva\nlid", false}, + {"inva\rlid", false}, + }; + + for (const auto& tc : test_cases) { + + { + bool actual = ecf::algorithm::is_valid_name(tc.name); + bool original = ecf::prototype::Str_valid_name(tc.name); + + BOOST_CHECK_MESSAGE(actual == tc.expected, + "For name '" << tc.name << "' expected " << tc.expected << ", found " << actual); + BOOST_CHECK_MESSAGE(actual == original, + "For name '" << tc.name << "' both original and new algorithms provide same result"); + } + { + std::string actual_error; + bool actual = ecf::algorithm::is_valid_name(tc.name, actual_error); + + std::string original_error; + bool original = ecf::prototype::Str_valid_name(tc.name, original_error); + + BOOST_CHECK_MESSAGE(actual == tc.expected, + "For name '" << tc.name << "' expected " << tc.expected << ", found " << actual); + BOOST_CHECK_MESSAGE(actual == original, + "For name '" << tc.name << "' both original and new algorithms provide same result"); + } + } +} + +BOOST_AUTO_TEST_CASE(test_node_name_validity_random) { + ECF_NAME_THIS_TEST(); + + struct timer + { + timer() = default; + ~timer() = default; + + void begin() { start = std::chrono::steady_clock::now(); } + void end() { finish = std::chrono::steady_clock::now(); } + + std::chrono::nanoseconds duration() const { + return std::chrono::duration_cast(finish - start); + } + + std::chrono::time_point start; + std::chrono::time_point finish; + std::string_view name; + std::string_view type; + }; + + auto generate_random_valid_name = []() { + std::string_view valid = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_."; + + static std::random_device rd; + static std::mt19937_64 gen(rd()); + std::uniform_int_distribution length_dist(1, 20); + + std::uniform_int_distribution first_chars_dist(0, 62); // abc...ABC...0123456789_ (63 chars, no '.'!) + std::uniform_int_distribution following_chars_dist(0, 63); // abc...ABC...0123456789_. (64 chars) + + int length = length_dist(gen); + std::string name; + name.reserve(length); + for (int i = 0; i < length; ++i) { + if (i == 0) { + name.push_back(valid[first_chars_dist(gen)]); + } + else { + name.push_back(valid[following_chars_dist(gen)]); + } + } + return name; + }; + + double n_time = 0.; + double o_time = 0.; + + int iterations = 1000; + + for (int i = 0; i < iterations; ++i) { + + auto name = generate_random_valid_name(); + + bool original; + { + timer t; + + t.begin(); + original = ecf::prototype::Str_valid_name(name); + t.end(); + + auto ns = t.duration().count(); + o_time += ns; + ECF_TEST_DBG(<< "Original approach, name: '" << name << "', duration: " << ns << " ns"); + } + + bool actual; + { + timer t; + + t.begin(); + actual = ecf::algorithm::is_valid_name(name); + t.end(); + + auto ns = t.duration().count(); + n_time += ns; + ECF_TEST_DBG(<< " New approach, name: " << name << ", duration: " << ns << " ns"); + } + + BOOST_CHECK_MESSAGE(actual == original, + "For name '" << name << "' both original and new approaches provide same result"); + } + + double old_time_per_iteration = o_time / iterations; + ECF_TEST_DBG(<< "Original approach time: " << old_time_per_iteration << " ns"); + double new_time_per_iteration = n_time / iterations; + ECF_TEST_DBG(<< " New approach time: " << new_time_per_iteration << " ns"); + + BOOST_CHECK_MESSAGE(new_time_per_iteration < old_time_per_iteration, "New algorithm is faster than original"); +} + +BOOST_AUTO_TEST_SUITE_END() + +BOOST_AUTO_TEST_SUITE_END() diff --git a/libs/core/test/TestStr.cpp b/libs/core/test/TestStr.cpp index 89fd3072e..5b090786d 100644 --- a/libs/core/test/TestStr.cpp +++ b/libs/core/test/TestStr.cpp @@ -885,64 +885,6 @@ BOOST_AUTO_TEST_CASE(test_int_to_str_perf, *boost::unit_test::disabled()) { } } -BOOST_AUTO_TEST_CASE(test_str_valid_name) { - ECF_NAME_THIS_TEST(); - - std::vector valid; - valid.emplace_back("a"); - valid.emplace_back("a122345"); - valid.emplace_back("_a122345"); - valid.emplace_back("_"); - valid.emplace_back("0"); - valid.emplace_back("1"); - valid.emplace_back("2"); - valid.emplace_back("3"); - valid.emplace_back("4"); - valid.emplace_back("5"); - valid.emplace_back("6"); - valid.emplace_back("7"); - valid.emplace_back("8"); - valid.emplace_back("9"); - valid.emplace_back("11"); - valid.emplace_back("111"); - for (const auto& i : valid) { - std::string msg; - BOOST_CHECK_MESSAGE(Str::valid_name(i, msg), "Expected " << i << " to be valid"); - BOOST_CHECK_MESSAGE(Str::valid_name(i), "Expected " << i << " to be valid"); - } - - BOOST_CHECK_MESSAGE(!Str::valid_name(""), "Expected empty string to be in-valid"); - BOOST_CHECK_MESSAGE(!Str::valid_name("."), "Expected '.' string to be in-valid"); - std::vector invalid; - invalid.emplace_back("?"); - invalid.emplace_back("!"); - invalid.emplace_back("\""); - invalid.emplace_back("$"); - invalid.emplace_back("%"); - invalid.emplace_back("^"); - invalid.emplace_back("*"); - invalid.emplace_back("("); - invalid.emplace_back(")"); - invalid.emplace_back("-"); - invalid.emplace_back("+"); - invalid.emplace_back(":"); - invalid.emplace_back(";"); - invalid.emplace_back("@"); - invalid.emplace_back("~"); - invalid.emplace_back("<"); - invalid.emplace_back(">"); - invalid.emplace_back("!"); - for (const auto& i : invalid) { - std::string msg; - BOOST_CHECK_MESSAGE(!Str::valid_name(i, msg), "Expected " << i << " to be in-valid"); - BOOST_CHECK_MESSAGE(!Str::valid_name(i), "Expected " << i << " to be in-valid"); - - std::string s = "a" + i; - BOOST_CHECK_MESSAGE(!Str::valid_name(s, msg), "Expected " << s << " to be in-valid"); - BOOST_CHECK_MESSAGE(!Str::valid_name(s), "Expected " << s << " to be in-valid"); - } -} - BOOST_AUTO_TEST_SUITE_END() BOOST_AUTO_TEST_SUITE_END() diff --git a/libs/node/src/ecflow/node/AvisoAttr.cpp b/libs/node/src/ecflow/node/AvisoAttr.cpp index 9cadff605..00acb673c 100644 --- a/libs/node/src/ecflow/node/AvisoAttr.cpp +++ b/libs/node/src/ecflow/node/AvisoAttr.cpp @@ -36,7 +36,7 @@ std::string ensure_single_quotes(const AvisoAttr::listener_t listener) { } // namespace implementation bool AvisoAttr::is_valid_name(const std::string& name) { - return ecf::Str::valid_name(name); + return ecf::algorithm::is_valid_name(name); } AvisoAttr::AvisoAttr(Node* parent, @@ -59,7 +59,7 @@ AvisoAttr::AvisoAttr(Node* parent, reason_{implementation::ensure_single_quotes(reason)}, revision_{revision}, controller_{nullptr} { - if (!ecf::Str::valid_name(name_)) { + if (!ecf::algorithm::is_valid_name(name_)) { THROW_EXCEPTION(ecf::InvalidArgument, "Invalid AvisoAttr name :" << name_); } } diff --git a/libs/node/src/ecflow/node/InLimit.cpp b/libs/node/src/ecflow/node/InLimit.cpp index 4e4c9ac2f..bad4a9800 100644 --- a/libs/node/src/ecflow/node/InLimit.cpp +++ b/libs/node/src/ecflow/node/InLimit.cpp @@ -35,7 +35,7 @@ InLimit::InLimit(const std::string& name, tokens_(tokens), limit_this_node_only_(limit_this_node_only), limit_submission_(limit_submission) { - if (check && !Str::valid_name(name)) { + if (check && !ecf::algorithm::is_valid_name(name)) { throw std::runtime_error("InLimit::InLimit: Invalid InLimit name: " + name); } if (limit_this_node_only_ && limit_submission_) { diff --git a/libs/node/src/ecflow/node/Limit.cpp b/libs/node/src/ecflow/node/Limit.cpp index 918aba1c2..6a22cb671 100644 --- a/libs/node/src/ecflow/node/Limit.cpp +++ b/libs/node/src/ecflow/node/Limit.cpp @@ -21,7 +21,7 @@ Limit::Limit(const std::string& name, int limit) : n_(name), lim_(limit) { - if (!ecf::Str::valid_name(name)) { + if (!ecf::algorithm::is_valid_name(name)) { throw std::runtime_error("Limit::Limit: Invalid Limit name: " + name); } } @@ -31,7 +31,7 @@ Limit::Limit(const std::string& name, int limit, int value, const std::set& vec, const py::list& lis std::string part_expr; if (py::extract(list[i]).check()) { part_expr = py::extract(list[i]); - if (ecf::Str::valid_name(part_expr)) { + if (ecf::algorithm::is_valid_name(part_expr)) { part_expr += " == complete"; } } From af41988768e8200ce6c540af2895af2aa6133634 Mon Sep 17 00:00:00 2001 From: Marcos Bento Date: Sat, 28 Mar 2026 08:52:06 +0000 Subject: [PATCH 17/90] refactor: Improve error message --- libs/core/src/ecflow/core/Str.cpp | 18 +++++------------- libs/core/test/TestNodeNameValidity.cpp | 9 +++++++++ 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/libs/core/src/ecflow/core/Str.cpp b/libs/core/src/ecflow/core/Str.cpp index df112238d..5c223a8fe 100644 --- a/libs/core/src/ecflow/core/Str.cpp +++ b/libs/core/src/ecflow/core/Str.cpp @@ -27,29 +27,21 @@ inline bool is_valid_node_name_following_character(char c) { bool is_valid_name(const std::string& name, std::string& error) { if (name.empty()) { - error = "Invalid name. Empty string."; + error = "Invalid name '': empty string"; return false; } if (!is_valid_node_name_first_character(name.front())) { - error = "Valid names can only consist of alphanumeric characters, " - "underscores and dots (The first character cannot be a dot). " - "The first character is not valid (only alphanumeric or an underscore is allowed): "; - error += "'"; + error = "Invalid name '"; error += name; - error += "'"; + error += "': only alphanumeric characters or underscore are accepted as first character"; return false; } if (!std::all_of(name.begin() + 1, name.end(), is_valid_node_name_following_character)) { - error = "Valid names can only consist of alphanumeric characters, " - "underscores and dots (The first character cannot be a dot). "; - if (name.find('\r') != std::string::npos) { - error += "Windows line ending ? "; - } - error += "'"; + error = "Invalid name '"; error += name; - error += "'"; + error += "': only alphanumeric characters, underscore or dot are accepted"; return false; } diff --git a/libs/core/test/TestNodeNameValidity.cpp b/libs/core/test/TestNodeNameValidity.cpp index 4a0ca742c..1524e658e 100644 --- a/libs/core/test/TestNodeNameValidity.cpp +++ b/libs/core/test/TestNodeNameValidity.cpp @@ -222,6 +222,15 @@ BOOST_AUTO_TEST_CASE(test_node_name_validity) { "For name '" << tc.name << "' expected " << tc.expected << ", found " << actual); BOOST_CHECK_MESSAGE(actual == original, "For name '" << tc.name << "' both original and new algorithms provide same result"); + + if (!tc.expected) { + auto expected_prefix = "Invalid name '" + tc.name + "': "; + auto actual_prefix = actual_error.substr(0, expected_prefix.size()); + + BOOST_CHECK_MESSAGE(actual_prefix == expected_prefix, + "The error message for '" << tc.name << "' expected prefix <" << expected_prefix + << ">, found <" << actual_prefix << ">"); + } } } } From b620cf5cbdb6b60a9fc35b0a196cc49d1873b54b Mon Sep 17 00:00:00 2001 From: Marcos Bento Date: Thu, 9 Apr 2026 16:23:57 +0100 Subject: [PATCH 18/90] fix: correct parsing of simple expressions Re ECFLOW-2075 --- libs/core/src/ecflow/core/Str.hpp | 31 ++++++ libs/core/test/TestNodeNameValidity.cpp | 2 +- libs/core/test/TestStr.cpp | 34 +++++++ libs/node/src/ecflow/node/ExprParser.cpp | 123 ++++++++++++++--------- libs/node/src/ecflow/node/ExprParser.hpp | 20 +++- libs/node/test/TestExprParser.cpp | 37 +++++++ 6 files changed, 196 insertions(+), 51 deletions(-) diff --git a/libs/core/src/ecflow/core/Str.hpp b/libs/core/src/ecflow/core/Str.hpp index b1f733a00..e3323764f 100644 --- a/libs/core/src/ecflow/core/Str.hpp +++ b/libs/core/src/ecflow/core/Str.hpp @@ -58,6 +58,32 @@ inline static auto join(const Sequence1& strings, const Sequence2& separator = s return ::boost::algorithm::join(strings, separator); } +template +ResultSequence split_by(ResultSequence& buffer, const Sequence1& input, const Sequence2& pattern) { + std::string::size_type start = 0; + std::string::size_type pos = 0; + while ((pos = input.find(pattern, start)) != std::string::npos) { + auto token = input.substr(start, pos - start); + if (!token.empty()) { + buffer.push_back(token); + } + if constexpr (std::is_same_v) { + start = pos + std::size(pattern); + } + else if constexpr (std::is_same_v) { + start = pos + std::size(pattern); + } + else { + start = pos + std::size(pattern) - 1; + } + } + auto token = input.substr(start); + if (!token.empty()) { + buffer.push_back(token); + } + return buffer; +} + template inline static auto replace_first(Sequence& input, const SearchSequence& search, const ReplaceSequence& replace) { return ::boost::algorithm::replace_first(input, search, replace); @@ -73,6 +99,11 @@ inline static bool starts_with(const Sequence1& input, const Sequence2& pattern) return ::boost::algorithm::starts_with(input, pattern); } +template +inline static bool ends_with(const Sequence1& input, const Sequence2& pattern) { + return ::boost::algorithm::ends_with(input, pattern); +} + template inline static void trim(Sequence& input) { ::boost::algorithm::trim(input); diff --git a/libs/core/test/TestNodeNameValidity.cpp b/libs/core/test/TestNodeNameValidity.cpp index 1524e658e..9c65dbb16 100644 --- a/libs/core/test/TestNodeNameValidity.cpp +++ b/libs/core/test/TestNodeNameValidity.cpp @@ -324,7 +324,7 @@ BOOST_AUTO_TEST_CASE(test_node_name_validity_random) { double new_time_per_iteration = n_time / iterations; ECF_TEST_DBG(<< " New approach time: " << new_time_per_iteration << " ns"); - BOOST_CHECK_MESSAGE(new_time_per_iteration < old_time_per_iteration, "New algorithm is faster than original"); + BOOST_WARN_MESSAGE(new_time_per_iteration < old_time_per_iteration, "New algorithm expected faster than original"); } BOOST_AUTO_TEST_SUITE_END() diff --git a/libs/core/test/TestStr.cpp b/libs/core/test/TestStr.cpp index 5b090786d..c26d39ea0 100644 --- a/libs/core/test/TestStr.cpp +++ b/libs/core/test/TestStr.cpp @@ -885,6 +885,40 @@ BOOST_AUTO_TEST_CASE(test_int_to_str_perf, *boost::unit_test::disabled()) { } } +BOOST_AUTO_TEST_CASE(test_str_split_by) { + + struct TestCase + { + std::string input; + std::string pattern; + std::vector expected; + }; + + std::vector testCases = { + {"This is a string", "...", {"This is a string"}}, + {"This...is...a...string", "...", {"This", "is", "a", "string"}}, + {"This...is...a...string...", "...", {"This", "is", "a", "string"}}, + {"...This...is...a...string", "...", {"This", "is", "a", "string"}}, + {"...This...is...a...string...", "...", {"This", "is", "a", "string"}}, + {"......This......is......a......string......", "...", {"This", "is", "a", "string"}}, + {"..This.....is.....a.....string.....", "...", {"..This", "..is", "..a", "..string", ".."}}, + {"expression 1==expression 2", "==", {"expression 1", "expression 2"}}, + {"expression 1==expression 2==expression 3", "==", {"expression 1", "expression 2", "expression 3"}}, + {"expression 1 == expression 2", " == ", {"expression 1", "expression 2"}}, + {"expression 1 == expression 2 == expression 3", " == ", {"expression 1", "expression 2", "expression 3"}}, + {"expression 1 eq expression 2", " eq ", {"expression 1", "expression 2"}}, + {"expression 1eqexpression 2", " eq ", {"expression 1eqexpression 2"}}}; + + for (const auto& testCase : testCases) { + std::vector result; + ecf::algorithm::split_by(result, testCase.input, testCase.pattern); + BOOST_CHECK_MESSAGE(result == testCase.expected, + "Failed for input: '" << testCase.input << "' pattern: '" << testCase.pattern + << "'. Expected: " << toString(testCase.expected) + << " but found: " << toString(result)); + } +} + BOOST_AUTO_TEST_SUITE_END() BOOST_AUTO_TEST_SUITE_END() diff --git a/libs/node/src/ecflow/node/ExprParser.cpp b/libs/node/src/ecflow/node/ExprParser.cpp index f4973af12..e015a5093 100644 --- a/libs/node/src/ecflow/node/ExprParser.cpp +++ b/libs/node/src/ecflow/node/ExprParser.cpp @@ -1196,66 +1196,95 @@ bool has_complex_expressions(const std::string& expr) { } bool SimpleExprParser::doParse() { - // 3199.def 57Mg - // a == b, || a eq b i.e simple 76940 - // complex 39240 + + // If expression has complex operators, return false if (has_complex_expressions(expr_)) { return false; } - // look for path == || path eq state - // This gets round issue with Str::split: - // "a = complete" will be split - // since will split on *ANY* of character, and hence this would not be reported as an error - std::vector tokens; - if (expr_.find("==") != std::string::npos) { - Str::split(expr_, tokens, "=="); + // Start by trimming the original expression + auto expression = expr_; + ecf::algorithm::trim(expression); + + // If expression is empty, return false + if (expression.empty()) { + return false; + } + + // If expression begins/ends with '==' or 'eq', return false + // n.b. for 'eq' the comparison is done considering the necessary whitespace after/before the operator + if (ecf::algorithm::starts_with(expression, "==") || ecf::algorithm::starts_with(expression, "eq ") || + ecf::algorithm::ends_with(expression, "==") || ecf::algorithm::ends_with(expression, " eq")) { + return false; + } + + // Split the expression into tokens, considering '==' or 'eq' operators. + // n.b. for 'eq' the comparison is done considering the necessary whitespace before and after the operator + std::vector operands; + if (expression.find("==") != std::string::npos) { + // Expecting expressions such as: + // `/path/to/node==` + // `/path/to/node == ` + // `==` + // ` == ` + ecf::algorithm::split_by(operands, expression, "=="); } - else if (expr_.find(" eq ") != std::string::npos) { - Str::split(expr_, tokens, " eq "); + else if (expression.find(" eq ") != std::string::npos) { + // Or, expecting expressions such as: + // `/path/to/node eq ` + // ` eq ` + ecf::algorithm::split_by(operands, expression, " eq "); } else { + // If the expression does not contain simple operators ('==' or 'eq'), return false return false; } - if (tokens.size() == 2) { + // If the expression does not have exactly two operands, return false + if (operands.size() != 2) { + return false; + } - ecf::algorithm::trim(tokens[0]); - ecf::algorithm::trim(tokens[1]); + // Trim the operands + ecf::algorithm::trim(operands.front()); + ecf::algorithm::trim(operands.back()); - if (tokens[0].find(' ') != std::string::npos) { - // cout << "Found space " << expr_ << "\n"; - return false; - } + const auto& left_operand = operands.front(); + const auto& right_operand = operands.back(); - if (DState::isValid(tokens[1])) { + // If the left operand (i.e. either a `/path/to/node` or ``) contain spaces, return false + if (left_operand.find(' ') != std::string::npos) { + return false; + } - ast_ = std::make_unique(); - Ast* someRoot = new AstEqual(); - someRoot->addChild(new AstNode(tokens[0])); - someRoot->addChild(new AstNodeState(DState::toState(tokens[1]))); - ast_->addChild(someRoot); - // cout << "simple expr : " << expr_ << " `" << tokens[0] << "' = '" << tokens[1] << "'\n"; - return true; - } - else { - try { - auto left = ecf::convert_to(tokens[0]); - auto right = ecf::convert_to(tokens[1]); - ast_ = std::make_unique(); - Ast* someRoot = new AstEqual(); - someRoot->addChild(new AstInteger(left)); - someRoot->addChild(new AstInteger(right)); - ast_->addChild(someRoot); - // cout << "simple INT expr : " << expr_ << " `" << tokens[0] << "' = '" << tokens[1] << - // "'\n"; - return true; - } - catch (const ecf::bad_conversion&) { - // cout << "simple INT FAILED expr : " << expr_ << " `" << tokens[0] << "' = '" - // << tokens[1] << "'\n"; - } - } + // If the right operand (i.e. either a `` or ``) contain spaces, return false + if (right_operand.find(' ') != std::string::npos) { + return false; + } + + // If the right operand is a valid ``), parse the expression and return true + if (DState::isValid(right_operand)) { + ast_ = std::make_unique(); + Ast* someRoot = new AstEqual(); + someRoot->addChild(new AstNode(left_operand)); + someRoot->addChild(new AstNodeState(DState::toState(right_operand))); + ast_->addChild(someRoot); + return true; + } + + // Otherwise, the expression must be a comparison of two numerical values. + // Parse the operands as integers, and return accordingly. + try { + auto left = ecf::convert_to(left_operand); + auto right = ecf::convert_to(right_operand); + ast_ = std::make_unique(); + Ast* someRoot = new AstEqual(); + someRoot->addChild(new AstInteger(left)); + someRoot->addChild(new AstInteger(right)); + ast_->addChild(someRoot); + return true; + } + catch (const ecf::bad_conversion&) { + return false; } - return false; } diff --git a/libs/node/src/ecflow/node/ExprParser.hpp b/libs/node/src/ecflow/node/ExprParser.hpp index c7d9a41b6..729a5b965 100644 --- a/libs/node/src/ecflow/node/ExprParser.hpp +++ b/libs/node/src/ecflow/node/ExprParser.hpp @@ -45,9 +45,23 @@ class ExprParser { std::string expr_; }; -// This class was added to mitigate the slowness of the boost classic spirit parser -// we will recognise very simple expression, and bypass spirit. Very limited -// But the simple expression do form a very large subset +/// +/// @brief This class enables quick 'simple' expression parsing without the overhead of the Boost spirit parser. +/// +/// This is used as a first pass to quickly parse simple expressions. A 'simple' expression is of the form: +/// - `/path/to/node==` +/// - `/path/to/node == ` +/// - `/path/to/node eq ` +/// - `==` +/// - ` == ` +/// - ` eq ` +/// +/// This optimisation significantly improves performance when a significant number of expressions are 'simple', +/// which is typical based on empirical observations. +/// +/// If the provided expression is not 'simple', the parsing fails and the overall parser falls back to the full boost +/// spirit parser. +/// class SimpleExprParser { public: explicit SimpleExprParser(const std::string& expression) diff --git a/libs/node/test/TestExprParser.cpp b/libs/node/test/TestExprParser.cpp index 9eb93464b..43fb4c290 100644 --- a/libs/node/test/TestExprParser.cpp +++ b/libs/node/test/TestExprParser.cpp @@ -110,6 +110,43 @@ BOOST_AUTO_TEST_CASE(test_expression_parser_basic) { } } +BOOST_AUTO_TEST_CASE(test_expression_simple_parser) { + ECF_NAME_THIS_TEST(); + + struct TestCase + { + std::string expression; + bool parseable; + }; + + std::vector test_cases = {{"1 eq 2", true}, + {"1eq2 eq ", false}, + {" eq 1eq2", false}, + {"1eq2 eq complete", true}, + {"xeqz eq complete ", true}, + {"xeqz eq complete a", false}, + {"xeqz eq complete a ", false}, + {"a eq complete", true}, + {"a eq complete ", true}, + {" a eq complete", true}, + {" a == complete ", true}, + {"a eq complete a", false}, + {"a eq complete a ", false}, + {"1 == 2", true}, + {"1==2", true}, + {"1==2 == ", false}, + {"1==2==", false}, + {" == 3==4", false}, + {"==3==4", false}}; + + for (const auto& tc : test_cases) { + SimpleExprParser parser(tc.expression); + auto parsed = parser.doParse(); + BOOST_REQUIRE_MESSAGE(parsed == tc.parseable, + "Parsing '" << tc.expression << "', expected " << tc.parseable << " found " << parsed); + } +} + BOOST_AUTO_TEST_CASE(test_expression_parser_basic_with_braces) { ECF_NAME_THIS_TEST(); From dea9a65bb495ee5528175c439fdb0795232c31d4 Mon Sep 17 00:00:00 2001 From: Marcos Bento Date: Fri, 10 Apr 2026 09:46:49 +0100 Subject: [PATCH 19/90] refactor: improve base64 encoding/decoding - Remove unnecessary Std/Boost dependencies - Improve documentation and set of test cases --- libs/core/src/ecflow/core/Base64.cpp | 179 ++++++++++++++++++++++++--- libs/core/src/ecflow/core/Base64.hpp | 17 +-- libs/core/test/TestBase64.cpp | 60 ++++++++- 3 files changed, 229 insertions(+), 27 deletions(-) diff --git a/libs/core/src/ecflow/core/Base64.cpp b/libs/core/src/ecflow/core/Base64.cpp index 8af273ea6..69317c224 100644 --- a/libs/core/src/ecflow/core/Base64.cpp +++ b/libs/core/src/ecflow/core/Base64.cpp @@ -10,33 +10,176 @@ #include "ecflow/core/Base64.hpp" -#include +#include +#include +#include -#include -#include -#include -#include +namespace { + +constexpr std::array make_encoding_table() { + return std::array{ + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', + 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', + 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'}; +} + +constexpr auto encoding_table = make_encoding_table(); + +constexpr unsigned char padding_marker = '='; +constexpr uint8_t invalid_marker = 0xFF; + +constexpr std::array make_decoding_table() { + std::array table{}; + for (auto& v : table) { + v = invalid_marker; + } + for (uint8_t i = 0; i < static_cast(encoding_table.size()); ++i) { + table[encoding_table[i]] = i; + } + table[padding_marker] = 0; + return table; +} + +constexpr auto decoding_table = make_decoding_table(); + +bool is_base64_char(unsigned char c) { + return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || (c == '+' || c == '/'); +} + +bool is_base64_padding(unsigned char c) { + return c == padding_marker; +} + +bool is_base64(unsigned char c) { + return is_base64_char(c) || is_base64_padding(c); +} + +} // namespace namespace ecf { -std::string decode_base64(const std::string& val) { - using namespace boost::archive::iterators; - using It = transform_width, 8, 6>; - return boost::algorithm::trim_right_copy_if(std::string(It(std::begin(val)), It(std::end(val))), - [](char c) { return c == '\0'; }); +std::string decode_base64(std::string_view value) { + if (value.empty()) { + return {}; + } + + const auto len = value.size(); + if (len % 4 != 0) { + throw std::invalid_argument("Invalid base64 input length"); + } + + // Determine output size, provisioning padding at the end when necessary + size_t out_len = (len / 4) * 3; + if (value[len - 1] == '=') { + --out_len; + } + if (value[len - 2] == '=') { + --out_len; + + // Detect invalid padding, with a non-equal sign after the first equals (e.g. Zg=a) + if (value[len - 1] != '=') { + throw std::invalid_argument("Invalid base64 padding"); + } + } + + std::string result; + result.reserve(out_len); + + for (size_t i = 0; i < len; i += 4) { + + auto a = static_cast(value[i]); + auto b = static_cast(value[i + 1]); + auto c = static_cast(value[i + 2]); + auto d = static_cast(value[i + 3]); + + if (!is_base64(a) || !is_base64(b) || !is_base64(c) || !is_base64(d)) { + throw std::invalid_argument("Invalid base64 input"); + } + + uint32_t sextet_a = decoding_table[a]; + uint32_t sextet_b = decoding_table[b]; + uint32_t sextet_c = decoding_table[c]; + uint32_t sextet_d = decoding_table[d]; + + uint32_t triple = (sextet_a << 18) | (sextet_b << 12) | (sextet_c << 6) | sextet_d; + + result += static_cast((triple >> 16) & 0xFF); + if (c != '=') { + result += static_cast((triple >> 8) & 0xFF); + } + if (d != '=') { + result += static_cast(triple & 0xFF); + } + } + + return result; } -std::string encode_base64(const std::string& val) { - using namespace boost::archive::iterators; - using It = base64_from_binary>; - auto tmp = std::string(It(std::begin(val)), It(std::end(val))); - return tmp.append((3 - val.size() % 3) % 3, '='); +std::string encode_base64(std::string_view value) { + if (value.empty()) { + return {}; + } + + const auto len = value.size(); + // Output size: 4 characters for every 3 input bytes, rounded up + size_t out_len = 4 * ((len + 2) / 3); + + std::string result; + result.reserve(out_len); + + for (size_t i = 0; i < len; i += 3) { + auto a = static_cast(value[i]); + auto b = (i + 1 < len) ? static_cast(value[i + 1]) : uint8_t{0}; + auto c = (i + 2 < len) ? static_cast(value[i + 2]) : uint8_t{0}; + + uint32_t triple = (static_cast(a) << 16) | (static_cast(b) << 8) | static_cast(c); + + result += encoding_table[(triple >> 18) & 0x3F]; + result += encoding_table[(triple >> 12) & 0x3F]; + result += (i + 1 < len) ? encoding_table[(triple >> 6) & 0x3F] : '='; + result += (i + 2 < len) ? encoding_table[triple & 0x3F] : '='; + } + + return result; } -bool validate_base64(const std::string& value) { - static const std::regex pattern("^([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{4}|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)$"); +bool validate_base64(std::string_view value) { + // An empty string is considered valid base64 (represents empty data) + if (value.empty()) { + return true; + } + + // An invalid sized string is considered invalid base64 (must be padded with '=' to multiple of 4) + if (value.size() % 4 != 0) { + return false; + } + + // Check that all characters are valid base64 characters + // Padding ('=') is only allowed at the end (last 1 or 2 characters) + size_t padding_start = value.size(); + for (size_t i = 0; i < value.size(); ++i) { + auto c = static_cast(value[i]); + if (value[i] == '=') { + padding_start = i; + break; + } + if (decoding_table[c] == 0xFF) { + return false; // invalid character + } + } + + // Verify padding: only 0, 1, or 2 '=' characters at the very end + size_t padding_len = value.size() - padding_start; + if (padding_len > 2) { + return false; + } + for (size_t i = padding_start; i < value.size(); ++i) { + if (value[i] != '=') { + return false; // non-padding character after padding + } + } - return value.size() % 4 == 0 && std::regex_match(value, pattern); + return true; } } // namespace ecf diff --git a/libs/core/src/ecflow/core/Base64.hpp b/libs/core/src/ecflow/core/Base64.hpp index 606f039ea..ccb1be9f4 100644 --- a/libs/core/src/ecflow/core/Base64.hpp +++ b/libs/core/src/ecflow/core/Base64.hpp @@ -21,8 +21,12 @@ namespace ecf { * * @param value the base64 encoded string * @return the decoded string + * + * @throws std::invalid_argument if the input string has size not multiple of 4 + * @throws std::invalid_argument if any character in the input string is invalid (not in A-Za-z0-9+/ or '=' for padding) + * @throws std::invalid_argument if padding is invalid (more than 2 '=', or non-padding characters after first '=') */ -std::string decode_base64(const std::string& value); +std::string decode_base64(std::string_view value); /** * @brief Encode a string to base64. @@ -30,21 +34,20 @@ std::string decode_base64(const std::string& value); * @param value the string to encode * @return the base64 encoded string */ -std::string encode_base64(const std::string& value); +std::string encode_base64(std::string_view value); /** * @brief Simple validation of base64 encoded string. * * Checks the following conditions: - * - length has to be multiple of 4 - * - valid characters are A-Za-z0-9+/ - * padding of 0-2 characters at the end of string can be = + * - length has to be multiple of 4 (n.b. 0 if a valid lenght) + * - valid characters are A-Za-z0-9+/ + * - '=' can be used for padding (0, 1, or 2 characters) at the end of string * * @param value the base64 encoded string * @return true if valid, false otherwise */ - -bool validate_base64(const std::string& value); +bool validate_base64(std::string_view value); } // namespace ecf diff --git a/libs/core/test/TestBase64.cpp b/libs/core/test/TestBase64.cpp index 12a5c2fed..97779154a 100644 --- a/libs/core/test/TestBase64.cpp +++ b/libs/core/test/TestBase64.cpp @@ -81,10 +81,66 @@ BOOST_AUTO_TEST_CASE(test_base64_is_able_to_decode_and_encode_long_string_with_n BOOST_AUTO_TEST_CASE(test_base64_is_able_to_validate) { ECF_NAME_THIS_TEST(); + BOOST_CHECK(ecf::validate_base64("")); + BOOST_CHECK(ecf::validate_base64("Zm9v")); + BOOST_CHECK(ecf::validate_base64("Zm8=")); BOOST_CHECK(ecf::validate_base64("SGVsbG8sIFdvcmxkIQ==")); BOOST_CHECK(ecf::validate_base64("U29tZSB0ZXh0IHdpdGggcGFkZGluZw==")); - BOOST_CHECK(!ecf::validate_base64("Invalid base64 string!")); - BOOST_CHECK(!ecf::validate_base64("SGVsbG8sIFdvcmxkIQ")); // missing padding + BOOST_CHECK(!ecf::validate_base64("Invalid base64 string!")); // invalid size + BOOST_CHECK(!ecf::validate_base64("Invalid base64 !")); // invalid characters + BOOST_CHECK(!ecf::validate_base64("SGVsbG8sIFdvcmxkIQ")); // missing padding + BOOST_CHECK(!ecf::validate_base64("Zm8=====")); // padding too long + BOOST_CHECK(!ecf::validate_base64("Zm9vYg=x")); // invalid padding characters +} + +BOOST_AUTO_TEST_CASE(test_base64_rfc_test_cases) { + ECF_NAME_THIS_TEST(); + + // The following test cases are based on RFC 4648, Section 10. + // The original tests can be found at https://datatracker.ietf.org/doc/html/rfc4648#section-10. + + // Encoding + { + BOOST_CHECK_EQUAL(ecf::encode_base64(""), ""); + BOOST_CHECK_EQUAL(ecf::encode_base64("f"), "Zg=="); + BOOST_CHECK_EQUAL(ecf::encode_base64("fo"), "Zm8="); + BOOST_CHECK_EQUAL(ecf::encode_base64("foo"), "Zm9v"); + BOOST_CHECK_EQUAL(ecf::encode_base64("foob"), "Zm9vYg=="); + BOOST_CHECK_EQUAL(ecf::encode_base64("fooba"), "Zm9vYmE="); + BOOST_CHECK_EQUAL(ecf::encode_base64("foobar"), "Zm9vYmFy"); + } + + // Decoding + { + BOOST_CHECK_EQUAL(ecf::decode_base64(""), ""); + BOOST_CHECK_EQUAL(ecf::decode_base64("Zg=="), "f"); + BOOST_CHECK_EQUAL(ecf::decode_base64("Zm8="), "fo"); + BOOST_CHECK_EQUAL(ecf::decode_base64("Zm9v"), "foo"); + BOOST_CHECK_EQUAL(ecf::decode_base64("Zm9vYg=="), "foob"); + BOOST_CHECK_EQUAL(ecf::decode_base64("Zm9vYmE="), "fooba"); + BOOST_CHECK_EQUAL(ecf::decode_base64("Zm9vYmFy"), "foobar"); + } +} + +BOOST_AUTO_TEST_CASE(test_base64_error_cases) { + ECF_NAME_THIS_TEST(); + + { + // invalid characters (' ' and '!') + BOOST_CHECK_THROW(ecf::decode_base64("Invalid base64 !"), std::invalid_argument); + + // invalid size (-2), missing padding + BOOST_CHECK_THROW(ecf::decode_base64("SGVsbG8sIFdvcmxkIQ"), std::invalid_argument); + + // invalid size (+1), newline after padding + BOOST_CHECK_THROW(ecf::decode_base64("SGVsbG8sIFdvcmxkIQ==\n"), std::invalid_argument); + + // invalid format, newline after padding + BOOST_CHECK_THROW(ecf::decode_base64("SGVsbG8sIFdvcmxkIQ=\n"), std::invalid_argument); + + // invalid format, valid base64 character but after padding + BOOST_CHECK_THROW(ecf::decode_base64("SGVsbG8sIFdvcmxkIQ=a"), std::invalid_argument); + } } BOOST_AUTO_TEST_SUITE_END() From a72753a11e8dae05ec2fc7ac9b6a412c07fd43b9 Mon Sep 17 00:00:00 2001 From: Marcos Bento Date: Fri, 10 Apr 2026 13:25:20 +0100 Subject: [PATCH 20/90] refactor: remove unnecessary replaceall For unclear reasons replaceall is an alias to replace_all function, and thus unnecessary. These changes replace replaceall with the more aptly named replace_all, and completely remove replaceall. Also, adds documentation and makes use of clear names for the parameters. Re ECFLOW-2076 --- .../src/ecflow/attribute/NodeAttr.cpp | 8 +- .../src/ecflow/attribute/Variable.cpp | 2 +- .../src/ecflow/base/cts/user/AlterCmd.cpp | 2 +- libs/core/src/ecflow/core/Str.cpp | 19 ++-- libs/core/src/ecflow/core/Str.hpp | 24 ++++-- libs/core/test/TestStr.cpp | 86 +++++++++---------- libs/node/src/ecflow/node/Defs.cpp | 2 +- libs/node/src/ecflow/node/NodeContainer.cpp | 2 +- libs/node/src/ecflow/node/Submittable.cpp | 4 +- .../src/ecflow/node/formatter/DefsWriter.hpp | 4 +- libs/node/test/parser/ParseTimer.cpp | 2 +- 11 files changed, 82 insertions(+), 73 deletions(-) diff --git a/libs/attribute/src/ecflow/attribute/NodeAttr.cpp b/libs/attribute/src/ecflow/attribute/NodeAttr.cpp index c7745698e..4aab7ed78 100644 --- a/libs/attribute/src/ecflow/attribute/NodeAttr.cpp +++ b/libs/attribute/src/ecflow/attribute/NodeAttr.cpp @@ -347,7 +347,7 @@ void Label::write(std::string& ret) const { else { // replace \n, otherwise re-parse will fail std::string value = v_; - Str::replaceall(value, "\n", "\\n"); + Str::replace_all(value, "\n", "\\n"); ret += value; } ret += "\""; @@ -399,7 +399,7 @@ void Label::parse(const std::string& line, Str::removeSingleQuotes(lineTokens[2]); the_value = lineTokens[2]; if (the_value.find("\\n") != std::string::npos) { - Str::replaceall(the_value, "\\n", "\n"); + Str::replace_all(the_value, "\\n", "\n"); } } else { @@ -422,7 +422,7 @@ void Label::parse(const std::string& line, Str::removeSingleQuotes(value); the_value = value; if (the_value.find("\\n") != std::string::npos) { - Str::replaceall(the_value, "\\n", "\n"); + Str::replace_all(the_value, "\\n", "\n"); } // state @@ -450,7 +450,7 @@ void Label::parse(const std::string& line, the_new_value = new_value; if (the_new_value.find("\\n") != std::string::npos) { - Str::replaceall(the_new_value, "\\n", "\n"); + Str::replace_all(the_new_value, "\\n", "\n"); } } } diff --git a/libs/attribute/src/ecflow/attribute/Variable.cpp b/libs/attribute/src/ecflow/attribute/Variable.cpp index efba46626..d1850d752 100644 --- a/libs/attribute/src/ecflow/attribute/Variable.cpp +++ b/libs/attribute/src/ecflow/attribute/Variable.cpp @@ -76,7 +76,7 @@ void Variable::write(std::string& ret) const { else { // replace \n, otherwise re-parse will fail std::string value = v_; - Str::replaceall(value, "\n", "\\n"); + Str::replace_all(value, "\n", "\\n"); ret += value; } ret += "'"; diff --git a/libs/base/src/ecflow/base/cts/user/AlterCmd.cpp b/libs/base/src/ecflow/base/cts/user/AlterCmd.cpp index 28b14b918..d7fbeb102 100644 --- a/libs/base/src/ecflow/base/cts/user/AlterCmd.cpp +++ b/libs/base/src/ecflow/base/cts/user/AlterCmd.cpp @@ -1511,7 +1511,7 @@ void AlterCmd::extract_name_and_value_for_change(AlterCmd::Change_attr_type theA } value = options[3]; if (value.find("\\n") != std::string::npos) { - Str::replaceall(value, "\\n", "\n"); + Str::replace_all(value, "\\n", "\n"); } } name = options[2]; diff --git a/libs/core/src/ecflow/core/Str.cpp b/libs/core/src/ecflow/core/Str.cpp index 5c223a8fe..f2f307c48 100644 --- a/libs/core/src/ecflow/core/Str.cpp +++ b/libs/core/src/ecflow/core/Str.cpp @@ -73,21 +73,20 @@ void Str::removeSingleQuotes(std::string& s) { } } -bool Str::replace(std::string& jobLine, const std::string& stringToFind, const std::string& stringToReplace) { - size_t pos = jobLine.find(stringToFind); - if (pos != std::string::npos) { - jobLine.replace(pos, stringToFind.length(), stringToReplace); +bool Str::replace(std::string& input, const std::string& find, const std::string& replace) { + if (size_t pos = input.find(find); pos != std::string::npos) { + input.replace(pos, find.length(), replace); return true; } return false; } -bool Str::replace_all(std::string& subject, const std::string& stringToFind, const std::string& stringToReplace) { +bool Str::replace_all(std::string& input, const std::string& find, const std::string& replace) { bool replaced = false; size_t pos = 0; - while ((pos = subject.find(stringToFind, pos)) != std::string::npos) { - subject.replace(pos, stringToFind.length(), stringToReplace); - pos += stringToReplace.length(); + while ((pos = input.find(find, pos)) != std::string::npos) { + input.replace(pos, find.length(), replace); + pos += replace.length(); replaced = true; } return replaced; @@ -115,10 +114,6 @@ bool Str::extract_data_member_value(const std::string& str, return false; } -void Str::replaceall(std::string& subject, const std::string& search, const std::string& replace) { - boost::replace_all(subject, search, replace); -} - #define USE_STRINGSPLITTER 1 void Str::split(const std::string& line, std::vector& tokens, const std::string& delimiters) { #ifdef USE_STRINGSPLITTER diff --git a/libs/core/src/ecflow/core/Str.hpp b/libs/core/src/ecflow/core/Str.hpp index e3323764f..adf325e20 100644 --- a/libs/core/src/ecflow/core/Str.hpp +++ b/libs/core/src/ecflow/core/Str.hpp @@ -173,11 +173,25 @@ class Str { // fred -> fred static void removeSingleQuotes(std::string&); - /// Find 'stringToFind' in 'jobLine' and replace with string 'stringToReplace' - /// return true if replace ok else returns false; - static bool replace(std::string& subject, const std::string& stringToFind, const std::string& stringToReplace); - static bool replace_all(std::string& subject, const std::string& stringToFind, const std::string& stringToReplace); - static void replaceall(std::string& subject, const std::string& stringToFind, const std::string& stringToReplace); + /// + /// @brief Replace the first occurrence of 'find' with 'replace' in 'input'. + /// + /// @param input The input string in which to perform the replacement. + /// @param find The string to search for within the input. + /// @param replace The string to replace occurrences of 'find' with. + /// @return true if a replacement was made, false otherwise. + /// + static bool replace(std::string& input, const std::string& find, const std::string& replace); + + /// + /// @brief Replace all occurrences of 'find' with 'replace' in 'input'. + /// + /// @param input The input string in which to perform the replacement. + /// @param find The string to search for within the input. + /// @param replace The string to replace occurrences of 'find' with. + /// @return true if at least one replacement was made, false otherwise. + /// + static bool replace_all(std::string& input, const std::string& find, const std::string& replace); // extract data member value, ie given a string of the form: // str=cmd a b fred:value diff --git a/libs/core/test/TestStr.cpp b/libs/core/test/TestStr.cpp index c26d39ea0..aec7bdc67 100644 --- a/libs/core/test/TestStr.cpp +++ b/libs/core/test/TestStr.cpp @@ -415,44 +415,55 @@ BOOST_AUTO_TEST_CASE(test_str_split_make_split_iterator) { check(line, Str::make_split_iterator(line), expected); } -static void -test_replace(std::string& testStr, const std::string& find, const std::string& replace, const std::string& expected) { - BOOST_CHECK_MESSAGE(Str::replace(testStr, find, replace), - "Replace failed for " << testStr << " find(" << find << ") replace(" << replace << ")"); - BOOST_CHECK_MESSAGE(testStr == expected, "Expected '" << expected << "' but found '" << testStr << "'"); +static void test_replace(const std::string& input, + const std::string& find, + const std::string& replace, + const std::string& expected) { + + auto actual = input; + + { // Check that replacement happens as expected + auto result = Str::replace(actual, find, replace); + + BOOST_CHECK_MESSAGE(result, + "Replace failed for " << actual << " find(" << find << ") replace(" << replace << ")"); + BOOST_CHECK_MESSAGE(actual == expected, "Expected '" << expected << "' but found '" << actual << "'"); + } } -static void test_replace_all(std::string& testStr, +static void test_replace_all(const std::string& input, const std::string& find, const std::string& replace, const std::string& expected) { - std::string testStrCopy = testStr; + auto actual = input; - BOOST_CHECK_MESSAGE(Str::replace_all(testStr, find, replace), - "Replace failed for " << testStr << " find(" << find << ") replace(" << replace << ")"); - BOOST_CHECK_MESSAGE(testStr == expected, "Expected '" << expected << "' but found '" << testStr << "'"); + { // Check that replacement happens as expected + bool result = Str::replace_all(actual, find, replace); - Str::replaceall(testStrCopy, find, replace); - BOOST_CHECK_MESSAGE(testStr == testStrCopy, "Expected '" << testStrCopy << "' but found '" << testStr << "'"); -} - -BOOST_AUTO_TEST_CASE(test_str_replace) { - ECF_NAME_THIS_TEST(); + BOOST_CHECK_MESSAGE(result, + "Replace successful for " << actual << " find(" << find << ") replace(" << replace << ")"); + BOOST_CHECK_MESSAGE(actual == expected, "Expected '" << expected << "' but found '" << actual << "'"); + } - std::string testStr = "This is a string"; - test_replace(testStr, "This", "That", "That is a string"); + auto copy = actual; - testStr = "This is a string"; - test_replace(testStr, "This is a string", "", ""); + { // Ensure that attempting replacement again returns false and does not change the input string + bool result = Str::replace_all(copy, find, replace); - testStr = "This is a string"; - test_replace(testStr, "is a", "was a", "This was a string"); + BOOST_CHECK_MESSAGE(!result, + "Replace unsuccessful for " << copy << " find(" << find << ") replace(" << replace + << "), since no occurrences were left"); + BOOST_CHECK_MESSAGE(actual == copy, "Expected '" << actual << "' but found '" << copy << "'"); + } +} - testStr = "This\n is a string"; - test_replace(testStr, "\n", "\\n", "This\\n is a string"); +BOOST_AUTO_TEST_CASE(test_str_replace) { + ECF_NAME_THIS_TEST(); - testStr = "This\n is\n a\n string\n"; - test_replace_all(testStr, "\n", "\\n", R"(This\n is\n a\n string\n)"); + test_replace("This is a string", "This", "That", "That is a string"); + test_replace("This is a string", "This is a string", "", ""); + test_replace("This is a string", "is a", "was a", "This was a string"); + test_replace("This\n is a string", "\n", "\\n", "This\\n is a string"); // Test case insenstive string comparison BOOST_CHECK_MESSAGE(Str::caseInsCompare("", ""), " bug1"); @@ -465,23 +476,12 @@ BOOST_AUTO_TEST_CASE(test_str_replace) { BOOST_AUTO_TEST_CASE(test_str_replace_all) { ECF_NAME_THIS_TEST(); - std::string testStr = "This is a string"; - test_replace_all(testStr, "This", "That", "That is a string"); - - testStr = "This is a string"; - test_replace_all(testStr, "This is a string", "", ""); - - testStr = "This is a string"; - test_replace_all(testStr, "is a", "was a", "This was a string"); - - testStr = "This\n is a string"; - test_replace_all(testStr, "\n", "\\n", "This\\n is a string"); - - testStr = "This\n is\n a\n string\n"; - test_replace_all(testStr, "\n", "\\n", R"(This\n is\n a\n string\n)"); - - testStr = "This\n is\n a\n string\n"; - test_replace_all(testStr, "\n", "", "This is a string"); + test_replace_all("This is a string", "This", "That", "That is a string"); + test_replace_all("This is a string", "This is a string", "", ""); + test_replace_all("This is a string", "is a", "was a", "This was a string"); + test_replace_all("This\n is a string", "\n", "\\n", "This\\n is a string"); + test_replace_all("This\n is\n a\n string\n", "\n", "\\n", R"(This\n is\n a\n string\n)"); + test_replace_all("This\n is\n a\n string\n", "\n", "", "This is a string"); } BOOST_AUTO_TEST_CASE(test_str_to_int) { diff --git a/libs/node/src/ecflow/node/Defs.cpp b/libs/node/src/ecflow/node/Defs.cpp index 9d80c9328..039097056 100644 --- a/libs/node/src/ecflow/node/Defs.cpp +++ b/libs/node/src/ecflow/node/Defs.cpp @@ -692,7 +692,7 @@ std::string Defs::dump_edit_history() const { } else { std::string h = c; - Str::replaceall(h, "\n", "\\n"); + Str::replace_all(h, "\n", "\\n"); ss << " "; ss << h; } diff --git a/libs/node/src/ecflow/node/NodeContainer.cpp b/libs/node/src/ecflow/node/NodeContainer.cpp index 10202ddb4..6ee2a87e9 100644 --- a/libs/node/src/ecflow/node/NodeContainer.cpp +++ b/libs/node/src/ecflow/node/NodeContainer.cpp @@ -1087,7 +1087,7 @@ std::string NodeContainer::archive_path() const { } std::string the_archive_file_name = absNodePath(); - Str::replaceall(the_archive_file_name, "/", ":"); // we use ':' since it is not allowed in the node names + Str::replace_all(the_archive_file_name, "/", ":"); // we use ':' since it is not allowed in the node names the_archive_file_name += ".check"; std::string port = ecf::string_constants::default_port_number; diff --git a/libs/node/src/ecflow/node/Submittable.cpp b/libs/node/src/ecflow/node/Submittable.cpp index 15228cb18..ec155e56b 100644 --- a/libs/node/src/ecflow/node/Submittable.cpp +++ b/libs/node/src/ecflow/node/Submittable.cpp @@ -205,8 +205,8 @@ void Submittable::write_state(std::string& ret, bool& added_comment_char) const if (!abr_.empty()) { add_comment_char(ret, added_comment_char); std::string the_abort_reason = abr_; - Str::replaceall(the_abort_reason, "\n", "\\n"); - Str::replaceall(the_abort_reason, ";", " "); + Str::replace_all(the_abort_reason, "\n", "\\n"); + Str::replace_all(the_abort_reason, ";", " "); ret += " abort<:"; ret += the_abort_reason; ret += ">abort"; diff --git a/libs/node/src/ecflow/node/formatter/DefsWriter.hpp b/libs/node/src/ecflow/node/formatter/DefsWriter.hpp index 8e4100914..ecd75c269 100644 --- a/libs/node/src/ecflow/node/formatter/DefsWriter.hpp +++ b/libs/node/src/ecflow/node/formatter/DefsWriter.hpp @@ -1265,7 +1265,7 @@ struct Writer } else { std::string value = item.new_value(); - Str::replaceall(value, "\n", "\\n"); + Str::replace_all(value, "\n", "\\n"); output << " # \""; output << value; output << "\""; @@ -2269,7 +2269,7 @@ struct Writer } else { std::string h = c; - Str::replaceall(h, "\n", "\\n"); + Str::replace_all(h, "\n", "\\n"); output << "\b"; output << h; } diff --git a/libs/node/test/parser/ParseTimer.cpp b/libs/node/test/parser/ParseTimer.cpp index 6ed412ebb..40328df2c 100644 --- a/libs/node/test/parser/ParseTimer.cpp +++ b/libs/node/test/parser/ParseTimer.cpp @@ -148,7 +148,7 @@ int main(int argc, char* argv[]) { #endif std::string json_filepath = MESSAGE("/var/tmp/ma0/JSON/" << prefix << fs_path.stem() << ".json"); - Str::replaceall(json_filepath, "\"", ""); // fs_path.stem() seems to add ", so remove them + Str::replace_all(json_filepath, "\"", ""); // fs_path.stem() seems to add ", so remove them // cout << " json_filepath: " << json_filepath << endl; std::remove(json_filepath.c_str()); From c2403f479ae9f25f4fe69c1c64ab46ac8511ab12 Mon Sep 17 00:00:00 2001 From: Marcos Bento Date: Fri, 10 Apr 2026 14:46:35 +0100 Subject: [PATCH 21/90] refactor: remove duplicate get_token implementations Also, add documentation and tests to cover the functionality. Re ECFLOW-2076 --- libs/core/src/ecflow/core/Str.cpp | 60 +++---------------- libs/core/src/ecflow/core/Str.hpp | 15 +++-- libs/core/test/TestStr.cpp | 95 ++++++++++++++++++++++++------- 3 files changed, 93 insertions(+), 77 deletions(-) diff --git a/libs/core/src/ecflow/core/Str.cpp b/libs/core/src/ecflow/core/Str.cpp index f2f307c48..f7cb82067 100644 --- a/libs/core/src/ecflow/core/Str.cpp +++ b/libs/core/src/ecflow/core/Str.cpp @@ -253,26 +253,21 @@ std::vector Str::tokenize_quotation(const std::string& s, std: return tokens; } -bool Str::get_token(std::string_view str, size_t pos, std::string& token, std::string_view delims) { - // Time for StringSplitter::get_token 250000 times = 1.457s wall, (1.460s user + 0.000s system = 1.460s) CPU - // (100.2%) Time for Str::get_token 250000 times = 0.566s wall, (0.560s user + 0.000s system = 0.560s) - // CPU (99.0%) Time for Str::get_token2 250000 times = 0.668s wall, (0.670s user + 0.000s system = - // 0.670s) CPU (100.3%) Time for Str::get_token3 250000 times = 0.620s wall, (0.620s user + 0.000s - // system = 0.620s) CPU (100.0%) +bool Str::get_token(std::string_view input, size_t index, std::string& token, std::string_view delimiters) { - size_t current_pos = 0; - auto first = std::cbegin(str); - auto end = std::cend(str); + size_t current_index = 0; + auto first = std::cbegin(input); + auto end = std::cend(input); while (first != end) { - const auto second = std::find_first_of(first, end, std::cbegin(delims), std::cend(delims)); + const auto second = std::find_first_of(first, end, std::cbegin(delimiters), std::cend(delimiters)); if (first != second) { - if (current_pos == pos) { + if (current_index == index) { token = std::string(first, second); return true; } - current_pos++; + current_index++; } if (second == end) { @@ -284,47 +279,6 @@ bool Str::get_token(std::string_view str, size_t pos, std::string& token, std::s return false; } -bool Str::get_token2(std::string_view strv, size_t pos, std::string& token, std::string_view delims) { - size_t current_pos = 0; - size_t first = 0; - while (first < strv.size()) { - const auto second = strv.find_first_of(delims, first); - - if (first != second) { - if (current_pos == pos) { - std::string_view ref = strv.substr(first, second - first); - token = std::string(ref.begin(), ref.end()); - return true; - } - current_pos++; - } - - if (second == std::string_view::npos) { - break; - } - - first = second + 1; - } - return false; -} - -bool Str::get_token3(std::string_view str, size_t pos, std::string& token, std::string_view delims) { - size_t current_pos = 0; - for (auto first = str.data(), second = str.data(), last = first + str.size(); second != last && first != last; - first = second + 1) { - - second = std::find_first_of(first, last, std::cbegin(delims), std::cend(delims)); - if (first != second) { - if (current_pos == pos) { - token = std::string(first, second - first); - return true; - } - current_pos++; - } - } - return false; -} - boost::split_iterator Str::make_split_iterator(const std::string& line, const std::string& delimiters) { return boost::make_split_iterator( diff --git a/libs/core/src/ecflow/core/Str.hpp b/libs/core/src/ecflow/core/Str.hpp index adf325e20..bf85437e4 100644 --- a/libs/core/src/ecflow/core/Str.hpp +++ b/libs/core/src/ecflow/core/Str.hpp @@ -224,10 +224,17 @@ class Str { std::vector& tokens, std::string_view delimiters = " \t"); - // Get token at a given pos. Two different implementations - static bool get_token(std::string_view line, size_t pos, std::string& token, std::string_view sep = " \t"); - static bool get_token2(std::string_view line, size_t pos, std::string& token, std::string_view sep = " \t"); - static bool get_token3(std::string_view line, size_t pos, std::string& token, std::string_view sep = " \t"); + /// + /// @brief Extract the token at the specified index from the input string, using the given delimiters. + /// + /// @param input The input string to tokenise. + /// @param index The (0-based) index of the token to extract. + /// @param token The extracted token. + /// @param delimiters The set of characters to use as token separators. + /// @return true if a token was found at the specified index, false otherwise. + /// + static bool + get_token(std::string_view input, size_t index, std::string& token, std::string_view delimiters = " \t"); // Uses boost::make_split_iterator will remove // consecutive delimiters in the middle of the string diff --git a/libs/core/test/TestStr.cpp b/libs/core/test/TestStr.cpp index aec7bdc67..9d082277f 100644 --- a/libs/core/test/TestStr.cpp +++ b/libs/core/test/TestStr.cpp @@ -186,26 +186,20 @@ static void check_splitters(const std::string& line, const std::vector testCases = { + // all tokens can be accessed + {"0,1,2,3,4,5,6,7,8,9,10", 0, ",", true, "0"}, + {"0,1,2,3,4,5,6,7,8,9,10", 1, ",", true, "1"}, + {"0,1,2,3,4,5,6,7,8,9,10", 2, ",", true, "2"}, + {"0,1,2,3,4,5,6,7,8,9,10", 3, ",", true, "3"}, + {"0,1,2,3,4,5,6,7,8,9,10", 4, ",", true, "4"}, + {"0,1,2,3,4,5,6,7,8,9,10", 5, ",", true, "5"}, + {"0,1,2,3,4,5,6,7,8,9,10", 6, ",", true, "6"}, + {"0,1,2,3,4,5,6,7,8,9,10", 7, ",", true, "7"}, + {"0,1,2,3,4,5,6,7,8,9,10", 8, ",", true, "8"}, + {"0,1,2,3,4,5,6,7,8,9,10", 9, ",", true, "9"}, + {"0,1,2,3,4,5,6,7,8,9,10", 10, ",", true, "10"}, + + // out-of-range tokens are correctly handled + {"0,1,2,3,4,5,6,7,8,9,10", 11, ",", false, ""}, + {"0,1,2,3,4,5,6,7,8,9,10", 12, ",", false, ""}, + + // now using another delimiter + {"0 1 2 3 4 5 6 7 8 9 10", 0, " ", true, "0"}, + {"0 1 2 3 4 5 6 7 8 9 10", 5, " ", true, "5"}, + {"0 1 2 3 4 5 6 7 8 9 10", 10, " ", true, "10"}, + {"0 1 2 3 4 5 6 7 8 9 10", 42, " ", false, ""}, + + // now using multiple delimiters + {"0 1\t2 3\t4 5\t6 7\t8 9\t10", 0, " \t", true, "0"}, + {"0 1\t2 3\t4 5\t6 7\t8 9\t10", 5, " \t", true, "5"}, + {"0 1\t2 3\t4 5\t6 7\t8 9\t10", 10, " \t", true, "10"}, + {"0 1\t2 3\t4 5\t6 7\t8 9\t10", 42, " \t", false, ""}, + }; + + for (const auto& testCase : testCases) { + std::string actual_token; + auto actual_outcome = ecf::Str::get_token(testCase.input, testCase.index, actual_token, testCase.delimiters); + + BOOST_REQUIRE_MESSAGE(actual_outcome == testCase.expected_outcome, + "Correct output getting token from '" << testCase.input << "' at index " << testCase.index + << " with delimiters '" << testCase.delimiters + << "'"); + + if (testCase.expected_outcome) { + BOOST_CHECK_MESSAGE(actual_token == testCase.expected_token, + "Found correct token with input: '" + << testCase.input << "' at index " << testCase.index << " with delimiters '" + << testCase.delimiters << "'. Expected '" << testCase.expected_token << "' found '" + << actual_token << "'"); + } + } +} + BOOST_AUTO_TEST_SUITE_END() BOOST_AUTO_TEST_SUITE_END() From 6234990d8902cd49b1fcec9bdc2db4bac2ed718b Mon Sep 17 00:00:00 2001 From: Marcos Bento Date: Thu, 26 Mar 2026 11:44:58 +0000 Subject: [PATCH 22/90] fix: correct configuration of __init__.py and setup.py Re ECFLOW-2077 --- libs/pyext/CMakeLists.txt | 18 ++++++++++-------- libs/pyext/python3/CMakeLists.txt | 11 +++++------ 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/libs/pyext/CMakeLists.txt b/libs/pyext/CMakeLists.txt index d47781a0b..ef593104e 100644 --- a/libs/pyext/CMakeLists.txt +++ b/libs/pyext/CMakeLists.txt @@ -33,19 +33,21 @@ # # ============================================================================= -# Configure __init__.py to add __version__ +# Configure __init__.py +# - effectively defines the __version__ used for bindings # ============================================================================= -set(INIT_PY_IN "${CMAKE_CURRENT_SOURCE_DIR}/ecflow/__init__.py.in") -set(INIT_PY_OUT "${CMAKE_CURRENT_SOURCE_DIR}/ecflow/__init__.py") -configure_file(${INIT_PY_IN} ${INIT_PY_OUT} ) +set(INIT_PY_IN "${CMAKE_CURRENT_SOURCE_DIR}/ecflow/__init__.py.in") +set(INIT_PY "${CMAKE_CURRENT_BINARY_DIR}/ecflow/__init__.py") +configure_file(${INIT_PY_IN} ${INIT_PY} ) # ============================================================================= -# Configure setup.py. Note used locally. Uses existing libraries +# Configure setup.py +# - used locally for tests # ============================================================================= -set(SETUP_PY_IN "${CMAKE_CURRENT_SOURCE_DIR}/setup.py.in") -set(SETUP_PY_OUT "${CMAKE_CURRENT_SOURCE_DIR}/setup.py") -configure_file(${SETUP_PY_IN} ${SETUP_PY_OUT} ) +set(SETUP_PY_IN "${CMAKE_CURRENT_SOURCE_DIR}/setup.py.in") +set(SETUP_PY "${CMAKE_CURRENT_BINARY_DIR}/setup.py") +configure_file(${SETUP_PY_IN} ${SETUP_PY} ) # ============================================================================== # source files (paths relative to dirs .../python3) diff --git a/libs/pyext/python3/CMakeLists.txt b/libs/pyext/python3/CMakeLists.txt index a4a5bb4c0..e9a26ce3d 100644 --- a/libs/pyext/python3/CMakeLists.txt +++ b/libs/pyext/python3/CMakeLists.txt @@ -140,7 +140,7 @@ if( CMAKE_PYTHON_INSTALL_TYPE MATCHES "local" OR NOT DEFINED CMAKE_PYTHON_INST ) install( FILES - ../ecflow/__init__.py + "${CMAKE_CURRENT_BINARY_DIR}/../ecflow/__init__.py" ../samples/api/ecf.py ../samples/api/sms2ecf.py DESTINATION @@ -162,18 +162,17 @@ else() message(STATUS "CMAKE_BINARY_DIR=${CMAKE_BINARY_DIR}") message(STATUS "CMAKE_PYTHON_INSTALL_PREFIX : ${CMAKE_PYTHON_INSTALL_PREFIX}" ) - set(SETUP_PY_IN "${CMAKE_CURRENT_SOURCE_DIR}/../setup.py.in") - set(SETUP_PY "${CMAKE_CURRENT_SOURCE_DIR}/../setup.py") - set(DEPS "${CMAKE_CURRENT_SOURCE_DIR}/../ecflow/__init__.py") set(OUTPUT "${CMAKE_CURRENT_SOURCE_DIR}/timestamp") - configure_file(${SETUP_PY_IN} ${SETUP_PY} ) + # Notice: + # (1) SETUP_PY and INIT_PY files are generated from .in files in the binary directory + # (2) these files are configured by the .../pyext/CMakeLists.txt add_custom_command( OUTPUT ${OUTPUT} COMMAND ${PYTHON} ${SETUP_PY} build COMMAND ${CMAKE_COMMAND} -E touch ${OUTPUT} - DEPENDS ${DEPS} + DEPENDS ${INIT_PY} ) add_custom_target(target ALL DEPENDS ${OUTPUT}) From dc328bb439e5557440a7186ff7921d725bc8eb86 Mon Sep 17 00:00:00 2001 From: Marcos Bento Date: Mon, 13 Apr 2026 14:34:14 +0100 Subject: [PATCH 23/90] refactor: remove unused make_split_iterator Re ECFLOW-2076 --- libs/core/src/ecflow/core/Str.cpp | 6 -- libs/core/src/ecflow/core/Str.hpp | 19 ----- libs/core/test/TestStr.cpp | 111 ------------------------------ 3 files changed, 136 deletions(-) diff --git a/libs/core/src/ecflow/core/Str.cpp b/libs/core/src/ecflow/core/Str.cpp index f7cb82067..2fb5140ec 100644 --- a/libs/core/src/ecflow/core/Str.cpp +++ b/libs/core/src/ecflow/core/Str.cpp @@ -279,12 +279,6 @@ bool Str::get_token(std::string_view input, size_t index, std::string& token, st return false; } -boost::split_iterator Str::make_split_iterator(const std::string& line, - const std::string& delimiters) { - return boost::make_split_iterator( - line, boost::algorithm::token_finder(boost::is_any_of(delimiters), boost::algorithm::token_compress_on)); -} - static bool caseInsCharCompare(char a, char b) { return (toupper(a) == toupper(b)); } diff --git a/libs/core/src/ecflow/core/Str.hpp b/libs/core/src/ecflow/core/Str.hpp index bf85437e4..462ccab4a 100644 --- a/libs/core/src/ecflow/core/Str.hpp +++ b/libs/core/src/ecflow/core/Str.hpp @@ -236,25 +236,6 @@ class Str { static bool get_token(std::string_view input, size_t index, std::string& token, std::string_view delimiters = " \t"); - // Uses boost::make_split_iterator will remove - // consecutive delimiters in the middle of the string - // ** However preserves leading and trailing empty tokens *IF* delimiters at start/end - // - // Usage: - // boost::split_iterator tokens = Str::make_split_iterator(str); - // for(; !tokens.eof(); ++tokens ) { - // boost::iterator_range range = *tokens; - // std::string the_string(range.begin(), range.end()) ; - // } - // - // std::vector vec; - // using split_iter_t = boost::split_iterator; - // for(split_iter_t i = Str::split(s,delim); i != split_iter_t(); i++) { - // vec.push_back(boost::copy_range(*i)); - // } - static boost::split_iterator - make_split_iterator(const std::string& str, const std::string& delimiters = " \t"); - /// case-insensitive string comparison static bool caseInsCompare(const std::string&, const std::string&); diff --git a/libs/core/test/TestStr.cpp b/libs/core/test/TestStr.cpp index 9d082277f..960d54201 100644 --- a/libs/core/test/TestStr.cpp +++ b/libs/core/test/TestStr.cpp @@ -298,117 +298,6 @@ BOOST_AUTO_TEST_CASE(test_str_split) { check_splitters(line, expected); } -BOOST_AUTO_TEST_CASE(test_str_split_make_split_iterator) { - ECF_NAME_THIS_TEST(); - - std::string line = "This is a string"; - std::vector expected; - expected.emplace_back("This"); - expected.emplace_back("is"); - expected.emplace_back("a"); - expected.emplace_back("string"); - check(line, Str::make_split_iterator(line), expected); - - expected.clear(); - expected.emplace_back(""); - line = ""; - check(line, Str::make_split_iterator(line), expected); - - expected.clear(); - expected.emplace_back(""); - expected.emplace_back(""); - line = " "; // If start/end is delimeter, then preserved as empty token - check(line, Str::make_split_iterator(line), expected); - - expected.clear(); - line = "a"; - expected.emplace_back("a"); - check(line, Str::make_split_iterator(line), expected); - - // Some implementation fail this test - expected.clear(); - line = "\n"; - expected.emplace_back("\n"); - check(line, Str::make_split_iterator(line), expected); - - expected.clear(); - line = "a "; - expected.emplace_back("a"); - expected.emplace_back(""); // delimeter at start/end preserved, as empty token - check(line, Str::make_split_iterator(line), expected); - - expected.clear(); - line = " a"; - expected.emplace_back(""); - expected.emplace_back("a"); // delimeter at start/end preserved, as empty token - check(line, Str::make_split_iterator(line), expected); - - expected.clear(); - line = " a"; // check tabs - expected.emplace_back(""); - expected.emplace_back("a"); // delimeter at start/end preserved, as empty token - check(line, Str::make_split_iterator(line), expected); - - expected.clear(); - line = " a "; // check sequential tabs - expected.emplace_back(""); - expected.emplace_back("a"); // delimeter at start/end preserved, as empty token - expected.emplace_back(""); - check(line, Str::make_split_iterator(line), expected); - - expected.clear(); - line = " a "; - expected.emplace_back(""); - expected.emplace_back("a"); // delimeter at start/end preserved, as empty token - expected.emplace_back(""); - check(line, Str::make_split_iterator(line), expected); - - expected.clear(); - line = " a b c d "; - expected.emplace_back(""); // delimeter at start/end preserved, as empty token - expected.emplace_back("a"); - expected.emplace_back("b"); - expected.emplace_back("c"); - expected.emplace_back("d"); - expected.emplace_back(""); - check(line, Str::make_split_iterator(line), expected); - - expected.clear(); - line = " - ! $ % ^ & * ( ) - + ?"; - expected.emplace_back(""); // delimeter at start/end preserved, as empty token - expected.emplace_back("-"); - expected.emplace_back("!"); - expected.emplace_back("$"); - expected.emplace_back("%"); - expected.emplace_back("^"); - expected.emplace_back("&"); - expected.emplace_back("*"); - expected.emplace_back("("); - expected.emplace_back(")"); - expected.emplace_back("-"); - expected.emplace_back("+"); - expected.emplace_back("?"); - check(line, Str::make_split_iterator(line), expected); - - // Check tabs - expected.clear(); - line = " verify complete:8 # 4 sundays in october hence expect 8 task completions"; - expected.emplace_back(""); // delimeter at start/end preserved, as empty token - expected.emplace_back("verify"); - expected.emplace_back("complete:8"); - expected.emplace_back("#"); - expected.emplace_back("4"); - expected.emplace_back("sundays"); - expected.emplace_back("in"); - expected.emplace_back("october"); - expected.emplace_back("hence"); - expected.emplace_back("expect"); - expected.emplace_back("8"); - expected.emplace_back("task"); - expected.emplace_back("completions"); - check(line, Str::make_split_iterator(line), expected); -} - static void test_replace(const std::string& input, const std::string& find, const std::string& replace, From 6e27a8b500c76e5f00976bf9f97933b6680cfc03 Mon Sep 17 00:00:00 2001 From: Marcos Bento Date: Thu, 26 Mar 2026 11:44:27 +0000 Subject: [PATCH 24/90] refactor: replace class Str with standalone functions Add documentation to all functions, and increase the functional tests. This effectively removes all uses of boost/algorithm/string. Re ECFLOW-2076 --- Viewer/ecflowUI/src/ActionHandler.cpp | 6 +- Viewer/ecflowUI/src/CommandDesignerWidget.cpp | 4 +- Viewer/ecflowUI/src/CommandHandler.cpp | 6 +- Viewer/ecflowUI/src/ExpandState.cpp | 4 +- Viewer/ecflowUI/src/MenuHandler.cpp | 4 +- Viewer/ecflowUI/src/NodeExpression.cpp | 4 +- Viewer/ecflowUI/src/ServerList.cpp | 12 +- Viewer/ecflowUI/src/SessionHandler.cpp | 2 +- Viewer/ecflowUI/src/VConfig.cpp | 2 +- Viewer/ecflowUI/src/VNode.cpp | 2 +- Viewer/ecflowUI/src/VProperty.cpp | 2 +- Viewer/libViewer/src/LogLoadData.cpp | 6 +- .../src/ecflow/attribute/CronAttr.cpp | 4 +- .../src/ecflow/attribute/LateAttr.cpp | 2 +- .../src/ecflow/attribute/NodeAttr.cpp | 18 +- .../src/ecflow/attribute/QueueAttr.cpp | 7 +- .../src/ecflow/attribute/TimeAttr.cpp | 2 +- .../src/ecflow/attribute/TodayAttr.cpp | 2 +- .../src/ecflow/attribute/Variable.cpp | 6 +- libs/attribute/test/TestCron.cpp | 12 +- libs/attribute/test/TestDateAttr.cpp | 2 +- libs/attribute/test/TestDayAttr.cpp | 2 +- libs/attribute/test/TestLabel.cpp | 8 +- libs/base/src/ecflow/base/Algorithms.hpp | 9 +- libs/base/src/ecflow/base/Gnuplot.cpp | 2 +- .../src/ecflow/base/cts/task/AbortCmd.cpp | 4 +- .../base/src/ecflow/base/cts/task/InitCmd.cpp | 2 +- .../src/ecflow/base/cts/user/AlterCmd.cpp | 2 +- .../src/ecflow/base/cts/user/BeginCmd.cpp | 5 +- .../src/ecflow/base/cts/user/CFileCmd.cpp | 2 +- .../src/ecflow/base/cts/user/ForceCmd.cpp | 4 +- .../src/ecflow/base/cts/user/GroupCTSCmd.cpp | 4 +- libs/base/test/TestSSyncCmdOrder.cpp | 121 +- libs/client/src/ecflow/client/Help.cpp | 2 +- libs/client/src/ecflow/client/HostsFile.cpp | 4 +- libs/client/test/TestServer.cpp | 2 +- libs/core/src/ecflow/core/Child.cpp | 4 +- libs/core/src/ecflow/core/File.cpp | 2 +- libs/core/src/ecflow/core/Log.cpp | 2 +- libs/core/src/ecflow/core/LogVerification.cpp | 2 +- libs/core/src/ecflow/core/NodePath.cpp | 2 +- libs/core/src/ecflow/core/PasswdFile.cpp | 4 +- libs/core/src/ecflow/core/Str.cpp | 272 +-- libs/core/src/ecflow/core/Str.hpp | 721 ++++++-- libs/core/src/ecflow/core/TimeSeries.cpp | 2 +- libs/core/src/ecflow/core/WhiteListFile.cpp | 6 +- libs/core/test/TestCalendar.cpp | 4 +- libs/core/test/TestStr.cpp | 1644 +++++++++++------ libs/core/test/TestStringSplitPerf.cpp | 14 +- libs/core/test/TestStringSplitter.cpp | 2 +- libs/core/test/TestTimeSeries.cpp | 12 +- libs/core/test/TestVersion.cpp | 2 +- libs/node/src/ecflow/node/Defs.cpp | 16 +- libs/node/src/ecflow/node/EcfFile.cpp | 15 +- libs/node/src/ecflow/node/Family.cpp | 2 +- libs/node/src/ecflow/node/Flag.cpp | 3 +- libs/node/src/ecflow/node/Node.cpp | 12 +- libs/node/src/ecflow/node/NodeContainer.cpp | 7 +- libs/node/src/ecflow/node/Permissions.cpp | 2 +- libs/node/src/ecflow/node/ServerState.cpp | 4 +- libs/node/src/ecflow/node/Submittable.cpp | 10 +- libs/node/src/ecflow/node/Suite.cpp | 2 +- libs/node/src/ecflow/node/Task.cpp | 6 +- .../src/ecflow/node/formatter/DefsWriter.hpp | 4 +- .../src/ecflow/node/parser/AvisoParser.cpp | 2 +- .../node/parser/DefsStructureParser.cpp | 6 +- .../src/ecflow/node/parser/MirrorParser.cpp | 2 +- .../src/ecflow/node/parser/RepeatParser.cpp | 20 +- .../src/ecflow/node/parser/TriggerParser.cpp | 2 +- .../src/ecflow/node/parser/VariableParser.cpp | 9 +- libs/node/test/TestEcfFile.cpp | 5 +- libs/node/test/TestOrder.cpp | 647 +++---- libs/node/test/parser/ParseTimer.cpp | 2 +- libs/pyext/script.py | 11 - libs/pyext/unicode.py | 13 - libs/rest/src/ecflow/http/ApiV1.cpp | 2 +- libs/rest/src/ecflow/http/ApiV1Impl.cpp | 2 +- libs/rest/src/ecflow/http/BasicAuth.cpp | 2 +- libs/rest/src/ecflow/http/Client.cpp | 2 +- libs/rest/src/ecflow/http/TokenStorage.cpp | 2 +- .../src/ecflow/service/aviso/etcd/Range.cpp | 5 +- .../ecflow/service/mirror/MirrorClient.cpp | 20 +- libs/test/overall/TestAlias.cpp | 56 +- 83 files changed, 2249 insertions(+), 1621 deletions(-) delete mode 100644 libs/pyext/script.py delete mode 100644 libs/pyext/unicode.py diff --git a/Viewer/ecflowUI/src/ActionHandler.cpp b/Viewer/ecflowUI/src/ActionHandler.cpp index 844bd6e65..7599e2751 100644 --- a/Viewer/ecflowUI/src/ActionHandler.cpp +++ b/Viewer/ecflowUI/src/ActionHandler.cpp @@ -352,12 +352,12 @@ bool ActionHandler::confirmCommand(const std::vector& filteredNodes, std::string question = questionIn; std::string placeholder(""); - ecf::Str::replace_all(question, placeholder, fullNames); + ecf::algorithm::replace_all(question, placeholder, fullNames); placeholder = ""; - ecf::Str::replace_all(question, placeholder, nodeNames); + ecf::algorithm::replace_all(question, placeholder, nodeNames); if (taskNum > 0) { placeholder = ""; - ecf::Str::replace_all(question, placeholder, "" + QString::number(taskNum).toStdString() + ""); + ecf::algorithm::replace_all(question, placeholder, "" + QString::number(taskNum).toStdString() + ""); } QString msg = QString::fromStdString(question); diff --git a/Viewer/ecflowUI/src/CommandDesignerWidget.cpp b/Viewer/ecflowUI/src/CommandDesignerWidget.cpp index 10f8076ab..05a7cd350 100644 --- a/Viewer/ecflowUI/src/CommandDesignerWidget.cpp +++ b/Viewer/ecflowUI/src/CommandDesignerWidget.cpp @@ -217,7 +217,7 @@ void CommandDesignerWidget::showCommandHelp(QListWidgetItem* item, bool showFull // get the command name QString qCommand(item->text()); std::string command = qCommand.toStdString(); - ecf::Str::replace_all(command, "--", ""); // remove the "--" from the start + ecf::algorithm::replace_all(command, "--", ""); // remove the "--" from the start // try to find it in our list of commands const po::option_description* od = @@ -230,7 +230,7 @@ void CommandDesignerWidget::showCommandHelp(QListWidgetItem* item, bool showFull if (od) { // get the description, but only take the first line std::vector lines; - ecf::Str::split(od->description(), lines, "\n"); + ecf::algorithm::split_at(lines, od->description(), "\n"); if (!lines.empty()) { QString text = qCommand + QString(": "); commandHelpLabel_->setText(text + QString::fromStdString(lines[0])); diff --git a/Viewer/ecflowUI/src/CommandHandler.cpp b/Viewer/ecflowUI/src/CommandHandler.cpp index 323f53272..e2f16d28b 100644 --- a/Viewer/ecflowUI/src/CommandHandler.cpp +++ b/Viewer/ecflowUI/src/CommandHandler.cpp @@ -106,13 +106,13 @@ void CommandHandler::run(std::vector info, const std::string& cmd) { // replace placeholders with real node names std::string placeholder(""); - ecf::Str::replace_all(realCommand, placeholder, targetNodeFullNames[serverHandler]); + ecf::algorithm::replace_all(realCommand, placeholder, targetNodeFullNames[serverHandler]); placeholder = ""; - ecf::Str::replace_all(realCommand, placeholder, targetNodeNames[serverHandler]); + ecf::algorithm::replace_all(realCommand, placeholder, targetNodeNames[serverHandler]); placeholder = ""; - ecf::Str::replace_all(realCommand, placeholder, targetParentFullNames[serverHandler]); + ecf::algorithm::replace_all(realCommand, placeholder, targetParentFullNames[serverHandler]); // Shell command if (realCommand.find("sh ") == 0) { diff --git a/Viewer/ecflowUI/src/ExpandState.cpp b/Viewer/ecflowUI/src/ExpandState.cpp index 97477af16..b2a86a8b0 100644 --- a/Viewer/ecflowUI/src/ExpandState.cpp +++ b/Viewer/ecflowUI/src/ExpandState.cpp @@ -300,7 +300,7 @@ ExpandStateNode* ExpandState::find(const std::string& fullPath) { } std::vector pathVec; - ecf::algorithm::split(pathVec, fullPath, "/"); + ecf::algorithm::split_at(pathVec, fullPath, "/"); if (pathVec.size() > 0 && pathVec[0].empty()) { pathVec.erase(pathVec.begin()); @@ -347,7 +347,7 @@ void ExpandState::collectParents(const std::string& fullPath, std::vector "" std::vector pathVec; - boost::split(pathVec, fullPath, boost::is_any_of("/")); + ecf::algorithm::split_at(pathVec, fullPath, "/"); if (pathVec.size() > 0 && pathVec[0].empty()) { pathVec.erase(pathVec.begin()); } diff --git a/Viewer/ecflowUI/src/MenuHandler.cpp b/Viewer/ecflowUI/src/MenuHandler.cpp index aa3410d27..b2d5dc5fb 100644 --- a/Viewer/ecflowUI/src/MenuHandler.cpp +++ b/Viewer/ecflowUI/src/MenuHandler.cpp @@ -420,9 +420,9 @@ void MenuHandler::interceptCommandsThatNeedConfirmation(MenuItem* item) { std::string cmdEquals = minusCmd + "="; std::string cmdEqualsYes = cmdEquals + "yes "; std::string cmdYes = minusCmd + " yes "; - if (!ecf::Str::replace(command, cmdEquals, cmdEqualsYes)) // --command=foo -> --command=yes foo + if (!ecf::algorithm::replace(command, cmdEquals, cmdEqualsYes)) // --command=foo -> --command=yes foo { - ecf::Str::replace(command, minusCmd, cmdYes); // --command foo -> --command yes foo + ecf::algorithm::replace(command, minusCmd, cmdYes); // --command foo -> --command yes foo } item->setCommand(command); } diff --git a/Viewer/ecflowUI/src/NodeExpression.cpp b/Viewer/ecflowUI/src/NodeExpression.cpp index 0f800214d..69635a629 100644 --- a/Viewer/ecflowUI/src/NodeExpression.cpp +++ b/Viewer/ecflowUI/src/NodeExpression.cpp @@ -201,8 +201,8 @@ BaseNodeCondition* NodeExpressionParser::parseWholeExpression(const std::string& UiLog().dbg() << "parseWholeExpression: " << expr; - ecf::Str::replace_all(expr, std::string("("), std::string(" ( ")); - ecf::Str::replace_all(expr, std::string(")"), std::string(" ) ")); + ecf::algorithm::replace_all(expr, std::string("("), std::string(" ( ")); + ecf::algorithm::replace_all(expr, std::string(")"), std::string(" ) ")); int index = 0; int length = expr.length(); diff --git a/Viewer/ecflowUI/src/ServerList.cpp b/Viewer/ecflowUI/src/ServerList.cpp index 2ee11fbb1..32807dfce 100644 --- a/Viewer/ecflowUI/src/ServerList.cpp +++ b/Viewer/ecflowUI/src/ServerList.cpp @@ -92,7 +92,7 @@ std::vector ServerListSystemFileManager::buildFileList() { if (!s.empty()) { useProp = true; std::vector sVec; - ecf::Str::split(s, sVec, ":"); + ecf::algorithm::split_at(sVec, s, ":"); for (auto v : sVec) { if (std::find(paths.begin(), paths.end(), v) == paths.end()) { paths.push_back(v); @@ -108,7 +108,7 @@ std::vector ServerListSystemFileManager::buildFileList() { auto s = std::string(ch); if (!s.empty()) { std::vector sVec; - ecf::Str::split(s, sVec, ":"); + ecf::algorithm::split_at(sVec, s, ":"); for (auto v : sVec) { if (std::find(paths.begin(), paths.end(), v) == paths.end()) { paths.push_back(v); @@ -276,13 +276,13 @@ void ServerListSystemFileManager::loadFile(const std::string& fPath, if (in.good()) { std::string line; while (getline(in, line)) { - std::string buf = boost::trim_left_copy(line); + std::string buf = ecf::algorithm::trim_leading_copy(line); if (buf.size() > 0 && buf[0] == '#') { continue; } if (buf.size() > 0 && buf[0] == '/') { - std::string p = boost::trim_right_copy(buf); + std::string p = ecf::algorithm::trim_trailing_copy(buf); if (std::find(includedPaths.begin(), includedPaths.end(), p) == includedPaths.end()) { includedPaths.emplace_back(p); } @@ -542,14 +542,14 @@ bool ServerList::load() { int lineCnt = 1; while (getline(in, line)) { // We ignore comment lines - std::string buf = boost::trim_left_copy(line); + std::string buf = ecf::algorithm::trim_leading_copy(line); if (buf.size() > 0 && buf.at(0) == '#') { lineCnt++; continue; } std::vector sv; - ecf::algorithm::split(sv, line, ","); + ecf::algorithm::split_at(sv, line, ","); bool favourite = false; if (sv.size() >= 4) { diff --git a/Viewer/ecflowUI/src/SessionHandler.cpp b/Viewer/ecflowUI/src/SessionHandler.cpp index b26a4dfa6..2089f7d95 100644 --- a/Viewer/ecflowUI/src/SessionHandler.cpp +++ b/Viewer/ecflowUI/src/SessionHandler.cpp @@ -172,7 +172,7 @@ void SessionHandler::readSessionListFromDisk() { std::string dirName = (*it); std::string toRemove = ".session"; std::string toReplaceWith = ""; - ecf::Str::replace(dirName, toRemove, toReplaceWith); + ecf::algorithm::replace(dirName, toRemove, toReplaceWith); add(dirName); } } diff --git a/Viewer/ecflowUI/src/VConfig.cpp b/Viewer/ecflowUI/src/VConfig.cpp index 02399ed75..24f2af910 100644 --- a/Viewer/ecflowUI/src/VConfig.cpp +++ b/Viewer/ecflowUI/src/VConfig.cpp @@ -403,7 +403,7 @@ bool VConfig::readRcFile(const std::string& rcFile, boost::property_tree::ptree& if (vec.size() >= 1) { std::vector par; - ecf::algorithm::split(par, vec[0], ":"); + ecf::algorithm::split_at(par, vec[0], ":"); if (par.size() == 2) { // Update diff --git a/Viewer/ecflowUI/src/VNode.cpp b/Viewer/ecflowUI/src/VNode.cpp index 4a892ef1a..61d69728e 100644 --- a/Viewer/ecflowUI/src/VNode.cpp +++ b/Viewer/ecflowUI/src/VNode.cpp @@ -1291,7 +1291,7 @@ VNode* VServer::find(const std::string& fullPath) { } std::vector pathVec; - ecf::algorithm::split(pathVec, fullPath, "/"); + ecf::algorithm::split_at(pathVec, fullPath, "/"); if (pathVec.size() > 0 && pathVec.at(0).empty()) { pathVec.erase(pathVec.begin()); diff --git a/Viewer/ecflowUI/src/VProperty.cpp b/Viewer/ecflowUI/src/VProperty.cpp index 9b5e1e76c..dedfed7a6 100644 --- a/Viewer/ecflowUI/src/VProperty.cpp +++ b/Viewer/ecflowUI/src/VProperty.cpp @@ -343,7 +343,7 @@ VProperty* VProperty::find(const std::string& fullPath) { } std::vector pathVec; - ecf::algorithm::split(pathVec, fullPath, "."); + ecf::algorithm::split_at(pathVec, fullPath, "."); if (pathVec.size() > 0) { if (pathVec.at(0) != strName_) { diff --git a/Viewer/libViewer/src/LogLoadData.cpp b/Viewer/libViewer/src/LogLoadData.cpp index 0b8539a8b..266089fb5 100644 --- a/Viewer/libViewer/src/LogLoadData.cpp +++ b/Viewer/libViewer/src/LogLoadData.cpp @@ -1121,7 +1121,7 @@ void LogLoadData::loadLogFileCore(const std::string& logFile, } std::string time_stamp = line.substr(0, first_closed_bracket); - ecf::Str::split(time_stamp, parseHelper_.new_time_stamp); + ecf::algorithm::split_at(parseHelper_.new_time_stamp, time_stamp); if (parseHelper_.new_time_stamp.size() != 2) { continue; } @@ -1141,7 +1141,7 @@ void LogLoadData::loadLogFileCore(const std::string& logFile, numOfRows_++; std::vector items; - ecf::Str::split(line, items, delimiter); + ecf::algorithm::split_at(items, line, delimiter); for (size_t i = 0; i < items.size(); ++i) { // Should be just left with " chd: " or " --, since we have removed the time // stamp @@ -1386,7 +1386,7 @@ bool LogLoadData::extract_suite_path(const std::string& line, if (!path.empty()) { if (path.find(":") != std::string::npos) { std::vector pathParts; - ecf::Str::split(path, pathParts, ":"); + ecf::algorithm::split_at(pathParts, path, ":"); if (pathParts.size() > 1) { path = pathParts[0]; } diff --git a/libs/attribute/src/ecflow/attribute/CronAttr.cpp b/libs/attribute/src/ecflow/attribute/CronAttr.cpp index 16a10b5d9..1fcef0b65 100644 --- a/libs/attribute/src/ecflow/attribute/CronAttr.cpp +++ b/libs/attribute/src/ecflow/attribute/CronAttr.cpp @@ -37,7 +37,7 @@ CronAttr::CronAttr(const std::string& str) { throw std::runtime_error("CronAttr::CronAttr : empty string passed"); } std::vector tokens; - Str::split(str, tokens); + ecf::algorithm::split_at(tokens, str); if (tokens.empty()) { throw std::runtime_error("CronAttr::CronAttr : incorrect time string ?"); } @@ -873,7 +873,7 @@ void CronAttr::parse(CronAttr& cronAttr, const std::vector& lineTok CronAttr CronAttr::create(const std::string& cronString) { std::vector lineTokens; - Str::split(cronString, lineTokens); + ecf::algorithm::split_at(lineTokens, cronString); CronAttr theCronAttr; if (lineTokens.empty()) { diff --git a/libs/attribute/src/ecflow/attribute/LateAttr.cpp b/libs/attribute/src/ecflow/attribute/LateAttr.cpp index bf0fefb6b..14ac02933 100644 --- a/libs/attribute/src/ecflow/attribute/LateAttr.cpp +++ b/libs/attribute/src/ecflow/attribute/LateAttr.cpp @@ -240,7 +240,7 @@ void LateAttr::parse(LateAttr& lateAttr, LateAttr LateAttr::create(const std::string& lateString) { std::vector lineTokens; - Str::split(lateString, lineTokens); + ecf::algorithm::split_at(lineTokens, lateString); if (lineTokens.empty()) { throw std::runtime_error("LateParser::create: empty string no late specified ?" + lateString); diff --git a/libs/attribute/src/ecflow/attribute/NodeAttr.cpp b/libs/attribute/src/ecflow/attribute/NodeAttr.cpp index 4aab7ed78..86b3da37c 100644 --- a/libs/attribute/src/ecflow/attribute/NodeAttr.cpp +++ b/libs/attribute/src/ecflow/attribute/NodeAttr.cpp @@ -242,7 +242,7 @@ Meter Meter::make_from_value(const std::string& name, const std::string& value) // value is expected to be of the form "min,max,value", where min, max and value are integers std::vector tokens; - ecf::algorithm::split(tokens, value, ","); + ecf::algorithm::split_at(tokens, value, ","); if (tokens.size() != 3) { throw std::runtime_error( MESSAGE("Meter::make_from_value: Expect three comma-separated values, but found: '" << value << "'")); @@ -347,7 +347,7 @@ void Label::write(std::string& ret) const { else { // replace \n, otherwise re-parse will fail std::string value = v_; - Str::replace_all(value, "\n", "\\n"); + ecf::algorithm::replace_all(value, "\n", "\\n"); ret += value; } ret += "\""; @@ -395,11 +395,11 @@ void Label::parse(const std::string& line, // parsing will always STRIP single or double quotes, print will add double quotes // label simple_label 'ecgems' if (line_token_size == 3) { - Str::removeQuotes(lineTokens[2]); - Str::removeSingleQuotes(lineTokens[2]); + ecf::algorithm::remove_double_quotes(lineTokens[2]); + ecf::algorithm::remove_single_quotes(lineTokens[2]); the_value = lineTokens[2]; if (the_value.find("\\n") != std::string::npos) { - Str::replace_all(the_value, "\\n", "\n"); + ecf::algorithm::replace_all(the_value, "\\n", "\n"); } } else { @@ -418,11 +418,11 @@ void Label::parse(const std::string& line, value += lineTokens[i]; } - Str::removeQuotes(value); - Str::removeSingleQuotes(value); + ecf::algorithm::remove_double_quotes(value); + ecf::algorithm::remove_single_quotes(value); the_value = value; if (the_value.find("\\n") != std::string::npos) { - Str::replace_all(the_value, "\\n", "\n"); + ecf::algorithm::replace_all(the_value, "\\n", "\n"); } // state @@ -450,7 +450,7 @@ void Label::parse(const std::string& line, the_new_value = new_value; if (the_new_value.find("\\n") != std::string::npos) { - Str::replace_all(the_new_value, "\\n", "\n"); + ecf::algorithm::replace_all(the_new_value, "\\n", "\n"); } } } diff --git a/libs/attribute/src/ecflow/attribute/QueueAttr.cpp b/libs/attribute/src/ecflow/attribute/QueueAttr.cpp index 60684a8b8..3f6bf23b5 100644 --- a/libs/attribute/src/ecflow/attribute/QueueAttr.cpp +++ b/libs/attribute/src/ecflow/attribute/QueueAttr.cpp @@ -208,8 +208,11 @@ void QueueAttr::parse(QueueAttr& queAttr, if (theEnum[0] == '#') { break; } - Str::removeSingleQuotes(theEnum); // remove quotes, they get added back when we persist - Str::removeQuotes(theEnum); // remove quotes, they get added back when we persist + + // remove quotes, as they get added back when we persist + ecf::algorithm::remove_single_quotes(theEnum); + ecf::algorithm::remove_double_quotes(theEnum); + theEnums.push_back(theEnum); } if (theEnums.empty()) { diff --git a/libs/attribute/src/ecflow/attribute/TimeAttr.cpp b/libs/attribute/src/ecflow/attribute/TimeAttr.cpp index d936b2936..5adf024ef 100644 --- a/libs/attribute/src/ecflow/attribute/TimeAttr.cpp +++ b/libs/attribute/src/ecflow/attribute/TimeAttr.cpp @@ -28,7 +28,7 @@ TimeAttr::TimeAttr(const std::string& str) { throw std::runtime_error("Time::Time: empty string passed"); } std::vector tokens; - Str::split(str, tokens); + ecf::algorithm::split_at(tokens, str); if (tokens.empty()) { throw std::runtime_error("Time::Time: incorrect time string ?"); } diff --git a/libs/attribute/src/ecflow/attribute/TodayAttr.cpp b/libs/attribute/src/ecflow/attribute/TodayAttr.cpp index ee15c1379..2b4041e1e 100644 --- a/libs/attribute/src/ecflow/attribute/TodayAttr.cpp +++ b/libs/attribute/src/ecflow/attribute/TodayAttr.cpp @@ -27,7 +27,7 @@ TodayAttr::TodayAttr(const std::string& str) { throw std::runtime_error("Today::Today: empty string passed"); } std::vector tokens; - Str::split(str, tokens); + ecf::algorithm::split_at(tokens, str); if (tokens.empty()) { throw std::runtime_error("Today::Today: incorrect time string ?"); } diff --git a/libs/attribute/src/ecflow/attribute/Variable.cpp b/libs/attribute/src/ecflow/attribute/Variable.cpp index d1850d752..b5272521e 100644 --- a/libs/attribute/src/ecflow/attribute/Variable.cpp +++ b/libs/attribute/src/ecflow/attribute/Variable.cpp @@ -45,8 +45,8 @@ void Variable::set_name(const std::string& v) { } int Variable::value() const { - // see if the value is convertible to an integer - return Str::to_int(v_, 0 /* value to return if conversion fails*/); + // check if the value is convertible to an integer + return ecf::algorithm::to_int(v_, 0 /* value to return if conversion fails*/); } bool Variable::operator==(const Variable& rhs) const { @@ -76,7 +76,7 @@ void Variable::write(std::string& ret) const { else { // replace \n, otherwise re-parse will fail std::string value = v_; - Str::replace_all(value, "\n", "\\n"); + ecf::algorithm::replace_all(value, "\n", "\\n"); ret += value; } ret += "'"; diff --git a/libs/attribute/test/TestCron.cpp b/libs/attribute/test/TestCron.cpp index 3630e8958..d6858bc1c 100644 --- a/libs/attribute/test/TestCron.cpp +++ b/libs/attribute/test/TestCron.cpp @@ -219,7 +219,7 @@ BOOST_AUTO_TEST_CASE(test_cron_state_parsing) { { std::string line = "cron 04:30 # isValid:false"; std::vector lineTokens; - Str::split(line, lineTokens); + ecf::algorithm::split_at(lineTokens, line); bool parse_state = true; CronAttr parsed_cronAttr; CronAttr::parse(parsed_cronAttr, lineTokens, index, parse_state); @@ -236,7 +236,7 @@ BOOST_AUTO_TEST_CASE(test_cron_state_parsing) { { std::string line = "cron 04:30 # free isValid:false"; std::vector lineTokens; - Str::split(line, lineTokens); + ecf::algorithm::split_at(lineTokens, line); bool parse_state = true; CronAttr parsed_cronAttr; CronAttr::parse(parsed_cronAttr, lineTokens, index, parse_state); @@ -254,7 +254,7 @@ BOOST_AUTO_TEST_CASE(test_cron_state_parsing) { { std::string line = "cron 00:01 23:59 01:00 # nextTimeSlot/12:01"; std::vector lineTokens; - Str::split(line, lineTokens); + ecf::algorithm::split_at(lineTokens, line); bool parse_state = true; CronAttr parsed_cronAttr; CronAttr::parse(parsed_cronAttr, lineTokens, index, parse_state); @@ -274,7 +274,7 @@ BOOST_AUTO_TEST_CASE(test_cron_state_parsing) { { std::string line = "cron 00:01 23:59 01:00 # free nextTimeSlot/12:01"; std::vector lineTokens; - Str::split(line, lineTokens); + ecf::algorithm::split_at(lineTokens, line); bool parse_state = true; CronAttr parsed_cronAttr; CronAttr::parse(parsed_cronAttr, lineTokens, index, parse_state); @@ -294,7 +294,7 @@ BOOST_AUTO_TEST_CASE(test_cron_state_parsing) { { std::string line = "cron 00:00 18:00 06:00 # isValid:false nextTimeSlot/24:00"; std::vector lineTokens; - Str::split(line, lineTokens); + ecf::algorithm::split_at(lineTokens, line); bool parse_state = true; CronAttr parsed_cronAttr; CronAttr::parse(parsed_cronAttr, lineTokens, index, parse_state); @@ -317,7 +317,7 @@ BOOST_AUTO_TEST_CASE(test_cron_state_parsing) { // Could not parse 'cron +00:00 23:59 00:01 # isValid:false nextTimeSlot/523:40' around line number 654 std::string line = "cron +00:00 23:59 00:01 # isValid:false nextTimeSlot/523:40"; std::vector lineTokens; - Str::split(line, lineTokens); + ecf::algorithm::split_at(lineTokens, line); bool parse_state = true; CronAttr parsed_cronAttr; CronAttr::parse(parsed_cronAttr, lineTokens, index, parse_state); diff --git a/libs/attribute/test/TestDateAttr.cpp b/libs/attribute/test/TestDateAttr.cpp index 7d3b45178..b62a2d15e 100644 --- a/libs/attribute/test/TestDateAttr.cpp +++ b/libs/attribute/test/TestDateAttr.cpp @@ -79,7 +79,7 @@ static DateAttr print_and_parse_attr(DateAttr& date) { output.erase(output.begin() + output.size() - 1); // remove trailing newline std::vector tokens; - Str::split_orig(output, tokens); + ecf::algorithm::split_at(tokens, output); return DateAttr::create(tokens, true /*read state*/); } diff --git a/libs/attribute/test/TestDayAttr.cpp b/libs/attribute/test/TestDayAttr.cpp index 20376434b..afebdbb49 100644 --- a/libs/attribute/test/TestDayAttr.cpp +++ b/libs/attribute/test/TestDayAttr.cpp @@ -105,7 +105,7 @@ static DayAttr print_and_parse_attr(DayAttr& day) { output.erase(output.begin() + output.size() - 1); // remove trailing newline std::vector tokens; - Str::split_orig(output, tokens); + ecf::algorithm::split_at(tokens, output); return DayAttr::create(tokens, true /*read state*/); } diff --git a/libs/attribute/test/TestLabel.cpp b/libs/attribute/test/TestLabel.cpp index b6bd6b98b..346b750e2 100644 --- a/libs/attribute/test/TestLabel.cpp +++ b/libs/attribute/test/TestLabel.cpp @@ -29,7 +29,7 @@ BOOST_AUTO_TEST_CASE(test_label_parsing) { { std::string line = "label name \"value\""; std::vector linetokens; - Str::split(line, linetokens); + ecf::algorithm::split_at(linetokens, line); Label label; label.parse(line, linetokens, false); @@ -42,7 +42,7 @@ BOOST_AUTO_TEST_CASE(test_label_parsing) { { std::string line = R"(label name "value\nvalue")"; std::vector linetokens; - Str::split(line, linetokens); + ecf::algorithm::split_at(linetokens, line); Label label; label.parse(line, linetokens, false); @@ -55,7 +55,7 @@ BOOST_AUTO_TEST_CASE(test_label_parsing) { { std::string line = "label name \"value that is multiple token !!!! 23445 !^ & * ( )\""; std::vector linetokens; - Str::split(line, linetokens); + ecf::algorithm::split_at(linetokens, line); Label label; label.parse(line, linetokens, false); @@ -68,7 +68,7 @@ BOOST_AUTO_TEST_CASE(test_label_parsing) { { std::string line = R"(label name "value\n that\n is\n multiple\n token\n and\n new\n \nlines")"; std::vector linetokens; - Str::split(line, linetokens); + ecf::algorithm::split_at(linetokens, line); Label label; label.parse(line, linetokens, false); diff --git a/libs/base/src/ecflow/base/Algorithms.hpp b/libs/base/src/ecflow/base/Algorithms.hpp index 1322e4452..960233a18 100644 --- a/libs/base/src/ecflow/base/Algorithms.hpp +++ b/libs/base/src/ecflow/base/Algorithms.hpp @@ -13,9 +13,6 @@ #include -#include -#include - #include "ecflow/base/AbstractServer.hpp" #include "ecflow/core/Result.hpp" #include "ecflow/node/Defs.hpp" @@ -44,10 +41,8 @@ struct Path } std::vector tokens; - boost::tokenizer> tokenizer(path, boost::char_separator("/ ")); - for (const auto& token : tokenizer) { - tokens.push_back(token); - } + ecf::algorithm::split_at(tokens, path, "/ "); + return Result::success(Path(std::move(tokens))); } diff --git a/libs/base/src/ecflow/base/Gnuplot.cpp b/libs/base/src/ecflow/base/Gnuplot.cpp index c72472dbc..181e4d2c3 100644 --- a/libs/base/src/ecflow/base/Gnuplot.cpp +++ b/libs/base/src/ecflow/base/Gnuplot.cpp @@ -155,7 +155,7 @@ std::string Gnuplot::create_gnuplot_file(std::vector& suite_vec, cons } std::string time_stamp = line.substr(0, first_closed_bracket); - Str::split(time_stamp, new_time_stamp); + ecf::algorithm::split_at(new_time_stamp, time_stamp); if (new_time_stamp.size() != 2) { continue; } diff --git a/libs/base/src/ecflow/base/cts/task/AbortCmd.cpp b/libs/base/src/ecflow/base/cts/task/AbortCmd.cpp index 72c3cff03..e127324b7 100644 --- a/libs/base/src/ecflow/base/cts/task/AbortCmd.cpp +++ b/libs/base/src/ecflow/base/cts/task/AbortCmd.cpp @@ -37,8 +37,8 @@ AbortCmd::AbortCmd(const std::string& pathToTask, if (!reason_.empty()) { // Do not use "\n" | ';' in Submittable::abr_, as this can mess up, --migrate output // Which would then affect --load. - Str::replace(reason_, "\n", ""); - Str::replace(reason_, ";", " "); + ecf::algorithm::replace(reason_, "\n", ""); + ecf::algorithm::replace(reason_, ";", " "); } } diff --git a/libs/base/src/ecflow/base/cts/task/InitCmd.cpp b/libs/base/src/ecflow/base/cts/task/InitCmd.cpp index ae4589043..f5e21b926 100644 --- a/libs/base/src/ecflow/base/cts/task/InitCmd.cpp +++ b/libs/base/src/ecflow/base/cts/task/InitCmd.cpp @@ -135,7 +135,7 @@ void InitCmd::create(Cmd_ptr& cmd, boost::program_options::variables_map& vm, Ab variable_vec.reserve(var_args.size()); for (const auto& v : var_args) { std::vector tokens; - Str::split(v, tokens, "="); + ecf::algorithm::split_at(tokens, v, "="); if (tokens.size() != 2) { throw std::runtime_error( "Could not parse variable provided to --add; Expected var1=value1 var2=value2 but found " + v); diff --git a/libs/base/src/ecflow/base/cts/user/AlterCmd.cpp b/libs/base/src/ecflow/base/cts/user/AlterCmd.cpp index d7fbeb102..40fe048ab 100644 --- a/libs/base/src/ecflow/base/cts/user/AlterCmd.cpp +++ b/libs/base/src/ecflow/base/cts/user/AlterCmd.cpp @@ -1511,7 +1511,7 @@ void AlterCmd::extract_name_and_value_for_change(AlterCmd::Change_attr_type theA } value = options[3]; if (value.find("\\n") != std::string::npos) { - Str::replace_all(value, "\\n", "\n"); + ecf::algorithm::replace_all(value, "\\n", "\n"); } } name = options[2]; diff --git a/libs/base/src/ecflow/base/cts/user/BeginCmd.cpp b/libs/base/src/ecflow/base/cts/user/BeginCmd.cpp index 3391d2b8b..bddcc583e 100644 --- a/libs/base/src/ecflow/base/cts/user/BeginCmd.cpp +++ b/libs/base/src/ecflow/base/cts/user/BeginCmd.cpp @@ -152,7 +152,8 @@ void BeginCmd::addOption(boost::program_options::options_description& desc) cons } void BeginCmd::create(Cmd_ptr& cmd, boost::program_options::variables_map& vm, AbstractClientEnv* ace) const { std::string beginArg = vm[arg()].as(); - Str::removeQuotes(beginArg); + + ecf::algorithm::remove_double_quotes(beginArg); if (ace->debug()) { std::cout << " BeginCmd::create arg = " << beginArg << "\n"; @@ -163,7 +164,7 @@ void BeginCmd::create(Cmd_ptr& cmd, boost::program_options::variables_map& vm, A if (!beginArg.empty()) { std::vector lineTokens; - Str::split(beginArg, lineTokens); + ecf::algorithm::split_at(lineTokens, beginArg); if (lineTokens.size() == 1) { if (lineTokens[0] == "--force") { force = true; diff --git a/libs/base/src/ecflow/base/cts/user/CFileCmd.cpp b/libs/base/src/ecflow/base/cts/user/CFileCmd.cpp index c7d744372..ad0b3f987 100644 --- a/libs/base/src/ecflow/base/cts/user/CFileCmd.cpp +++ b/libs/base/src/ecflow/base/cts/user/CFileCmd.cpp @@ -318,7 +318,7 @@ STC_Cmd_ptr CFileCmd::doHandleRequest(AbstractServer* as) const { } /// The file could get very large, hence truncate at the start - if (Str::truncate_at_start(fileContents, max_lines_)) { + if (ecf::algorithm::tail(fileContents, max_lines_)) { fileContents += MESSAGE("\n# >>>>>>>> File truncated down to " << max_lines_ << ". Truncated from the end of the file <<<<<<<<<\n"); } diff --git a/libs/base/src/ecflow/base/cts/user/ForceCmd.cpp b/libs/base/src/ecflow/base/cts/user/ForceCmd.cpp index 7155672c2..cba96f4a3 100644 --- a/libs/base/src/ecflow/base/cts/user/ForceCmd.cpp +++ b/libs/base/src/ecflow/base/cts/user/ForceCmd.cpp @@ -278,10 +278,10 @@ void ForceCmd::create(Cmd_ptr& cmd, boost::program_options::variables_map& vm, A std::string stateOrEvent; size_t options_size = options.size(); for (size_t i = 0; i < options_size; i++) { - if (Str::caseInsCompare(options[i], "recursive")) { + if (ecf::algorithm::case_insensitive_compare(options[i], "recursive")) { recursive = true; } - else if (Str::caseInsCompare(options[i], "full")) { + else if (ecf::algorithm::case_insensitive_compare(options[i], "full")) { setRepeatToLastValue = true; } else if (NState::isValid(options[i])) { diff --git a/libs/base/src/ecflow/base/cts/user/GroupCTSCmd.cpp b/libs/base/src/ecflow/base/cts/user/GroupCTSCmd.cpp index 8bf593013..4522a31a2 100644 --- a/libs/base/src/ecflow/base/cts/user/GroupCTSCmd.cpp +++ b/libs/base/src/ecflow/base/cts/user/GroupCTSCmd.cpp @@ -36,7 +36,7 @@ using namespace ecf; GroupCTSCmd::GroupCTSCmd(const std::string& cmdSeries, AbstractClientEnv* clientEnv) { std::vector individualCmdVec; - Str::split(cmdSeries, individualCmdVec, ";"); + ecf::algorithm::split_at(individualCmdVec, cmdSeries, ";"); if (individualCmdVec.empty()) { throw std::runtime_error("GroupCTSCmd::GroupCTSCmd: Please provide a list of ';' separated commands\n"); } @@ -88,7 +88,7 @@ GroupCTSCmd::GroupCTSCmd(const std::string& cmdSeries, AbstractClientEnv* client // Each sub command can have, many args std::vector subCmdArgs; - Str::split(subCmd, subCmdArgs); + ecf::algorithm::split_at(subCmdArgs, subCmd); if (replaced_spaces) { for (auto& str : subCmdArgs) { diff --git a/libs/base/test/TestSSyncCmdOrder.cpp b/libs/base/test/TestSSyncCmdOrder.cpp index 8b5f7b7ad..553d1e72f 100644 --- a/libs/base/test/TestSSyncCmdOrder.cpp +++ b/libs/base/test/TestSSyncCmdOrder.cpp @@ -33,28 +33,18 @@ static bool bypass_state_modify_change_check = false; BOOST_AUTO_TEST_SUITE(U_Base) +auto to_name = [](const auto& node) { return node->name(); }; + BOOST_AUTO_TEST_SUITE(T_SSyncCmdOrder) /// define a function which returns nothing, and takes a defs_ptr parameter using defs_change_cmd = boost::function; static std::vector vector_abcd() { - std::vector names; - names.reserve(4); - names.emplace_back("a"); - names.emplace_back("b"); - names.emplace_back("c"); - names.emplace_back("d"); - return names; + return std::vector{"a", "b", "c", "d"}; } static std::vector vector_dcba() { - std::vector names; - names.reserve(4); - names.emplace_back("d"); - names.emplace_back("c"); - names.emplace_back("b"); - names.emplace_back("a"); - return names; + return std::vector{"d", "c", "b", "a"}; } static defs_ptr create_defs() { @@ -67,9 +57,6 @@ static defs_ptr create_defs() { family_ptr f = s->add_family(names[k]); for (const auto& name : names) { task_ptr t = f->add_task(name); - // t->add_alias_only(); // alias0 - // t->add_alias_only(); // alias1 - // t->add_alias_only(); // alias2 } } } @@ -111,9 +98,6 @@ static void test_sync_scaffold(defs_change_cmd the_defs_change_command, Ecf::set_server(false); } - // cout << "test_sync_scaffold AFTER Command before SYNC: Server:\n" << *server_defs << "\n"; - // cout << "test_sync_scaffold AFTER Command before SYNC: Client:\n" << *server_reply.client_defs() << "\n"; - MockServer mock_server(server_defs); SSyncCmd cmd(client_handle, client_state_change_no, client_modify_change_no, &mock_server); std::string error_msg; @@ -136,22 +120,21 @@ static void test_sync_scaffold(defs_change_cmd the_defs_change_command, static void reorder_suites(defs_ptr theDefs) { TestHelper::invokeRequest(theDefs.get(), Cmd_ptr(new OrderNodeCmd("/a", NOrder::ALPHA))); - BOOST_REQUIRE_MESSAGE(ecf::algorithm::transform_to_name_vector(theDefs->suiteVec()) == vector_abcd(), - "NOrder::ALPHA expected " - << ecf::algorithm::join(vector_abcd()) << " but found: " - << ecf::algorithm::join(ecf::algorithm::transform_to_name_vector(theDefs->suiteVec()))); + auto names = ecf::algorithm::transform_to_vector(theDefs->suiteVec(), to_name); + BOOST_REQUIRE_MESSAGE(names == vector_abcd(), + "NOrder::ALPHA expected " << ecf::algorithm::join(vector_abcd()) + << " but found: " << ecf::algorithm::join(names)); } static void reorder_family(defs_ptr theDefs) { - // std::cout << "reorder_family\n" << *theDefs << "\n"; TestHelper::invokeRequest(theDefs.get(), Cmd_ptr(new OrderNodeCmd("/d/d", NOrder::ALPHA))); auto families = ecf::get_all_families(*theDefs->findAbsNode("/d")); - BOOST_REQUIRE_MESSAGE(ecf::algorithm::transform_to_name_vector(families) == vector_abcd(), - "NOrder::ALPHA expected " - << ecf::algorithm::join(vector_abcd()) << " but found: " - << ecf::algorithm::join(ecf::algorithm::transform_to_name_vector(families))); + auto names = ecf::algorithm::transform_to_vector(families, to_name); + BOOST_REQUIRE_MESSAGE(names == vector_abcd(), + "NOrder::ALPHA expected " << ecf::algorithm::join(vector_abcd()) + << " but found: " << ecf::algorithm::join(names)); } static void reorder_task(defs_ptr theDefs) { @@ -160,24 +143,12 @@ static void reorder_task(defs_ptr theDefs) { TestHelper::invokeRequest(theDefs.get(), Cmd_ptr(new OrderNodeCmd("/d/d/d", NOrder::ALPHA))); auto tasks = ecf::get_all_tasks(*theDefs->findAbsNode("/d/d")); - BOOST_REQUIRE_MESSAGE(ecf::algorithm::transform_to_name_vector(tasks) == vector_abcd(), - "NOrder::ALPHA expected " - << ecf::algorithm::join(vector_abcd()) << " but found: " - << ecf::algorithm::join(ecf::algorithm::transform_to_name_vector(tasks))); + auto names = ecf::algorithm::transform_to_vector(tasks, to_name); + BOOST_REQUIRE_MESSAGE(names == vector_abcd(), + "NOrder::ALPHA expected " << ecf::algorithm::join(vector_abcd()) + << " but found: " << ecf::algorithm::join(names)); } -// static void reorder_alias(defs_ptr theDefs) { -// //std::cout << "reorder_task\n" << *theDefs << "\n"; -// -// TestHelper::invokeRequest(theDefs.get(),Cmd_ptr( new OrderNodeCmd("/d/d/d/alias0",NOrder::ALPHA))); -// -// std::vector aliases; -// theDefs->findAbsNode("/d/d/d")->get_all_aliases(aliases); -// BOOST_REQUIRE_MESSAGE( ecf::algorithm::transform_to_name_vector(aliases) == vector_abcd(),"NOrder::ALPHA expected -// " << ecf::algorithm::join(vector_abcd())<< " but found: " << -// ecf::algorithm::join(ecf::algorithm::transform_to_name_vector(tasks))); -// } - static void reorder_suites_using_handles(defs_ptr theDefs) { // *** NOTE ****: Whenever we register a handle, we get a *FULL* sync @@ -193,41 +164,45 @@ static void reorder_suites_using_handles(defs_ptr theDefs) { "Expected 1 Client suites but found " << theDefs->client_suite_mgr().clientSuites().size()); TestHelper::invokeRequest(theDefs.get(), Cmd_ptr(new OrderNodeCmd("/a", NOrder::ALPHA))); - BOOST_REQUIRE_MESSAGE(ecf::algorithm::transform_to_name_vector(theDefs->suiteVec()) == vector_abcd(), - "NOrder::ALPHA expected " - << ecf::algorithm::join(vector_abcd()) << " but found: " - << ecf::algorithm::join(ecf::algorithm::transform_to_name_vector(theDefs->suiteVec()))); + auto names = ecf::algorithm::transform_to_vector(theDefs->suiteVec(), to_name); + BOOST_REQUIRE_MESSAGE(names == vector_abcd(), + "NOrder::ALPHA expected " << ecf::algorithm::join(vector_abcd()) + << " but found: " << ecf::algorithm::join(names)); } static void reorder_family_using_handles(defs_ptr theDefs) { // *** NOTE ****: Whenever we register a handle, we get a *FULL* sync + { + // create client handle which references all the suites + // It should be noted that invokeRequest, uses a MockServer, which set/unsets + // Hence after this call Ecf::server_ is false. Hence we need to ensure that following + // commands/ DM function set Ecf::server_ to true. + std::vector suite_names{"d"}; // client handle for suite 'd' ONLY + TestHelper::invokeRequest( + theDefs.get(), Cmd_ptr(new ClientHandleCmd(0, suite_names, false)), bypass_state_modify_change_check); + BOOST_CHECK_MESSAGE(theDefs->client_suite_mgr().clientSuites().size() == 1, + "Expected 1 Client suites but found " << theDefs->client_suite_mgr().clientSuites().size()); + } - // create client handle which references all the suites - // It should be noted that invokeRequest, uses a MockServer, which set/unsets - // Hence after this call Ecf::server_ is false. Hence we need to ensure that following - // commands/ DM function set Ecf::server_ to true. - std::vector suite_names; - suite_names.emplace_back("d"); // clinet handle for suite 'd' ONLY - TestHelper::invokeRequest( - theDefs.get(), Cmd_ptr(new ClientHandleCmd(0, suite_names, false)), bypass_state_modify_change_check); - BOOST_CHECK_MESSAGE(theDefs->client_suite_mgr().clientSuites().size() == 1, - "Expected 1 Client suites but found " << theDefs->client_suite_mgr().clientSuites().size()); + { + /// Don't call, data model function directly, since Ecf::server_ is false. *here* + /// The suite should stay the same, only suite d's family should change + TestHelper::invokeRequest(theDefs.get(), Cmd_ptr(new OrderNodeCmd("/d/d", NOrder::ALPHA))); + auto names = ecf::algorithm::transform_to_vector(theDefs->suiteVec(), to_name); + BOOST_REQUIRE_MESSAGE(names == vector_dcba(), + "expected " << ecf::algorithm::join(vector_dcba()) + << " but found: " << ecf::algorithm::join(names)); + } - /// Don't call, data model function directly, since Ecf::server_ is false. *here* - /// The suite should stay the same, only suite d's family should change - TestHelper::invokeRequest(theDefs.get(), Cmd_ptr(new OrderNodeCmd("/d/d", NOrder::ALPHA))); - BOOST_REQUIRE_MESSAGE( - ecf::algorithm::transform_to_name_vector(theDefs->suiteVec()) == vector_dcba(), - "expected " << ecf::algorithm::join(vector_dcba()) << " but found: " - << ecf::algorithm::join(ecf::algorithm::transform_to_name_vector(theDefs->suiteVec()))); - - suite_ptr suite_a = theDefs->findSuite("d"); - auto families = ecf::get_all_families(*suite_a); - BOOST_REQUIRE_MESSAGE(ecf::algorithm::transform_to_name_vector(families) == vector_abcd(), - "NOrder::ALPHA expected " - << ecf::algorithm::join(vector_abcd()) << " but found: " - << ecf::algorithm::join(ecf::algorithm::transform_to_name_vector(families))); + { + suite_ptr suite_a = theDefs->findSuite("d"); + auto families = ecf::get_all_families(*suite_a); + auto names = ecf::algorithm::transform_to_vector(families, to_name); + BOOST_REQUIRE_MESSAGE(names == vector_abcd(), + "NOrder::ALPHA expected " << ecf::algorithm::join(vector_abcd()) + << " but found: " << ecf::algorithm::join(names)); + } } BOOST_AUTO_TEST_CASE(test_ssync_cmd_test_order) { diff --git a/libs/client/src/ecflow/client/Help.cpp b/libs/client/src/ecflow/client/Help.cpp index b410efbcf..ffe38c058 100644 --- a/libs/client/src/ecflow/client/Help.cpp +++ b/libs/client/src/ecflow/client/Help.cpp @@ -345,7 +345,7 @@ void Documentation::show_summary(std::ostream& os, PREDICATE select) const { int max_width = get_options_max_width(options); for (const auto& option : options) { std::vector lines; - ecf::Str::split(option->description(), lines, "\n"); + ecf::algorithm::split_at(lines, option->description(), "\n"); if (!lines.empty()) { std::string name = option->long_name(); os << " " << std::left << std::setw(max_width) << name << " "; diff --git a/libs/client/src/ecflow/client/HostsFile.cpp b/libs/client/src/ecflow/client/HostsFile.cpp index ff808e473..a79666f86 100644 --- a/libs/client/src/ecflow/client/HostsFile.cpp +++ b/libs/client/src/ecflow/client/HostsFile.cpp @@ -40,7 +40,7 @@ HostsFile HostsFile::parse(std::istream& is, const int default_port) { for (const auto& line : lines) { std::vector tokens; - ecf::Str::split(line, tokens); + ecf::algorithm::split_at(tokens, line); if (tokens.empty() || tokens[0].empty() || tokens[0][0] == '#') { continue; // skip empty lines and comments } @@ -58,7 +58,7 @@ HostsFile HostsFile::parse(std::istream& is, const int default_port) { // The port can be empty, in which case we use the default port port = std::to_string(default_port); } - else if (!ecf::Str::is_int(port)) { + else if (!ecf::algorithm::is_int(port)) { // If the port must be an integer, otherwise throw an exception THROW_EXCEPTION(HostsFileFailure, "Non-integer port number in hosts file: " << port); } diff --git a/libs/client/test/TestServer.cpp b/libs/client/test/TestServer.cpp index d064b1923..e663e6047 100644 --- a/libs/client/test/TestServer.cpp +++ b/libs/client/test/TestServer.cpp @@ -230,7 +230,7 @@ BOOST_AUTO_TEST_CASE(test_server_state_changes_with_auto_sync) { Defs::max_edit_history_size_per_node(), "Expected edit history of size 8, but found " << theClient.server_reply().get_string_vec().size() << "\n" - << Str::dump_string_vec(theClient.server_reply().get_string_vec())); + << ecf::algorithm::as_string(theClient.server_reply().get_string_vec())); // make sure edit history was *NOT* serialized, It is only serialized when check pointing BOOST_REQUIRE_MESSAGE(theClient.getDefs() == 0, diff --git a/libs/core/src/ecflow/core/Child.cpp b/libs/core/src/ecflow/core/Child.cpp index 6d032012d..2a627ad32 100644 --- a/libs/core/src/ecflow/core/Child.cpp +++ b/libs/core/src/ecflow/core/Child.cpp @@ -97,7 +97,7 @@ std::string Child::to_string(Child::CmdType ct) { std::vector Child::child_cmds(const std::string& s) { // expect single or , separated tokens std::vector tokens; - Str::split(s, tokens, ","); + ecf::algorithm::split_at(tokens, s, ","); std::vector ret; ret.reserve(tokens.size()); for (const auto& token : tokens) { @@ -122,7 +122,7 @@ bool Child::valid_child_cmds(const std::string& s) { // expect single or , separated tokens std::vector tokens; - Str::split(s, tokens, ","); + ecf::algorithm::split_at(tokens, s, ","); for (const auto& token : tokens) { if (!valid_child_cmd(token)) { return false; diff --git a/libs/core/src/ecflow/core/File.cpp b/libs/core/src/ecflow/core/File.cpp index 7334ecb94..d8f9c57bf 100644 --- a/libs/core/src/ecflow/core/File.cpp +++ b/libs/core/src/ecflow/core/File.cpp @@ -56,7 +56,7 @@ std::string File::which(const std::string& file) { if (!env_paths.empty()) { std::string path; std::vector paths; - Str::split(env_paths, paths, ":"); + ecf::algorithm::split_at(paths, env_paths, ":"); size_t path_size = paths.size(); for (size_t i = 0; i < path_size; ++i) { path.clear(); diff --git a/libs/core/src/ecflow/core/Log.cpp b/libs/core/src/ecflow/core/Log.cpp index b699cd231..5c7ed11e0 100644 --- a/libs/core/src/ecflow/core/Log.cpp +++ b/libs/core/src/ecflow/core/Log.cpp @@ -392,7 +392,7 @@ bool LogImpl::do_log(Log::LogType lt, const std::string& message, bool newline) else { // If message has \n then split into multiple lines std::vector lines; - Str::split(message, lines, "\n"); + ecf::algorithm::split_at(lines, message, "\n"); size_t theSize = lines.size(); for (size_t i = 0; i < theSize; ++i) { file_ << log_type_and_time_stamp_ << lines[i] << '\n'; diff --git a/libs/core/src/ecflow/core/LogVerification.cpp b/libs/core/src/ecflow/core/LogVerification.cpp index ee4328934..712a330be 100644 --- a/libs/core/src/ecflow/core/LogVerification.cpp +++ b/libs/core/src/ecflow/core/LogVerification.cpp @@ -42,7 +42,7 @@ bool LogVerification::extractNodePathAndState(const std::string& logfile, } std::vector lineTokens; - Str::split(line, lineTokens); + ecf::algorithm::split_at(lineTokens, line); if (lineTokens.size() < 3) { continue; } diff --git a/libs/core/src/ecflow/core/NodePath.cpp b/libs/core/src/ecflow/core/NodePath.cpp index 8f126a876..e944a2dcb 100644 --- a/libs/core/src/ecflow/core/NodePath.cpp +++ b/libs/core/src/ecflow/core/NodePath.cpp @@ -16,7 +16,7 @@ using namespace ecf; void NodePath::split(const std::string& path, std::vector& thePath) { /// The path is of the form "/suite/family/task" - Str::split(path, thePath, ecf::string_constants::path_separator); + ecf::algorithm::split_at(thePath, path, ecf::string_constants::path_separator); } bool NodePath::extractHostPort(const std::string& path, std::string& host, std::string& port) { diff --git a/libs/core/src/ecflow/core/PasswdFile.cpp b/libs/core/src/ecflow/core/PasswdFile.cpp index d69b1053a..e5e030ea4 100644 --- a/libs/core/src/ecflow/core/PasswdFile.cpp +++ b/libs/core/src/ecflow/core/PasswdFile.cpp @@ -72,7 +72,7 @@ bool PasswdFile::load(const std::string& file, bool debug, std::string& errorMsg ecf::algorithm::trim(theLine); // remove leading and trailing spaces std::vector lineTokens; - Str::split(theLine, lineTokens); + ecf::algorithm::split_at(lineTokens, theLine); if (lineTokens.empty()) { continue; } @@ -214,7 +214,7 @@ bool PasswdFile::validateVersionNumber(const std::string& line, std::string& err if (firstCharIsNumeric && line.find(".") != std::string::npos) { std::vector versionNumberTokens; - Str::split(line, versionNumberTokens, "."); + ecf::algorithm::split_at(versionNumberTokens, line, "."); if (versionNumberTokens.size() != 3) { errorMsg += "Expected version of the form .. i.e 4.4.0. but found invalid version number\n"; return false; diff --git a/libs/core/src/ecflow/core/Str.cpp b/libs/core/src/ecflow/core/Str.cpp index 2fb5140ec..9985db6f7 100644 --- a/libs/core/src/ecflow/core/Str.cpp +++ b/libs/core/src/ecflow/core/Str.cpp @@ -53,174 +53,51 @@ bool is_valid_name(const std::string& name) { std::all_of(name.begin() + 1, name.end(), is_valid_node_name_following_character); } -} // namespace algorithm - -void Str::removeQuotes(std::string& s) { - if (!s.empty()) { - if (s[0] == '"' && s[s.size() - 1] == '"') { - s.erase(s.begin()); - s.erase(s.begin() + s.size() - 1); - } - } -} -// 047 is octal for ' -void Str::removeSingleQuotes(std::string& s) { - if (!s.empty()) { - if (s[0] == 047 && s[s.size() - 1] == 047) { - s.erase(s.begin()); - s.erase(s.begin() + s.size() - 1); - } - } -} - -bool Str::replace(std::string& input, const std::string& find, const std::string& replace) { - if (size_t pos = input.find(find); pos != std::string::npos) { - input.replace(pos, find.length(), replace); - return true; - } - return false; -} - -bool Str::replace_all(std::string& input, const std::string& find, const std::string& replace) { - bool replaced = false; - size_t pos = 0; - while ((pos = input.find(find, pos)) != std::string::npos) { - input.replace(pos, find.length(), replace); - pos += replace.length(); - replaced = true; +bool tail(std::string& fileContents, size_t max_lines) { + if (fileContents.empty()) { + return false; } - return replaced; -} -bool Str::extract_data_member_value(const std::string& str, - const std::string& data_member_name, - std::string& data_member_value) { - // 012345678901234567, - // str=cmd 1 user:mao - // data_member_name=user: - // data_member_value=ma0 - std::string::size_type start = str.find(data_member_name); - if (start != std::string::npos) { - start += data_member_name.size(); - data_member_value.clear(); - for (size_t i = start; i < str.size(); i++) { - if (str[i] == ' ') { - break; + size_t count = 0; + auto it = fileContents.end(); + while (it != fileContents.begin()) { + --it; + if (*it == '\n') { + ++count; + if (count == max_lines) { + fileContents.erase(fileContents.begin(), it + 1); + return true; } - data_member_value += str[i]; } - return true; } return false; } -#define USE_STRINGSPLITTER 1 -void Str::split(const std::string& line, std::vector& tokens, const std::string& delimiters) { -#ifdef USE_STRINGSPLITTER - Str::split_using_string_view(line, tokens, delimiters); -#else - Str::split_orig(line, tokens, delimiters); -#endif - // This test will split a line 1000000 times: 'This is a long string that is going to be used to test the - // performance of splitting with different Implementations the fastest times wins ' Time for istreamstream 1000000 - // times = 2.131s wall, (2.140s user + 0.000s system = 2.140s) CPU (100.4%) Time for std::getline 1000000 times - // = 1.490s wall, (1.490s user + 0.000s system = 1.490s) CPU (100.0%) Time for boost::split 1000000 times = 0.930s - // wall, (0.920s user + 0.000s system = 0.920s) CPU (99.0%) Time for Str::split_orig 1000000 times - // = 1.065s wall, (1.070s user + 0.000s system = 1.070s) CPU (100.5%) Time for Str::split_orig1 1000000 times = - // 0.561s wall, (0.560s user + 0.000s system = 0.560s) CPU (99.9%) Time for Str::split_using_string_view2 1000000 - // times = 0.686s wall, (0.690s user + 0.000s system = 0.690s) CPU (100.6%) Time for Str::split_using_string_view - // 1000000 times = 0.482s wall, (0.480s user + 0.000s system = 0.480s) CPU (99.6%) Time for - // make_split_iterator::split 1000000 times = 3.611s wall, (3.610s user + 0.000s system = 3.610s) CPU (100.0%) - // Time for std::string_view 1000000 times = 0.769s wall, (0.770s user + 0.000s system = 0.770s) CPU - // (100.1%) Time for std::string_view(2) 1000000 times = 0.688s wall, (0.690s user + 0.000s system = - // 0.690s) CPU (100.3%) - // core :: test_str_split_perf_with_file - // This test will split each line in file ${ECF_TEST_DEFS_DIR}vsms2.31415.def - // Time for istreamstream 2001774 times = 1.567s wall, (1.570s user + 0.000s system = 1.570s) CPU - // (100.2%) Time for std::getline 2001774 times = 2.456s wall, (2.460s user + 0.000s system - // = 2.460s) CPU (100.2%) Time for boost::split 2001774 times = 1.698s wall, (1.690s user + - // 0.000s system = 1.690s) CPU (99.5%) Time for Str::split_orig 2001774 times = 0.822s wall, (0.830s - // user + 0.000s system = 0.830s) CPU (101.0%) Time for Str::split_orig1 2001774 times = 0.502s wall, - // (0.500s user + 0.000s system = 0.500s) CPU (99.7%) Time for Str::split_using_string_view 2001774 times = - // 0.489s wall, (0.490s user + 0.000s system = 0.490s) CPU (100.3%) Time for Str::split_using_string_view2 2001774 - // times = 0.639s wall, (0.630s user + 0.000s system = 0.630s) CPU (98.6%) Time for boost::make_split_iterator - // 2001774 times = 3.338s wall, (3.340s user + 0.000s system = 3.340s) CPU (100.1%) Time for std::string_view - // 2001774 times = 0.599s wall, (0.600s user + 0.000s system = 0.600s) CPU (100.1%) Time for - // std::string_view(2) 2001774 times = 0.696s wall, (0.700s user + 0.000s system = 0.700s) CPU (100.6%) -} - -void Str::split_orig(const std::string& line, std::vector& tokens, const std::string& delimiters) { - // Skip delimiters at beginning. - auto lastPos = line.find_first_not_of(delimiters, 0); - - // Find first "non-delimiter". - auto pos = line.find_first_of(delimiters, lastPos); - - while (std::string::npos != pos || std::string::npos != lastPos) { - tokens.push_back(line.substr(lastPos, pos - lastPos)); // Found a token, add it to the vector. - lastPos = line.find_first_not_of(delimiters, pos); // Skip delimiters. Note the "not_of" - pos = line.find_first_of(delimiters, lastPos); // Find next "non-delimiter" - } -} - -void Str::split_orig1(const std::string& line, std::vector& tokens, const std::string& delims) { - auto first = std::cbegin(line); - auto end = std::cend(line); - - while (first != end) { - const auto second = std::find_first_of(first, end, std::cbegin(delims), std::cend(delims)); - - if (first != second) { - tokens.emplace_back(first, second); - } - - if (second == end) { - break; - } - - first = std::next(second); - } -} - -void Str::split_using_string_view(std::string_view strv, std::vector& output, std::string_view delims) { - // Uses pointers - for (auto first = strv.data(), second = strv.data(), last = first + strv.size(); second != last && first != last; - first = second + 1) { - second = std::find_first_of(first, last, std::cbegin(delims), std::cend(delims)); - - if (first != second) { - output.emplace_back(first, second - first); - } +bool head(std::string& fileContents, size_t max_lines) { + if (fileContents.empty()) { + return false; } -} -void Str::split_using_string_view2(std::string_view strv, std::vector& output, std::string_view delims) { - size_t first = 0; - - size_t strv_size = strv.size(); - while (first < strv_size) { - const auto second = strv.find_first_of(delims, first); - - if (first != second) { - std::string_view ref = strv.substr(first, second - first); - output.emplace_back(ref.begin(), ref.end()); - } - - if (second == std::string_view::npos) { - break; + size_t count = 0; + for (size_t i = 0; i < fileContents.size(); ++i) { + if (fileContents[i] == '\n') { + ++count; + if (count == max_lines) { + fileContents.erase(i + 1); + return true; + } } - - first = second + 1; } + return false; } -std::vector Str::tokenize_quotation(const std::string& s, std::string_view quotes) { +std::vector split_within_quotes(const std::string& input, std::string_view quotes) { std::vector tokens; std::string levels; - const char* current = &s[0]; + const char* current = &input[0]; const char* start = current; while (*current != 0) { if (*current == ' ' && levels.empty()) { @@ -253,7 +130,7 @@ std::vector Str::tokenize_quotation(const std::string& s, std: return tokens; } -bool Str::get_token(std::string_view input, size_t index, std::string& token, std::string_view delimiters) { +bool get_token(std::string_view input, size_t index, std::string& token, std::string_view delimiters) { size_t current_index = 0; auto first = std::cbegin(input); @@ -279,99 +156,6 @@ bool Str::get_token(std::string_view input, size_t index, std::string& token, st return false; } -static bool caseInsCharCompare(char a, char b) { - return (toupper(a) == toupper(b)); -} - -bool Str::caseInsCompare(const std::string& s1, const std::string& s2) { - return ((s1.size() == s2.size()) && equal(s1.begin(), s1.end(), s2.begin(), caseInsCharCompare)); -} - -bool Str::caseInsLess(const std::string& a, const std::string& b) { - return std::lexicographical_compare(a.begin(), a.end(), b.begin(), b.end(), [](char x, char y) -> bool { - if (toupper(x) == toupper(y)) { - return x > y; - } - return toupper(static_cast(x)) < toupper(static_cast(y)); - }); -} - -bool Str::caseInsGreater(const std::string& a, const std::string& b) { - return std::lexicographical_compare(a.begin(), a.end(), b.begin(), b.end(), [](char x, char y) -> bool { - if (toupper(x) == toupper(y)) { - return x < y; - } - return toupper(static_cast(x)) > toupper(static_cast(y)); - }); -} - -int Str::to_int(const std::string& the_str, int error_return) { - if (the_str.find_first_of(ecf::string_constants::numeric_chars, 0) != std::string::npos) { - try { - return ecf::convert_to(the_str); - } - catch (const ecf::bad_conversion&) { - } - } - return error_return; -} - -bool Str::is_int(const std::string& s) { - try { - boost::lexical_cast(s); - return true; - } - catch (...) { - return false; - } -} - -bool Str::truncate_at_start(std::string& fileContents, size_t max_lines) { - if (fileContents.empty()) { - return false; - } - - /// Truncate from the front - size_t no_of_new_lines = 0; - for (size_t i = fileContents.size() - 1; i > 0; --i) { - if (fileContents[i] == '\n') { - no_of_new_lines++; - } - if (no_of_new_lines >= max_lines) { - fileContents.erase(fileContents.begin(), fileContents.begin() + i + 1); // skip new line at start of file - return true; - } - } - return false; -} - -bool Str::truncate_at_end(std::string& fileContents, size_t max_lines) { - if (fileContents.empty()) { - return false; - } - - /// Truncate from the back - size_t no_of_new_lines = 0; - size_t the_size = fileContents.size(); - for (size_t i = 0; i < the_size; ++i) { - if (fileContents[i] == '\n') { - no_of_new_lines++; - } - if (no_of_new_lines >= max_lines) { - fileContents.erase(fileContents.begin() + i + 1, fileContents.end()); // skip new line at end of file - return true; - } - } - return false; -} - -std::string Str::dump_string_vec(const std::vector& vec) { - std::string str; - for (const auto& s : vec) { - str += s; - str += "\n"; - } - return str; -} +} // namespace algorithm } // namespace ecf diff --git a/libs/core/src/ecflow/core/Str.hpp b/libs/core/src/ecflow/core/Str.hpp index 462ccab4a..d2c934666 100644 --- a/libs/core/src/ecflow/core/Str.hpp +++ b/libs/core/src/ecflow/core/Str.hpp @@ -11,13 +11,17 @@ #ifndef ecflow_core_Str_HPP #define ecflow_core_Str_HPP +#include +#include #include #include +#include +#include #include #include #include -#include +#include "ecflow/core/Converter.hpp" namespace ecf { @@ -53,13 +57,121 @@ inline const std::string numeric_chars = "0123456789"; namespace algorithm { +/// +/// @brief Join a sequence of strings into a single string with a separator between them. The (optional) separator +/// defaults to ", " if not provided. +/// +/// @tparam Sequence1 A container type that supports iteration and whose elements can be converted to std::string_view. +/// @tparam Sequence2 A string-like type. +/// +/// @param strings The sequence of string-like objects to join. +/// @param separator The string-like object used as the separator between joined strings. +/// @return The joined string. +/// template -inline static auto join(const Sequence1& strings, const Sequence2& separator = std::string(", ")) { - return ::boost::algorithm::join(strings, separator); +inline std::string join(const Sequence1& strings, const Sequence2& separator = ", ") { + std::string buffer; + auto first = true; + for (const auto& s : strings) { + if (!first) { + buffer.append(std::data(separator), std::size(separator)); + } + buffer.append(s); + first = false; + } + return buffer; } +/// +/// @brief Replace the first occurrence of a search string in the input sequence with a replacement string. +/// +/// No replacement is performed if the search string is not found. +/// +/// @tparam Sequence A string-like type. +/// @tparam SearchSequence A string-like type. +/// @tparam ReplaceSequence A string-like type. +/// +/// @param input The input sequence to search and replace within. +/// @param search The string to search for. +/// @param replace The string to replace the search string with. +/// +template +inline void replace_first(Sequence& input, const SearchSequence& search, const ReplaceSequence& replace) { + auto found = input.find(search); + if (found != std::string::npos) { + input.replace(found, std::string_view(search).size(), replace); + } +} + +/// +/// @brief Splits an input string into a sequence of substrings based on a set of separators. +/// +/// The resulting buffer is cleared of any contents, before being populated with the substrings. +/// Consecutive separators are treated as a single separator, meaning no empty substrings are produced. +/// +/// Splitting the input `"a,,b,c;d; ;e;;f;"` with the separator `",;"` would result +/// in a buffer containing `["a", "b", "c", "d", " ", "e", "f"]`. +/// It is important to notice that: +/// 1) the separators define a set of single characters, and thus duplications are treated as a single separator. +/// 2) consecutive separators are collapsed into one, so no empty substrings are produced. +/// 3) leading and trailing separators are ignored. +/// +/// If the input string is empty, or contains only separator characters, the result be ane empty sequence (i.e. `[ ]`). +/// +/// @tparam ResultSequence A container type to hold the resulting substrings. +/// @tparam Sequence1 A string-like type representing the input string. +/// @tparam Sequence2 A string-like type representing single character separators. +/// +/// @param buffer A reference to the container where the substrings will be stored. +/// @param input The input string to be split. +/// @param separators The string containing characters to use as separators (n.b. duplicates they will be treated as +/// a single separator) +/// @return A reference to the container holding the resulting substrings. +/// +template +inline ResultSequence& +split_at(ResultSequence& buffer, const Sequence1& input, const Sequence2& separators = std::string_view(" \t")) { + buffer.clear(); + std::string_view in(input); + std::string_view seps(separators); + size_t start = in.find_first_not_of(seps); + while (start != std::string_view::npos) { + auto pos = in.find_first_of(seps, start); + if (pos == std::string_view::npos) { + buffer.emplace_back(in.substr(start)); + break; + } + buffer.emplace_back(in.substr(start, pos - start)); + start = in.find_first_not_of(seps, pos + 1); + } + return buffer; +} + +/// +/// @brief Splits an input string into a sequence of substrings based on a given pattern. +/// +/// The resulting buffer is cleared of any contents, before being populated with the substrings. +/// Consecutive separator patterns are treated as a single separator, meaning no empty substrings are produced. +/// +/// Splitting the input `"a==b==c!=d==e == f"` with the pattern `"=="` would result +/// in a buffer containing `["a", "b", "c!=d", "e ", " f"]`. +/// +/// iIf the input string is empty, or contains only repetirion of the separator pattern, the result be ane empty +/// sequence (i.e. `[ ]`). +/// +/// @tparam ResultSequence A container type to hold the resulting substrings. +/// @tparam Sequence1 A string-like type representing the input string. +/// @tparam Sequence2 A string-like type representing the pattern to split by (n.b. if the pattern contains duplicated +/// characters, they will be treated as a single separator). +/// +/// @param buffer A reference to the container where the resulting substrings will be stored. +/// @param input The input string to be split. +/// @param pattern The pattern to split the input string by. +/// @return A reference to the container holding the resulting substrings. +/// template -ResultSequence split_by(ResultSequence& buffer, const Sequence1& input, const Sequence2& pattern) { +inline ResultSequence& split_by(ResultSequence& buffer, const Sequence1& input, const Sequence2& pattern) { + buffer.clear(); std::string::size_type start = 0; std::string::size_type pos = 0; while ((pos = input.find(pattern, start)) != std::string::npos) { @@ -84,49 +196,220 @@ ResultSequence split_by(ResultSequence& buffer, const Sequence1& input, const Se return buffer; } -template -inline static auto replace_first(Sequence& input, const SearchSequence& search, const ReplaceSequence& replace) { - return ::boost::algorithm::replace_first(input, search, replace); -} - -template -inline static ResultSequence& split(ResultSequence& result, const Sequence1& input, const Sequence2& separators) { - return ::boost::algorithm::split(result, input, ::boost::is_any_of(separators)); -} +/// +/// @brief Tokenize the input string selecting the content within the specified quotation marks. +/// +/// The quotation marks are included in the resulting tokens! +/// +/// Some example: +/// - split_within_quotes("'a' x 'b' y 'c' zz ", "'") will return tokens ['a', x, 'b', y, 'c', zz]. +/// Notice that any non-whitespace characters _outside_ the quotation marks are also collected (without quotes). +/// +/// - split_within_quotes("'a \"b\" c' \"d 'e' f\" , "\"'") will return ['a \"b\" c', 'd \'e\' f'] +/// Every quotation mark provided is considered, but only one level/quoted region is considered. +/// This means that regions are detected by matching the first quote found. +/// +/// @param input The input string to tokenize. +/// @param quotes The set of characters to use as quotation marks. +/// @return A vector of string views representing the tokens extracted from the input string. +/// +std::vector split_within_quotes(const std::string& input, std::string_view quotes); +/// +/// @brief Check if the input string starts with the given pattern. +/// +/// Notice that an empty pattern will match any string (empty or otherwise). +/// +/// @tparam Sequence1 A string-like type representing the input string. +/// @tparam Sequence2 A string-like type representing the pattern. +/// +/// @param input The input string to check. +/// @param pattern The pattern to check for at the start of the input. +/// @return true if the input starts with the pattern, false otherwise. +/// template inline static bool starts_with(const Sequence1& input, const Sequence2& pattern) { - return ::boost::algorithm::starts_with(input, pattern); + std::string_view in(input); + std::string_view p(pattern); + return in.size() >= p.size() && in.substr(0, p.size()) == p; } +/// +/// @brief Check if the input string ends with the given pattern. +/// +/// @tparam Sequence1 A string-like type representing the input string. +/// @tparam Sequence2 A string-like type representing the pattern. +/// +/// @param input The input string to check. +/// @param pattern The pattern to check for at the end of the input. +/// @return true if the input ends with the pattern, false otherwise +/// . template inline static bool ends_with(const Sequence1& input, const Sequence2& pattern) { - return ::boost::algorithm::ends_with(input, pattern); + std::string_view in(input); + std::string_view p(pattern); + return in.size() >= p.size() && in.substr(in.size() - p.size()) == p; } +/// +/// @brief Trim leading and trailing whitespace from the input string in-place. +/// +/// Whitespace is determined by `std::isspace` (i.e. considers ' ', '\f', '\r', '\n', '\t', '\v'). +/// +/// @tparam Sequence A string-like type representing the string to trim. +/// +/// @param input The string to trim. +/// template inline static void trim(Sequence& input) { - ::boost::algorithm::trim(input); + // remove leading whitespace + auto leading = std::find_if(std::begin(input), std::end(input), [](unsigned char ch) { return !std::isspace(ch); }); + input.erase(std::begin(input), leading); + + // remove trailing whitespace + auto trailing = + std::find_if(std::rbegin(input), std::rend(input), [](unsigned char ch) { return !std::isspace(ch); }); + input.erase(trailing.base(), std::end(input)); } +/// +/// @brief Trim leading whitespace from the input string in-place. +/// +/// Whitespace is determined by `std::isspace` (i.e. considers ' ', '\f', '\r', '\n', '\t', '\v'). +/// +/// @tparam Sequence A string-like type representing the string to trim. +/// +/// @param input The string to trim. +/// +template +inline static void trim_leading(Sequence& input) { + // remove leading whitespace + auto leading = std::find_if(input.begin(), input.end(), [](unsigned char ch) { return !std::isspace(ch); }); + input.erase(input.begin(), leading); +} + +/// +/// @brief Return a copy of the input string with leading whitespace removed. +/// +/// Whitespace is determined by `std::isspace` (i.e. considers ' ', '\f', '\r', '\n', '\t', '\v'). +/// +/// @tparam Sequence A string-like type representing the string to trim. +/// +/// @param input The string to trim. +/// @return A copy of the input with leading whitespace removed. +/// +template +inline static Sequence trim_leading_copy(const Sequence& input) { + Sequence buffer = input; + // remove leading whitespace + auto leading = std::find_if(buffer.begin(), buffer.end(), [](unsigned char ch) { return !std::isspace(ch); }); + buffer.erase(buffer.begin(), leading); + return buffer; +} + +/// +/// @brief Trim trailing whitespace from the input string in-place. +/// +/// Whitespace is determined by `std::isspace` (i.e. considers ' ', '\f', '\r', '\n', '\t', '\v'). +/// +/// @tparam Sequence A string-like type representing the string to trim. +/// +/// @param input The string to trim. +/// +template +inline static void trim_trailing(Sequence& input) { + // remove trailing whitespace + auto trailing = std::find_if(input.rbegin(), input.rend(), [](unsigned char ch) { return !std::isspace(ch); }); + input.erase(trailing.base(), input.end()); +} + +/// +/// @brief Return a copy of the input string with trailing whitespace removed. +/// +/// Whitespace is determined by `std::isspace` (i.e. considers ' ', '\f', '\r', '\n', '\t', '\v'). +/// +/// @tparam Sequence A string-like type representing the string to trim. +/// +/// @param input The string to trim. +/// @return A copy of the input with trailing whitespace removed. +/// +template +inline static Sequence trim_trailing_copy(const Sequence& input) { + Sequence buffer = input; + // remove trailing whitespace + auto trailing = std::find_if(buffer.rbegin(), buffer.rend(), [](unsigned char ch) { return !std::isspace(ch); }); + buffer.erase(trailing.base(), buffer.end()); + return buffer; +} + +/// +/// @brief Check if the input string contains the given pattern. +/// +/// Notice that an empty pattern is considered to be contained in any string. +/// +/// @tparam Sequence A string-like type representing the input string. +/// @tparam Pattern A string-like type representing the pattern to search for. +/// +/// @param input The input string to search within. +/// @param pattern The pattern to search for. +/// @return true if the input contains the pattern, false otherwise. +/// template inline static bool contains(const Sequence& input, const Pattern& pattern) { - return ::boost::algorithm::contains(input, pattern); + return std::string_view(input).find(std::string_view(pattern)) != std::string_view::npos; } +/// +/// @brief Remove all occurrences of the given pattern from the input string in-place. +/// +/// @tparam Sequence A string-like type representing the input string. +/// @tparam Pattern A string-like type representing the pattern to remove. +/// +/// @param input The input string to modify. +/// @param pattern The pattern to remove from the input. +/// template inline static void remove_all(Sequence& input, const Pattern& pattern) { - ::boost::algorithm::erase_all(input, pattern); + if (std::empty(std::string_view(pattern))) { + return; + } + + std::string_view pat(pattern); + size_t pos = 0; + while ((pos = input.find(pat.data(), pos, pat.size())) != std::string::npos) { + input.erase(pos, pat.size()); + } } -template -static std::vector transform_to_name_vector(const std::vector& i) { - std::vector o; - o.reserve(i.size()); - std::transform(std::begin(i), std::end(i), std::back_inserter(o), [](const auto& v) { return v->name(); }); - return o; +/// +/// @brief Transform a vector of elements into a new vector by applying a function to each element. +/// +/// @tparam Sequence A container type containing elements to be transformed. +/// @tparam Functor A callable type that takes a value of type T and returns the transformed value. +/// +/// @param input The sequence of elements to transform. +/// @param transformation The function to apply to each element. +/// @return A new vector containing the transformed elements. +/// +template +inline auto transform_to_vector(const Sequence& input, Functor transformation) { + std::vector()))> buffer; + buffer.reserve(input.size()); + std::transform(std::begin(input), std::end(input), std::back_inserter(buffer), [&transformation](const auto& v) { + return transformation(v); + }); + return buffer; } +/// +/// @brief Convert a string to lowercase. +/// +/// Returns a copy of the input string with all characters converted to their lowercase equivalents +/// using `std::tolower`. +/// +/// @param s The input string +/// @return A new string with all characters converted to lowercase. +/// inline std::string tolower(std::string s) { std::transform(std::begin(s), std::end(s), std::begin(s), [](unsigned char c) { return std::tolower(c); }); return s; @@ -156,122 +439,282 @@ bool is_valid_name(const std::string& name, std::string& error); /// bool is_valid_name(const std::string& name); -} // namespace algorithm +/// +/// @brief Remove surrounding double quotes from a string in-place. +/// +/// Only if double quotes exist *at the beginning and at the end* of the input, will any change be performed. +/// +/// @param input The input string to modify. +/// +inline void remove_double_quotes(std::string& input) { + if (input.size() >= 2 && input.front() == '"' && input.back() == '"') { + input.erase(0, 1); + input.erase(input.size() - 1, 1); + } +} + +/// +/// @brief Remove surrounding double quotes from a string. +/// +/// Only if double quotes exist *at the beginning and at the end* of the input, will any change be performed. +/// +/// @param input The input string. +/// @return A new string with surrounding double quotes removed. +/// +inline std::string remove_double_quotes_copy(std::string input) { + remove_double_quotes(input); + return input; +} + +/// +/// @brief Remove surrounding single quotes from a string in-place. +/// +/// Only if single quotes exist *at the beginning and at the end* of the input, will any change be performed. +/// +/// @param input The input string to modify. +/// +inline void remove_single_quotes(std::string& input) { + if (input.size() >= 2 && input.front() == '\'' && input.back() == '\'') { + input.erase(0, 1); + input.erase(input.size() - 1, 1); + } +} + +/// +/// @brief Remove surrounding single quotes from a string. +/// +/// Only if single quotes exist *at the beginning and at the end* of the input, will any change be performed. +/// +/// @param input The input string. +/// @return A new string with surrounding single quotes removed. +/// +inline std::string remove_single_quotes_copy(std::string input) { + remove_single_quotes(input); + return input; +} + +/// +/// @brief Compare two strings case-insensitively. +/// +/// @param s1 The first string to compare. +/// @param s2 The second string to compare. +/// @return True if the strings are equal when case is ignored, false otherwise. +/// +inline bool case_insensitive_compare(const std::string& s1, const std::string& s2) { + auto compare = [](char a, char b) { return (toupper(a) == toupper(b)); }; + return ((s1.size() == s2.size()) && equal(s1.begin(), s1.end(), s2.begin(), compare)); +} + +/// +/// @brief Compare two strings case-insensitively using a less-than ordering. +/// +/// When characters differ only in case, uppercase is considered greater (i.e. 'a' < 'A'). +/// Otherwise, characters are compared by their uppercase equivalents. +/// +/// @param a The first string to compare. +/// @param b The second string to compare. +/// @return True if \p a is less than \p b under case-insensitive ordering, false otherwise. +/// +inline bool case_insensitive_less(const std::string& a, const std::string& b) { + return std::lexicographical_compare(a.begin(), a.end(), b.begin(), b.end(), [](char x, char y) -> bool { + if (toupper(x) == toupper(y)) { + return x > y; + } + return toupper(static_cast(x)) < toupper(static_cast(y)); + }); +} + +/// +/// @brief Compare two strings case-insensitively using a greater-than ordering. +/// +/// When characters differ only in case, lowercase is considered greater (i.e. 'A' < 'a'). +/// Otherwise, characters are compared by their uppercase equivalents in reverse order. +/// +/// @param a The first string to compare. +/// @param b The second string to compare. +/// @return True if \p a is greater than \p b under case-insensitive ordering, false otherwise. +/// +inline bool case_insensitive_greater(const std::string& a, const std::string& b) { + return std::lexicographical_compare(a.begin(), a.end(), b.begin(), b.end(), [](char x, char y) -> bool { + if (toupper(x) == toupper(y)) { + return x < y; + } + return toupper(static_cast(x)) > toupper(static_cast(y)); + }); +} + +/// +/// @brief Check if the string can be converted to an integer. +/// +/// @param input the string to check. +/// @return true if the string can be converted to an integer, false otherwise. +/// +inline bool is_int(const std::string& input) { + if (input.empty()) { + return false; + } + size_t start = 0; + if (input[0] == '+' || input[0] == '-') { + start = 1; + } + if (start >= input.size()) { + return false; + } + return std::all_of(input.begin() + start, input.end(), [](unsigned char ch) { return std::isdigit(ch); }); +} + +/// +/// @brief Convert a given string to an integer. +/// +/// This function checks if the string has any digits before attempting the conversion -- this approach is deemed +/// faster than using ecf::convert_to directly (and thus always attempt to perform the conversion). +/// +/// Use this function when it is not possible to ensure the string is convertible to an integer (e.g. user input). +/// +/// @param input The string to convert. +/// @param error_return The value to return if the conversion fails (e.g. if the string contains non-numerical chars). +/// @return upon successful conversion, the converted value; otherwise the given `error_return` value. +/// +inline int to_int(const std::string& input, int error_return = std::numeric_limits::max()) { + if (input.find_first_of(ecf::string_constants::numeric_chars, 0) != std::string::npos) { + try { + return ecf::convert_to(input); + } + catch (const ecf::bad_conversion&) { + // falls through to returning error... + } + } + return error_return; +} + +/// +/// @brief Trim the string \param fileContents, so that \param max_lines at the tail of the file are kept. +/// +/// Lines are counted by the number of '\n' characters found when scanning from the end of the string towards the +/// beginning. Once \param max_lines newlines have been encountered, all content before (and including) the newline +/// at the split point is erased. +/// +/// @param fileContents The string to truncate in-place. +/// @param max_lines The maximum number of lines to retain (counted from the end). +/// @return true if the string was truncated, false if it was empty or already within the limit. +/// +bool tail(std::string& fileContents, size_t max_lines); + +/// +/// @brief Trim the string \param fileContents, so that \param max_lines at the head of the file are kept. +/// +/// Lines are counted by the number of '\n' characters found when scanning from the begining of the string towards +/// the end. Once \param max_lines newlines have been encountered, all content after (and including) the +/// newline at the split point is erased. +/// +/// @param fileContents The string to truncate in-place. +/// @param max_lines The maximum number of lines to retain (counted from the beginninf). +/// @return true if the string was truncated, false if it was empty or already within the limit. +/// +bool head(std::string& fileContents, size_t max_lines); + +/// +/// @brief Convert a boolean value to a string representation. +/// +/// @param value The boolean value to convert to a string. +/// @return 'true' if the value is true, 'false' otherwise. +/// +inline std::string as_string(bool value) { + return value ? "true" : "false"; +} + +/// +/// @brief Convert a string vector to a string representation. +/// +/// @param values The string vector to convert to a string. +/// @return A string representation of the vector, with elements separated by ", " and enclosed in square brackets. +/// +inline std::string as_string(const std::vector& values) { + return "[ " + ecf::algorithm::join(values) + " ]"; +} + +/// +/// @brief Convert a string_view vector to a string representation. +/// +/// @param values The string_view vector to convert to a string. +/// @return A string representation of the vector, with elements separated by ", " and enclosed in square brackets. +/// +inline std::string as_string(const std::vector& values) { + return "[ " + ecf::algorithm::join(values) + " ]"; +} -class Str { -public: - // Disable default construction - Str() = delete; - - // remove any quotes on the string, else does nothing - // "fred" -> fred - // fred -> fred - static void removeQuotes(std::string&); - - // remove any single quotes on the string, else does nothing - // 'fred' -> fred - // fred -> fred - static void removeSingleQuotes(std::string&); - - /// - /// @brief Replace the first occurrence of 'find' with 'replace' in 'input'. - /// - /// @param input The input string in which to perform the replacement. - /// @param find The string to search for within the input. - /// @param replace The string to replace occurrences of 'find' with. - /// @return true if a replacement was made, false otherwise. - /// - static bool replace(std::string& input, const std::string& find, const std::string& replace); - - /// - /// @brief Replace all occurrences of 'find' with 'replace' in 'input'. - /// - /// @param input The input string in which to perform the replacement. - /// @param find The string to search for within the input. - /// @param replace The string to replace occurrences of 'find' with. - /// @return true if at least one replacement was made, false otherwise. - /// - static bool replace_all(std::string& input, const std::string& find, const std::string& replace); - - // extract data member value, ie given a string of the form: - // str=cmd a b fred:value - // data_member_name=fred: - // extract value - static bool extract_data_member_value(const std::string& str, - const std::string& data_member_name, - std::string& data_member_value); - - /// split string using default delimiters of space and tab as a separator; - /// The split is based on *ANY* of the characters in the delimiters. - /// **** Hence a delimiter of "==" will still split "a = complete" - /// **** sequential delimiter character are ignored **** - /// This function is used to choose the fastest implementation - static void split(const std::string& line, std::vector& tokens, const std::string& delimiters = " \t"); - - static std::vector tokenize_quotation(const std::string& s, std::string_view quotes); - - static void - split_orig(const std::string& line, std::vector& tokens, const std::string& delimiters = " \t"); - - static void - split_orig1(const std::string& line, std::vector& tokens, const std::string& delimiters = " \t"); - - static void split_using_string_view(std::string_view line, - std::vector& tokens, - std::string_view delimiters = " \t"); - - static void split_using_string_view2(std::string_view line, - std::vector& tokens, - std::string_view delimiters = " \t"); - - /// - /// @brief Extract the token at the specified index from the input string, using the given delimiters. - /// - /// @param input The input string to tokenise. - /// @param index The (0-based) index of the token to extract. - /// @param token The extracted token. - /// @param delimiters The set of characters to use as token separators. - /// @return true if a token was found at the specified index, false otherwise. - /// - static bool - get_token(std::string_view input, size_t index, std::string& token, std::string_view delimiters = " \t"); - - /// case-insensitive string comparison - static bool caseInsCompare(const std::string&, const std::string&); - - /// case-insensitive less - static bool caseInsLess(const std::string&, const std::string&); - - /// case-insensitive Greater - static bool caseInsGreater(const std::string&, const std::string&); - - /** - * Convert a given string to an integer. - * - * This function checks if the string has any digits before attempting the conversion -- this approach is deemed - * faster than using ecf::convert_to directly (and thus always attempt to perform the conversion). - * - * Use this function when it is not possible to ensure the string is convertible to an integer (e.g. user input). - * - * @return upon successful conversion, the converted value; otherwise the given `error_return` value. - */ - static int to_int(const std::string&, int error_return = std::numeric_limits::max()); - - /** - * Check if the string can be converted to an integer. - * - * @param s the string to check - * @return true if the string can be converted to an integer, false otherwise. - */ - static bool is_int(const std::string& s); - - /// Truncate the input string at the start/end if exceeds max_lines_ newlines - /// returns true if truncated false otherwise - static bool truncate_at_start(std::string& fileContents, size_t max_lines); - static bool truncate_at_end(std::string& fileContents, size_t max_lines); - - static std::string dump_string_vec(const std::vector& vec); -}; +/// +/// @brief Convert a vector of arithmetic values to a string representation. +/// +/// @tparam T The type of elements in the vector -- must be an arithmetic type (e.g. int, double, etc.). +/// @param values The vector of arithmetic values to convert to a string. +/// @return A string representation of the vector, with elements separated by ", " and enclosed in square brackets. +/// +template >> +inline std::string as_string(const std::vector& values) { + auto strings = ecf::algorithm::transform_to_vector(values, [](const auto& v) { return std::to_string(v); }); + return "[ " + ecf::algorithm::join(strings) + " ]"; +} + +/// +/// @brief Replace the first occurrence of 'find' with 'replace' in 'input'. +/// +/// When 'find' pattern is empty, no replacement is made and false is returned. +/// +/// @param input The input string in which to perform the replacement. +/// @param find The string to search for within the input. +/// @param replace The string to replace occurrences of 'find' with. +/// @return true if a replacement was made, false otherwise. +/// +inline bool replace(std::string& input, const std::string& find, const std::string& replace) { + if (find.empty()) { + return false; + } + + if (size_t pos = input.find(find); pos != std::string::npos) { + input.replace(pos, find.length(), replace); + return true; + } + return false; +} + +/// +/// @brief Replace all occurrences of 'find' with 'replace' in 'input'. +/// +/// When 'find' pattern is empty, no replacement is made and false is returned. +/// +/// @param input The input string in which to perform the replacement. +/// @param find The string to search for within the input. +/// @param replace The string to replace occurrences of 'find' with. +/// @return true if at least one replacement was made, false otherwise. +/// +inline bool replace_all(std::string& input, const std::string& find, const std::string& replace) { + if (find.empty()) { + return false; + } + + bool replaced = false; + size_t pos = 0; + while ((pos = input.find(find, pos)) != std::string::npos) { + input.replace(pos, find.length(), replace); + pos += replace.length(); + replaced = true; + } + return replaced; +} + +/// +/// @brief Extract the token at the specified index from the input string, using the given delimiters. +/// +/// @param input The input string to tokenise. +/// @param index The (0-based) index of the token to extract. +/// @param token The extracted token. +/// @param delimiters The set of characters to use as token separators. +/// @return true if a token was found at the specified index, false otherwise. +/// +bool get_token(std::string_view input, size_t index, std::string& token, std::string_view delimiters = " \t"); + +} // namespace algorithm } // namespace ecf diff --git a/libs/core/src/ecflow/core/TimeSeries.cpp b/libs/core/src/ecflow/core/TimeSeries.cpp index 6d38a8e59..aaedf586c 100644 --- a/libs/core/src/ecflow/core/TimeSeries.cpp +++ b/libs/core/src/ecflow/core/TimeSeries.cpp @@ -726,7 +726,7 @@ std::ostream& operator<<(std::ostream& os, const TimeSeries& d) { ecf::TimeSeries TimeSeries::create(const std::string& str) { std::vector lineTokens; - Str::split(str, lineTokens); + ecf::algorithm::split_at(lineTokens, str); size_t index = 0; return TimeSeries::create(index, lineTokens); } diff --git a/libs/core/src/ecflow/core/WhiteListFile.cpp b/libs/core/src/ecflow/core/WhiteListFile.cpp index 7f36f93c9..ae4126aa6 100644 --- a/libs/core/src/ecflow/core/WhiteListFile.cpp +++ b/libs/core/src/ecflow/core/WhiteListFile.cpp @@ -342,7 +342,7 @@ bool WhiteListFile::load(const std::string& file, std::string& errorMsg) { ecf::algorithm::trim(theLine); // remove leading and trailing spaces std::vector lineTokens; - Str::split(theLine, lineTokens); + ecf::algorithm::split_at(lineTokens, theLine); if (lineTokens.empty()) { continue; } @@ -421,7 +421,7 @@ bool WhiteListFile::validateVersionNumber(const std::string& line, std::string& if (firstCharIsNumeric && line.find(".") != std::string::npos) { std::vector versionNumberTokens; - Str::split(line, versionNumberTokens, "."); + ecf::algorithm::split_at(versionNumberTokens, line, "."); if (versionNumberTokens.size() != 3) { errorMsg += "Expected version of the form .. i.e 4.4.14. but found invalid version number\n"; return false; @@ -561,7 +561,7 @@ bool WhiteListFile::add_user(std::vector& tokens, std::string& erro else if (tok[0] == '/') { // path or set of paths std::vector local_paths; - Str::split(tok, local_paths, ","); + ecf::algorithm::split_at(local_paths, tok, ","); std::copy(local_paths.begin(), local_paths.end(), std::back_inserter(paths)); // root path '/' means apply to all suites, in which case the paths may as well be empty. diff --git a/libs/core/test/TestCalendar.cpp b/libs/core/test/TestCalendar.cpp index c222e7eec..dca6af7cd 100644 --- a/libs/core/test/TestCalendar.cpp +++ b/libs/core/test/TestCalendar.cpp @@ -359,7 +359,7 @@ BOOST_AUTO_TEST_CASE(test_calendar_state_parsing) { // read the state, into a different calendar & compare std::vector lineTokens; - Str::split(calendar_state, lineTokens); + ecf::algorithm::split_at(lineTokens, calendar_state); Calendar calendar2; calendar2.read_state(calendar_state, lineTokens); BOOST_CHECK_MESSAGE(calendar == calendar2, @@ -376,7 +376,7 @@ BOOST_AUTO_TEST_CASE(test_calendar_state_parsing) { calendar_state.clear(); calendar.write_state(calendar_state); - Str::split(calendar_state, lineTokens); + ecf::algorithm::split_at(lineTokens, calendar_state); calendar2.read_state(calendar_state, lineTokens); BOOST_CHECK_MESSAGE(calendar == calendar2, "Calendar should be the same"); diff --git a/libs/core/test/TestStr.cpp b/libs/core/test/TestStr.cpp index 960d54201..a48be1009 100644 --- a/libs/core/test/TestStr.cpp +++ b/libs/core/test/TestStr.cpp @@ -20,543 +20,1066 @@ #include "ecflow/core/Timer.hpp" #include "ecflow/test/scaffold/Naming.hpp" -using namespace ecf; - BOOST_AUTO_TEST_SUITE(U_Core) BOOST_AUTO_TEST_SUITE(T_Str) -BOOST_AUTO_TEST_CASE(test_str) { +BOOST_AUTO_TEST_CASE(test_algorithm_join) { ECF_NAME_THIS_TEST(); + struct tc { - std::string str; + std::vector input; + std::string delimiter; std::string expected; - Str::removeQuotes(str); - BOOST_CHECK_MESSAGE(str == expected, " Expected " << expected << " but found " << str); - - str = "\"\""; - expected = ""; - Str::removeQuotes(str); - BOOST_CHECK_MESSAGE(str == expected, " Expected " << expected << " but found " << str); + }; - str = "fred"; - expected = "fred"; - Str::removeQuotes(str); - BOOST_CHECK_MESSAGE(str == expected, " Expected " << expected << " but found " << str); + std::vector test_cases = { + {{}, ",", ""}, + {{"a", "b", "c"}, "", "abc"}, + {{"a"}, ",", "a"}, + {{"a", "b"}, ",", "a,b"}, + {{"a", "b", "c"}, ",", "a,b,c"}, + {{"a", "b", "c"}, ";;", "a;;b;;c"}, + }; - str = "\"fred\""; - expected = "fred"; - Str::removeQuotes(str); - BOOST_CHECK_MESSAGE(str == expected, " Expected " << expected << " but found " << str); + for (const auto& tc : test_cases) { + auto actual = ecf::algorithm::join(tc.input, tc.delimiter); + BOOST_CHECK_MESSAGE(actual == tc.expected, "Expected '" << tc.expected << "', found '" << actual << "'"); } +} + +BOOST_AUTO_TEST_CASE(test_algorithm_replace_first) { + ECF_NAME_THIS_TEST(); + + struct tc { - std::string str; + std::string input; + std::string search; + std::string replace; std::string expected; - Str::removeSingleQuotes(str); - BOOST_CHECK_MESSAGE(str == expected, " Expected " << expected << " but found " << str); - - str = "''"; - expected = ""; - Str::removeSingleQuotes(str); - BOOST_CHECK_MESSAGE(str == expected, " Expected " << expected << " but found " << str); + }; - str = "fred"; - expected = "fred"; - Str::removeSingleQuotes(str); - BOOST_CHECK_MESSAGE(str == expected, " Expected " << expected << " but found " << str); + std::vector test_cases = { + {"abc def abc def abc", " ", "", "abcdef abc def abc"}, + {"abc def abc def abc", "a", "x", "xbc def abc def abc"}, + {"abc def abc def abc", "b", "x", "axc def abc def abc"}, + {"abc def abc def abc", "c", "x", "abx def abc def abc"}, + {"abc def abc def abc", "d", "x", "abc xef abc def abc"}, + {"abc def abc def abc", "abc", "xxx", "xxx def abc def abc"}, + }; - str = "'fred'"; - expected = "fred"; - Str::removeSingleQuotes(str); - BOOST_CHECK_MESSAGE(str == expected, " Expected " << expected << " but found " << str); + for (const auto& tc : test_cases) { + auto actual = tc.input; + ecf::algorithm::replace_first(actual, tc.search, tc.replace); + BOOST_CHECK_MESSAGE(actual == tc.expected, "Expected '" << tc.expected << "', found '" << actual << "'"); } +} + +BOOST_AUTO_TEST_CASE(test_algorithm_split_at) { + ECF_NAME_THIS_TEST(); + struct tc { - std::string test; - BOOST_CHECK_MESSAGE(!Str::truncate_at_start(test, 7), "Empty sring should return false"); + std::string input; + std::vector expected; + }; - test = "this\nis\na\nstring\nwith\nlots\nof\nnew\nline"; - std::string expected = "line"; - BOOST_CHECK_MESSAGE(Str::truncate_at_start(test, 1) && test == expected, + std::vector test_cases = { + {"", {}}, + {" ", {}}, + {"a", {"a"}}, + {" a", {"a"}}, + {"a ", {"a"}}, + {" a ", {"a"}}, + {"\ta", {"a"}}, + {"a\t", {"a"}}, + {"\ta\t", {"a"}}, + {"\n", {"\n"}}, + {" \t a\t \t", {"a"}}, + {"\t\t\ta\t\t\t", {"a"}}, + {"\n", {"\n"}}, + {" a b c d ", {"a", "b", "c", "d"}}, + {" - ! $ % ^ & * ( ) - + ?", {"-", "!", "$", "%", "^", "&", "*", "(", ")", "-", "+", "?"}}, + {"This is a string", {"This", "is", "a", "string"}}, + {" verify complete:8 # 4 sundays in october hence expect 8 task completions", + {"verify", + "complete:8", + "#", + "4", + "sundays", + "in", + "october", + "hence", + "expect", + "8", + "task", + "completions"}}}; + + for (const auto& tc : test_cases) { + std::vector result; + ecf::algorithm::split_at(result, tc.input); + BOOST_CHECK_MESSAGE(result == tc.expected, "Expected:\n" - << expected << "\nbut found:\n" - << test); + << ecf::algorithm::as_string(tc.expected) << "\nbut found:\n" + << ecf::algorithm::as_string(result)); + } +} - test = "this\nis\na\nstring\nwith\nlots\nof\nnew\nline"; - expected = "a\nstring\nwith\nlots\nof\nnew\nline"; - BOOST_CHECK_MESSAGE(Str::truncate_at_start(test, 7) && test == expected, - "Expected:\n" - << expected << "\nbut found:\n" - << test); +BOOST_AUTO_TEST_CASE(test_algorithm_split_at_with_separators) { + ECF_NAME_THIS_TEST(); - test = "this\nis\na\nstring\nwith\nlots\nof\nnew\nline"; - expected = test; - BOOST_CHECK_MESSAGE(!Str::truncate_at_start(test, 9) && test == expected, - "Expected:\n" - << expected << "\nbut found:\n" - << test); + struct tc + { + std::string input; + std::string separator; + std::vector expected; + }; + + std::vector test_cases = {{"", ",", {}}, + {"abc,def,ghi", ",", {"abc", "def", "ghi"}}, + {"abc,,def,ghi", ",", {"abc", "def", "ghi"}}, + {",abc,def,ghi", ",", {"abc", "def", "ghi"}}, + {"abc,def,ghi,", ",", {"abc", "def", "ghi"}}, + {"xxx;,;yyy;,;zzz", ";,", {"xxx", "yyy", "zzz"}}, + {"xxx;a,b;yyy;c,d;zzz", ";,", {"xxx", "a", "b", "yyy", "c", "d", "zzz"}}, + {"aeiou,12345 12345,aeiou", " ", {"aeiou,12345", "12345,aeiou"}}, + {"a,,b,c;d; ;e;;;f;", ",,;", {"a", "b", "c", "d", " ", "e", "f"}}}; + + for (const auto& tc : test_cases) { + std::vector splits{"this", "is", "garbage"}; + + const auto& actual = ecf::algorithm::split_at(splits, tc.input, tc.separator); + + BOOST_CHECK_MESSAGE(actual == splits, "Expected the returned value and the input vector to be the same object"); + BOOST_CHECK_MESSAGE(actual == tc.expected, + "Splitting >>>" << tc.input << "<<<, Expected " << ecf::algorithm::as_string(tc.expected) + << ", found " << ecf::algorithm::as_string(actual)); } +} + +BOOST_AUTO_TEST_CASE(test_algorithm_split_by_with_separators) { + + struct TestCase { - std::string test; - BOOST_CHECK_MESSAGE(!Str::truncate_at_end(test, 7), "Empty string should return false"); + std::string input; + std::string pattern; + std::vector expected; + }; - test = "this\nis\na\nstring\nwith\nlots\nof\nnew\nline"; - std::string expected = "this\n"; - BOOST_CHECK_MESSAGE(Str::truncate_at_end(test, 1) && test == expected, - "Expected:\n" - << expected << "\nbut found:\n" - << test); + std::vector testCases = { + {"This is a string", "...", {"This is a string"}}, + {"This...is...a...string", "...", {"This", "is", "a", "string"}}, + {"This...is...a...string...", "...", {"This", "is", "a", "string"}}, + {"...This...is...a...string", "...", {"This", "is", "a", "string"}}, + {"...This...is...a...string...", "...", {"This", "is", "a", "string"}}, + {"......This......is......a......string......", "...", {"This", "is", "a", "string"}}, + {"..This.....is.....a.....string.....", "...", {"..This", "..is", "..a", "..string", ".."}}, + {"expression 1==expression 2", "==", {"expression 1", "expression 2"}}, + {"expression 1==expression 2==expression 3", "==", {"expression 1", "expression 2", "expression 3"}}, + {"expression 1 == expression 2", " == ", {"expression 1", "expression 2"}}, + {"expression 1 == expression 2 == expression 3", " == ", {"expression 1", "expression 2", "expression 3"}}, + {"expression 1 eq expression 2", " eq ", {"expression 1", "expression 2"}}, + {"expression 1eqexpression 2", " eq ", {"expression 1eqexpression 2"}}}; - test = "this\nis\na\nstring\nwith\nlots\nof\nnew\nline"; - expected = "this\nis\n"; - BOOST_CHECK_MESSAGE(Str::truncate_at_end(test, 2) && test == expected, - "Expected:\n" - << expected << "\nbut found:\n" - << test); + for (const auto& testCase : testCases) { + std::vector result; + ecf::algorithm::split_by(result, testCase.input, testCase.pattern); + BOOST_CHECK_MESSAGE(result == testCase.expected, + "Failed for input: '" << testCase.input << "' pattern: '" << testCase.pattern + << "'. Expected: " << ecf::algorithm::as_string(testCase.expected) + << " but found: " << ecf::algorithm::as_string(result)); + } +} - test = "this\nis\na\nstring\nwith\nlots\nof\nnew\nline"; - expected = "this\nis\na\nstring\nwith\nlots\nof\n"; - BOOST_CHECK_MESSAGE(Str::truncate_at_end(test, 7) && test == expected, - "Expected:\n" - << expected << "\nbut found:\n" - << test); +BOOST_AUTO_TEST_CASE(test_algorithm_split_within_quotes) { + ECF_NAME_THIS_TEST(); - test = "this\nis\na\nstring\nwith\nlots\nof\nnew\nline"; - expected = test; - BOOST_CHECK_MESSAGE(!Str::truncate_at_end(test, 9) && test == expected, - "Expected:\n" - << expected << "\nbut found:\n" - << test); + struct tc + { + std::string input; + std::string quotes; + std::vector expected; + }; + + std::vector test_cases = {{"", "'", {}}, + {"''", "'", {"''"}}, + {"'' ' '", "'", {"''", "' '"}}, + {"'a' 'b' ''", "'", {"'a'", "'b'", "''"}}, + {"'abc' 'def' 'ghi'", "'", {"'abc'", "'def'", "'ghi'"}}, + {"'a' 'b' '", "'", {"'a'", "'b'", "'"}}, + {"'a' b' '", "'", {"'a'", "' '"}}, + {"'a' x 'b' y 'c' zz ", "'", {"'a'", "x", "'b'", "y", "'c'", "zz"}}, + {"\"a\" x \"b\" x \"c\"", "\"", {"\"a\"", "x", "\"b\"", "x", "\"c\""}}, + {"'a \" b \" c' \"d ' e ' f\"", "\"'", {"'a \" b \" c'", "\"d ' e ' f\""}}}; + + for (const auto& tc : test_cases) { + const auto& actual = ecf::algorithm::split_within_quotes(tc.input, tc.quotes); + + BOOST_CHECK_MESSAGE(actual == tc.expected, + "Splitting >>>" << tc.input << "<<<, Expected " << ecf::algorithm::as_string(tc.expected) + << ", found " << ecf::algorithm::as_string(actual)); } } -static void -check(const std::string& line, const std::vector& result, const std::vector& expected) { - BOOST_CHECK_MESSAGE(result.size() == expected.size(), - "expected size " << expected.size() << " but found " << result.size() << " for '" << line - << "'"); - BOOST_CHECK_MESSAGE(result == expected, "failed for '" << line << "'"); - if (result != expected) { - ECF_TEST_DBG(<< "Line :'" << line); - ECF_TEST_DBG(<< "Actual :"); - for (const std::string& t : result) { - ECF_TEST_DBG(<< " '" << t << "'"); - } - ECF_TEST_DBG(<< "Expected:"); - for (const std::string& t : expected) { - ECF_TEST_DBG(<< "'" << t << "'"); - } +BOOST_AUTO_TEST_CASE(test_algorithm_starts_with) { + ECF_NAME_THIS_TEST(); + + struct tc + { + std::string input; + std::string prefix; + bool expected; + }; + + std::vector test_cases = {{"", "", true}, + {"abcdef", "", true}, + {"abcdef", "a", true}, + {"abcdef", "ab", true}, + {"abcdef", "abc", true}, + {"abcdef", "abcx", false}, + {"abcdef", "abcdefx", false}, + {"zabcdef", "abcdef", false}}; + + for (const auto& tc : test_cases) { + auto actual = ecf::algorithm::starts_with(tc.input, tc.prefix); + BOOST_CHECK_MESSAGE(actual == tc.expected, + "Expected " << ecf::algorithm::as_string(tc.expected) << ", found " + << ecf::algorithm::as_string(actual)); + } +} + +BOOST_AUTO_TEST_CASE(test_algorithm_ends_with) { + ECF_NAME_THIS_TEST(); + + struct tc + { + std::string input; + std::string prefix; + bool expected; + }; + + std::vector test_cases = {{"", "", true}, + {"abcdef", "", true}, + {"abcdef", "f", true}, + {"abcdef", "ef", true}, + {"abcdef", "def", true}, + {"abcdef", "xdef", false}, + {"abcdef", "xabcdef", false}, + {"abcdefz", "abcdef", false}}; + + for (const auto& tc : test_cases) { + auto actual = ecf::algorithm::ends_with(tc.input, tc.prefix); + BOOST_CHECK_MESSAGE(actual == tc.expected, + "Expected " << ecf::algorithm::as_string(tc.expected) << ", found " + << ecf::algorithm::as_string(actual)); + } +} + +BOOST_AUTO_TEST_CASE(test_algorithm_trim) { + ECF_NAME_THIS_TEST(); + + struct tc + { + std::string input; + std::string expected_trim_both; + std::string expected_trim_leading; + std::string expected_trim_trailing; + }; + + std::vector test_cases = { + {"", "", "", ""}, + {" ", "", "", ""}, + {" ", "", "", ""}, + {"abc", "abc", "abc", "abc"}, + {" abc", "abc", "abc", " abc"}, + {"abc ", "abc", "abc ", "abc"}, + {" abc ", "abc", "abc ", " abc"}, + {"\tabc\t", "abc", "abc\t", "\tabc"}, + {"\nabc\n", "abc", "abc\n", "\nabc"}, + {" \t\nabc \t\n", "abc", "abc \t\n", " \t\nabc"}, + {" \t\n abc \t\n ", "abc", "abc \t\n ", " \t\n abc"}, + }; + + for (const auto& tc : test_cases) { + auto actual = tc.input; + ecf::algorithm::trim(actual); + auto expected = tc.expected_trim_both; + BOOST_CHECK_MESSAGE(actual == expected, "Expected '" << expected << "', found '" << actual << "'"); + } + + for (const auto& tc : test_cases) { + auto actual = tc.input; + ecf::algorithm::trim_leading(actual); + auto expected = tc.expected_trim_leading; + BOOST_CHECK_MESSAGE(actual == expected, "Expected '" << expected << "', found '" << actual << "'"); + } + + for (const auto& tc : test_cases) { + auto actual = ecf::algorithm::trim_leading_copy(tc.input); + auto expected = tc.expected_trim_leading; + BOOST_CHECK_MESSAGE(actual == expected, "Expected '" << expected << "', found '" << actual << "'"); + } + + for (const auto& tc : test_cases) { + auto actual = tc.input; + ecf::algorithm::trim_trailing(actual); + auto expected = tc.expected_trim_trailing; + BOOST_CHECK_MESSAGE(actual == expected, "Expected '" << expected << "', found '" << actual << "'"); + } + + for (const auto& tc : test_cases) { + auto actual = ecf::algorithm::trim_trailing_copy(tc.input); + auto expected = tc.expected_trim_trailing; + BOOST_CHECK_MESSAGE(actual == expected, "Expected '" << expected << "', found '" << actual << "'"); + } +} + +BOOST_AUTO_TEST_CASE(test_algorithm_contains) { + ECF_NAME_THIS_TEST(); + + struct tc + { + std::string input; + std::string pattern; + bool expected; + }; + + std::vector test_cases = {{"", "", true}, + {"abcdef", "", true}, + {"abcdef", "a", true}, + {"abcdef", "ab", true}, + {"abcdef", "abc", true}, + {"abcdef", "abcx", false}, + {"abcdef", "abcdefx", false}, + {"zabcdef", "abcdef", true}}; + + for (const auto& tc : test_cases) { + auto actual = ecf::algorithm::contains(tc.input, tc.pattern); + BOOST_CHECK_MESSAGE(actual == tc.expected, + "Expected " << ecf::algorithm::as_string(tc.expected) << ", found " + << ecf::algorithm::as_string(actual)); + } +} + +BOOST_AUTO_TEST_CASE(test_algorithm_remove_all) { + ECF_NAME_THIS_TEST(); + + struct tc + { + std::string input; + std::string pattern; + std::string expected; + }; + + std::vector test_cases = {{"", "", ""}, + {"abcdef", "", "abcdef"}, + {"abcdef", "a", "bcdef"}, + {"abcdef", "ab", "cdef"}, + {"abcdef", "abc", "def"}, + {"abcdef", "abcd", "ef"}, + {"abcdef", "abcx", "abcdef"}, + {"abcdef", "abcdefx", "abcdef"}, + {"zabcdef", "abcdef", "z"}, + {"abcxyzdef", "xyz", "abcdef"}, + {"xyzabcdef", "xyz", "abcdef"}, + {"abcxyzdef", "xyz", "abcdef"}, + {"xaxbxcxdxexfx", "x", "abcdef"}, + {"xyzaxyzbxyzcxyzdxyzexyzfxyz", "xyz", "abcdef"}}; + + for (const auto& tc : test_cases) { + auto actual = tc.input; + ecf::algorithm::remove_all(actual, tc.pattern); + BOOST_CHECK_MESSAGE(actual == tc.expected, "Expected " << tc.expected << ", found " << actual); } } -static void -check(const std::string& line, const std::vector& result2, const std::vector& expected) { - std::vector result; - for (auto ref : result2) { - result.emplace_back(ref.begin(), ref.end()); +BOOST_AUTO_TEST_CASE(test_algorithm_transform_to_vector) { + ECF_NAME_THIS_TEST(); + + using namespace ecf::algorithm; + + { + std::vector input = {1, 2, 3, 4, 5, 6, 7, 8, 9, 0}; + std::vector expected = {1, 4, 9, 16, 25, 36, 49, 64, 81, 0}; + + auto actual = ecf::algorithm::transform_to_vector(input, [](int v) { return v * v; }); + BOOST_CHECK_MESSAGE(actual == expected, + "Expected '" << ecf::algorithm::as_string(expected) << "', found '" + << ecf::algorithm::as_string(actual) << "'"); + } + + { + std::vector input = {1, 2, 3, 4, 5, 6, 7, 8, 9, 0}; + std::vector expected = {"1", "2", "3", "4", "5", "6", "7", "8", "9", "0"}; + + auto actual = ecf::algorithm::transform_to_vector(input, [](int v) { return std::to_string(v); }); + BOOST_CHECK_MESSAGE(actual == expected, + "Expected '" << ecf::algorithm::as_string(expected) << "', found '" + << ecf::algorithm::as_string(actual) << "'"); + } + + { + std::vector input = {"1", "2", "3", "4", "5", "6", "7", "8", "9", "0"}; + std::vector expected = {1, 2, 3, 4, 5, 6, 7, 8, 9, 0}; + + auto actual = ecf::algorithm::transform_to_vector(input, [](const std::string& v) { return std::stoi(v); }); + BOOST_CHECK_MESSAGE(actual == expected, + "Expected '" << ecf::algorithm::as_string(expected) << "', found '" + << ecf::algorithm::as_string(actual) << "'"); } - check(line, result, expected); } -static void check(const std::string& line, - boost::split_iterator res, - const std::vector& expected) { - std::vector result; - using split_iter_t = boost::split_iterator; - for (; res != split_iter_t(); res++) { - result.push_back(boost::copy_range(*res)); +BOOST_AUTO_TEST_CASE(test_algorithm_tolower) { + ECF_NAME_THIS_TEST(); + + struct tc + { + std::string input; + std::string expected; + }; + + std::vector test_cases = {{"", ""}, + {"a", "a"}, + {"A", "a"}, + {"a1", "a1"}, + {"A1", "a1"}, + {"abc", "abc"}, + {"ABC", "abc"}, + {"AbC", "abc"}, + {"aBc", "abc"}, + {"aBC", "abc"}, + {"Abc", "abc"}, + {"abC", "abc"}, + {"123", "123"}, + {"!@#$%^&*()_+", "!@#$%^&*()_+"}, + {"a1B2c3D4e5F6g7H8i9J0", "a1b2c3d4e5f6g7h8i9j0"}}; + + for (const auto& tc : test_cases) { + auto actual = ecf::algorithm::tolower(tc.input); + BOOST_CHECK_MESSAGE(actual == tc.expected, "Expected '" << tc.expected << "', found '" << actual << "'"); } - check(line, result, expected); } -static void check_splitters(const std::string& line, const std::vector& expected) { - std::vector result, result1, result2, result3; - std::vector result4; +BOOST_AUTO_TEST_CASE(test_algorithm_is_valid_name) { + ECF_NAME_THIS_TEST(); - Str::split_orig(line, result); - check(line, result, expected); - Str::split_orig1(line, result1); - check(line, result1, expected); - Str::split_using_string_view(line, result2); - check(line, result2, expected); - Str::split_using_string_view2(line, result3); - check(line, result3, expected); - StringSplitter::split(line, result4); - check(line, result4, expected); + struct tc + { + std::string input; + bool expected; + }; + + std::vector test_cases = { + {"", false}, + {"a", true}, + {"abc", true}, + {"1abc", true}, + {"abc1", true}, + {"a1b2c3", true}, + {"a1c", true}, + {"_abc", true}, // '_' is an allowed character as the first character of a name + {"a_b_c", true}, // '_' is an allowed character as the first character of a name + {"-abc", false}, // '-' is not an allowed character + {"a-b-c", false}, // '-' is not an allowed character + {".abc", false}, // '.' is an allowed character but not as the first character of a name' + {"a.b.c", true}, // '.' is an allowed character but not as the first character of a name' + {"a_b-c.d", false}, // '-' is not an allowed character + }; + + for (const auto& tc : test_cases) { + auto actual = ecf::algorithm::is_valid_name(tc.input); + BOOST_CHECK_MESSAGE(actual == tc.expected, + "Validating '" << tc.input << "', Expected '" << tc.expected << "', found '" << actual + << "'"); + } - // While were at it also check get_token - for (size_t i = 0; i < result1.size(); i++) { + for (const auto& tc : test_cases) { + std::string error; + auto actual = ecf::algorithm::is_valid_name(tc.input, error); + BOOST_CHECK_MESSAGE(actual == tc.expected, "Expected '" << tc.expected << "', found '" << actual << "'"); + if (tc.expected) { + BOOST_CHECK_MESSAGE(error.empty(), "Expected empty error, found '" << error << "'"); + } + else { + BOOST_CHECK_MESSAGE(!error.empty(), "Expected non-empty error, found '" << error << "'"); + } + } +} + +BOOST_AUTO_TEST_CASE(test_algorithm_remove_double_quotes) { + ECF_NAME_THIS_TEST(); + + struct tc + { + std::string input; + std::string expected; + }; + + const std::vector test_cases = { + {"", ""}, + {"\"\"", ""}, + {"\"", "\""}, + {"''", "''"}, + {"'", "'"}, + {"abc", "abc"}, + {"\"abc\"", "abc"}, + {"\"abc", "\"abc"}, + {"abc\"", "abc\""}, + {"'abc", "'abc"}, + {"abc'", "abc'"}, + {"'abc'", "'abc'"}, + }; + + for (const auto& tc : test_cases) { { - std::string token; - BOOST_CHECK_MESSAGE(Str::get_token(line, i, token) && token == result1[i], - "Str::get_token failed for pos " << i << " line:'" << line << "' expected '" - << result1[i] << "' but found '" << token << "'"); + std::string actual = tc.input; + ecf::algorithm::remove_double_quotes(actual); + BOOST_CHECK_MESSAGE(actual == tc.expected, "Expected '" << tc.expected << "', found '" << actual << "'"); } + { + std::string actual = ecf::algorithm::remove_double_quotes_copy(tc.input); + BOOST_CHECK_MESSAGE(actual == tc.expected, "Expected '" << tc.expected << "', found '" << actual << "'"); + } + } +} + +BOOST_AUTO_TEST_CASE(test_algorithm_remove_single_quotes) { + ECF_NAME_THIS_TEST(); + struct tc + { + std::string input; + std::string expected; + }; + + std::vector test_cases = { + {"", ""}, + {"\"\"", "\"\""}, + {"\"", "\""}, + {"''", ""}, + {"'", "'"}, + {"abc", "abc"}, + {"\"abc\"", "\"abc\""}, + {"\"abc", "\"abc"}, + {"abc\"", "abc\""}, + {"'abc", "'abc"}, + {"abc'", "abc'"}, + {"'abc'", "abc"}, + }; + + for (const auto& tc : test_cases) { + { + std::string actual = tc.input; + ecf::algorithm::remove_single_quotes(actual); + BOOST_CHECK_MESSAGE(actual == tc.expected, "Expected '" << tc.expected << "', found '" << actual << "'"); + } { - std::string token; - BOOST_CHECK_MESSAGE(StringSplitter::get_token(line, i, token) && token == result1[i], - "StringSplitter::get_token failed for pos " << i << " line:'" << line << "' expected '" - << result1[i] << "' but found '" << token - << "'"); - } - } -} - -BOOST_AUTO_TEST_CASE(test_str_split) { - ECF_NAME_THIS_TEST(); - - std::vector expected; - - std::string line = "This is a string"; - expected.emplace_back("This"); - expected.emplace_back("is"); - expected.emplace_back("a"); - expected.emplace_back("string"); - check_splitters(line, expected); - - expected.clear(); - line = " "; - check_splitters(line, expected); - - line = "a"; - expected.clear(); - expected.emplace_back("a"); - check_splitters(line, expected); - - // Some implementation fail this test - line = "\n"; - expected.clear(); - expected.emplace_back("\n"); - check_splitters(line, expected); - - line = "a "; - expected.clear(); - expected.emplace_back("a"); - check_splitters(line, expected); - - line = " a"; - expected.clear(); - expected.emplace_back("a"); - check_splitters(line, expected); - - line = " a"; // check tabs - expected.clear(); - expected.emplace_back("a"); - check_splitters(line, expected); + std::string actual = ecf::algorithm::remove_single_quotes_copy(tc.input); + BOOST_CHECK_MESSAGE(actual == tc.expected, "Expected '" << tc.expected << "', found '" << actual << "'"); + } + } +} - line = " a "; // check sequential tabs - expected.clear(); - expected.emplace_back("a"); - check_splitters(line, expected); - - line = " a "; - expected.clear(); - expected.emplace_back("a"); - check_splitters(line, expected); - - line = " a b c d "; - expected.clear(); - expected.emplace_back("a"); - expected.emplace_back("b"); - expected.emplace_back("c"); - expected.emplace_back("d"); - check_splitters(line, expected); +BOOST_AUTO_TEST_CASE(test_algorithm_case_insensitive_compare) { + ECF_NAME_THIS_TEST(); - line = " - ! $ % ^ & * ( ) - + ?"; - expected.clear(); - expected.emplace_back("-"); - expected.emplace_back("!"); - expected.emplace_back("$"); - expected.emplace_back("%"); - expected.emplace_back("^"); - expected.emplace_back("&"); - expected.emplace_back("*"); - expected.emplace_back("("); - expected.emplace_back(")"); - expected.emplace_back("-"); - expected.emplace_back("+"); - expected.emplace_back("?"); - check_splitters(line, expected); + struct tc + { + std::string lhs; + std::string rhs; + bool expected; + }; - // Check tabs - line = " verify complete:8 # 4 sundays in october hence expect 8 task " - "completions"; - expected.clear(); - expected.emplace_back("verify"); - expected.emplace_back("complete:8"); - expected.emplace_back("#"); - expected.emplace_back("4"); - expected.emplace_back("sundays"); - expected.emplace_back("in"); - expected.emplace_back("october"); - expected.emplace_back("hence"); - expected.emplace_back("expect"); - expected.emplace_back("8"); - expected.emplace_back("task"); - expected.emplace_back("completions"); - check_splitters(line, expected); + std::vector test_cases = {{"", "", true}, + {"a", "a", true}, + {"a", "A", true}, + {"A", "a", true}, + {"abc", "abc", true}, + {"abc", "ABC", true}, + {"Abc", "aBC", true}, + {"Str", "Str", true}, + {"Str", "Str1", false}, + {"", "Str1", false}, + {"Str", "STR", true}, + {"Case", "CaSE", true}}; + + for (const auto& tc : test_cases) { + auto actual = ecf::algorithm::case_insensitive_compare(tc.lhs, tc.rhs); + BOOST_CHECK_MESSAGE(actual == tc.expected, "Expected '" << tc.expected << "', found '" << actual << "'"); + } } -static void test_replace(const std::string& input, - const std::string& find, - const std::string& replace, - const std::string& expected) { +BOOST_AUTO_TEST_CASE(test_algorithm_case_insensitive_less) { + ECF_NAME_THIS_TEST(); - auto actual = input; + struct tc + { + std::string lhs; + std::string rhs; + bool expected; + }; - { // Check that replacement happens as expected - auto result = Str::replace(actual, find, replace); - - BOOST_CHECK_MESSAGE(result, - "Replace failed for " << actual << " find(" << find << ") replace(" << replace << ")"); - BOOST_CHECK_MESSAGE(actual == expected, "Expected '" << expected << "' but found '" << actual << "'"); - } -} - -static void test_replace_all(const std::string& input, - const std::string& find, - const std::string& replace, - const std::string& expected) { - auto actual = input; - - { // Check that replacement happens as expected - bool result = Str::replace_all(actual, find, replace); - - BOOST_CHECK_MESSAGE(result, - "Replace successful for " << actual << " find(" << find << ") replace(" << replace << ")"); - BOOST_CHECK_MESSAGE(actual == expected, "Expected '" << expected << "' but found '" << actual << "'"); - } - - auto copy = actual; + std::vector test_cases = {{"", "", false}, + {"a", "a", false}, + {"a", "A", true}, + {"A", "a", false}, + {"abc", "abc", false}, + {"abc", "ABC", true}, + {"Abc", "aBC", false}, + {"Str", "Str", false}, + {"Str", "Str1", true}, + {"", "Str1", true}, + {"Str", "STR", true}, + {"Case", "CaSE", true}, + {"abc", "abd", true}, + {"abd", "abc", false}}; + + for (const auto& tc : test_cases) { + auto actual = ecf::algorithm::case_insensitive_less(tc.lhs, tc.rhs); + BOOST_CHECK_MESSAGE(actual == tc.expected, + "Comparing '" << tc.lhs << "' < '" << tc.rhs << "' Expected '" << tc.expected + << "', found '" << actual << "'"); + } +} + +BOOST_AUTO_TEST_CASE(test_algorithm_case_insensitive_greater) { + ECF_NAME_THIS_TEST(); + + struct tc + { + std::string lhs; + std::string rhs; + bool expected; + }; - { // Ensure that attempting replacement again returns false and does not change the input string - bool result = Str::replace_all(copy, find, replace); + std::vector test_cases = {{"", "", false}, + {"a", "a", false}, + {"a", "A", false}, + {"A", "a", true}, + {"abc", "abc", false}, + {"abc", "ABC", false}, + {"Abc", "aBC", true}, + {"Str", "Str", false}, + {"Str", "Str1", true}, + {"", "Str1", true}, + {"Str", "STR", false}, + {"Case", "CaSE", false}, + {"abc", "abd", false}, + {"abd", "abc", true}}; + + for (const auto& tc : test_cases) { + auto actual = ecf::algorithm::case_insensitive_greater(tc.lhs, tc.rhs); + BOOST_CHECK_MESSAGE(actual == tc.expected, + "Comparing '" << tc.lhs << "' > '" << tc.rhs << "' Expected '" << tc.expected + << "', found '" << actual << "'"); + } +} + +BOOST_AUTO_TEST_CASE(test_algorithm_is_int) { + ECF_NAME_THIS_TEST(); + + struct test_case + { + std::string input; + bool expected; + }; - BOOST_CHECK_MESSAGE(!result, - "Replace unsuccessful for " << copy << " find(" << find << ") replace(" << replace - << "), since no occurrences were left"); - BOOST_CHECK_MESSAGE(actual == copy, "Expected '" << actual << "' but found '" << copy << "'"); + std::vector test_cases = {{"0", true}, + {"1", true}, + {"-0", true}, + {"-1", true}, + {"", false}, + {"-", false}, + {" ", false}, + {"q", false}, + {"q22", false}, + {"99 99", false}, + {"99\t99", false}}; + + for (const auto& tc : test_cases) { + auto actual = ecf::algorithm::is_int(tc.input); + BOOST_CHECK_MESSAGE(actual == tc.expected, + "Checking '" << tc.input << "', Expected " << tc.expected << ", found " << actual); } } -BOOST_AUTO_TEST_CASE(test_str_replace) { +BOOST_AUTO_TEST_CASE(test_algorithm_to_int) { ECF_NAME_THIS_TEST(); - test_replace("This is a string", "This", "That", "That is a string"); - test_replace("This is a string", "This is a string", "", ""); - test_replace("This is a string", "is a", "was a", "This was a string"); - test_replace("This\n is a string", "\n", "\\n", "This\\n is a string"); + struct test_case + { + std::string input; + int expected; + }; - // Test case insenstive string comparison - BOOST_CHECK_MESSAGE(Str::caseInsCompare("", ""), " bug1"); - BOOST_CHECK_MESSAGE(!Str::caseInsCompare("Str", "Str1"), " bug1"); - BOOST_CHECK_MESSAGE(!Str::caseInsCompare("", "Str1"), " bug1"); - BOOST_CHECK_MESSAGE(Str::caseInsCompare("Str", "STR"), " bug1"); - BOOST_CHECK_MESSAGE(Str::caseInsCompare("Case", "CaSE"), " bug1"); + std::vector test_cases = {{"0", 0}, + {"1", 1}, + {"-0", 0}, + {"-1", -1}, + {"", std::numeric_limits::max()}, + {"-", std::numeric_limits::max()}, + {" ", std::numeric_limits::max()}, + {"q", std::numeric_limits::max()}, + {"q22", std::numeric_limits::max()}, + {"99 99", std::numeric_limits::max()}}; + + for (const auto& tc : test_cases) { + auto actual = ecf::algorithm::to_int(tc.input); + BOOST_CHECK_MESSAGE(actual == tc.expected, + "Converting '" << tc.input << "' to int, Expected " << tc.expected << ", found " << actual); + } } -BOOST_AUTO_TEST_CASE(test_str_replace_all) { +BOOST_AUTO_TEST_CASE(test_algorithm_tail) { ECF_NAME_THIS_TEST(); + std::string test; + BOOST_CHECK_MESSAGE(!ecf::algorithm::tail(test, 7), "Empty sring should return false"); + + test = "this\nis\na\nstring\nwith\nlots\nof\nnew\nline"; + std::string expected = "line"; + BOOST_CHECK_MESSAGE(ecf::algorithm::tail(test, 1) && test == expected, + "Expected:\n" + << expected << "\nbut found:\n" + << test); + + test = "this\nis\na\nstring\nwith\nlots\nof\nnew\nline"; + expected = "a\nstring\nwith\nlots\nof\nnew\nline"; + BOOST_CHECK_MESSAGE(ecf::algorithm::tail(test, 7) && test == expected, + "Expected:\n" + << expected << "\nbut found:\n" + << test); + + test = "this\nis\na\nstring\nwith\nlots\nof\nnew\nline"; + expected = test; + BOOST_CHECK_MESSAGE(!ecf::algorithm::tail(test, 9) && test == expected, + "Expected:\n" + << expected << "\nbut found:\n" + << test); +} - test_replace_all("This is a string", "This", "That", "That is a string"); - test_replace_all("This is a string", "This is a string", "", ""); - test_replace_all("This is a string", "is a", "was a", "This was a string"); - test_replace_all("This\n is a string", "\n", "\\n", "This\\n is a string"); - test_replace_all("This\n is\n a\n string\n", "\n", "\\n", R"(This\n is\n a\n string\n)"); - test_replace_all("This\n is\n a\n string\n", "\n", "", "This is a string"); +BOOST_AUTO_TEST_CASE(test_algorithm_head) { + ECF_NAME_THIS_TEST(); + std::string test; + BOOST_CHECK_MESSAGE(!ecf::algorithm::head(test, 7), "Empty string should return false"); + + test = "this\nis\na\nstring\nwith\nlots\nof\nnew\nline"; + std::string expected = "this\n"; + BOOST_CHECK_MESSAGE(ecf::algorithm::head(test, 1) && test == expected, + "Expected:\n" + << expected << "\nbut found:\n" + << test); + + test = "this\nis\na\nstring\nwith\nlots\nof\nnew\nline"; + expected = "this\nis\n"; + BOOST_CHECK_MESSAGE(ecf::algorithm::head(test, 2) && test == expected, + "Expected:\n" + << expected << "\nbut found:\n" + << test); + + test = "this\nis\na\nstring\nwith\nlots\nof\nnew\nline"; + expected = "this\nis\na\nstring\nwith\nlots\nof\n"; + BOOST_CHECK_MESSAGE(ecf::algorithm::head(test, 7) && test == expected, + "Expected:\n" + << expected << "\nbut found:\n" + << test); + + test = "this\nis\na\nstring\nwith\nlots\nof\nnew\nline"; + expected = test; + BOOST_CHECK_MESSAGE(!ecf::algorithm::head(test, 9) && test == expected, + "Expected:\n" + << expected << "\nbut found:\n" + << test); } -BOOST_AUTO_TEST_CASE(test_str_to_int) { +BOOST_AUTO_TEST_CASE(test_algorithm_as_string_for_bool) { ECF_NAME_THIS_TEST(); - BOOST_CHECK_MESSAGE(Str::to_int("0") == 0, "Expected 0"); - BOOST_CHECK_MESSAGE(Str::to_int("1") == 1, "Expected 1"); - BOOST_CHECK_MESSAGE(Str::to_int("-0") == 0, "Expected 0"); - BOOST_CHECK_MESSAGE(Str::to_int("-1") == -1, "Expected -1"); - BOOST_CHECK_MESSAGE(Str::to_int("") == std::numeric_limits::max(), "Expected max int"); - BOOST_CHECK_MESSAGE(Str::to_int("-") == std::numeric_limits::max(), "Expected max int"); - BOOST_CHECK_MESSAGE(Str::to_int(" ") == std::numeric_limits::max(), "Expected max int"); - BOOST_CHECK_MESSAGE(Str::to_int("q") == std::numeric_limits::max(), "Expected max int"); - BOOST_CHECK_MESSAGE(Str::to_int("q22") == std::numeric_limits::max(), "Expected max int"); - BOOST_CHECK_MESSAGE(Str::to_int("q22", -1) == -1, "Expected -1 on failure"); - BOOST_CHECK_MESSAGE(Str::to_int("99 99") == std::numeric_limits::max(), "Expected max int"); - BOOST_CHECK_MESSAGE(Str::to_int("99 99", 0) == 0, "Expected 0 for failure"); + struct tc + { + bool input; + std::string expected; + }; + + std::vector test_cases = {{true, "true"}, {false, "false"}}; + + for (const auto& tc : test_cases) { + auto actual = ecf::algorithm::as_string(tc.input); + BOOST_CHECK_MESSAGE(actual == tc.expected, "Expected '" << tc.expected << "', found '" << actual << "'"); + }; } -BOOST_AUTO_TEST_CASE(test_str_is_int) { +BOOST_AUTO_TEST_CASE(test_algorithm_as_string_for_vector_of_arithmetic) { ECF_NAME_THIS_TEST(); - BOOST_CHECK_EQUAL(Str::is_int("0"), true); - BOOST_CHECK_EQUAL(Str::is_int("1"), true); - BOOST_CHECK_EQUAL(Str::is_int("-0"), true); - BOOST_CHECK_EQUAL(Str::is_int("-1"), true); - BOOST_CHECK_EQUAL(Str::is_int(""), false); - BOOST_CHECK_EQUAL(Str::is_int("-"), false); - BOOST_CHECK_EQUAL(Str::is_int(" "), false); - BOOST_CHECK_EQUAL(Str::is_int("q"), false); - BOOST_CHECK_EQUAL(Str::is_int("q22"), false); - BOOST_CHECK_EQUAL(Str::is_int("99 99"), false); - BOOST_CHECK_EQUAL(Str::is_int("99 99"), false); + struct tc + { + std::vector input; + std::string expected; + }; + + std::vector test_cases = { + {{}, "[ ]"}, {{1}, "[ 1 ]"}, {{1, 2}, "[ 1, 2 ]"}, {{123, 456, 789}, "[ 123, 456, 789 ]"}}; + + for (const auto& tc : test_cases) { + auto actual = ecf::algorithm::as_string(tc.input); + BOOST_CHECK_MESSAGE(actual == tc.expected, "Expected '" << tc.expected << "', found '" << actual << "'"); + }; +} + +BOOST_AUTO_TEST_CASE(test_algorithm_as_string_for_vector_of_strings) { + ECF_NAME_THIS_TEST(); + + struct tc + { + std::vector input; + std::string expected; + }; + + std::vector test_cases = { + {{}, "[ ]"}, {{"a"}, "[ a ]"}, {{"a", "b"}, "[ a, b ]"}, {{"abc", "def", "ghi"}, "[ abc, def, ghi ]"}}; + + for (const auto& tc : test_cases) { + auto actual = ecf::algorithm::as_string(tc.input); + BOOST_CHECK_MESSAGE(actual == tc.expected, "Expected '" << tc.expected << "', found '" << actual << "'"); + }; } -BOOST_AUTO_TEST_CASE(test_extract_data_member_value) { +BOOST_AUTO_TEST_CASE(test_algorithm_as_string_for_vector_of_string_views) { ECF_NAME_THIS_TEST(); - std::string expected = "value"; - std::string actual; - std::string str = "aa bb c fred:value"; - BOOST_CHECK_MESSAGE(Str::extract_data_member_value(str, "fred:", actual), " failed"); - BOOST_CHECK_MESSAGE(expected == actual, "expected '" << expected << "' but found '" << actual << "'"); + struct tc + { + std::vector input; + std::string expected; + }; - str = "fred:x bill:zzz jake:12345 1234:99 6677"; - expected = "x"; - BOOST_CHECK_MESSAGE(Str::extract_data_member_value(str, "fred:", actual), " failed"); - BOOST_CHECK_MESSAGE(expected == actual, "expected '" << expected << "' but found '" << actual << "'"); + std::vector test_cases = { + {{}, "[ ]"}, {{"a"}, "[ a ]"}, {{"a", "b"}, "[ a, b ]"}, {{"abc", "def", "ghi"}, "[ abc, def, ghi ]"}}; - expected = "zzz"; - BOOST_CHECK_MESSAGE(Str::extract_data_member_value(str, "bill:", actual), " failed"); - BOOST_CHECK_MESSAGE(expected == actual, "expected '" << expected << "' but found '" << actual << "'"); + for (const auto& tc : test_cases) { + auto actual = ecf::algorithm::as_string(tc.input); + BOOST_CHECK_MESSAGE(actual == tc.expected, "Expected '" << tc.expected << "', found '" << actual << "'"); + }; +} - expected = "12345"; - BOOST_CHECK_MESSAGE(Str::extract_data_member_value(str, "jake:", actual), " failed"); - BOOST_CHECK_MESSAGE(expected == actual, "expected '" << expected << "' but found '" << actual << "'"); +BOOST_AUTO_TEST_CASE(test_algortihm_replace) { + ECF_NAME_THIS_TEST(); - expected = "99"; - BOOST_CHECK_MESSAGE(Str::extract_data_member_value(str, "1234:", actual), " failed"); - BOOST_CHECK_MESSAGE(expected == actual, "expected '" << expected << "' but found '" << actual << "'"); + struct tc + { + std::string input; + std::string find; + std::string replace; + std::string expected; + bool result; + }; + + std::vector test_cases = {{"This is a string", "", "Replacement", "This is a string", false}, + {"This is a string", "Pattern", "Replacment", "This is a string", false}, + {"This is a string", "This", "That", "That is a string", true}, + {"This is a string", "This is a string", "", "", true}, + {"This is a string", "is a", "was a", "This was a string", true}, + {"This\n is a string", "\n", "\\n", "This\\n is a string", true}}; + + for (const auto& tc : test_cases) { + std::string actual = tc.input; - expected = "77"; - BOOST_CHECK_MESSAGE(Str::extract_data_member_value(str, "66", actual), " failed"); - BOOST_CHECK_MESSAGE(expected == actual, "expected '" << expected << "' but found '" << actual << "'"); + auto result = ecf::algorithm::replace(actual, tc.find, tc.replace); + + BOOST_CHECK_MESSAGE(result == tc.result, + "Replace failed for " << actual << " find(" << tc.find << ") replace(" << tc.replace + << ")"); + BOOST_CHECK_MESSAGE(actual == tc.expected, "Expected '" << tc.expected << "' but found '" << actual << "'"); + } } -std::string toString(const std::vector& c) { - std::ostringstream ss; - std::copy(c.begin(), c.end(), std::ostream_iterator(ss, ", ")); - return ss.str(); +BOOST_AUTO_TEST_CASE(test_algorithm_replace_all) { + ECF_NAME_THIS_TEST(); + + struct tc + { + std::string input; + std::string find; + std::string replace; + std::string expected; + bool result; + }; + + std::vector test_cases = { + {"This is a string", "", "Replacement", "This is a string", false}, + {"This is a string", "Pattern", "Replacement", "This is a string", false}, + {"This is a string", "This", "That", "That is a string", true}, + {"This is a string", "This is a string", "", "", true}, + {"This is a string", "is a", "was a", "This was a string", true}, + {"This\n is a string", "\n", "\\n", "This\\n is a string", true}, + {"This\n is\n a\n string\n", "\n", "\\n", R"(This\n is\n a\n string\n)", true}, + {"This\n is\n a\n string\n", "\n", "", "This is a string", true}, + }; + + for (const auto& tc : test_cases) { + auto actual = tc.input; + + { // Check that replacement happens as expected + bool result = ecf::algorithm::replace_all(actual, tc.find, tc.replace); + + BOOST_CHECK_MESSAGE(result == tc.result, + "Replace successful for " << actual << " find(" << tc.find << ") replace(" << tc.replace + << ")"); + BOOST_CHECK_MESSAGE(actual == tc.expected, "Expected '" << tc.expected << "' but found '" << actual << "'"); + } + + auto copy = actual; + + { // Ensure that attempting replacement again returns false and does not change the input string + bool result = ecf::algorithm::replace_all(copy, tc.find, tc.replace); + + BOOST_CHECK_MESSAGE(!result, + "Replace unsuccessful for " << copy << " find(" << tc.find << ") replace(" << tc.replace + << "), since no occurrences were left"); + BOOST_CHECK_MESSAGE(actual == copy, "Expected '" << actual << "' but found '" << copy << "'"); + } + } } -BOOST_AUTO_TEST_CASE(test_str_less_greater) { +BOOST_AUTO_TEST_CASE(test_algorithm_sort_using_case_insensitive_order) { ECF_NAME_THIS_TEST(); - - std::vector expected; - expected.emplace_back("a1"); - expected.emplace_back("A2"); - expected.emplace_back("b1"); - expected.emplace_back("B2"); - expected.emplace_back("c"); - - std::vector expectedGreater; - expectedGreater.emplace_back("c"); - expectedGreater.emplace_back("B2"); - expectedGreater.emplace_back("b1"); - expectedGreater.emplace_back("A2"); - expectedGreater.emplace_back("a1"); - - std::vector vec; - vec.emplace_back("c"); - vec.emplace_back("A2"); - vec.emplace_back("a1"); - vec.emplace_back("b1"); - vec.emplace_back("B2"); - - std::sort(vec.begin(), vec.end(), Str::caseInsLess); - BOOST_REQUIRE_MESSAGE(vec == expected, "expected " << toString(expected) << " but found " << toString(vec)); - - std::sort(vec.begin(), vec.end(), Str::caseInsGreater); - BOOST_REQUIRE_MESSAGE(vec == expectedGreater, - "expected " << toString(expectedGreater) << " but found " << toString(vec)); - - // -------------------------------------------------------------------- - - expected.clear(); - expected.emplace_back("a"); - expected.emplace_back("A"); - expected.emplace_back("b"); - expected.emplace_back("B"); - expected.emplace_back("c"); - - expectedGreater.clear(); - expectedGreater.emplace_back("c"); - expectedGreater.emplace_back("B"); - expectedGreater.emplace_back("b"); - expectedGreater.emplace_back("A"); - expectedGreater.emplace_back("a"); - - vec.clear(); - vec.emplace_back("c"); - vec.emplace_back("B"); - vec.emplace_back("A"); - vec.emplace_back("b"); - vec.emplace_back("a"); - - std::sort(vec.begin(), vec.end(), Str::caseInsLess); - BOOST_REQUIRE_MESSAGE(vec == expected, "expected " << toString(expected) << " but found " << toString(vec)); - - std::sort(vec.begin(), vec.end(), Str::caseInsGreater); - BOOST_REQUIRE_MESSAGE(vec == expectedGreater, - "expected " << toString(expectedGreater) << " but found " << toString(vec)); - - // -------------------------------------------------------------------- - - expected.clear(); - expected.emplace_back("1234"); - expected.emplace_back("baSE"); - expected.emplace_back("Base"); - expected.emplace_back("case"); - expected.emplace_back("CaSe"); - expected.emplace_back("suite"); - expected.emplace_back("SUITE"); - - expectedGreater.clear(); - expectedGreater.emplace_back("SUITE"); - expectedGreater.emplace_back("suite"); - expectedGreater.emplace_back("CaSe"); - expectedGreater.emplace_back("case"); - expectedGreater.emplace_back("Base"); - expectedGreater.emplace_back("baSE"); - expectedGreater.emplace_back("1234"); - - vec.clear(); - vec.emplace_back("suite"); - vec.emplace_back("SUITE"); - vec.emplace_back("baSE"); - vec.emplace_back("Base"); - vec.emplace_back("case"); - vec.emplace_back("CaSe"); - vec.emplace_back("1234"); - - std::sort(vec.begin(), vec.end(), Str::caseInsLess); - BOOST_REQUIRE_MESSAGE(vec == expected, "expected " << toString(expected) << " but found " << toString(vec)); - - std::sort(vec.begin(), vec.end(), Str::caseInsGreater); - BOOST_REQUIRE_MESSAGE(vec == expectedGreater, - "expected " << toString(expectedGreater) << " but found " << toString(vec)); -} - -//// ============================================================== -//// Timing to find the fastest looping -//// ============================================================== -class Fred { -public: - explicit Fred(int i = 0) - : i_(i) { - // Do nothing... - } - Fred(const Fred& rhs) = default; - Fred& operator=(const Fred& rhs) = default; - ~Fred() = default; - - void inc() { i_++; } - -private: - int i_; -}; - -BOOST_AUTO_TEST_CASE(test_loop, *boost::unit_test::disabled()) { + + struct tc + { + std::vector input; + std::vector expected_less; + std::vector expected_greater; + }; + + std::vector test_cases = { + // clang-format off + { + {"c", "A2", "a1", "b1", "B2"}, + {"a1", "A2", "b1", "B2", "c"}, + {"c", "B2", "b1", "A2", "a1"} + }, + { + {"c", "B", "A", "b", "a"}, + {"a", "A", "b", "B", "c"}, + {"c", "B", "b", "A", "a"} + }, + { + {"suite", "SUITE", "baSE", "Base", "case", "CaSe", "1234"}, + {"1234", "baSE", "Base", "case", "CaSe", "suite", "SUITE"}, + {"SUITE", "suite", "CaSe", "case", "Base", "baSE", "1234"} + } + // clang-format on + }; + + for (const auto& tc : test_cases) { + + { + auto actual = tc.input; + std::sort(actual.begin(), actual.end(), ecf::algorithm::case_insensitive_less); + BOOST_REQUIRE_MESSAGE(actual == tc.expected_less, + "expected " << ecf::algorithm::as_string(tc.expected_less) << " but found " + << ecf::algorithm::as_string(actual)); + } + + { + auto actual = tc.input; + std::sort(actual.begin(), actual.end(), ecf::algorithm::case_insensitive_greater); + BOOST_REQUIRE_MESSAGE(actual == tc.expected_greater, + "expected " << ecf::algorithm::as_string(tc.expected_greater) << " but found " + << ecf::algorithm::as_string(actual)); + } + } +} + +BOOST_AUTO_TEST_CASE(test_algorithm_get_token) { + + struct TestCase + { + std::string input; + size_t index; + std::string delimiters; + bool expected_outcome; + std::string expected_token; + }; + + std::vector testCases = { + // all tokens can be accessed + {"0,1,2,3,4,5,6,7,8,9,10", 0, ",", true, "0"}, + {"0,1,2,3,4,5,6,7,8,9,10", 1, ",", true, "1"}, + {"0,1,2,3,4,5,6,7,8,9,10", 2, ",", true, "2"}, + {"0,1,2,3,4,5,6,7,8,9,10", 3, ",", true, "3"}, + {"0,1,2,3,4,5,6,7,8,9,10", 4, ",", true, "4"}, + {"0,1,2,3,4,5,6,7,8,9,10", 5, ",", true, "5"}, + {"0,1,2,3,4,5,6,7,8,9,10", 6, ",", true, "6"}, + {"0,1,2,3,4,5,6,7,8,9,10", 7, ",", true, "7"}, + {"0,1,2,3,4,5,6,7,8,9,10", 8, ",", true, "8"}, + {"0,1,2,3,4,5,6,7,8,9,10", 9, ",", true, "9"}, + {"0,1,2,3,4,5,6,7,8,9,10", 10, ",", true, "10"}, + + // out-of-range tokens are correctly handled + {"0,1,2,3,4,5,6,7,8,9,10", 11, ",", false, ""}, + {"0,1,2,3,4,5,6,7,8,9,10", 12, ",", false, ""}, + + // now using another delimiter + {"0 1 2 3 4 5 6 7 8 9 10", 0, " ", true, "0"}, + {"0 1 2 3 4 5 6 7 8 9 10", 5, " ", true, "5"}, + {"0 1 2 3 4 5 6 7 8 9 10", 10, " ", true, "10"}, + {"0 1 2 3 4 5 6 7 8 9 10", 42, " ", false, ""}, + + // now using multiple delimiters + {"0 1\t2 3\t4 5\t6 7\t8 9\t10", 0, " \t", true, "0"}, + {"0 1\t2 3\t4 5\t6 7\t8 9\t10", 5, " \t", true, "5"}, + {"0 1\t2 3\t4 5\t6 7\t8 9\t10", 10, " \t", true, "10"}, + {"0 1\t2 3\t4 5\t6 7\t8 9\t10", 42, " \t", false, ""}, + }; + + for (const auto& testCase : testCases) { + std::string actual_token; + auto actual_outcome = + ecf::algorithm::get_token(testCase.input, testCase.index, actual_token, testCase.delimiters); + + BOOST_REQUIRE_MESSAGE(actual_outcome == testCase.expected_outcome, + "Correct output getting token from '" << testCase.input << "' at index " << testCase.index + << " with delimiters '" << testCase.delimiters + << "'"); + + if (testCase.expected_outcome) { + BOOST_CHECK_MESSAGE(actual_token == testCase.expected_token, + "Found correct token with input: '" + << testCase.input << "' at index " << testCase.index << " with delimiters '" + << testCase.delimiters << "'. Expected '" << testCase.expected_token << "' found '" + << actual_token << "'"); + } + } +} + +BOOST_AUTO_TEST_CASE(test_performance_loop, *boost::unit_test::disabled()) { ECF_NAME_THIS_TEST(); + // + // The goal of this test is to investigate the fastest looping mechanism over an std::vector, by comparing: + // - raw for loop + // - std::for_each + // - std::vector::iterator-based iteration + // - index-based iteration + // + + class Fred { + public: + explicit Fred(int i = 0) + : i_(i) { + // Do nothing... + } + Fred(const Fred& rhs) = default; + Fred& operator=(const Fred& rhs) = default; + ~Fred() = default; + + void inc() { i_++; } + + private: + int i_; + }; + const size_t size = 200000000; std::vector vec; vec.reserve(size); @@ -565,7 +1088,7 @@ BOOST_AUTO_TEST_CASE(test_loop, *boost::unit_test::disabled()) { } { - PerformanceTimer timer; + ecf::PerformanceTimer timer; for (auto& fred : vec) { fred.inc(); } @@ -574,14 +1097,14 @@ BOOST_AUTO_TEST_CASE(test_loop, *boost::unit_test::disabled()) { } { - PerformanceTimer timer; + ecf::PerformanceTimer timer; std::for_each(vec.begin(), vec.end(), [](Fred& fred) { fred.inc(); }); ECF_TEST_DBG(<< "Time: std::for_each(vec.begin(),vec.end(),[](Fred& fred) { fred.inc();} ); " << timer); } { - PerformanceTimer timer; + ecf::PerformanceTimer timer; auto theEnd = vec.end(); for (auto i = vec.begin(); i < theEnd; i++) { (*i).inc(); @@ -591,7 +1114,7 @@ BOOST_AUTO_TEST_CASE(test_loop, *boost::unit_test::disabled()) { } { - PerformanceTimer timer; + ecf::PerformanceTimer timer; size_t theSize = vec.size(); for (size_t i = 0; i < theSize; i++) { vec[i].inc(); @@ -601,36 +1124,32 @@ BOOST_AUTO_TEST_CASE(test_loop, *boost::unit_test::disabled()) { } } -/// ============================================================== -/// Timing to find the fastest conversion from string to int -/// ============================================================== -static void methodX(const std::string& str, std::vector& stringRes, std::vector& numberRes) { - // 0.81 - // for bad conversion istringstream seems to return 0, hence add guard - if (str.find_first_of(ecf::string_constants::numeric_chars, 0) == 0) { - int number = 0; - std::istringstream(str) >> number; - numberRes.push_back(number); - } - else { - stringRes.push_back(str); - } -} +BOOST_AUTO_TEST_CASE(test_performance_convert_string_to_int, *boost::unit_test::disabled()) { + ECF_NAME_THIS_TEST(); -static void method1(const std::string& str, std::vector& stringRes, std::vector& numberRes) { - // 12.2 - try { - int number = ecf::convert_to(str); - numberRes.push_back(number); - } - catch (const ecf::bad_conversion&) { - stringRes.push_back(str); - } -} + // + // The goal of this test is to investigate the fastest mechanism to convert from string to int, by comparing: + // - methodX, using istringstream marshalling + // - method1, using ecf::convert_to with try/catch + // - method2, using ecf::convert_to with try/catch but only if the first character of the string is a numeric char + // - method3, using atoi (which we known to be fast but does not handle errors) + // + + auto methodX = [](const std::string& str, std::vector& stringRes, std::vector& numberRes) { + // 0.81 + // for bad conversion istringstream seems to return 0, hence add guard + if (str.find_first_of(ecf::string_constants::numeric_chars, 0) == 0) { + int number = 0; + std::istringstream(str) >> number; + numberRes.push_back(number); + } + else { + stringRes.push_back(str); + } + }; -static void method2(const std::string& str, std::vector& stringRes, std::vector& numberRes) { - // 0.6 - if (str.find_first_of(ecf::string_constants::numeric_chars, 0) == 0) { + auto method1 = [](const std::string& str, std::vector& stringRes, std::vector& numberRes) { + // 12.2 try { int number = ecf::convert_to(str); numberRes.push_back(number); @@ -638,26 +1157,35 @@ static void method2(const std::string& str, std::vector& stringRes, catch (const ecf::bad_conversion&) { stringRes.push_back(str); } - } - else { - stringRes.push_back(str); - } -} + }; -static void method3(const std::string& str, std::vector& stringRes, std::vector& numberRes) { - // 0.14 - // atoi return 0 for errors, - int number = atoi(str.c_str()); // does not handle errors - if (number == 0 && str.size() != 1) { - stringRes.push_back(str); - } - else { - numberRes.push_back(number); - } -} + auto method2 = [](const std::string& str, std::vector& stringRes, std::vector& numberRes) { + // 0.6 + if (str.find_first_of(ecf::string_constants::numeric_chars, 0) == 0) { + try { + int number = ecf::convert_to(str); + numberRes.push_back(number); + } + catch (const ecf::bad_conversion&) { + stringRes.push_back(str); + } + } + else { + stringRes.push_back(str); + } + }; -BOOST_AUTO_TEST_CASE(test_lexical_cast_perf, *boost::unit_test::disabled()) { - ECF_NAME_THIS_TEST(); + auto method3 = [](const std::string& str, std::vector& stringRes, std::vector& numberRes) { + // 0.14 + // atoi return 0 for errors, + int number = atoi(str.c_str()); // does not handle errors + if (number == 0 && str.size() != 1) { + stringRes.push_back(str); + } + else { + numberRes.push_back(number); + } + }; size_t the_size = 1000000; std::vector stringTokens; @@ -677,7 +1205,7 @@ BOOST_AUTO_TEST_CASE(test_lexical_cast_perf, *boost::unit_test::disabled()) { numberRes.reserve(expectedNumberRes.size()); { - PerformanceTimer timer; + ecf::PerformanceTimer timer; for (size_t i = 0; i < stringTokens.size(); i++) { method1(stringTokens[i], stringRes, numberRes); } @@ -692,7 +1220,7 @@ BOOST_AUTO_TEST_CASE(test_lexical_cast_perf, *boost::unit_test::disabled()) { } { - PerformanceTimer timer; + ecf::PerformanceTimer timer; for (size_t i = 0; i < stringTokens.size(); i++) { methodX(stringTokens[i], stringRes, numberRes); } @@ -707,7 +1235,7 @@ BOOST_AUTO_TEST_CASE(test_lexical_cast_perf, *boost::unit_test::disabled()) { } { - PerformanceTimer timer; + ecf::PerformanceTimer timer; for (size_t i = 0; i < stringTokens.size(); i++) { method2(stringTokens[i], stringRes, numberRes); } @@ -722,7 +1250,7 @@ BOOST_AUTO_TEST_CASE(test_lexical_cast_perf, *boost::unit_test::disabled()) { } { - PerformanceTimer timer; + ecf::PerformanceTimer timer; for (size_t i = 0; i < stringTokens.size(); i++) { method3(stringTokens[i], stringRes, numberRes); } @@ -741,16 +1269,19 @@ BOOST_AUTO_TEST_CASE(test_lexical_cast_perf, *boost::unit_test::disabled()) { } } -BOOST_AUTO_TEST_CASE(test_int_to_str_perf, *boost::unit_test::disabled()) { +BOOST_AUTO_TEST_CASE(test_performance_convert_int_to_string, *boost::unit_test::disabled()) { ECF_NAME_THIS_TEST(); - // Lexical_cast is approx twice as fast as using streams - // time for ostream = 0.97 - // time for lexical_cast = 0.45 + // + // The goal of this test is to investigate the fastest mechanism to convert from string to int, by comparing: + // - ostream = 0.97 + // - lexical_cast = 0.45 + // const int the_size = 1000000; + { - PerformanceTimer timer; + ecf::PerformanceTimer timer; for (size_t i = 0; i < the_size; i++) { std::ostringstream st; st << i; @@ -760,7 +1291,7 @@ BOOST_AUTO_TEST_CASE(test_int_to_str_perf, *boost::unit_test::disabled()) { } { - PerformanceTimer timer; + ecf::PerformanceTimer timer; for (size_t i = 0; i < the_size; i++) { [[maybe_unused]] std::string s = ecf::convert_to(i); } @@ -768,101 +1299,6 @@ BOOST_AUTO_TEST_CASE(test_int_to_str_perf, *boost::unit_test::disabled()) { } } -BOOST_AUTO_TEST_CASE(test_str_split_by) { - - struct TestCase - { - std::string input; - std::string pattern; - std::vector expected; - }; - - std::vector testCases = { - {"This is a string", "...", {"This is a string"}}, - {"This...is...a...string", "...", {"This", "is", "a", "string"}}, - {"This...is...a...string...", "...", {"This", "is", "a", "string"}}, - {"...This...is...a...string", "...", {"This", "is", "a", "string"}}, - {"...This...is...a...string...", "...", {"This", "is", "a", "string"}}, - {"......This......is......a......string......", "...", {"This", "is", "a", "string"}}, - {"..This.....is.....a.....string.....", "...", {"..This", "..is", "..a", "..string", ".."}}, - {"expression 1==expression 2", "==", {"expression 1", "expression 2"}}, - {"expression 1==expression 2==expression 3", "==", {"expression 1", "expression 2", "expression 3"}}, - {"expression 1 == expression 2", " == ", {"expression 1", "expression 2"}}, - {"expression 1 == expression 2 == expression 3", " == ", {"expression 1", "expression 2", "expression 3"}}, - {"expression 1 eq expression 2", " eq ", {"expression 1", "expression 2"}}, - {"expression 1eqexpression 2", " eq ", {"expression 1eqexpression 2"}}}; - - for (const auto& testCase : testCases) { - std::vector result; - ecf::algorithm::split_by(result, testCase.input, testCase.pattern); - BOOST_CHECK_MESSAGE(result == testCase.expected, - "Failed for input: '" << testCase.input << "' pattern: '" << testCase.pattern - << "'. Expected: " << toString(testCase.expected) - << " but found: " << toString(result)); - } -} - -BOOST_AUTO_TEST_CASE(test_str_get_token) { - - struct TestCase - { - std::string input; - size_t index; - std::string delimiters; - bool expected_outcome; - std::string expected_token; - }; - - std::vector testCases = { - // all tokens can be accessed - {"0,1,2,3,4,5,6,7,8,9,10", 0, ",", true, "0"}, - {"0,1,2,3,4,5,6,7,8,9,10", 1, ",", true, "1"}, - {"0,1,2,3,4,5,6,7,8,9,10", 2, ",", true, "2"}, - {"0,1,2,3,4,5,6,7,8,9,10", 3, ",", true, "3"}, - {"0,1,2,3,4,5,6,7,8,9,10", 4, ",", true, "4"}, - {"0,1,2,3,4,5,6,7,8,9,10", 5, ",", true, "5"}, - {"0,1,2,3,4,5,6,7,8,9,10", 6, ",", true, "6"}, - {"0,1,2,3,4,5,6,7,8,9,10", 7, ",", true, "7"}, - {"0,1,2,3,4,5,6,7,8,9,10", 8, ",", true, "8"}, - {"0,1,2,3,4,5,6,7,8,9,10", 9, ",", true, "9"}, - {"0,1,2,3,4,5,6,7,8,9,10", 10, ",", true, "10"}, - - // out-of-range tokens are correctly handled - {"0,1,2,3,4,5,6,7,8,9,10", 11, ",", false, ""}, - {"0,1,2,3,4,5,6,7,8,9,10", 12, ",", false, ""}, - - // now using another delimiter - {"0 1 2 3 4 5 6 7 8 9 10", 0, " ", true, "0"}, - {"0 1 2 3 4 5 6 7 8 9 10", 5, " ", true, "5"}, - {"0 1 2 3 4 5 6 7 8 9 10", 10, " ", true, "10"}, - {"0 1 2 3 4 5 6 7 8 9 10", 42, " ", false, ""}, - - // now using multiple delimiters - {"0 1\t2 3\t4 5\t6 7\t8 9\t10", 0, " \t", true, "0"}, - {"0 1\t2 3\t4 5\t6 7\t8 9\t10", 5, " \t", true, "5"}, - {"0 1\t2 3\t4 5\t6 7\t8 9\t10", 10, " \t", true, "10"}, - {"0 1\t2 3\t4 5\t6 7\t8 9\t10", 42, " \t", false, ""}, - }; - - for (const auto& testCase : testCases) { - std::string actual_token; - auto actual_outcome = ecf::Str::get_token(testCase.input, testCase.index, actual_token, testCase.delimiters); - - BOOST_REQUIRE_MESSAGE(actual_outcome == testCase.expected_outcome, - "Correct output getting token from '" << testCase.input << "' at index " << testCase.index - << " with delimiters '" << testCase.delimiters - << "'"); - - if (testCase.expected_outcome) { - BOOST_CHECK_MESSAGE(actual_token == testCase.expected_token, - "Found correct token with input: '" - << testCase.input << "' at index " << testCase.index << " with delimiters '" - << testCase.delimiters << "'. Expected '" << testCase.expected_token << "' found '" - << actual_token << "'"); - } - } -} - BOOST_AUTO_TEST_SUITE_END() BOOST_AUTO_TEST_SUITE_END() diff --git a/libs/core/test/TestStringSplitPerf.cpp b/libs/core/test/TestStringSplitPerf.cpp index 51313ccaf..f0121c894 100644 --- a/libs/core/test/TestStringSplitPerf.cpp +++ b/libs/core/test/TestStringSplitPerf.cpp @@ -184,16 +184,15 @@ BOOST_AUTO_TEST_CASE(test_str_split_perf) { BOOST_CHECK_MESSAGE(line == reconstructed, " error"); } - { // boost::make_split_iterator + { // make_split_iterator (std::vector based) boost::timer::cpu_timer timer; for (size_t i = 0; i < times; i++) { auto tokens = Str::make_split_iterator(line); std::ostringstream ss; - for (; !tokens.eof(); ++tokens) { - boost::iterator_range range = *tokens; - ss << range << " "; + for (const auto& token : tokens) { + ss << token << " "; } reconstructed = ss.str(); } @@ -361,13 +360,12 @@ BOOST_AUTO_TEST_CASE(test_str_split_perf_with_file) { std::ostringstream ss; auto tokens = Str::make_split_iterator(file_contents[i]); - for (; !tokens.eof(); ++tokens) { - auto range = *tokens; - ss << range << " "; + for (const auto& token : tokens) { + ss << token << " "; } std::string reconstructed = ss.str(); } - ECF_TEST_DBG(<< " Time for boost::make_split_iterator " << file_contents.size() + ECF_TEST_DBG(<< " Time for make_split_iterator " << file_contents.size() << " times = " << timer.format(3, Str::cpu_timer_format())); } diff --git a/libs/core/test/TestStringSplitter.cpp b/libs/core/test/TestStringSplitter.cpp index 6a5fab5d1..910c4696b 100644 --- a/libs/core/test/TestStringSplitter.cpp +++ b/libs/core/test/TestStringSplitter.cpp @@ -274,7 +274,7 @@ BOOST_AUTO_TEST_CASE(test_str_split_StringSplitter) { static void test_get_token(const std::string& line, const char* delims = " \t") { std::vector tokens; - Str::split_orig(line, tokens, delims); + ecf::algorithm::split_at(tokens, line, delims); for (size_t i = 0; i < tokens.size(); i++) { std::string token; BOOST_CHECK_MESSAGE(StringSplitter::get_token(line, i, token, delims) && token == tokens[i], diff --git a/libs/core/test/TestTimeSeries.cpp b/libs/core/test/TestTimeSeries.cpp index 22c93684e..a38773148 100644 --- a/libs/core/test/TestTimeSeries.cpp +++ b/libs/core/test/TestTimeSeries.cpp @@ -689,7 +689,7 @@ BOOST_AUTO_TEST_CASE(test_time_series_state_parsing) { size_t index = 0; std::string the_time = "+00:00 20:00 00:10"; std::vector lineTokens; - Str::split(the_time, lineTokens); + ecf::algorithm::split_at(lineTokens, the_time); BOOST_CHECK_MESSAGE(TimeSeries::create(index, lineTokens) == ecf::TimeSeries(TimeSlot(0, 0), TimeSlot(20, 0), TimeSlot(0, 10), true), "Error"); @@ -698,7 +698,7 @@ BOOST_AUTO_TEST_CASE(test_time_series_state_parsing) { size_t index = 0; std::string the_time = "+10:10"; std::vector lineTokens; - Str::split(the_time, lineTokens); + ecf::algorithm::split_at(lineTokens, the_time); BOOST_CHECK_MESSAGE(TimeSeries::create(index, lineTokens) == ecf::TimeSeries(TimeSlot(10, 10), true), "Error"); } { @@ -708,7 +708,7 @@ BOOST_AUTO_TEST_CASE(test_time_series_state_parsing) { expected.set_isValid(false); std::vector lineTokens; - Str::split(the_time, lineTokens); + ecf::algorithm::split_at(lineTokens, the_time); ecf::TimeSeries parsed_ts = TimeSeries::create(index, lineTokens, true); BOOST_CHECK_MESSAGE(parsed_ts == expected, "Expected \n'" << expected.toString() << expected.state_to_string(false) << "'" @@ -722,7 +722,7 @@ BOOST_AUTO_TEST_CASE(test_time_series_state_parsing) { expected.set_isValid(false); std::vector lineTokens; - Str::split(the_time, lineTokens); + ecf::algorithm::split_at(lineTokens, the_time); ecf::TimeSeries parsed_ts = TimeSeries::create(index, lineTokens, true); BOOST_CHECK_MESSAGE(parsed_ts == expected, "Expected \n'" << expected.toString() << expected.state_to_string(false) << "'" @@ -743,7 +743,7 @@ BOOST_AUTO_TEST_CASE(test_time_series_state_parsing) { expected.calendarChanged(calendar); std::vector lineTokens; - Str::split(the_time, lineTokens); + ecf::algorithm::split_at(lineTokens, the_time); ecf::TimeSeries parsed_ts = TimeSeries::create(index, lineTokens, true); BOOST_CHECK_MESSAGE(parsed_ts == expected, "Expected \n'" << expected.toString() << expected.state_to_string(false) << "'" @@ -764,7 +764,7 @@ BOOST_AUTO_TEST_CASE(test_time_series_state_parsing) { expected.set_isValid(false); std::vector lineTokens; - Str::split(the_time, lineTokens); + ecf::algorithm::split_at(lineTokens, the_time); size_t index = 0; ecf::TimeSeries parsed_ts = TimeSeries::create(index, lineTokens, true); diff --git a/libs/core/test/TestVersion.cpp b/libs/core/test/TestVersion.cpp index b8e81104e..84a677d6d 100644 --- a/libs/core/test/TestVersion.cpp +++ b/libs/core/test/TestVersion.cpp @@ -34,7 +34,7 @@ std::string find_cmake_version(const std::vector& cmake_content) { for (auto& line : cmake_content) { if (contains(line, "project", "ecflow", "LANGUAGES", "CXX", "VERSION")) { std::vector tokens; - Str::split(line, tokens); + ecf::algorithm::split_at(tokens, line); auto version_arg = std::find(tokens.begin(), tokens.end(), "VERSION"); auto version_val = version_arg + 1; diff --git a/libs/node/src/ecflow/node/Defs.cpp b/libs/node/src/ecflow/node/Defs.cpp index 039097056..9ff63fbdf 100644 --- a/libs/node/src/ecflow/node/Defs.cpp +++ b/libs/node/src/ecflow/node/Defs.cpp @@ -692,7 +692,7 @@ std::string Defs::dump_edit_history() const { } else { std::string h = c; - Str::replace_all(h, "\n", "\\n"); + ecf::algorithm::replace_all(h, "\n", "\\n"); ss << " "; ss << h; } @@ -791,7 +791,7 @@ void Defs::read_history(const std::string& line, const std::vector& size_t close_p = parsed_message.find("]"); std::string date = parsed_message.substr(space_pos + 1, close_p - space_pos - 1); vec.clear(); - Str::split(date, vec, "."); + ecf::algorithm::split_at(vec, date, "."); if (vec.size() == 3) { try { int day = ecf::convert_to(vec[0]); @@ -1007,19 +1007,19 @@ node_ptr Defs::find_node(const std::string& type, const std::string& pathToNode) return node_p; } - if (Str::caseInsCompare(type, "task")) { + if (ecf::algorithm::case_insensitive_compare(type, "task")) { if (node_p->isTask()) { return node_p; } return node_ptr(); } - if (Str::caseInsCompare(type, "family")) { + if (ecf::algorithm::case_insensitive_compare(type, "family")) { if (node_p->isFamily()) { return node_p; } return node_ptr(); } - if (Str::caseInsCompare(type, "suite")) { + if (ecf::algorithm::case_insensitive_compare(type, "suite")) { if (node_p->suite()) { return node_p; } @@ -1495,7 +1495,7 @@ void Defs::order(Node* immediateChild, NOrder::Order ord) { catch (const ecf::bad_conversion&) { } - return Str::caseInsLess(a->name(), b->name()); + return ecf::algorithm::case_insensitive_less(a->name(), b->name()); }); order_state_change_no_ = Ecf::incr_state_change_no(); client_suite_mgr_.update_suite_order(); @@ -1503,7 +1503,7 @@ void Defs::order(Node* immediateChild, NOrder::Order ord) { } case NOrder::ORDER: { std::sort(suiteVec_.begin(), suiteVec_.end(), [](const suite_ptr& a, const suite_ptr& b) { - return Str::caseInsGreater(a->name(), b->name()); + return ecf::algorithm::case_insensitive_greater(a->name(), b->name()); }); order_state_change_no_ = Ecf::incr_state_change_no(); client_suite_mgr_.update_suite_order(); @@ -1901,7 +1901,7 @@ void DefsHistoryParser::parse(const std::string& line) { if (pos != std::string::npos) { // keep compatibility with the current way of writing history std::string requests = line.substr(pos); - Str::split(requests, parsed_messages_, "\b"); + ecf::algorithm::split_at(parsed_messages_, requests, "\b"); return; } diff --git a/libs/node/src/ecflow/node/EcfFile.cpp b/libs/node/src/ecflow/node/EcfFile.cpp index b45d0bf90..1926d55c1 100644 --- a/libs/node/src/ecflow/node/EcfFile.cpp +++ b/libs/node/src/ecflow/node/EcfFile.cpp @@ -465,7 +465,7 @@ std::vector EcfFile::get_ecf_include_paths(const EcfFile& ecf) { // if ECF_INCLUDE is a set a paths, search in order. i.e., like $PATH if (ecf_include.find(':') != std::string::npos) { - Str::split(ecf_include, paths, ":"); + ecf::algorithm::split_at(paths, ecf_include, ":"); } else { paths = {ecf_include}; @@ -719,12 +719,12 @@ static void replace(std::string::size_type commentPos, if (commentPos == std::string::npos) { std::string replace1 = clientPath; replace1 += ecfEquiv; - Str::replace(jobLine, smsChildCmd, replace1); + ecf::algorithm::replace(jobLine, smsChildCmd, replace1); } else if (childPos < commentPos) { std::string replace2 = clientPath; replace2 += ecfEquiv; - Str::replace(jobLine, smsChildCmd, replace2); + ecf::algorithm::replace(jobLine, smsChildCmd, replace2); } } } @@ -754,7 +754,7 @@ bool EcfFile::replaceSmsChildCmdsWithEcf(const std::string& clientPath, std::str } bool EcfFile::extract_ecfmicro(const std::string& line, std::string& ecfmicro, std::string& error_msg) const { - if (!Str::get_token(line, 1, ecfmicro)) { + if (!ecf::algorithm::get_token(line, 1, ecfmicro)) { error_msg += MESSAGE("ecfmicro does not have a replacement character, in " << script_path_or_cmd_); return false; } @@ -1582,7 +1582,7 @@ void PreProcessor::preProcess_line() { // allow : %FRED:val% // disallow: %FRED std::string the_include_token; - if (!Str::get_token(script_line, 1, the_include_token)) { + if (!ecf::algorithm::get_token(script_line, 1, the_include_token)) { int ecfMicroCount = EcfFile::countEcfMicro(script_line, ecf_micro_); if (ecfMicroCount % 2 != 0) { throw std::runtime_error( @@ -1624,7 +1624,7 @@ void PreProcessor::preProcess_includes() { } std::string the_include_token; - if (!Str::get_token(script_line, 1, the_include_token)) { + if (!ecf::algorithm::get_token(script_line, 1, the_include_token)) { throw std::runtime_error( MESSAGE(error_context() << "Could not extract include token at : " << script_line)); } @@ -1768,7 +1768,8 @@ std::string PreProcessor::getIncludedFilePath(const std::string& includedFile1, if (includedFile.find("./") == 1 || includedFile.find("../") == 1) { // remove the leading and trailing '"' std::string the_included_file = includedFile; - Str::removeQuotes(the_included_file); + + ecf::algorithm::remove_double_quotes(the_included_file); // Get the root path, i.e. script_or_job_path() is of the form: // "/user/home/ma/mao/course/t1.ecf || /user/home/ma/mao/course/t1.job" diff --git a/libs/node/src/ecflow/node/Family.cpp b/libs/node/src/ecflow/node/Family.cpp index 44d2af239..9bd64ee6a 100644 --- a/libs/node/src/ecflow/node/Family.cpp +++ b/libs/node/src/ecflow/node/Family.cpp @@ -154,7 +154,7 @@ void Family::gen_variables(std::vector& vec) const { } std::string Family::find_node_path(const std::string& type, const std::string& node_name) const { - if (Str::caseInsCompare(type, "family")) { + if (ecf::algorithm::case_insensitive_compare(type, "family")) { if (node_name == name()) { return absNodePath(); } diff --git a/libs/node/src/ecflow/node/Flag.cpp b/libs/node/src/ecflow/node/Flag.cpp index 17984f18f..d03f900d4 100644 --- a/libs/node/src/ecflow/node/Flag.cpp +++ b/libs/node/src/ecflow/node/Flag.cpp @@ -10,6 +10,7 @@ #include "ecflow/node/Flag.hpp" +#include #include #include "ecflow/core/Ecf.hpp" @@ -332,7 +333,7 @@ void Flag::write(std::string& ret) const { void Flag::set_flag(const std::string& flags) { std::vector the_flags_vec; - Str::split(flags, the_flags_vec, ","); + ecf::algorithm::split_at(the_flags_vec, flags, ","); for (const auto& i : the_flags_vec) { if (i == "migrated") { diff --git a/libs/node/src/ecflow/node/Node.cpp b/libs/node/src/ecflow/node/Node.cpp index 216506cfe..5ef419469 100644 --- a/libs/node/src/ecflow/node/Node.cpp +++ b/libs/node/src/ecflow/node/Node.cpp @@ -2693,13 +2693,15 @@ void Node::sort_attributes(ecf::Attr::Type attr, bool recursive, const std::vect } } } - auto caseInsen = [](const auto& a, const auto& b) { return Str::caseInsLess(a.name(), b.name()); }; + auto caseInsen = [](const auto& a, const auto& b) { + return ecf::algorithm::case_insensitive_less(a.name(), b.name()); + }; state_change_no_ = Ecf::incr_state_change_no(); switch (attr) { case Attr::EVENT: sort(events_.begin(), events_.end(), [](const Event& a, const Event& b) { - return Str::caseInsLess(a.name_or_number(), b.name_or_number()); + return ecf::algorithm::case_insensitive_less(a.name_or_number(), b.name_or_number()); }); break; case Attr::METER: @@ -2710,7 +2712,7 @@ void Node::sort_attributes(ecf::Attr::Type attr, bool recursive, const std::vect break; case Attr::LIMIT: sort(limits_.begin(), limits_.end(), [](const limit_ptr& a, const limit_ptr& b) { - return Str::caseInsLess(a->name(), b->name()); + return ecf::algorithm::case_insensitive_less(a->name(), b->name()); }); break; case Attr::VARIABLE: @@ -2719,12 +2721,12 @@ void Node::sort_attributes(ecf::Attr::Type attr, bool recursive, const std::vect case Attr::ALL: sort(vars_.begin(), vars_.end(), caseInsen); sort(events_.begin(), events_.end(), [](const Event& a, const Event& b) { - return Str::caseInsLess(a.name_or_number(), b.name_or_number()); + return ecf::algorithm::case_insensitive_less(a.name_or_number(), b.name_or_number()); }); sort(meters_.begin(), meters_.end(), caseInsen); sort(labels_.begin(), labels_.end(), caseInsen); sort(limits_.begin(), limits_.end(), [](const limit_ptr& a, const limit_ptr& b) { - return Str::caseInsLess(a->name(), b->name()); + return ecf::algorithm::case_insensitive_less(a->name(), b->name()); }); break; case Attr::UNKNOWN: diff --git a/libs/node/src/ecflow/node/NodeContainer.cpp b/libs/node/src/ecflow/node/NodeContainer.cpp index 6ee2a87e9..5aba57720 100644 --- a/libs/node/src/ecflow/node/NodeContainer.cpp +++ b/libs/node/src/ecflow/node/NodeContainer.cpp @@ -359,14 +359,14 @@ void NodeContainer::order(Node* immediateChild, NOrder::Order ord) { catch (const ecf::bad_conversion&) { } - return Str::caseInsLess(a->name(), b->name()); + return ecf::algorithm::case_insensitive_less(a->name(), b->name()); }); order_state_change_no_ = Ecf::incr_state_change_no(); break; } case NOrder::ORDER: { std::sort(nodes_.begin(), nodes_.end(), [](const node_ptr& a, const node_ptr& b) { - return Str::caseInsGreater(a->name(), b->name()); + return ecf::algorithm::case_insensitive_greater(a->name(), b->name()); }); order_state_change_no_ = Ecf::incr_state_change_no(); break; @@ -1087,7 +1087,8 @@ std::string NodeContainer::archive_path() const { } std::string the_archive_file_name = absNodePath(); - Str::replace_all(the_archive_file_name, "/", ":"); // we use ':' since it is not allowed in the node names + // The following uses ':' since it is not allowed in the node names + ecf::algorithm::replace_all(the_archive_file_name, "/", ":"); the_archive_file_name += ".check"; std::string port = ecf::string_constants::default_port_number; diff --git a/libs/node/src/ecflow/node/Permissions.cpp b/libs/node/src/ecflow/node/Permissions.cpp index d715de782..8b4a6a6cd 100644 --- a/libs/node/src/ecflow/node/Permissions.cpp +++ b/libs/node/src/ecflow/node/Permissions.cpp @@ -17,7 +17,7 @@ namespace ecf { Permissions Permissions::make_from_variable(const std::string& value) { std::vector allowed; - ecf::Str::split(value, allowed, ","); + ecf::algorithm::split_at(allowed, value, ","); return Permissions(std::move(allowed)); } diff --git a/libs/node/src/ecflow/node/ServerState.cpp b/libs/node/src/ecflow/node/ServerState.cpp index 9e7f9e08d..eeee1b2ce 100644 --- a/libs/node/src/ecflow/node/ServerState.cpp +++ b/libs/node/src/ecflow/node/ServerState.cpp @@ -140,7 +140,9 @@ bool ServerState::compare(const ServerState& rhs) const { void ServerState::sort_variables() { variable_state_change_no_ = Ecf::incr_state_change_no(); - auto caseless = [](const Variable& a, const Variable& b) { return Str::caseInsLess(a.name(), b.name()); }; + auto caseless = [](const Variable& a, const Variable& b) { + return ecf::algorithm::case_insensitive_less(a.name(), b.name()); + }; sort(user_variables_.begin(), user_variables_.end(), caseless); sort(server_variables_.begin(), server_variables_.end(), caseless); } diff --git a/libs/node/src/ecflow/node/Submittable.cpp b/libs/node/src/ecflow/node/Submittable.cpp index ec155e56b..ced0cff7a 100644 --- a/libs/node/src/ecflow/node/Submittable.cpp +++ b/libs/node/src/ecflow/node/Submittable.cpp @@ -205,8 +205,8 @@ void Submittable::write_state(std::string& ret, bool& added_comment_char) const if (!abr_.empty()) { add_comment_char(ret, added_comment_char); std::string the_abort_reason = abr_; - Str::replace_all(the_abort_reason, "\n", "\\n"); - Str::replace_all(the_abort_reason, ";", " "); + ecf::algorithm::replace_all(the_abort_reason, "\n", "\\n"); + ecf::algorithm::replace_all(the_abort_reason, ";", " "); ret += " abort<:"; ret += the_abort_reason; ret += ">abort"; @@ -832,7 +832,7 @@ void Submittable::kill(const std::string& zombie_pid) { } // replace %ECF_RID% with the input args - Str::replace(ecf_kill_cmd, "%ECF_RID%", zombie_pid); + ecf::algorithm::replace(ecf_kill_cmd, "%ECF_RID%", zombie_pid); } if (!variableSubstitution(ecf_kill_cmd)) { @@ -948,8 +948,8 @@ void Submittable::set_aborted_only(const std::string& reason) { // Do not use "\n" | ';' in abr_, as this can mess up, --migrate output // Which would then affect --load. - Str::replace(abr_, "\n", ""); - Str::replace(abr_, ";", " "); + ecf::algorithm::replace(abr_, "\n", ""); + ecf::algorithm::replace(abr_, ";", " "); // This will set the state and bubble up the most significant state set_state(NState::ABORTED); diff --git a/libs/node/src/ecflow/node/Suite.cpp b/libs/node/src/ecflow/node/Suite.cpp index b30eb0d57..ff70d0173 100644 --- a/libs/node/src/ecflow/node/Suite.cpp +++ b/libs/node/src/ecflow/node/Suite.cpp @@ -745,7 +745,7 @@ void Suite::gen_variables(std::vector& vec) const { } std::string Suite::find_node_path(const std::string& type, const std::string& node_name) const { - if (Str::caseInsCompare(type, "suite") && node_name == name()) { + if (ecf::algorithm::case_insensitive_compare(type, "suite") && node_name == name()) { return absNodePath(); } return NodeContainer::find_node_path(type, node_name); diff --git a/libs/node/src/ecflow/node/Task.cpp b/libs/node/src/ecflow/node/Task.cpp index 461f514c1..4ffd48894 100644 --- a/libs/node/src/ecflow/node/Task.cpp +++ b/libs/node/src/ecflow/node/Task.cpp @@ -289,7 +289,7 @@ node_ptr Task::find_immediate_child(const std::string_view& name) const { } std::string Task::find_node_path(const std::string& type, const std::string& node_name) const { - if (ecf::Str::caseInsCompare(type, "task")) { + if (ecf::algorithm::case_insensitive_compare(type, "task")) { if (node_name == name()) { return absNodePath(); } @@ -623,14 +623,14 @@ void Task::order(Node* immediateChild, NOrder::Order ord) { } case NOrder::ALPHA: { std::sort(aliases_.begin(), aliases_.end(), [](const alias_ptr& a, const alias_ptr& b) { - return ecf::Str::caseInsLess(a->name(), b->name()); + return ecf::algorithm::case_insensitive_less(a->name(), b->name()); }); order_state_change_no_ = Ecf::incr_state_change_no(); break; } case NOrder::ORDER: { std::sort(aliases_.begin(), aliases_.end(), [](const alias_ptr& a, const alias_ptr& b) { - return ecf::Str::caseInsGreater(a->name(), b->name()); + return ecf::algorithm::case_insensitive_greater(a->name(), b->name()); }); order_state_change_no_ = Ecf::incr_state_change_no(); break; diff --git a/libs/node/src/ecflow/node/formatter/DefsWriter.hpp b/libs/node/src/ecflow/node/formatter/DefsWriter.hpp index ecd75c269..f229a330c 100644 --- a/libs/node/src/ecflow/node/formatter/DefsWriter.hpp +++ b/libs/node/src/ecflow/node/formatter/DefsWriter.hpp @@ -1265,7 +1265,7 @@ struct Writer } else { std::string value = item.new_value(); - Str::replace_all(value, "\n", "\\n"); + ecf::algorithm::replace_all(value, "\n", "\\n"); output << " # \""; output << value; output << "\""; @@ -2269,7 +2269,7 @@ struct Writer } else { std::string h = c; - Str::replace_all(h, "\n", "\\n"); + ecf::algorithm::replace_all(h, "\n", "\\n"); output << "\b"; output << h; } diff --git a/libs/node/src/ecflow/node/parser/AvisoParser.cpp b/libs/node/src/ecflow/node/parser/AvisoParser.cpp index c4b4aead8..654f4c00c 100644 --- a/libs/node/src/ecflow/node/parser/AvisoParser.cpp +++ b/libs/node/src/ecflow/node/parser/AvisoParser.cpp @@ -49,7 +49,7 @@ ecf::AvisoAttr AvisoParser::parse_aviso_line(const std::string& line, Node* pare std::vector tokens; { // Since po::command_line_parser requires a vector of strings, we need convert from string_view to string - std::vector extracted = ecf::Str::tokenize_quotation(line, "'"); + std::vector extracted = ecf::algorithm::split_within_quotes(line, "'"); std::transform(std::begin(extracted), std::end(extracted), std::back_inserter(tokens), diff --git a/libs/node/src/ecflow/node/parser/DefsStructureParser.cpp b/libs/node/src/ecflow/node/parser/DefsStructureParser.cpp index 884c24ddd..a8d582f96 100644 --- a/libs/node/src/ecflow/node/parser/DefsStructureParser.cpp +++ b/libs/node/src/ecflow/node/parser/DefsStructureParser.cpp @@ -132,7 +132,7 @@ bool DefsStructureParser::do_parse_line(const std::string& line, std::vector& lineTokens, std::string& errorMsg) { lineTokens.clear(); // This is re-used, hence clear up front - Str::split(line, lineTokens); + ecf::algorithm::split_at(lineTokens, line); if (lineTokens.empty()) { return true; // ignore empty lines } @@ -186,7 +186,7 @@ void DefsStructureParser::getNextLine(std::string& line) { // # task a, task b /// calling trim can be very expensive, hence avoid if possible std::vector lineTokens; - Str::split(line, lineTokens); + ecf::algorithm::split_at(lineTokens, line); if (!lineTokens.empty()) { if (lineTokens[0][0] == '#') { // found leading_comment can ignore this line @@ -275,7 +275,7 @@ DefsString::DefsString(const std::string& defs_as_string) : empty_(defs_as_string.empty()) { if (!empty_) { lines_.reserve(256); - Str::split_using_string_view(defs_as_string, lines_, "\n"); + ecf::algorithm::split_at(lines_, defs_as_string, "\n"); } } diff --git a/libs/node/src/ecflow/node/parser/MirrorParser.cpp b/libs/node/src/ecflow/node/parser/MirrorParser.cpp index 0b1dfb63f..2d5a9226a 100644 --- a/libs/node/src/ecflow/node/parser/MirrorParser.cpp +++ b/libs/node/src/ecflow/node/parser/MirrorParser.cpp @@ -57,7 +57,7 @@ ecf::MirrorAttr MirrorParser::parse_mirror_line(const std::string& line, Node* p std::vector tokens; { // Since po::command_line_parser requires a vector of strings, we need convert from string_view to string - std::vector extracted = ecf::Str::tokenize_quotation(line, "'"); + std::vector extracted = ecf::algorithm::split_within_quotes(line, "'"); std::transform(std::begin(extracted), std::end(extracted), std::back_inserter(tokens), diff --git a/libs/node/src/ecflow/node/parser/RepeatParser.cpp b/libs/node/src/ecflow/node/parser/RepeatParser.cpp index c1278cb79..13b2a83be 100644 --- a/libs/node/src/ecflow/node/parser/RepeatParser.cpp +++ b/libs/node/src/ecflow/node/parser/RepeatParser.cpp @@ -86,8 +86,10 @@ bool RepeatParser::doParse(const std::string& line, std::vector& li if (theEnum[0] == '#') { break; } - Str::removeSingleQuotes(theEnum); // remove quotes, they get added back when we persist - Str::removeQuotes(theEnum); // remove quotes, they get added back when we persist + + // remove quotes, as they get added back when we persist + ecf::algorithm::remove_double_quotes(theEnum); + ecf::algorithm::remove_single_quotes(theEnum); int date = 0; try { @@ -126,8 +128,11 @@ bool RepeatParser::doParse(const std::string& line, std::vector& li if (theEnum[0] == '#') { break; } - Str::removeSingleQuotes(theEnum); // remove quotes, they get added back when we persist - Str::removeQuotes(theEnum); // remove quotes, they get added back when we persist + + // remove quotes, as they get added back when we persist + ecf::algorithm::remove_single_quotes(theEnum); + ecf::algorithm::remove_double_quotes(theEnum); + theEnums.push_back(theEnum); } if (theEnums.empty()) { @@ -187,8 +192,11 @@ bool RepeatParser::doParse(const std::string& line, std::vector& li if (theEnum[0] == '#') { break; } - Str::removeSingleQuotes(theEnum); // remove quotes, they get added back when we persist - Str::removeQuotes(theEnum); // remove quotes, they get added back when we persist + + // remove quotes, as they get added back when we persist + ecf::algorithm::remove_single_quotes(theEnum); + ecf::algorithm::remove_double_quotes(theEnum); + theEnums.push_back(theEnum); } if (theEnums.empty()) { diff --git a/libs/node/src/ecflow/node/parser/TriggerParser.cpp b/libs/node/src/ecflow/node/parser/TriggerParser.cpp index 6f4cef5d7..18362e450 100644 --- a/libs/node/src/ecflow/node/parser/TriggerParser.cpp +++ b/libs/node/src/ecflow/node/parser/TriggerParser.cpp @@ -71,7 +71,7 @@ void TriggerCompleteParser::getExpression(const std::string& line, rootParser()->getNextLine(line2); std::vector lineTokens2; - Str::split(line2, lineTokens2); + ecf::algorithm::split_at(lineTokens2, line2); std::copy(lineTokens2.begin(), lineTokens2.end(), std::back_inserter(accumalatedTokens)); diff --git a/libs/node/src/ecflow/node/parser/VariableParser.cpp b/libs/node/src/ecflow/node/parser/VariableParser.cpp index 87807a789..4d2de1379 100644 --- a/libs/node/src/ecflow/node/parser/VariableParser.cpp +++ b/libs/node/src/ecflow/node/parser/VariableParser.cpp @@ -77,8 +77,9 @@ bool VariableParser::doParse(const std::string& line, std::vector& // edit OWNER "'fred'" => value = fred * tick are not preserved * if (line_tokens_size == 3) { // The order of removing double quotes and then single quotes is significant here - Str::removeQuotes(lineTokens[2]); // if first *and* last character is " - Str::removeSingleQuotes(lineTokens[2]); // if first *and* last character is ' + ecf::algorithm::remove_double_quotes(lineTokens[2]); + ecf::algorithm::remove_single_quotes(lineTokens[2]); + if (node) { if (net || node->isAlias()) { node->add_variable_bypass_name_check(lineTokens[1], lineTokens[2]); // bypass name checking @@ -111,8 +112,8 @@ bool VariableParser::doParse(const std::string& line, std::vector& value += lineTokens[i]; } - Str::removeQuotes(value); - Str::removeSingleQuotes(value); + ecf::algorithm::remove_double_quotes(value); + ecf::algorithm::remove_single_quotes(value); if (node) { if (net || node->isAlias()) { node->add_variable_bypass_name_check(lineTokens[1], value); // bypass name checking diff --git a/libs/node/test/TestEcfFile.cpp b/libs/node/test/TestEcfFile.cpp index 06ff04fe6..e00d1ff19 100644 --- a/libs/node/test/TestEcfFile.cpp +++ b/libs/node/test/TestEcfFile.cpp @@ -983,16 +983,13 @@ BOOST_AUTO_TEST_CASE(test_ecf_file) { /// Test extraction of all the used variables std::vector script_lines; - Str::split(file_with_used_variables, script_lines, "\n"); // will ignore empty lines, but will do for this case + ecf::algorithm::split_at(script_lines, file_with_used_variables, "\n"); NameValueMap extracted_used_variables; EcfFile::extract_used_variables(extracted_used_variables, script_lines); for (std::pair p : expected_used_variables) { BOOST_CHECK_MESSAGE(extracted_used_variables.find(p.first) != extracted_used_variables.end(), " expected to find variable " << p.first << " in the extracted variables\n"); } - // std::cout << "Expected:----\n"; for(p:expected_used_variables) { std::cout << p.first << " " << p.second << - // "\n";} std::cout << "Actual:------\n"; for(p:extracted_used_variables) { std::cout << p.first << " " << - // p.second << "\n";} /// Test pre-processing std::string pre_processed_file; diff --git a/libs/node/test/TestOrder.cpp b/libs/node/test/TestOrder.cpp index 62c2ddb94..368c8ee0f 100644 --- a/libs/node/test/TestOrder.cpp +++ b/libs/node/test/TestOrder.cpp @@ -25,6 +25,8 @@ using namespace ecf; BOOST_AUTO_TEST_SUITE(U_Node) +auto to_name = [](const auto& node) { return node->name(); }; + BOOST_AUTO_TEST_SUITE(T_Order) static void test_invariants(Defs& the_defs, int line) { @@ -56,279 +58,295 @@ BOOST_AUTO_TEST_CASE(test_order) { } } - std::vector alpha; - alpha.emplace_back("a"); - alpha.emplace_back("A"); - alpha.emplace_back("b"); - alpha.emplace_back("B"); - alpha.emplace_back("c"); - - std::vector order; - order.emplace_back("c"); - order.emplace_back("B"); - order.emplace_back("b"); - order.emplace_back("A"); - order.emplace_back("a"); + std::vector alpha{"a", "A", "b", "B", "c"}; + std::vector order{"c", "B", "b", "A", "a"}; // Test suite ordering ========================================================================== - // In init state all suite should be in alpha order - theDefs.order(theDefs.findAbsNode("/A").get(), NOrder::ALPHA); - test_invariants(theDefs, __LINE__); - BOOST_REQUIRE_MESSAGE(ecf::algorithm::transform_to_name_vector(theDefs.suiteVec()) == alpha, - "NOrder::ALPHA expected " - << ecf::algorithm::join(alpha) << " but found " - << ecf::algorithm::join(ecf::algorithm::transform_to_name_vector(theDefs.suiteVec()))); - - // sort in reverse order - theDefs.order(theDefs.findAbsNode("/a").get(), NOrder::ORDER); - test_invariants(theDefs, __LINE__); - BOOST_REQUIRE_MESSAGE(ecf::algorithm::transform_to_name_vector(theDefs.suiteVec()) == order, - "NOrder::ORDER expected " - << ecf::algorithm::join(order) << " but found " - << ecf::algorithm::join(ecf::algorithm::transform_to_name_vector(theDefs.suiteVec()))); - - // Change back to alpha, then move suite 'c' to the top - theDefs.order(theDefs.findAbsNode("/A").get(), NOrder::ALPHA); - BOOST_REQUIRE_MESSAGE(ecf::algorithm::transform_to_name_vector(theDefs.suiteVec()) == alpha, - "NOrder::ALPHA expected " - << ecf::algorithm::join(alpha) << " but found " - << ecf::algorithm::join(ecf::algorithm::transform_to_name_vector(theDefs.suiteVec()))); - test_invariants(theDefs, __LINE__); - - std::vector expected; - expected.emplace_back("c"); - expected.emplace_back("a"); - expected.emplace_back("A"); - expected.emplace_back("b"); - expected.emplace_back("B"); - theDefs.order(theDefs.findAbsNode("/c").get(), NOrder::TOP); - test_invariants(theDefs, __LINE__); - BOOST_REQUIRE_MESSAGE(ecf::algorithm::transform_to_name_vector(theDefs.suiteVec()) == expected, - "NOrder::TOP expected " - << ecf::algorithm::join(expected) << " but found " - << ecf::algorithm::join(ecf::algorithm::transform_to_name_vector(theDefs.suiteVec()))); - - // move suite 'c' back to the bottom - theDefs.order(theDefs.findAbsNode("/c").get(), NOrder::BOTTOM); - test_invariants(theDefs, __LINE__); - BOOST_REQUIRE_MESSAGE(ecf::algorithm::transform_to_name_vector(theDefs.suiteVec()) == alpha, - "NOrder::BOTTOM order not as expected"); - - // move suite 'a' up one place. Should be no change, since its already at the top - theDefs.order(theDefs.findAbsNode("/a").get(), NOrder::UP); - test_invariants(theDefs, __LINE__); - BOOST_REQUIRE_MESSAGE(ecf::algorithm::transform_to_name_vector(theDefs.suiteVec()) == alpha, - "NOrder::UP order not as expected"); - - // move suite 'c' down one place. Should be no change, since its already at the bottom - theDefs.order(theDefs.findAbsNode("/c").get(), NOrder::DOWN); - test_invariants(theDefs, __LINE__); - BOOST_REQUIRE_MESSAGE(ecf::algorithm::transform_to_name_vector(theDefs.suiteVec()) == alpha, - "NOrder::DOWN order not as expected"); - - // Move suite 'a' down by one place - expected.clear(); - expected.emplace_back("A"); - expected.emplace_back("a"); - expected.emplace_back("b"); - expected.emplace_back("B"); - expected.emplace_back("c"); - theDefs.order(theDefs.findAbsNode("/a").get(), NOrder::DOWN); - test_invariants(theDefs, __LINE__); - BOOST_REQUIRE_MESSAGE(ecf::algorithm::transform_to_name_vector(theDefs.suiteVec()) == expected, - "NOrder::DOWN order not as expected"); - - // Move suite 'b' up by one place - expected.clear(); - expected.emplace_back("A"); - expected.emplace_back("b"); - expected.emplace_back("a"); - expected.emplace_back("B"); - expected.emplace_back("c"); - theDefs.order(theDefs.findAbsNode("/b").get(), NOrder::UP); - test_invariants(theDefs, __LINE__); - BOOST_REQUIRE_MESSAGE(ecf::algorithm::transform_to_name_vector(theDefs.suiteVec()) == expected, - "NOrder::UP order not as expected"); + { + // In init state all suite should be in alpha order + theDefs.order(theDefs.findAbsNode("/A").get(), NOrder::ALPHA); + test_invariants(theDefs, __LINE__); + auto names = ecf::algorithm::transform_to_vector(theDefs.suiteVec(), to_name); + BOOST_REQUIRE_MESSAGE(names == alpha, + "NOrder::ALPHA expected " << ecf::algorithm::join(alpha) << " but found " + << ecf::algorithm::join(names)); + } + + { + // sort in reverse order + + theDefs.order(theDefs.findAbsNode("/a").get(), NOrder::ORDER); + test_invariants(theDefs, __LINE__); + auto names = ecf::algorithm::transform_to_vector(theDefs.suiteVec(), to_name); + BOOST_REQUIRE_MESSAGE(names == order, + "NOrder::ORDER expected " << ecf::algorithm::join(order) << " but found " + << ecf::algorithm::join(names)); + } + + { + // Change back to alpha, then move suite 'c' to the top + theDefs.order(theDefs.findAbsNode("/A").get(), NOrder::ALPHA); + auto names = ecf::algorithm::transform_to_vector(theDefs.suiteVec(), to_name); + BOOST_REQUIRE_MESSAGE(names == alpha, + "NOrder::ALPHA expected " << ecf::algorithm::join(alpha) << " but found " + << ecf::algorithm::join(names)); + test_invariants(theDefs, __LINE__); + } + + { + std::vector expected; + expected.emplace_back("c"); + expected.emplace_back("a"); + expected.emplace_back("A"); + expected.emplace_back("b"); + expected.emplace_back("B"); + theDefs.order(theDefs.findAbsNode("/c").get(), NOrder::TOP); + test_invariants(theDefs, __LINE__); + auto names = ecf::algorithm::transform_to_vector(theDefs.suiteVec(), to_name); + BOOST_REQUIRE_MESSAGE(names == expected, + "NOrder::TOP expected " << ecf::algorithm::join(expected) << " but found " + << ecf::algorithm::join(names)); + } + + { + // move suite 'c' back to the bottom + theDefs.order(theDefs.findAbsNode("/c").get(), NOrder::BOTTOM); + test_invariants(theDefs, __LINE__); + auto names = ecf::algorithm::transform_to_vector(theDefs.suiteVec(), to_name); + BOOST_REQUIRE_MESSAGE(names == alpha, "NOrder::BOTTOM order not as expected"); + } + + { + // move suite 'a' up one place. Should be no change, since its already at the top + theDefs.order(theDefs.findAbsNode("/a").get(), NOrder::UP); + test_invariants(theDefs, __LINE__); + auto names = ecf::algorithm::transform_to_vector(theDefs.suiteVec(), to_name); + BOOST_REQUIRE_MESSAGE(names == alpha, "NOrder::UP order not as expected"); + } + + { + // move suite 'c' down one place. Should be no change, since its already at the bottom + theDefs.order(theDefs.findAbsNode("/c").get(), NOrder::DOWN); + test_invariants(theDefs, __LINE__); + auto names = ecf::algorithm::transform_to_vector(theDefs.suiteVec(), to_name); + BOOST_REQUIRE_MESSAGE(names == alpha, "NOrder::DOWN order not as expected"); + } + + { + // Move suite 'a' down by one place + std::vector expected = {"A", "a", "b", "B", "c"}; + + theDefs.order(theDefs.findAbsNode("/a").get(), NOrder::DOWN); + test_invariants(theDefs, __LINE__); + auto names = ecf::algorithm::transform_to_vector(theDefs.suiteVec(), to_name); + BOOST_REQUIRE_MESSAGE(names == expected, "NOrder::DOWN order not as expected"); + } + + { + std::vector expected = {"A", "b", "a", "B", "c"}; + + // Move suite 'b' up by one place + theDefs.order(theDefs.findAbsNode("/b").get(), NOrder::UP); + test_invariants(theDefs, __LINE__); + auto names = ecf::algorithm::transform_to_vector(theDefs.suiteVec(), to_name); + BOOST_REQUIRE_MESSAGE(names == expected, "NOrder::UP order not as expected"); + } // Test family ordering ========================================================================== - // In init state all suite should be in alpha order - suite_ptr suite = theDefs.findSuite("a"); + auto suite = theDefs.findSuite("a"); BOOST_REQUIRE_MESSAGE(suite.get(), "Expected suite /a to exist "); - theDefs.order(theDefs.findAbsNode("/a/a").get(), NOrder::ALPHA); - test_invariants(theDefs, __LINE__); - BOOST_REQUIRE_MESSAGE(ecf::algorithm::transform_to_name_vector(suite->nodeVec()) == alpha, - "NOrder::ALPHA Init order " - << ecf::algorithm::join(ecf::algorithm::transform_to_name_vector(suite->nodeVec())) - << " not as expected " << ecf::algorithm::join(alpha)); - - // sort in reverse order - std::sort(expected.begin(), expected.end(), std::greater()); - suite->order(theDefs.findAbsNode("/a/a").get(), NOrder::ORDER); - test_invariants(theDefs, __LINE__); - BOOST_REQUIRE_MESSAGE(ecf::algorithm::transform_to_name_vector(suite->nodeVec()) == order, - "NOrder::ORDER order " - << ecf::algorithm::join(ecf::algorithm::transform_to_name_vector(suite->nodeVec())) - << " not as expected " << ecf::algorithm::join(order)); - - // Change back to alpha, then move family 'e' to the top - suite->order(theDefs.findAbsNode("/a/a").get(), NOrder::ALPHA); - BOOST_REQUIRE_MESSAGE(ecf::algorithm::transform_to_name_vector(suite->nodeVec()) == alpha, - "NOrder::ALPHA expected " - << ecf::algorithm::join(alpha) << " but found " - << ecf::algorithm::join(ecf::algorithm::transform_to_name_vector(suite->nodeVec()))); - test_invariants(theDefs, __LINE__); - expected.clear(); - expected.emplace_back("c"); - expected.emplace_back("a"); - expected.emplace_back("A"); - expected.emplace_back("b"); - expected.emplace_back("B"); - suite->order(theDefs.findAbsNode("/a/c").get(), NOrder::TOP); - BOOST_REQUIRE_MESSAGE(ecf::algorithm::transform_to_name_vector(suite->nodeVec()) == expected, - "NOrder::TOP order " - << ecf::algorithm::join(ecf::algorithm::transform_to_name_vector(suite->nodeVec())) - << " not as expected " << ecf::algorithm::join(expected)); - - // move family 'c' back to the bottom - suite->order(theDefs.findAbsNode("/a/c").get(), NOrder::BOTTOM); - test_invariants(theDefs, __LINE__); - BOOST_REQUIRE_MESSAGE(ecf::algorithm::transform_to_name_vector(suite->nodeVec()) == alpha, - "NOrder::BOTTOM order " - << ecf::algorithm::join(ecf::algorithm::transform_to_name_vector(suite->nodeVec())) - << " not as expected " << ecf::algorithm::join(alpha)); - - // move family 'a' up one place. Should be no change, since its already at the top - suite->order(theDefs.findAbsNode("/a/a").get(), NOrder::UP); - test_invariants(theDefs, __LINE__); - BOOST_REQUIRE_MESSAGE(ecf::algorithm::transform_to_name_vector(suite->nodeVec()) == alpha, - "NOrder::UP order " - << ecf::algorithm::join(ecf::algorithm::transform_to_name_vector(suite->nodeVec())) - << " not as expected " << ecf::algorithm::join(alpha)); - - // move family 'c' down one place. Should be no change, since its already at the bottom - suite->order(theDefs.findAbsNode("/a/c").get(), NOrder::DOWN); - test_invariants(theDefs, __LINE__); - BOOST_REQUIRE_MESSAGE(ecf::algorithm::transform_to_name_vector(suite->nodeVec()) == alpha, - "NOrder::DOWN order " - << ecf::algorithm::join(ecf::algorithm::transform_to_name_vector(suite->nodeVec())) - << " not as expected " << ecf::algorithm::join(alpha)); - - // Move family 'a' down by one place - expected.clear(); - expected.emplace_back("A"); - expected.emplace_back("a"); - expected.emplace_back("b"); - expected.emplace_back("B"); - expected.emplace_back("c"); - suite->order(theDefs.findAbsNode("/a/a").get(), NOrder::DOWN); - test_invariants(theDefs, __LINE__); - BOOST_REQUIRE_MESSAGE(ecf::algorithm::transform_to_name_vector(suite->nodeVec()) == expected, - "NOrder::DOWN order " - << ecf::algorithm::join(ecf::algorithm::transform_to_name_vector(suite->nodeVec())) - << " not as expected " << ecf::algorithm::join(expected)); - - // Move family 'b' up by one place - suite->order(theDefs.findAbsNode("/a/a").get(), NOrder::ALPHA); // reset - test_invariants(theDefs, __LINE__); - expected.clear(); - expected.emplace_back("a"); - expected.emplace_back("b"); - expected.emplace_back("A"); - expected.emplace_back("B"); - expected.emplace_back("c"); - suite->order(theDefs.findAbsNode("/a/b").get(), NOrder::UP); - BOOST_REQUIRE_MESSAGE(ecf::algorithm::transform_to_name_vector(suite->nodeVec()) == expected, - "NOrder::UP order " - << ecf::algorithm::join(ecf::algorithm::transform_to_name_vector(suite->nodeVec())) - << " not as expected " << ecf::algorithm::join(expected)); + { + // In init state all suite should be in alpha order + theDefs.order(theDefs.findAbsNode("/a/a").get(), NOrder::ALPHA); + test_invariants(theDefs, __LINE__); + auto names = ecf::algorithm::transform_to_vector(theDefs.suiteVec(), to_name); + BOOST_REQUIRE_MESSAGE(names == alpha, + "NOrder::ALPHA Init order " << ecf::algorithm::join(names) << " not as expected " + << ecf::algorithm::join(alpha)); + } + + { + std::vector expected = {"A", "b", "a", "B", "c"}; + + // sort in reverse order + std::sort(expected.begin(), expected.end(), std::greater()); + suite->order(theDefs.findAbsNode("/a/a").get(), NOrder::ORDER); + test_invariants(theDefs, __LINE__); + auto names = ecf::algorithm::transform_to_vector(suite->nodeVec(), to_name); + BOOST_REQUIRE_MESSAGE(names == order, + "NOrder::ORDER order " << ecf::algorithm::join(names) << " not as expected " + << ecf::algorithm::join(order)); + } + + { + // Change back to alpha, then move family 'e' to the top + suite->order(theDefs.findAbsNode("/a/a").get(), NOrder::ALPHA); + auto names = ecf::algorithm::transform_to_vector(suite->nodeVec(), to_name); + BOOST_REQUIRE_MESSAGE(names == alpha, + "NOrder::ALPHA expected " << ecf::algorithm::join(alpha) << " but found " + << ecf::algorithm::join(names)); + test_invariants(theDefs, __LINE__); + + std::vector expected = {"c", "a", "A", "b", "B"}; + + suite->order(theDefs.findAbsNode("/a/c").get(), NOrder::TOP); + names = ecf::algorithm::transform_to_vector(suite->nodeVec(), to_name); + BOOST_REQUIRE_MESSAGE(names == expected, + "NOrder::TOP order " << ecf::algorithm::join(names) << " not as expected " + << ecf::algorithm::join(expected)); + } + + { + // move family 'c' back to the bottom + suite->order(theDefs.findAbsNode("/a/c").get(), NOrder::BOTTOM); + test_invariants(theDefs, __LINE__); + auto names = ecf::algorithm::transform_to_vector(suite->nodeVec(), to_name); + BOOST_REQUIRE_MESSAGE(names == alpha, + "NOrder::BOTTOM order " << ecf::algorithm::join(names) << " not as expected " + << ecf::algorithm::join(alpha)); + } + + { + // move family 'a' up one place. Should be no change, since its already at the top + suite->order(theDefs.findAbsNode("/a/a").get(), NOrder::UP); + test_invariants(theDefs, __LINE__); + auto names = ecf::algorithm::transform_to_vector(suite->nodeVec(), to_name); + BOOST_REQUIRE_MESSAGE(names == alpha, + "NOrder::UP order " << ecf::algorithm::join(names) << " not as expected " + << ecf::algorithm::join(alpha)); + } + + { + // move family 'c' down one place. Should be no change, since its already at the bottom + suite->order(theDefs.findAbsNode("/a/c").get(), NOrder::DOWN); + test_invariants(theDefs, __LINE__); + auto names = ecf::algorithm::transform_to_vector(suite->nodeVec(), to_name); + BOOST_REQUIRE_MESSAGE(names == alpha, + "NOrder::DOWN order " << ecf::algorithm::join(names) << " not as expected " + << ecf::algorithm::join(alpha)); + } + + { + // Move family 'a' down by one place + std::vector expected = {"A", "a", "b", "B", "c"}; + + suite->order(theDefs.findAbsNode("/a/a").get(), NOrder::DOWN); + test_invariants(theDefs, __LINE__); + auto names = ecf::algorithm::transform_to_vector(suite->nodeVec(), to_name); + BOOST_REQUIRE_MESSAGE(names == expected, + "NOrder::DOWN order " << ecf::algorithm::join(names) << " not as expected " + << ecf::algorithm::join(expected)); + } + + { + // Move family 'b' up by one place + suite->order(theDefs.findAbsNode("/a/a").get(), NOrder::ALPHA); // reset + test_invariants(theDefs, __LINE__); + std::vector expected; + expected.emplace_back("a"); + expected.emplace_back("b"); + expected.emplace_back("A"); + expected.emplace_back("B"); + expected.emplace_back("c"); + suite->order(theDefs.findAbsNode("/a/b").get(), NOrder::UP); + auto names = ecf::algorithm::transform_to_vector(suite->nodeVec(), to_name); + BOOST_REQUIRE_MESSAGE(names == expected, + "NOrder::UP order " << ecf::algorithm::join(names) << " not as expected " + << ecf::algorithm::join(expected)); + } // Test Task ordering ========================================================================== - // In init state all tasks should be in alpha order Family* family = theDefs.findAbsNode("/a/a")->isFamily(); - BOOST_REQUIRE_MESSAGE(family, "Expected family /a/a to exist "); - - family->order(theDefs.findAbsNode("/a/a/a").get(), NOrder::ALPHA); - test_invariants(theDefs, __LINE__); - BOOST_REQUIRE_MESSAGE(ecf::algorithm::transform_to_name_vector(family->nodeVec()) == alpha, - "NOrder::ALPHA Init state " - << ecf::algorithm::join(ecf::algorithm::transform_to_name_vector(family->nodeVec())) - << " not as expected " << ecf::algorithm::join(alpha)); - - // sort in reverse order - family->order(theDefs.findAbsNode("/a/a/a").get(), NOrder::ORDER); - test_invariants(theDefs, __LINE__); - BOOST_REQUIRE_MESSAGE(ecf::algorithm::transform_to_name_vector(family->nodeVec()) == order, - "NOrder::ORDER " - << ecf::algorithm::join(ecf::algorithm::transform_to_name_vector(family->nodeVec())) - << " not as expected " << ecf::algorithm::join(order)); - - // Change back to alpha, then move task 'c' to the top - family->order(theDefs.findAbsNode("/a/a/a").get(), NOrder::ALPHA); // reset - expected.clear(); - expected.emplace_back("c"); - expected.emplace_back("a"); - expected.emplace_back("A"); - expected.emplace_back("b"); - expected.emplace_back("B"); - family->order(theDefs.findAbsNode("/a/a/c").get(), NOrder::TOP); - test_invariants(theDefs, __LINE__); - BOOST_REQUIRE_MESSAGE(ecf::algorithm::transform_to_name_vector(family->nodeVec()) == expected, - "NOrder::TOP order " - << ecf::algorithm::join(ecf::algorithm::transform_to_name_vector(family->nodeVec())) - << " not as expected " << ecf::algorithm::join(expected)); - - // move task 'c' back to the bottom - family->order(theDefs.findAbsNode("/a/a/c").get(), NOrder::BOTTOM); - test_invariants(theDefs, __LINE__); - BOOST_REQUIRE_MESSAGE(ecf::algorithm::transform_to_name_vector(family->nodeVec()) == alpha, - "NOrder::BOTTOM order " - << ecf::algorithm::join(ecf::algorithm::transform_to_name_vector(family->nodeVec())) - << " not as expected " << ecf::algorithm::join(alpha)); - - // move task 'a' up one place. Should be no change, since its already at the top - family->order(theDefs.findAbsNode("/a/a/a").get(), NOrder::UP); - test_invariants(theDefs, __LINE__); - BOOST_REQUIRE_MESSAGE(ecf::algorithm::transform_to_name_vector(family->nodeVec()) == alpha, - "NOrder::UP order " - << ecf::algorithm::join(ecf::algorithm::transform_to_name_vector(family->nodeVec())) - << " not as expected " << ecf::algorithm::join(alpha)); - - // move task 'e' down one place. Should be no change, since its already at the bottom - family->order(theDefs.findAbsNode("/a/a/c").get(), NOrder::DOWN); - test_invariants(theDefs, __LINE__); - BOOST_REQUIRE_MESSAGE(ecf::algorithm::transform_to_name_vector(family->nodeVec()) == alpha, - "NOrder::DOWN order " - << ecf::algorithm::join(ecf::algorithm::transform_to_name_vector(family->nodeVec())) - << " not as expected " << ecf::algorithm::join(alpha)); - - // Move task 'a' down by one place - expected.clear(); - expected.emplace_back("A"); - expected.emplace_back("a"); - expected.emplace_back("b"); - expected.emplace_back("B"); - expected.emplace_back("c"); - family->order(theDefs.findAbsNode("/a/a/a").get(), NOrder::DOWN); - test_invariants(theDefs, __LINE__); - BOOST_REQUIRE_MESSAGE(ecf::algorithm::transform_to_name_vector(family->nodeVec()) == expected, - "NOrder::DOWN order " - << ecf::algorithm::join(ecf::algorithm::transform_to_name_vector(family->nodeVec())) - << " not as expected " << ecf::algorithm::join(expected)); - - // Move task 'b' up by one place - family->order(theDefs.findAbsNode("/a/a/b").get(), NOrder::DOWN); - expected.clear(); - expected.emplace_back("A"); - expected.emplace_back("a"); - expected.emplace_back("B"); - expected.emplace_back("b"); - expected.emplace_back("c"); - test_invariants(theDefs, __LINE__); - BOOST_REQUIRE_MESSAGE(ecf::algorithm::transform_to_name_vector(family->nodeVec()) == expected, - "NOrder::UP order " - << ecf::algorithm::join(ecf::algorithm::transform_to_name_vector(family->nodeVec())) - << " not as expected " << ecf::algorithm::join(expected)); + { + // In init state all tasks should be in alpha order + + BOOST_REQUIRE_MESSAGE(family, "Expected family /a/a to exist "); + family->order(theDefs.findAbsNode("/a/a/a").get(), NOrder::ALPHA); + test_invariants(theDefs, __LINE__); + auto names = ecf::algorithm::transform_to_vector(family->nodeVec(), to_name); + BOOST_REQUIRE_MESSAGE(names == alpha, + "NOrder::ALPHA Init state " << ecf::algorithm::join(names) << " not as expected " + << ecf::algorithm::join(alpha)); + } + + { + // sort in reverse order + family->order(theDefs.findAbsNode("/a/a/a").get(), NOrder::ORDER); + test_invariants(theDefs, __LINE__); + auto names = ecf::algorithm::transform_to_vector(family->nodeVec(), to_name); + BOOST_REQUIRE_MESSAGE(names == order, + "NOrder::ORDER " << ecf::algorithm::join(names) << " not as expected " + << ecf::algorithm::join(order)); + } + + { + // Change back to alpha, then move task 'c' to the top + family->order(theDefs.findAbsNode("/a/a/a").get(), NOrder::ALPHA); // reset + family->order(theDefs.findAbsNode("/a/a/c").get(), NOrder::TOP); + test_invariants(theDefs, __LINE__); + + std::vector expected = {"c", "a", "A", "b", "B"}; + + auto names = ecf::algorithm::transform_to_vector(family->nodeVec(), to_name); + BOOST_REQUIRE_MESSAGE(names == expected, + "NOrder::TOP order " << ecf::algorithm::join(names) << " not as expected " + << ecf::algorithm::join(expected)); + } + + { + // move task 'c' back to the bottom + family->order(theDefs.findAbsNode("/a/a/c").get(), NOrder::BOTTOM); + test_invariants(theDefs, __LINE__); + auto names = ecf::algorithm::transform_to_vector(family->nodeVec(), to_name); + BOOST_REQUIRE_MESSAGE(names == alpha, + "NOrder::BOTTOM order " << ecf::algorithm::join(names) << " not as expected " + << ecf::algorithm::join(alpha)); + } + + { + // move task 'a' up one place. Should be no change, since its already at the top + family->order(theDefs.findAbsNode("/a/a/a").get(), NOrder::UP); + test_invariants(theDefs, __LINE__); + auto names = ecf::algorithm::transform_to_vector(family->nodeVec(), to_name); + BOOST_REQUIRE_MESSAGE(names == alpha, + "NOrder::UP order " << ecf::algorithm::join(names) << " not as expected " + << ecf::algorithm::join(alpha)); + } + + { + // move task 'e' down one place. Should be no change, since its already at the bottom + family->order(theDefs.findAbsNode("/a/a/c").get(), NOrder::DOWN); + test_invariants(theDefs, __LINE__); + auto names = ecf::algorithm::transform_to_vector(family->nodeVec(), to_name); + BOOST_REQUIRE_MESSAGE(names == alpha, + "NOrder::DOWN order " << ecf::algorithm::join(names) << " not as expected " + << ecf::algorithm::join(alpha)); + } + + { + // Move task 'a' down by one place + std::vector expected = {"A", "a", "b", "B", "c"}; + + family->order(theDefs.findAbsNode("/a/a/a").get(), NOrder::DOWN); + test_invariants(theDefs, __LINE__); + auto names = ecf::algorithm::transform_to_vector(family->nodeVec(), to_name); + BOOST_REQUIRE_MESSAGE(names == expected, + "NOrder::DOWN order " << ecf::algorithm::join(names) << " not as expected " + << ecf::algorithm::join(expected)); + } + + { + // Move task 'b' up by one place + std::vector expected = {"A", "a", "B", "b", "c"}; + + family->order(theDefs.findAbsNode("/a/a/b").get(), NOrder::DOWN); + test_invariants(theDefs, __LINE__); + auto names = ecf::algorithm::transform_to_vector(family->nodeVec(), to_name); + BOOST_REQUIRE_MESSAGE(names == expected, + "NOrder::UP order " << ecf::algorithm::join(names) << " not as expected " + << ecf::algorithm::join(expected)); + } } BOOST_AUTO_TEST_CASE(test_alias_order) { @@ -346,59 +364,57 @@ BOOST_AUTO_TEST_CASE(test_alias_order) { } // Test alias ordering ========================================================================== + // In init state all suite should be in alpha order alias_ptr alias0 = task->find_alias("alias0"); BOOST_REQUIRE_MESSAGE(alias0, "expected to find alias0"); - std::vector expected; - expected.emplace_back("alias1"); - expected.emplace_back("alias0"); - expected.emplace_back("alias2"); - expected.emplace_back("alias3"); - task->order(alias0.get(), NOrder::DOWN); - test_invariants(theDefs, __LINE__); - BOOST_REQUIRE_MESSAGE(ecf::algorithm::transform_to_name_vector(task->aliases()) == expected, - "NOrder::DOWN expected " - << ecf::algorithm::join(expected) << " but found " - << ecf::algorithm::join(ecf::algorithm::transform_to_name_vector(task->aliases()))); - - task->order(alias0.get(), NOrder::ALPHA); - test_invariants(theDefs, __LINE__); - expected.clear(); - expected.emplace_back("alias0"); - expected.emplace_back("alias1"); - expected.emplace_back("alias2"); - expected.emplace_back("alias3"); - BOOST_REQUIRE_MESSAGE(ecf::algorithm::transform_to_name_vector(task->aliases()) == expected, - "NOrder::ALPHA expectex " - << ecf::algorithm::join(expected) << " but found " - << ecf::algorithm::join(ecf::algorithm::transform_to_name_vector(task->aliases()))); - - task->order(task->find_alias("alias3").get(), NOrder::UP); - test_invariants(theDefs, __LINE__); - expected.clear(); - expected.emplace_back("alias0"); - expected.emplace_back("alias1"); - expected.emplace_back("alias3"); - expected.emplace_back("alias2"); - BOOST_REQUIRE_MESSAGE(ecf::algorithm::transform_to_name_vector(task->aliases()) == expected, - "NOrder::UP expected " - << ecf::algorithm::join(expected) << " but found " - << ecf::algorithm::join(ecf::algorithm::transform_to_name_vector(task->aliases()))); - - // sort in reverse order - std::sort(expected.begin(), expected.end(), std::greater()); - task->order(alias0.get(), NOrder::ORDER); - test_invariants(theDefs, __LINE__); - expected.clear(); - expected.emplace_back("alias3"); - expected.emplace_back("alias2"); - expected.emplace_back("alias1"); - expected.emplace_back("alias0"); - BOOST_REQUIRE_MESSAGE(ecf::algorithm::transform_to_name_vector(task->aliases()) == expected, - "NOrder::ORDER expected " - << ecf::algorithm::join(expected) << " but found " - << ecf::algorithm::join(ecf::algorithm::transform_to_name_vector(task->aliases()))); + { + std::vector expected = {"alias1", "alias0", "alias2", "alias3"}; + + task->order(alias0.get(), NOrder::DOWN); + test_invariants(theDefs, __LINE__); + auto names = ecf::algorithm::transform_to_vector(task->aliases(), to_name); + BOOST_REQUIRE_MESSAGE(names == expected, + "NOrder::DOWN expected " << ecf::algorithm::join(expected) << " but found " + << ecf::algorithm::join(names)); + } + + { + std::vector expected = {"alias0", "alias1", "alias2", "alias3"}; + + task->order(alias0.get(), NOrder::ALPHA); + test_invariants(theDefs, __LINE__); + + auto names = ecf::algorithm::transform_to_vector(task->aliases(), to_name); + BOOST_REQUIRE_MESSAGE(names == expected, + "NOrder::ALPHA expectex " << ecf::algorithm::join(expected) << " but found " + << ecf::algorithm::join(names)); + } + + { + std::vector expected = {"alias0", "alias1", "alias3", "alias2"}; + + task->order(task->find_alias("alias3").get(), NOrder::UP); + test_invariants(theDefs, __LINE__); + + auto names = ecf::algorithm::transform_to_vector(task->aliases(), to_name); + BOOST_REQUIRE_MESSAGE(names == expected, + "NOrder::UP expected " << ecf::algorithm::join(expected) << " but found " + << ecf::algorithm::join(names)); + } + + { + std::vector expected = {"alias3", "alias2", "alias1", "alias0"}; // sorted in reverse order + + task->order(alias0.get(), NOrder::ORDER); + test_invariants(theDefs, __LINE__); + + auto names = ecf::algorithm::transform_to_vector(task->aliases(), to_name); + BOOST_REQUIRE_MESSAGE(names == expected, + "NOrder::ORDER expected " << ecf::algorithm::join(expected) << " but found " + << ecf::algorithm::join(names)); + } } BOOST_AUTO_TEST_CASE(test_order_by_runtime) { @@ -430,7 +446,6 @@ BOOST_AUTO_TEST_CASE(test_order_by_runtime) { } } } - // std::cout << defs; defs.beginAll(); expectedDefs.beginAll(); @@ -455,9 +470,6 @@ BOOST_AUTO_TEST_CASE(test_order_by_runtime) { s->set_state(NState::COMPLETE); } - // PrintStyle style(PrintStyle::MIGRATE); - // std::cout << defs; - defs.order(nullptr, NOrder::RUNTIME); // moot when you only have one suite for (auto suite : defs.suiteVec()) { suite->order(nullptr, NOrder::RUNTIME); @@ -465,7 +477,6 @@ BOOST_AUTO_TEST_CASE(test_order_by_runtime) { family->order(nullptr, NOrder::RUNTIME); } } - // std::cout << defs; defs.requeue(); expectedDefs.requeue(); diff --git a/libs/node/test/parser/ParseTimer.cpp b/libs/node/test/parser/ParseTimer.cpp index 40328df2c..67990e537 100644 --- a/libs/node/test/parser/ParseTimer.cpp +++ b/libs/node/test/parser/ParseTimer.cpp @@ -148,7 +148,7 @@ int main(int argc, char* argv[]) { #endif std::string json_filepath = MESSAGE("/var/tmp/ma0/JSON/" << prefix << fs_path.stem() << ".json"); - Str::replace_all(json_filepath, "\"", ""); // fs_path.stem() seems to add ", so remove them + ecf::algorithm::replace_all(json_filepath, "\"", ""); // fs_path.stem() seems to add ", so remove them // cout << " json_filepath: " << json_filepath << endl; std::remove(json_filepath.c_str()); diff --git a/libs/pyext/script.py b/libs/pyext/script.py deleted file mode 100644 index ee6fbbe5b..000000000 --- a/libs/pyext/script.py +++ /dev/null @@ -1,11 +0,0 @@ -## Copyright 2009- ECMWF. -## This software is licensed under the terms of the Apache Licence version 2.0 -## which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. -## In applying this licence, ECMWF does not waive the privileges and immunities -## granted to it by virtue of its status as an intergovernmental organisation -## nor does it submit to any jurisdiction. - -# -# -print 'Hello World !' -number = 42 diff --git a/libs/pyext/unicode.py b/libs/pyext/unicode.py deleted file mode 100644 index d793d8482..000000000 --- a/libs/pyext/unicode.py +++ /dev/null @@ -1,13 +0,0 @@ -## Copyright 2009- ECMWF. -## This software is licensed under the terms of the Apache Licence version 2.0 -## which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. -## In applying this licence, ECMWF does not waive the privileges and immunities -## granted to it by virtue of its status as an intergovernmental organisation -## nor does it submit to any jurisdiction. -# Determine the python unicode - -import sys -if sys.maxunicode > 65535: - print('UCS4 build') -else: - print('UCS2 build') diff --git a/libs/rest/src/ecflow/http/ApiV1.cpp b/libs/rest/src/ecflow/http/ApiV1.cpp index d8cd45b76..e9d4c661f 100644 --- a/libs/rest/src/ecflow/http/ApiV1.cpp +++ b/libs/rest/src/ecflow/http/ApiV1.cpp @@ -155,7 +155,7 @@ ojson filter_json(const ojson& j, const httplib::Request& r) { // the elements are consumed by the dive() function std::vector path_elems; - ecf::Str::split(path, path_elems, "."); + ecf::algorithm::split_at(path_elems, path, "."); std::reverse(path_elems.begin(), path_elems.end()); if (path_elems.empty()) { diff --git a/libs/rest/src/ecflow/http/ApiV1Impl.cpp b/libs/rest/src/ecflow/http/ApiV1Impl.cpp index b014c30c9..146463794 100644 --- a/libs/rest/src/ecflow/http/ApiV1Impl.cpp +++ b/libs/rest/src/ecflow/http/ApiV1Impl.cpp @@ -592,7 +592,7 @@ ecf::AutoCancelAttr create_from_text(const std::string& line) { template <> ecf::AutoRestoreAttr create_from_text(const std::string& line) { std::vector tasks; - ecf::Str::split(line, tasks, " "); + ecf::algorithm::split_at(tasks, line, " "); return ecf::AutoRestoreAttr(tasks); } diff --git a/libs/rest/src/ecflow/http/BasicAuth.cpp b/libs/rest/src/ecflow/http/BasicAuth.cpp index d07f08fc5..3dfaf41b0 100644 --- a/libs/rest/src/ecflow/http/BasicAuth.cpp +++ b/libs/rest/src/ecflow/http/BasicAuth.cpp @@ -19,7 +19,7 @@ namespace ecf::http { std::pair BasicAuth::get_credentials(const std::string& token) { std::vector elems; - ecf::Str::split(decode_base64(token), elems, ":"); + ecf::algorithm::split_at(elems, decode_base64(token), ":"); return std::make_pair(elems[0], PasswordEncryption::encrypt(elems[1], elems[0])); } diff --git a/libs/rest/src/ecflow/http/Client.cpp b/libs/rest/src/ecflow/http/Client.cpp index f066b1f2b..858ae5995 100644 --- a/libs/rest/src/ecflow/http/Client.cpp +++ b/libs/rest/src/ecflow/http/Client.cpp @@ -54,7 +54,7 @@ bool authenticate(const httplib::Request& request, ClientInvoker* ci) { if (auth != header.end()) { std::vector elems; - ecf::Str::split(auth->second, elems, " "); + ecf::algorithm::split_at(elems, auth->second, " "); if (elems[0] == "Basic") { auto [username, password] = BasicAuth::get_credentials(elems[1]); diff --git a/libs/rest/src/ecflow/http/TokenStorage.cpp b/libs/rest/src/ecflow/http/TokenStorage.cpp index c3a2686f5..b5cf2819c 100644 --- a/libs/rest/src/ecflow/http/TokenStorage.cpp +++ b/libs/rest/src/ecflow/http/TokenStorage.cpp @@ -182,7 +182,7 @@ std::vector ReadTokens(const std::string& filename) { const std::string& hash = o.at("hash").get(); std::vector elems; - ecf::Str::split(hash, elems, "$"); + ecf::algorithm::split_at(elems, hash, "$"); if (elems.size() != 3) { throw std::invalid_argument("Invalid hash format: " + hash); diff --git a/libs/service/src/ecflow/service/aviso/etcd/Range.cpp b/libs/service/src/ecflow/service/aviso/etcd/Range.cpp index 7df3a5026..ee75ac11d 100644 --- a/libs/service/src/ecflow/service/aviso/etcd/Range.cpp +++ b/libs/service/src/ecflow/service/aviso/etcd/Range.cpp @@ -10,10 +10,7 @@ #include "ecflow/service/aviso/etcd/Range.hpp" -#include -#include -#include -#include +#include namespace ecf::service::aviso::etcd { diff --git a/libs/service/src/ecflow/service/mirror/MirrorClient.cpp b/libs/service/src/ecflow/service/mirror/MirrorClient.cpp index 79de88c7a..e3f088016 100644 --- a/libs/service/src/ecflow/service/mirror/MirrorClient.cpp +++ b/libs/service/src/ecflow/service/mirror/MirrorClient.cpp @@ -10,8 +10,6 @@ #include "ecflow/service/mirror/MirrorClient.hpp" -#include - #include "ecflow/client/ClientInvoker.hpp" #include "ecflow/core/Message.hpp" #include "ecflow/core/PasswordEncryption.hpp" @@ -121,15 +119,15 @@ MirrorData MirrorClient::get_node_status(const std::string& remote_host, data.generated_variables = ecf::generated_variables(*node); // Filter out the Definitions structural variables (SUITE, to avoid conflicts with "local" side definitions - data.generated_variables.erase( - std::remove_if(std::begin(data.generated_variables), - std::end(data.generated_variables), - [](const auto& variable) { - return boost::algorithm::starts_with(variable.name(), "TASK") || - boost::algorithm::starts_with(variable.name(), "FAMILY") || - boost::algorithm::starts_with(variable.name(), "SUITE"); - }), - std::end(data.generated_variables)); + data.generated_variables.erase(std::remove_if(std::begin(data.generated_variables), + std::end(data.generated_variables), + [](const auto& variable) { + using ecf::algorithm::starts_with; + return starts_with(variable.name(), "TASK") || + starts_with(variable.name(), "FAMILY") || + starts_with(variable.name(), "SUITE"); + }), + std::end(data.generated_variables)); // ** Node Labels data.labels = node->labels(); diff --git a/libs/test/overall/TestAlias.cpp b/libs/test/overall/TestAlias.cpp index 53d11588e..9a9a1f0df 100644 --- a/libs/test/overall/TestAlias.cpp +++ b/libs/test/overall/TestAlias.cpp @@ -41,6 +41,8 @@ using namespace ecf; BOOST_AUTO_TEST_SUITE(S_Test) +auto to_name = [](const auto& node) { return node->name(); }; + BOOST_AUTO_TEST_SUITE(T_Alias) void wait_for_alias_to_complete(const std::string& alias_path) { @@ -103,7 +105,7 @@ BOOST_AUTO_TEST_CASE(test_alias) { // TEST Alias CREATION and running ============================================================================= // Split the file into line std::vector script_lines; - Str::split(script, script_lines, "\n"); + ecf::algorithm::split_at(script_lines, script, "\n"); NameValueVec used_variables; int result = TestFixture::client().edit_script_submit( @@ -147,13 +149,12 @@ BOOST_AUTO_TEST_CASE(test_alias) { Task* task = defs->findAbsNode(task_a->absNodePath())->isTask(); BOOST_REQUIRE_MESSAGE(task, "expected to find Task"); - std::vector expected; - expected.push_back("alias1"); - expected.push_back("alias0"); - BOOST_REQUIRE_MESSAGE(ecf::algorithm::transform_to_name_vector(task->aliases()) == expected, - "NOrder::DOWN expected " - << ecf::algorithm::join(expected) << " but found " - << ecf::algorithm::join(ecf::algorithm::transform_to_name_vector(task->aliases()))); + std::vector expected = {"alias1", "alias0"}; + + auto names = ecf::algorithm::transform_to_vector(task->aliases(), to_name); + BOOST_REQUIRE_MESSAGE(names == expected, + "NOrder::DOWN expected " << ecf::algorithm::join(expected) << " but found " + << ecf::algorithm::join(names)); } BOOST_REQUIRE_MESSAGE(TestFixture::client().order(alias0_path, NOrder::toString(NOrder::UP)) == 0, @@ -167,13 +168,12 @@ BOOST_AUTO_TEST_CASE(test_alias) { Task* task = defs->findAbsNode(task_a->absNodePath())->isTask(); BOOST_REQUIRE_MESSAGE(task, "expected to find Task"); - std::vector expected; - expected.push_back("alias0"); - expected.push_back("alias1"); - BOOST_REQUIRE_MESSAGE(ecf::algorithm::transform_to_name_vector(task->aliases()) == expected, - "NOrder::UP expected " - << ecf::algorithm::join(expected) << " but found " - << ecf::algorithm::join(ecf::algorithm::transform_to_name_vector(task->aliases()))); + std::vector expected = {"alias0", "alias1"}; + + auto names = ecf::algorithm::transform_to_vector(task->aliases(), to_name); + BOOST_REQUIRE_MESSAGE(names == expected, + "NOrder::UP expected " << ecf::algorithm::join(expected) << " but found " + << ecf::algorithm::join(names)); } BOOST_REQUIRE_MESSAGE(TestFixture::client().order(alias0_path, NOrder::toString(NOrder::ORDER)) == 0, @@ -187,13 +187,12 @@ BOOST_AUTO_TEST_CASE(test_alias) { Task* task = defs->findAbsNode(task_a->absNodePath())->isTask(); BOOST_REQUIRE_MESSAGE(task, "expected to find Task"); - std::vector expected; - expected.push_back("alias1"); - expected.push_back("alias0"); - BOOST_REQUIRE_MESSAGE(ecf::algorithm::transform_to_name_vector(task->aliases()) == expected, - "NOrder::ORDER expected " - << ecf::algorithm::join(expected) << " but found " - << ecf::algorithm::join(ecf::algorithm::transform_to_name_vector(task->aliases()))); + std::vector expected = {"alias1", "alias0"}; + + auto names = ecf::algorithm::transform_to_vector(task->aliases(), to_name); + BOOST_REQUIRE_MESSAGE(names == expected, + "NOrder::ORDER expected " << ecf::algorithm::join(expected) << " but found " + << ecf::algorithm::join(names)); } BOOST_REQUIRE_MESSAGE(TestFixture::client().order(alias0_path, NOrder::toString(NOrder::ALPHA)) == 0, @@ -207,13 +206,12 @@ BOOST_AUTO_TEST_CASE(test_alias) { Task* task = defs->findAbsNode(task_a->absNodePath())->isTask(); BOOST_REQUIRE_MESSAGE(task, "expected to find Task"); - std::vector expected; - expected.push_back("alias0"); - expected.push_back("alias1"); - BOOST_REQUIRE_MESSAGE(ecf::algorithm::transform_to_name_vector(task->aliases()) == expected, - "NOrder::ALPHA expected " - << ecf::algorithm::join(expected) << " but found " - << ecf::algorithm::join(ecf::algorithm::transform_to_name_vector(task->aliases()))); + std::vector expected = {"alias0", "alias1"}; + + auto names = ecf::algorithm::transform_to_vector(task->aliases(), to_name); + BOOST_REQUIRE_MESSAGE(names == expected, + "NOrder::ALPHA expected " << ecf::algorithm::join(expected) << " but found " + << ecf::algorithm::join(names)); } // TEST Alias DELETION ============================================================================= From e405c5f87e6945d28aeb3777938f39d33857b20a Mon Sep 17 00:00:00 2001 From: Marcos Bento Date: Wed, 15 Apr 2026 11:55:25 +0100 Subject: [PATCH 25/90] refactor: add split by field Re ECFLOW-2076 --- libs/core/src/ecflow/core/Str.hpp | 57 ++++++++++++++++++++++--- libs/core/test/TestStr.cpp | 69 +++++++++++++++++++++++++++---- 2 files changed, 111 insertions(+), 15 deletions(-) diff --git a/libs/core/src/ecflow/core/Str.hpp b/libs/core/src/ecflow/core/Str.hpp index d2c934666..a37e0cb58 100644 --- a/libs/core/src/ecflow/core/Str.hpp +++ b/libs/core/src/ecflow/core/Str.hpp @@ -116,7 +116,7 @@ inline void replace_first(Sequence& input, const SearchSequence& search, const R /// 2) consecutive separators are collapsed into one, so no empty substrings are produced. /// 3) leading and trailing separators are ignored. /// -/// If the input string is empty, or contains only separator characters, the result be ane empty sequence (i.e. `[ ]`). +/// If the input string is empty, or contains only separator characters, the result be one empty sequence (i.e. `[ ]`). /// /// @tparam ResultSequence A container type to hold the resulting substrings. /// @tparam Sequence1 A string-like type representing the input string. @@ -124,8 +124,7 @@ inline void replace_first(Sequence& input, const SearchSequence& search, const R /// /// @param buffer A reference to the container where the substrings will be stored. /// @param input The input string to be split. -/// @param separators The string containing characters to use as separators (n.b. duplicates they will be treated as -/// a single separator) +/// @param separators The string containing characters to use as separators /// @return A reference to the container holding the resulting substrings. /// template @@ -147,6 +146,52 @@ split_at(ResultSequence& buffer, const Sequence1& input, const Sequence2& separa return buffer; } +/// +/// @brief Splits an input string into a sequence of substrings based on a set of separators. +/// +/// The resulting buffer is cleared of any contents, before being populated with the substrings. +/// +/// A field separator is defined as any single character contained in the `separators` string. +/// Consecutive separators are used to split the input string into empty field. +/// An empty field is included in result, when a separator is found at the beginning and or at end of +/// the input. +/// +/// Splitting the input `",,a,,b,c;d; ;e,;f;"` with the separators `",;"` would result +/// in a buffer containing `["", "", "a", "", "b", "c", "d", " ", "e", " ", "f", " "]`. +/// +/// If the input string is empty, the result be one empty sequence (i.e. `[ ]`). +/// +/// @tparam ResultSequence A container type to hold the resulting substrings. +/// @tparam Sequence1 A string-like type representing the input string. +/// @tparam Sequence2 A string-like type representing single character separators. +/// +/// @param buffer A reference to the container where the substrings will be stored. +/// @param input The input string to be split. +/// @param separators The string containing characters to use as separators +/// @return A reference to the container holding the resulting substrings. +/// +template +static ResultSequence& split_fields_at(ResultSequence& buffer, const Sequence1& input, const Sequence2& pattern) { + buffer.clear(); + + if (input.empty()) { + return buffer; + } + + std::string::size_type start = 0; + std::string::size_type pos = std::string::npos; + while ((pos = input.find_first_of(pattern, start)) != std::string::npos) { + auto token = input.substr(start, pos - start); + buffer.push_back(token); + start = pos + 1; + } + + auto token = input.substr(start); + buffer.push_back(token); + + return buffer; +} + /// /// @brief Splits an input string into a sequence of substrings based on a given pattern. /// @@ -156,13 +201,13 @@ split_at(ResultSequence& buffer, const Sequence1& input, const Sequence2& separa /// Splitting the input `"a==b==c!=d==e == f"` with the pattern `"=="` would result /// in a buffer containing `["a", "b", "c!=d", "e ", " f"]`. /// -/// iIf the input string is empty, or contains only repetirion of the separator pattern, the result be ane empty +/// If the input string is empty, or contains only repetited separator patterns, the result will be one empty /// sequence (i.e. `[ ]`). /// /// @tparam ResultSequence A container type to hold the resulting substrings. /// @tparam Sequence1 A string-like type representing the input string. -/// @tparam Sequence2 A string-like type representing the pattern to split by (n.b. if the pattern contains duplicated -/// characters, they will be treated as a single separator). +/// @tparam Sequence2 A string-like type representing the pattern to split by (n.b. if the pattern contains +/// duplicated characters, they will be treated as a single separator). /// /// @param buffer A reference to the container where the resulting substrings will be stored. /// @param input The input string to be split. diff --git a/libs/core/test/TestStr.cpp b/libs/core/test/TestStr.cpp index a48be1009..df6cea7ba 100644 --- a/libs/core/test/TestStr.cpp +++ b/libs/core/test/TestStr.cpp @@ -116,12 +116,17 @@ BOOST_AUTO_TEST_CASE(test_algorithm_split_at) { "completions"}}}; for (const auto& tc : test_cases) { - std::vector result; - ecf::algorithm::split_at(result, tc.input); - BOOST_CHECK_MESSAGE(result == tc.expected, + std::vector splits; + + auto& actual = ecf::algorithm::split_at(splits, tc.input); + + BOOST_CHECK_MESSAGE(&actual == &splits, + "Expected the returned value and the input vector to be the same object"); + BOOST_CHECK_MESSAGE(actual == splits, "Expected the returned value and the input vector to have same content"); + BOOST_CHECK_MESSAGE(splits == tc.expected, "Expected:\n" << ecf::algorithm::as_string(tc.expected) << "\nbut found:\n" - << ecf::algorithm::as_string(result)); + << ecf::algorithm::as_string(splits)); } } @@ -150,7 +155,9 @@ BOOST_AUTO_TEST_CASE(test_algorithm_split_at_with_separators) { const auto& actual = ecf::algorithm::split_at(splits, tc.input, tc.separator); - BOOST_CHECK_MESSAGE(actual == splits, "Expected the returned value and the input vector to be the same object"); + BOOST_CHECK_MESSAGE(&actual == &splits, + "Expected the returned value and the input vector to be the same object"); + BOOST_CHECK_MESSAGE(actual == splits, "Expected the returned value and the input vector to have same content"); BOOST_CHECK_MESSAGE(actual == tc.expected, "Splitting >>>" << tc.input << "<<<, Expected " << ecf::algorithm::as_string(tc.expected) << ", found " << ecf::algorithm::as_string(actual)); @@ -182,12 +189,56 @@ BOOST_AUTO_TEST_CASE(test_algorithm_split_by_with_separators) { {"expression 1eqexpression 2", " eq ", {"expression 1eqexpression 2"}}}; for (const auto& testCase : testCases) { - std::vector result; - ecf::algorithm::split_by(result, testCase.input, testCase.pattern); - BOOST_CHECK_MESSAGE(result == testCase.expected, + std::vector splits; + auto& actual = ecf::algorithm::split_by(splits, testCase.input, testCase.pattern); + + BOOST_CHECK_MESSAGE(&actual == &splits, + "Expected the returned value and the input vector to be the same object"); + BOOST_CHECK_MESSAGE(actual == splits, "Expected the returned value and the input vector to have same content"); + BOOST_CHECK_MESSAGE(splits == testCase.expected, "Failed for input: '" << testCase.input << "' pattern: '" << testCase.pattern << "'. Expected: " << ecf::algorithm::as_string(testCase.expected) - << " but found: " << ecf::algorithm::as_string(result)); + << " but found: " << ecf::algorithm::as_string(splits)); + } +} + +BOOST_AUTO_TEST_CASE(test_algorithm_split_fields_at_with_separators) { + ECF_NAME_THIS_TEST(); + + struct tc + { + std::string input; + std::string separator; + std::vector expected; + }; + + std::vector test_cases = { + {"", ",", {}}, + {"abc,def,ghi", ",", {"abc", "def", "ghi"}}, + {"abc,def,ghi,", ",", {"abc", "def", "ghi", ""}}, + {",abc,def,ghi", ",", {"", "abc", "def", "ghi"}}, + {",abc,def,ghi,", ",", {"", "abc", "def", "ghi", ""}}, + {",abc,,def,,ghi,", ",", {"", "abc", "", "def", "", "ghi", ""}}, + {"abc,,def,ghi", ",", {"abc", "", "def", "ghi"}}, + {"abc,def,,ghi", ",", {"abc", "def", "", "ghi"}}, + {"abc,,def,,ghi,", ",", {"abc", "", "def", "", "ghi", ""}}, + {"xxx;,;yyy;,;zzz", ";,", {"xxx", "", "", "yyy", "", "", "zzz"}}, + {"xxx;a,b;yyy;c,d;zzz", ";,", {"xxx", "a", "b", "yyy", "c", "d", "zzz"}}, + {"aeiou,12345 12345,aeiou", " ", {"aeiou,12345", "12345,aeiou"}}, + {"a;,b,c;d; ;e;,; f;", ",,;", {"a", "", "b", "c", "d", " ", "e", "", "", " f", ""}}, + {",,a,,b,c;d; ;e,;f;", ",,;", {"", "", "a", "", "b", "c", "d", " ", "e", "", "f", ""}}}; + + for (const auto& tc : test_cases) { + std::vector splits{"this", "is", "garbage"}; + + const auto& actual = ecf::algorithm::split_fields_at(splits, tc.input, tc.separator); + + BOOST_CHECK_MESSAGE(&actual == &splits, + "Expected the returned value and the input vector to be the same object"); + BOOST_CHECK_MESSAGE(actual == splits, "Expected the returned value and the input vector to have same content"); + BOOST_CHECK_MESSAGE(actual == tc.expected, + "Splitting >>>" << tc.input << "<<<, Expected " << ecf::algorithm::as_string(tc.expected) + << ", found " << ecf::algorithm::as_string(actual)); } } From 1d755463791399c35a3766fce7675902f710c61c Mon Sep 17 00:00:00 2001 From: Marcos Bento Date: Tue, 14 Apr 2026 15:07:01 +0100 Subject: [PATCH 26/90] refactor: replace class NodePath with standalone functions Re ECFLOW-2076 --- Viewer/libViewer/src/LogLoadData.cpp | 2 +- libs/base/src/ecflow/base/Gnuplot.cpp | 2 +- .../base/src/ecflow/base/cts/user/PlugCmd.cpp | 4 +- libs/core/src/ecflow/core/File.cpp | 10 +- libs/core/src/ecflow/core/NodePath.cpp | 25 +-- libs/core/src/ecflow/core/NodePath.hpp | 88 ++++++---- libs/core/test/TestFile.cpp | 8 +- libs/core/test/TestNodePath.cpp | 152 +++++++----------- libs/node/src/ecflow/node/Defs.cpp | 2 +- libs/node/src/ecflow/node/NodeFind.cpp | 4 +- 10 files changed, 151 insertions(+), 146 deletions(-) diff --git a/Viewer/libViewer/src/LogLoadData.cpp b/Viewer/libViewer/src/LogLoadData.cpp index 266089fb5..4a7c140c4 100644 --- a/Viewer/libViewer/src/LogLoadData.cpp +++ b/Viewer/libViewer/src/LogLoadData.cpp @@ -1394,7 +1394,7 @@ bool LogLoadData::extract_suite_path(const std::string& line, std::vector theNodeNames; theNodeNames.reserve(4); - NodePath::split(path, theNodeNames); + ecf::node::split_path(path, theNodeNames); if (!theNodeNames.empty()) { suite_name = theNodeNames[0]; } diff --git a/libs/base/src/ecflow/base/Gnuplot.cpp b/libs/base/src/ecflow/base/Gnuplot.cpp index 181e4d2c3..1a72cea0c 100644 --- a/libs/base/src/ecflow/base/Gnuplot.cpp +++ b/libs/base/src/ecflow/base/Gnuplot.cpp @@ -384,7 +384,7 @@ bool Gnuplot::extract_suite_path(const std::string& line, std::vector theNodeNames; theNodeNames.reserve(4); - NodePath::split(path, theNodeNames); + ecf::node::split_path(path, theNodeNames); if (!theNodeNames.empty()) { for (size_t n = 0; n < suite_vec.size(); n++) { if (suite_vec[n].suite_name_ == theNodeNames[0]) { diff --git a/libs/base/src/ecflow/base/cts/user/PlugCmd.cpp b/libs/base/src/ecflow/base/cts/user/PlugCmd.cpp index 41444968c..d4cc7dfbc 100644 --- a/libs/base/src/ecflow/base/cts/user/PlugCmd.cpp +++ b/libs/base/src/ecflow/base/cts/user/PlugCmd.cpp @@ -140,7 +140,7 @@ STC_Cmd_ptr PlugCmd::doHandleRequest(AbstractServer* as) const { // Dest could still be on the same server. Extract host and port // expect: host:port/suite/family/node - if (!NodePath::extractHostPort(dest_, host, port)) { + if (!ecf::node::extract_host_and_port_from_path(dest_, host, port)) { std::string errorMsg = "Plug command failed. The destination path "; errorMsg += dest_; errorMsg += " does not exist on server, and could not extract host/port from the destination path"; @@ -148,7 +148,7 @@ STC_Cmd_ptr PlugCmd::doHandleRequest(AbstractServer* as) const { } // Remove the host:port from the path - destPath = NodePath::removeHostPortFromPath(dest_); + destPath = ecf::node::remove_host_and_port_from_path(dest_); std::pair hostPortPair = as->hostPort(); if ((hostPortPair.first == host || host == "localhost") && hostPortPair.second == port) { diff --git a/libs/core/src/ecflow/core/File.cpp b/libs/core/src/ecflow/core/File.cpp index d8f9c57bf..3b27417a4 100644 --- a/libs/core/src/ecflow/core/File.cpp +++ b/libs/core/src/ecflow/core/File.cpp @@ -473,7 +473,7 @@ bool File::createMissingDirectories(const std::string& pathToFileOrDir) { } std::vector thePath; - NodePath::split(pathToFileOrDir, thePath); + ecf::node::split_path(pathToFileOrDir, thePath); try { if (!thePath.empty()) { @@ -642,7 +642,7 @@ File::backwardSearch(const std::string& rootPath, const std::string& nodePath, c // /task.ecf std::vector nodePathTokens; - NodePath::split(nodePath, nodePathTokens); + ecf::node::split_path(nodePath, nodePathTokens); LOG_ASSERT(!nodePathTokens.empty(), ""); std::string leafName; // i.e. task in the example above @@ -657,7 +657,7 @@ File::backwardSearch(const std::string& rootPath, const std::string& nodePath, c while (nodePathTokens.size() > 0) { // Reconstitute the path - std::string path = NodePath::createPath(nodePathTokens); + std::string path = ecf::node::create_node_path(nodePathTokens); path += fileExtn; // .ecf, .man , etc std::string combinedPath = rootPath; @@ -712,7 +712,7 @@ std::string File::forwardSearch(const std::string& rootPath, const std::string& /// /task.ecf std::vector nodePathTokens; - NodePath::split(nodePath, nodePathTokens); + ecf::node::split_path(nodePath, nodePathTokens); LOG_ASSERT(!nodePathTokens.empty(), ""); std::string leafName; @@ -727,7 +727,7 @@ std::string File::forwardSearch(const std::string& rootPath, const std::string& while (nodePathTokens.size() > 0) { // Reconstitute the path - std::string path = NodePath::createPath(nodePathTokens); + std::string path = ecf::node::create_node_path(nodePathTokens); path += fileExtn; // .ecf, .man , etc std::string combinedPath = rootPath; diff --git a/libs/core/src/ecflow/core/NodePath.cpp b/libs/core/src/ecflow/core/NodePath.cpp index e944a2dcb..f0265f2ad 100644 --- a/libs/core/src/ecflow/core/NodePath.cpp +++ b/libs/core/src/ecflow/core/NodePath.cpp @@ -12,20 +12,22 @@ #include "ecflow/core/Str.hpp" -using namespace ecf; +namespace ecf { -void NodePath::split(const std::string& path, std::vector& thePath) { +namespace node { + +void split_path(const std::string& path, std::vector& thePath) { /// The path is of the form "/suite/family/task" ecf::algorithm::split_at(thePath, path, ecf::string_constants::path_separator); } -bool NodePath::extractHostPort(const std::string& path, std::string& host, std::string& port) { +bool extract_host_and_port_from_path(const std::string& path, std::string& host, std::string& port) { if (path.empty()) { return false; } std::vector thePath; - NodePath::split(path, thePath); + split_path(path, thePath); if (thePath.empty()) { return false; @@ -53,7 +55,7 @@ bool NodePath::extractHostPort(const std::string& path, std::string& host, std:: return true; } -std::string NodePath::createPath(const std::vector& vec) { +std::string create_node_path(const std::vector& vec) { if (vec.empty()) { return std::string{}; } @@ -67,13 +69,18 @@ std::string NodePath::createPath(const std::vector& vec) { return ret; } -std::string NodePath::removeHostPortFromPath(const std::string& path) { +std::string remove_host_and_port_from_path(const std::string& path) { std::vector pathVec; - NodePath::split(path, pathVec); + split_path(path, pathVec); + pathVec.erase(pathVec.begin()); - return NodePath::createPath(pathVec); + return create_node_path(pathVec); } -bool NodePath::isAbsolutePath(const std::string& path) { +bool is_absolute_path(const std::string& path) { return !path.empty() && path[0] == '/'; } + +} // namespace node + +} // namespace ecf diff --git a/libs/core/src/ecflow/core/NodePath.hpp b/libs/core/src/ecflow/core/NodePath.hpp index 307fc30bc..32a02c979 100644 --- a/libs/core/src/ecflow/core/NodePath.hpp +++ b/libs/core/src/ecflow/core/NodePath.hpp @@ -14,35 +14,63 @@ #include #include -class NodePath { -public: - // Disable default construction - NodePath() = delete; - - /// returns the path as a vector of strings, preserving the order - /// Note: multiple path separator '/' are treated as one separator. - /// Mimics unix path conventions. hence - /// '/suite//family///task' will be extracted as 'suite','family','task' - static void split(const std::string& path, std::vector&); - - /// If the path has form: - /// :/suite/family/task - /// extract the host and port. Return OK, if successful - static bool extractHostPort(const std::string& path, std::string& host, std::string& port); - - /// Given a vector of strings , create a path. "suite","family", returns /suite/family - static std::string createPath(const std::vector&); - - /// Given a path like: //localhost:3141/suite/family/task - /// returns /suite/family/task - static std::string removeHostPortFromPath(const std::string& path); - - /// - /// @brief Check if the given path is an absolute path. - /// - /// @return true if absolute path, false otherwise. - /// - static bool isAbsolutePath(const std::string& path); -}; +namespace ecf { + +namespace node { + +/// +/// @brief Split the given node path into its components, using '/' as separator. +/// +/// The containers storing the path components will be cleared before storing any results. +/// +/// Multiple path separator '/' are treated as one separator, mimicing unix path conventions: +/// The path '/suite//family///task' will be split into [ 'suite', 'family', 'task' ] +/// +/// @param path The input node path to split. +/// @param components The vector to store the extracted components of the path. +/// +void split_path(const std::string& path, std::vector& components); + +/// +/// @brief Retrieve host and port values from the given path. +/// +/// Considers the path has the following form, and extracts the and values: +/// :/suite/family/task +/// +/// @param path The input node path to extract host and port from. +/// @param host The buffer to store the extracted host value. +/// @param port The buffer to store the extracted port value. +/// @return true if host and port were successfully extracted, false otherwise. +/// +bool extract_host_and_port_from_path(const std::string& path, std::string& host, std::string& port); + +/// @brief Creates a node path based on the given a vector of strings +/// +/// For the components [ "suite", "family", "task" ], returns the string "/suite/family/task". +/// +/// @param components The vector with the node path components to use to create the node path string. +/// @return The node path string created from the components. +/// +std::string create_node_path(const std::vector& components); + +/// +/// @brief Remove host and port information from the given path. +/// +/// @param path The input node path to remove host and port from. +/// @return The node path with host and port removed. +/// +/// For the node path "/localhost:3141/suite/family/task", return the path "/suite/family/task" +std::string remove_host_and_port_from_path(const std::string& path); + +/// +/// @brief Check if the given path is an absolute path. +/// +/// @return true if absolute path, false otherwise. +/// +bool is_absolute_path(const std::string& path); + +} // namespace node + +} // namespace ecf #endif /* ecflow_core_NodePath_HPP */ diff --git a/libs/core/test/TestFile.cpp b/libs/core/test/TestFile.cpp index 56a128a94..cffa04057 100644 --- a/libs/core/test/TestFile.cpp +++ b/libs/core/test/TestFile.cpp @@ -189,11 +189,11 @@ BOOST_AUTO_TEST_CASE(test_file_backwardSearch) { std::vector fileContents; fileContents.emplace_back("something"); std::vector nodePathTokens; - NodePath::split(nodePath, nodePathTokens); + ecf::node::split_path(nodePath, nodePathTokens); while (nodePathTokens.size() > 0) { // Reconstitute the path - std::string path = NodePath::createPath(nodePathTokens); + std::string path = ecf::node::create_node_path(nodePathTokens); std::string combinedPath = rootPath + path; BOOST_REQUIRE_MESSAGE(File::createDirectories(combinedPath), "Failed to create dirs " << combinedPath); @@ -263,10 +263,10 @@ BOOST_AUTO_TEST_CASE(test_file_forwardSearch) { std::vector fileContents; fileContents.emplace_back("something"); std::vector nodePathTokens; - NodePath::split(nodePath, nodePathTokens); + ecf::node::split_path(nodePath, nodePathTokens); while (nodePathTokens.size() > 0) { - std::string path = NodePath::createPath(nodePathTokens); + std::string path = ecf::node::create_node_path(nodePathTokens); std::string combinedPath = rootPath + path + File::ECF_EXTN(); // .ecf, .man , etc diff --git a/libs/core/test/TestNodePath.cpp b/libs/core/test/TestNodePath.cpp index 90c9659a9..924aca5ff 100644 --- a/libs/core/test/TestNodePath.cpp +++ b/libs/core/test/TestNodePath.cpp @@ -14,6 +14,7 @@ #include #include "ecflow/core/NodePath.hpp" +#include "ecflow/core/Str.hpp" #include "ecflow/core/Timer.hpp" #include "ecflow/test/scaffold/Naming.hpp" @@ -21,104 +22,72 @@ BOOST_AUTO_TEST_SUITE(U_Core) BOOST_AUTO_TEST_SUITE(T_NodePath) -static void checkPath(const std::vector& expectedPath, const std::string& path) { - std::vector thePath; - NodePath::split(path, thePath); - if (thePath != expectedPath) { - BOOST_CHECK_MESSAGE(false, "Failed for " << path); - std::ostringstream ss; - ss << "Expected '"; - std::copy(expectedPath.begin(), expectedPath.end(), std::ostream_iterator(ss, " ")); - ss << "'\nbut found '"; - std::copy(thePath.begin(), thePath.end(), std::ostream_iterator(ss, " ")); - ECF_TEST_DBG(<< ss.str()); - } -} - -BOOST_AUTO_TEST_CASE(test_path_extractor_constructor) { - ECF_NAME_THIS_TEST(); - - BOOST_CHECK(true); // stop boost test from complaining about no checks - - std::vector theExpectedPath; - checkPath(theExpectedPath, ""); - - theExpectedPath.emplace_back("suite"); - checkPath(theExpectedPath, "/suite"); - checkPath(theExpectedPath, "suite"); -} - -BOOST_AUTO_TEST_CASE(test_path_extractor) { - ECF_NAME_THIS_TEST(); - - BOOST_CHECK(true); // stop boost test from complaining about no checks - - std::vector theExpectedPath; - theExpectedPath.emplace_back("suite"); - theExpectedPath.emplace_back("family"); - theExpectedPath.emplace_back("task"); - - checkPath(theExpectedPath, "/suite/family/task"); - checkPath(theExpectedPath, "/suite/family/task/"); -} - BOOST_AUTO_TEST_CASE(test_unix_path_extractor) { ECF_NAME_THIS_TEST(); - BOOST_CHECK(true); // stop boost test from complaining about no checks - - // On Unix multiple '/' are treated as one. - std::vector theExpectedPath; - theExpectedPath.emplace_back("suite"); - theExpectedPath.emplace_back("family"); - theExpectedPath.emplace_back("task"); - - checkPath(theExpectedPath, "/suite///family////task"); - checkPath(theExpectedPath, "/suite///family////task//"); - checkPath(theExpectedPath, "//suite///family////task//"); - checkPath(theExpectedPath, "///suite///family////task//"); - checkPath(theExpectedPath, "///suite///family////task///"); + struct tc + { + std::string path; + std::vector expected; + }; + + std::vector test_cases = {{"suite", {"suite"}}, + {"/suite", {"suite"}}, + {"/suite/family/task", {"suite", "family", "task"}}, + {"/suite/family/task/", {"suite", "family", "task"}}, + {"/suite///family////task", {"suite", "family", "task"}}, + {"/suite///family////task//", {"suite", "family", "task"}}, + {"//suite///family////task//", {"suite", "family", "task"}}, + {"///suite///family////task//", {"suite", "family", "task"}}, + {"///suite///family////task///", {"suite", "family", "task"}}}; + + for (const auto& tc : test_cases) { + + std::vector actual; + ecf::node::split_path(tc.path, actual); + BOOST_CHECK_MESSAGE(actual == tc.expected, + "Splitting " << tc.path << ", expected " << ecf::algorithm::as_string(tc.expected) + << ", found " << ecf::algorithm::as_string(actual)); + } } BOOST_AUTO_TEST_CASE(test_extractHostPort) { ECF_NAME_THIS_TEST(); - std::string path; - std::string host; - std::string port; - BOOST_CHECK_MESSAGE(!NodePath::extractHostPort(path, host, port), "expected failure"); - - path = "Apath"; - BOOST_CHECK_MESSAGE(!NodePath::extractHostPort(path, host, port), "expected failure"); - - path = " : "; - BOOST_CHECK_MESSAGE(!NodePath::extractHostPort(path, host, port), "expected failure"); - - path = "host:"; - BOOST_CHECK_MESSAGE(!NodePath::extractHostPort(path, host, port), "expected failure"); - - path = ":port"; - BOOST_CHECK_MESSAGE(!NodePath::extractHostPort(path, host, port), "expected failure"); - - path = "host:port"; - BOOST_CHECK_MESSAGE(NodePath::extractHostPort(path, host, port), "expected success " << host << ":" << port); - BOOST_CHECK_MESSAGE(host == "host" && port == "port", "expected 'host:port' found " << host << ":" << port); - - path = "//host:port"; - BOOST_CHECK_MESSAGE(NodePath::extractHostPort(path, host, port), "expected success " << host << ":" << port); - BOOST_CHECK_MESSAGE(host == "host" && port == "port", "expected 'host:port' found " << host << ":" << port); - - path = "//host:port/"; - BOOST_CHECK_MESSAGE(NodePath::extractHostPort(path, host, port), "expected success " << host << ":" << port); - BOOST_CHECK_MESSAGE(host == "host" && port == "port", "expected 'host:port' found " << host << ":" << port); - - path = "//host:port/suite"; - BOOST_CHECK_MESSAGE(NodePath::extractHostPort(path, host, port), "expected success " << host << ":" << port); - BOOST_CHECK_MESSAGE(host == "host" && port == "port", "expected 'host:port' found " << host << ":" << port); - - path = "//host:port/suite/family/task"; - BOOST_CHECK_MESSAGE(NodePath::extractHostPort(path, host, port), "expected success " << host << ":" << port); - BOOST_CHECK_MESSAGE(host == "host" && port == "port", "expected 'host:port' found " << host << ":" << port); + struct tc + { + std::string path; + bool expected; + std::string expected_host; + std::string expected_port; + }; + + std::vector test_cases = {{"", false, "", ""}, + {"Apath", false, "", ""}, + {" : ", false, "", ""}, + {"host:", false, "host", ""}, + {":port", false, "", "port"}, + {"host:port", true, "host", "port"}, + {"//host:port", true, "host", "port"}, + {"//host:port/", true, "host", "port"}, + {"//host:port/suite", true, "host", "port"}, + {"//host:port/suite/family/task", true, "host", "port"}}; + + for (const auto& tc : test_cases) { + std::string actual_host; + std::string actual_port; + auto actual = ecf::node::extract_host_and_port_from_path(tc.path, actual_host, actual_port); + + BOOST_REQUIRE_MESSAGE(actual == tc.expected, + "For path " << tc.path << ", expected " << tc.expected << " found " << actual); + + BOOST_REQUIRE_MESSAGE(actual_host == tc.expected_host, + "For path '" << tc.path << "', expected host '" << tc.expected_host << "' found '" + << actual_host << "'"); + BOOST_REQUIRE_MESSAGE(actual_port == tc.expected_port, + "For path '" << tc.path << "', expected port '" << tc.expected_port << "' found '" + << actual_port << "'"); + } } BOOST_AUTO_TEST_CASE(test_NodePath_perf, *boost::unit_test::disabled()) { @@ -136,7 +105,8 @@ BOOST_AUTO_TEST_CASE(test_NodePath_perf, *boost::unit_test::disabled()) { thePath.reserve(20); for (int i = 0; i < n; i++) { thePath.clear(); - NodePath::split("/this/is/a/test/string/that/will/be/used/to/check/perf/of/node/path/extraction", thePath); + ecf::node::split_path("/this/is/a/test/string/that/will/be/used/to/check/perf/of/node/path/extraction", + thePath); } ECF_TEST_DBG(<< "Timing for " << n << " NodePath is " << timer); } diff --git a/libs/node/src/ecflow/node/Defs.cpp b/libs/node/src/ecflow/node/Defs.cpp index 9ff63fbdf..734d6f958 100644 --- a/libs/node/src/ecflow/node/Defs.cpp +++ b/libs/node/src/ecflow/node/Defs.cpp @@ -941,7 +941,7 @@ node_ptr Defs::findAbsNode(const std::string& pathToNode) const { node_ptr Defs::find_closest_matching_node(const std::string& pathToNode) const { std::vector theNodeNames; - NodePath::split(pathToNode, theNodeNames); + ecf::node::split_path(pathToNode, theNodeNames); if (theNodeNames.empty()) { return node_ptr(); } diff --git a/libs/node/src/ecflow/node/NodeFind.cpp b/libs/node/src/ecflow/node/NodeFind.cpp index 052dd34e5..68891a380 100644 --- a/libs/node/src/ecflow/node/NodeFind.cpp +++ b/libs/node/src/ecflow/node/NodeFind.cpp @@ -745,7 +745,7 @@ Node::findReferencedNode(const std::string& nodePath, const std::string& extern_ #endif // if an absolute path cut in early - if (NodePath::isAbsolutePath(nodePath)) { + if (ecf::node::is_absolute_path(nodePath)) { #ifdef DEBUG_FIND_REFERENCED_NODE debug_path += "(!nodePath.empty() && nodePath[0] == '/') \n"; @@ -779,7 +779,7 @@ Node::findReferencedNode(const std::string& nodePath, const std::string& extern_ /// Path is something other than ABSOLUTE path /// ============================================================================= std::vector theExtractedPath; - NodePath::split(nodePath, theExtractedPath); + ecf::node::split_path(nodePath, theExtractedPath); #ifdef DEBUG_FIND_REFERENCED_NODE debug_path += "extracted path = "; From 02a695f33b7c810614fccc5bb44c8cbba179af5d Mon Sep 17 00:00:00 2001 From: Marcos Bento Date: Thu, 16 Apr 2026 11:36:58 +0100 Subject: [PATCH 27/90] refactor: improve documentation --- libs/core/src/ecflow/core/Base64.hpp | 54 +++---- libs/core/src/ecflow/core/CommandLine.hpp | 101 ++++++++------ libs/core/src/ecflow/core/Enumerate.hpp | 101 ++++++++------ libs/core/src/ecflow/core/Environment.hpp | 80 ++++++----- libs/core/src/ecflow/core/Extract.hpp | 63 +++++---- libs/core/src/ecflow/core/Filesystem.hpp | 28 ++-- libs/core/src/ecflow/core/Host.hpp | 83 +++++++++-- libs/core/src/ecflow/core/NodePath.hpp | 46 +++--- libs/core/src/ecflow/core/Str.hpp | 46 +++--- libs/core/src/ecflow/core/TimeStamp.cpp | 34 ++++- libs/core/src/ecflow/core/TimeStamp.hpp | 41 ++++-- libs/core/src/ecflow/core/Timer.hpp | 163 +++++++++++++++++----- libs/core/src/ecflow/core/User.hpp | 5 + libs/core/src/ecflow/core/Version.hpp | 68 +++++++-- 14 files changed, 607 insertions(+), 306 deletions(-) diff --git a/libs/core/src/ecflow/core/Base64.hpp b/libs/core/src/ecflow/core/Base64.hpp index ccb1be9f4..24379e729 100644 --- a/libs/core/src/ecflow/core/Base64.hpp +++ b/libs/core/src/ecflow/core/Base64.hpp @@ -16,37 +16,37 @@ namespace ecf { -/** - * @brief Decode a base64 encoded string. - * - * @param value the base64 encoded string - * @return the decoded string - * - * @throws std::invalid_argument if the input string has size not multiple of 4 - * @throws std::invalid_argument if any character in the input string is invalid (not in A-Za-z0-9+/ or '=' for padding) - * @throws std::invalid_argument if padding is invalid (more than 2 '=', or non-padding characters after first '=') - */ +/// +/// @brief Decode a base64 encoded string. +/// +/// @param value the base64 encoded string +/// @return the decoded string +/// +/// @throws std::invalid_argument if the input has size not multiple of 4 +/// @throws std::invalid_argument if any character in the input is invalid (not in A-Za-z0-9+/ or '=' for padding) +/// @throws std::invalid_argument if padding is invalid (more than 2 '=', or non-padding characters after first '=') +/// std::string decode_base64(std::string_view value); -/** - * @brief Encode a string to base64. - * - * @param value the string to encode - * @return the base64 encoded string - */ +/// +/// @brief Encode a string to base64. +/// +/// @param value the string to encode +/// @return the base64 encoded string +/// std::string encode_base64(std::string_view value); -/** - * @brief Simple validation of base64 encoded string. - * - * Checks the following conditions: - * - length has to be multiple of 4 (n.b. 0 if a valid lenght) - * - valid characters are A-Za-z0-9+/ - * - '=' can be used for padding (0, 1, or 2 characters) at the end of string - * - * @param value the base64 encoded string - * @return true if valid, false otherwise - */ +/// +/// @brief Simple validation of base64 encoded string. +/// +/// Checks the following conditions: +/// - length has to be multiple of 4 (n.b. 0 if a valid lenght) +/// - valid characters are A-Za-z0-9+/ +/// - '=' can be used for padding (0, 1, or 2 characters) at the end of string +/// +/// @param value the base64 encoded string +/// @return true if valid, false otherwise +/// bool validate_base64(std::string_view value); } // namespace ecf diff --git a/libs/core/src/ecflow/core/CommandLine.hpp b/libs/core/src/ecflow/core/CommandLine.hpp index 15e752260..0e5308956 100644 --- a/libs/core/src/ecflow/core/CommandLine.hpp +++ b/libs/core/src/ecflow/core/CommandLine.hpp @@ -15,12 +15,12 @@ #include #include -/** - * CommandLine is a thin wrapper/container for the contents of an ecFlow client command line. - * - * Note: This class is related to class ArgvCreator, which takes the vector of the command - * line tokens and converts them into argc+argv. - */ +/// +/// @brief CommandLine is a thin wrapper/container for the contents of an ecFlow client command line. +/// +/// Note: This class is related to class ArgvCreator, which takes the vector of the command +/// line tokens and converts them into argc+argv. +/// class CommandLine { public: using cl_t = std::string; @@ -28,37 +28,40 @@ class CommandLine { using size_t = tokens_t::size_type; public: - /** - * Create a Command Line instance based on argc + argv - * - * @param argc the number of tokens in the command line - * @param argv the tokens in the command line - */ + /// + /// @brief Create a Command Line instance based on argc + argv + /// + /// @param argc the number of tokens in the command line + /// @param argv the tokens in the command line + /// CommandLine(int argc, char** argv); CommandLine(int argc, const char** argv); - /** - * Create a Command Line instance based on a sequence of characters, - * considering that it might include single/double quotes. - * - * @param cl the command line as a sequence of characters - */ + /// + /// @brief Create a Command Line instance based on a sequence of characters, + /// considering that it might include single/double quotes. + /// + /// @param cl the command line as a sequence of characters + /// @throws std::runtime_error if the command line contains an invalid escape sequence + /// or unmatched quotation marks + /// explicit CommandLine(const cl_t& cl); - /** - * Create a Command Line instance based on a sequence of tokens. - * - * @param tokens the command line as a sequence of tokens - */ + /// + /// @brief Create a Command Line instance based on a sequence of tokens. + /// + /// @param tokens the command line as a sequence of tokens + /// explicit CommandLine(tokens_t tokens); - /** - * Create a Command Line instance based on multiple tokens. - * Tokens can be either strings or collections of strings. In the latter case, the collection of strings are - * added to the command line element-by-element. - * - * @param tokens the command line as multiple tokens - */ + /// + /// @brief Create a Command Line instance based on multiple tokens. + /// Tokens can be either strings or collections of strings. In the latter case, the collection of strings are + /// added to the command line element-by-element. + /// + /// @param tokens the command line as multiple tokens + /// @return a CommandLine instance composed of the provided tokens + /// template static CommandLine make_command_line(TOKENS... tokens) { auto push = Overload{ @@ -72,28 +75,34 @@ class CommandLine { return CommandLine(ts); } - /** - * Access the original content of the command line. - */ + /// + /// @brief Access the original content of the command line. + /// + /// @return the command line as a single string, with tokens separated by spaces + /// [[nodiscard]] cl_t original() const; - /** - * Access the number of tokens composing the command line. - * - * @return the number of tokens - */ + /// + /// @brief Access the number of tokens composing the command line. + /// + /// @return the number of tokens + /// [[nodiscard]] size_t size() const; - /** - * Access the sequence of tokens composing the command line. - * - * @return the command line tokens - */ + /// + /// @brief Access the sequence of tokens composing the command line. + /// + /// @return the command line tokens + /// [[nodiscard]] const tokens_t& tokens() const; - /** - * Pretty print the command line - */ + /// + /// @brief Pretty print the command line + /// + /// @param os the output stream to write to + /// @param cl the command line to print + /// @return the output stream @p os + /// friend std::ostream& operator<<(std::ostream& os, const CommandLine& cl); private: diff --git a/libs/core/src/ecflow/core/Enumerate.hpp b/libs/core/src/ecflow/core/Enumerate.hpp index dd1fe6b40..0c240c959 100644 --- a/libs/core/src/ecflow/core/Enumerate.hpp +++ b/libs/core/src/ecflow/core/Enumerate.hpp @@ -24,16 +24,15 @@ namespace ecf { namespace detail { -/** - * EnumTraits defines the mapping between a set of enum values and their designation. - * - * EnumTraits must define: - * - the mapping `map`, provided as a std::array composed of a std::pair for each enum value - * - the `size`, holding the number os entries in `map` - * - * @tparam E - */ - +/// +/// @brief EnumTraits defines the mapping between a set of enum values and their designation. +/// +/// EnumTraits must define: +/// - the mapping `map`, provided as a std::array composed of a std::pair for each enum value +/// - the `size`, holding the number of entries in `map` +/// +/// @tparam E the enum type for which the traits are defined +/// template struct EnumTraits { @@ -41,6 +40,12 @@ struct EnumTraits } // namespace detail +/// +/// @brief Enumerate provides bidirectional mapping between enum values and their string designations. +/// +/// @tparam E the enum type to map +/// @tparam TRAITS the traits type providing the mapping; defaults to detail::EnumTraits +/// template > struct Enumerate { @@ -48,15 +53,15 @@ struct Enumerate using enum_t = E; using string_t = std::string_view; - /** - * Convert the given enum value to its designation - * - * This is an "unsafe" operation, as it assumes that the given enum value exists in the mapping. - * If the enum value does not exist, an assertion failure is raised. - * - * @param e the enum value - * @return the associated designation - */ + /// + /// @brief Convert the given enum value to its designation. + /// + /// This is an "unsafe" operation, as it assumes that the given enum value exists in the mapping. + /// If the enum value does not exist, an assertion failure is raised. + /// + /// @param e the enum value + /// @return the associated designation + /// static constexpr string_t as_string(enum_t e) noexcept { auto found = std::find_if( std::begin(TRAITS::map), std::end(TRAITS::map), [&](const auto& item) { return item.first == e; }); @@ -66,12 +71,12 @@ struct Enumerate return found->second; } - /** - * Convert the given enum value to its designation - * - * @param e the enum value - * @return the associated designation, in case it exists; an empty optional, otherwise - */ + /// + /// @brief Convert the given enum value to its designation. + /// + /// @param e the enum value + /// @return the associated designation, in case it exists; an empty optional, otherwise + /// static constexpr std::optional to_string(enum_t e) noexcept { if (auto found = std::find_if( std::begin(TRAITS::map), std::end(TRAITS::map), [&](const auto& item) { return item.first == e; }); @@ -82,12 +87,12 @@ struct Enumerate return std::nullopt; } - /** - * Convert the given designation to the related enum value - * - * @param s the designation - * @return the enum value, in case it exists; an empty optional, otherwise - */ + /// + /// @brief Convert the given designation to the related enum value. + /// + /// @param s the designation + /// @return the enum value, in case it exists; an empty optional, otherwise + /// static constexpr std::optional to_enum(string_t s) noexcept { if (auto found = std::find_if( std::begin(TRAITS::map), std::end(TRAITS::map), [&](const auto& item) { return item.second == s; }); @@ -98,26 +103,28 @@ struct Enumerate return std::nullopt; } - /** - * Checks if the given designation is valid (i.e. has a related enum value) - * - * @param s the designation - * @return true, if valid; false, otherwise - */ + /// + /// @brief Check if the given designation is valid (i.e. has a related enum value). + /// + /// @param s the designation + /// @return true, if valid; false, otherwise + /// static constexpr bool is_valid(string_t s) { auto found = std::find_if( std::begin(TRAITS::map), std::end(TRAITS::map), [&](const auto& item) { return item.second == s; }); return found != std::end(TRAITS::map); } - /** - * The number of mapped enum values - */ + /// + /// @brief The number of mapped enum values. + /// static const size_t size = TRAITS::size; - /** - * The vector of mapped enum values - */ + /// + /// @brief Collect all mapped enum values. + /// + /// @return a vector containing all mapped enum values + /// static auto enums() { std::vector result; result.reserve(TRAITS::size); @@ -128,9 +135,11 @@ struct Enumerate return result; } - /** - * The vector of mapped designations - */ + /// + /// @brief Collect all mapped designations. + /// + /// @return a vector containing all mapped designations + /// static auto designations() { std::vector result; result.reserve(TRAITS::size); diff --git a/libs/core/src/ecflow/core/Environment.hpp b/libs/core/src/ecflow/core/Environment.hpp index 674d34973..63e982dca 100644 --- a/libs/core/src/ecflow/core/Environment.hpp +++ b/libs/core/src/ecflow/core/Environment.hpp @@ -143,23 +143,26 @@ struct Wrapper } // namespace +/// +/// @brief Exception thrown when a required environment variable is not found. +/// struct EnvVarNotFound : public std::runtime_error { explicit EnvVarNotFound(std::basic_string what) : std::runtime_error(what) {} }; -/** - * @brief Retrieves the environment variable value and stores it in the given variable. - * If the environment variable is not found, the variable is left unchanged. - * - * In case of integral types, the environment variable is converted to the corresponding type. - * In case of bool type, if the environment variable is set, the variable is set to true. - * - * @tparam T - * @param name - * @param value - */ +/// +/// @brief Retrieve the environment variable value and store it in the given variable. +/// If the environment variable is not found, the variable is left unchanged. +/// +/// For integral types, the environment variable value is converted to the corresponding type. +/// For bool type, if the environment variable is set, the variable is set to true. +/// +/// @tparam T the type to convert the environment variable value to +/// @param name the name of the environment variable +/// @param value the variable to store the retrieved value in +/// template void get(const char* name, T& value) { if (auto found = Wrapper::get(name); found) { @@ -167,16 +170,18 @@ void get(const char* name, T& value) { } } -/** - * @brief Retrieves the environment variable value and returns it. - * If the environment variable is not found, an exception is thrown. - * - * In case of integral types, the environment variable is converted to the corresponding type. - * In case of bool type, if the environment variable is set, the result is true. - * - * @tparam T - * @param name - */ +/// +/// @brief Retrieve the environment variable value and return it. +/// If the environment variable is not found, an exception is thrown. +/// +/// For integral types, the environment variable value is converted to the corresponding type. +/// For bool type, if the environment variable is set, the result is true. +/// +/// @tparam T the type to convert the environment variable value to; defaults to std::string +/// @param name the name of the environment variable +/// @return the value of the environment variable, converted to type T +/// @throws EnvVarNotFound if the environment variable is not set +/// template T get(const char* name) { if (auto found = Wrapper::get(name); found) { @@ -186,27 +191,28 @@ T get(const char* name) { throw EnvVarNotFound(Message(name).str()); } -/** - * @brief Retrieves the environment variable value and returns it (wrapped in a std::optional<>). - * If the environment variable is not found, std::nullopt is returned. - * - * In case of integral types, the environment variable is converted to the corresponding type. - * In case of bool type, if the environment variable is set, the result is true. - * - * @tparam T - * @param name - */ +/// +/// @brief Retrieve the environment variable value and return it wrapped in a std::optional. +/// If the environment variable is not found, std::nullopt is returned. +/// +/// For integral types, the environment variable value is converted to the corresponding type. +/// For bool type, if the environment variable is set, the result is true. +/// +/// @tparam T the type to convert the environment variable value to; defaults to std::string +/// @param name the name of the environment variable +/// @return the value of the environment variable converted to type T, or std::nullopt if not set +/// template std::optional fetch(const char* name) { return Wrapper::get(name); } -/** - * @brief Checks if an environment variable is set. - * - * @tparam T - * @param name - */ +/// +/// @brief Check if an environment variable is set. +/// +/// @param name the name of the environment variable +/// @return true if the environment variable is set; false otherwise +/// inline bool has(const char* name) { return std::getenv(name) != nullptr; } diff --git a/libs/core/src/ecflow/core/Extract.hpp b/libs/core/src/ecflow/core/Extract.hpp index 6bee6fbd8..f6b01db14 100644 --- a/libs/core/src/ecflow/core/Extract.hpp +++ b/libs/core/src/ecflow/core/Extract.hpp @@ -23,31 +23,38 @@ class Extract { Extract() = delete; /// - /// Extract path and name from given token. + /// @brief Extract path and name from given token. /// - /// Given + /// Given: /// "/suite/family:obj", extracts path = "/suite/family", and name = "obj" /// "/suite/family", extracts path = "/suite/family", and name = "" /// "obj", extracts path = "", and name = "obj" - /// @returns true if extraction succeeded; false, otherwise + /// + /// @param token the input token to extract from + /// @param path the extracted path component + /// @param name the extracted name component + /// @return true if extraction succeeded; false, otherwise /// static bool pathAndName(const std::string& token, std::string& path, std::string& name); /// - /// Extract 2nd token from the given string (considering provided separator) + /// @brief Extract the 2nd token from the given string, using the provided separator. /// - /// Given str = "HH:MM", extracts ret = "MM" + /// Given str = "HH:MM", extracts ret = "MM". /// - /// @returns true if extraction succeeded; false, otherwise + /// @param str the input string to split + /// @param ret the extracted second token + /// @param separator the character used to split the string + /// @return true if extraction succeeded; false, otherwise /// static bool split_get_second(const std::string& str, std::string& ret, char separator = ':'); /// - /// Extract a value of the given type from the provided token + /// @brief Extract a value of the given arithmetic type from the provided token. /// - /// @tparam TO the type of the value to extract - /// @param token the input token - /// @return a Result object, containing either the extracted value, or an error message + /// @tparam TO the arithmetic type of the value to extract + /// @param token the input token to convert + /// @return a Result object containing either the extracted value, or an error message /// template >> static ecf::Result value(const std::string& token) { @@ -61,12 +68,13 @@ class Extract { } /// - /// Extract a value of the given type from the provided token + /// @brief Extract a value of the given type from the provided token, throwing on failure. /// /// @tparam TO the type of the value to extract - /// @param token the input token - /// @param error the error message to the included in the thrown exception - /// @return the extracted value, or throws std::runtime_error exception with the specified error message + /// @param token the input token to convert + /// @param error the error message to include in the thrown exception + /// @return the extracted value + /// @throws std::runtime_error if the token cannot be converted to the requested type /// template static TO value(const std::string& token, const std::string& error) { @@ -78,32 +86,39 @@ class Extract { } /// - /// Extract YMD integer, of the form yyyymmdd, from the given token + /// @brief Extract a YMD integer of the form yyyymmdd from the given token. /// - /// @throws std::runtime_error if extractions fails, with the provided error message included in exception message + /// @param ymdToken the input token containing the date in yyyymmdd format + /// @param errorMsg the error message to include in the thrown exception on failure + /// @return the extracted date as an integer of the form yyyymmdd + /// @throws std::runtime_error if the token is not a valid 8-character date /// static int ymd(const std::string& ymdToken, std::string& errorMsg); /// - /// Extract (optional) integer, from token in index pos + /// @brief Extract an optional integer from the token at the given index. /// - /// Notice: when first character of selected token is '#', the return is the provided default value + /// When the first character of the selected token is '#', the default value is returned. /// - /// Example of tokens: + /// Example: /// ["repeat", "integer", "variable", "1", "2", "#a", "comment"] /// - extracting from "repeat", "integer", "variable", or "comment" throws std::runtime_error /// - extracting from "1", returns 1 - /// - extracting from "2", returns 3 + /// - extracting from "2", returns 2 /// - extracting from "#a", returns the given default value /// ["repeat", "integer", "variable", "1", "2", "#", "a", "comment"] /// - extracting from "repeat", "integer", "variable", "a", or "comment" throws std::runtime_error /// - extracting from "1", returns 1 - /// - extracting from "2", returns 3 + /// - extracting from "2", returns 2 /// - extracting from "#", returns the given default value /// - /// @returns the extracted integer, if extraction succeeded; default value if selected token starts with '#' - /// @throws std::runtime_error if extractions fails, with the provided error message included in exception message - /// + /// @tparam TO the type to use for the conversion + /// @param tokens the sequence of tokens to extract from + /// @param pos the index of the token to extract + /// @param defaultValue the value to return when the selected token starts with '#' + /// @param errorMsg the error message to include in the thrown exception on failure + /// @return the extracted integer, or @p defaultValue if the selected token starts with '#' + /// @throws std::runtime_error if extraction fails /// template static int diff --git a/libs/core/src/ecflow/core/Filesystem.hpp b/libs/core/src/ecflow/core/Filesystem.hpp index 2db589bd0..a0dcecedd 100644 --- a/libs/core/src/ecflow/core/Filesystem.hpp +++ b/libs/core/src/ecflow/core/Filesystem.hpp @@ -18,21 +18,23 @@ namespace fs = std::filesystem; namespace ecf { namespace fsx { -/** - * Generate a unique path from the given model. - * The one or more '%' characters in the model which will be replaced by random hexadecimal characters (0-9, A-F). - * - * @param model the model for the unique path - * @return the unique path - */ +/// +/// @brief Generate a unique path from the given model. +/// +/// Each '%' character in @p model is replaced by a random hexadecimal digit (0-9, A-F). +/// +/// @param model the path model, with '%' characters as placeholders for random hex digits +/// @return the generated path with all '%' characters replaced by random hex digits +/// std::filesystem::path unique_path(std::string model); -/** - * Get the last write time of the given path, as a std::chrono::system_clock::time_point. - * - * @param path the path - * @return the last write time - */ +/// +/// @brief Get the last write time of the given path as a std::chrono::system_clock::time_point. +/// +/// @param path the path to query +/// @return the last write time of @p path +/// @throws std::filesystem::filesystem_error if @p path does not exist or cannot be accessed +/// std::chrono::system_clock::time_point last_write_time(const std::filesystem::path& path); } // namespace fsx diff --git a/libs/core/src/ecflow/core/Host.hpp b/libs/core/src/ecflow/core/Host.hpp index 12316e485..4e66e8d9e 100644 --- a/libs/core/src/ecflow/core/Host.hpp +++ b/libs/core/src/ecflow/core/Host.hpp @@ -15,11 +15,27 @@ namespace ecf { +/// +/// @brief Represents a host and provides helpers to build ecFlow server file names +/// prefixed with the host and port (e.g. @c ..ecf.log). +/// class Host { public: - /// can throw std::runtime_error if the gethostname fails + /// + /// @brief Create a Host instance by resolving the current machine's hostname. + /// + /// @throws std::runtime_error if the hostname cannot be determined + /// Host(); + /// + /// @brief Create a Host instance with the given hostname. + /// + /// If @p host is @c "localhost", the actual machine hostname is resolved. + /// + /// @param host the hostname to use + /// @throws std::runtime_error if @p host is @c "localhost" and the hostname cannot be determined + /// explicit Host(const std::string& host); // Disable copy (and move) semantics @@ -30,28 +46,77 @@ class Host { ~Host() = default; - /// return the host name + /// + /// @brief Get the hostname. + /// + /// @return the hostname + /// std::string name() const; - /// returns the log file name + /// + /// @brief Get the log file name prefixed with the host and port. + /// + /// @param port the server port + /// @return the log file name of the form @c ..ecf.log + /// std::string ecf_log_file(const std::string& port) const; - /// return checkPoint file + /// + /// @brief Get the checkpoint file name prefixed with the host and port. + /// + /// @param port the server port + /// @return the checkpoint file name of the form @c ..check + /// std::string ecf_checkpt_file(const std::string& port) const; - /// return backup checkPoint file + /// + /// @brief Get the backup checkpoint file name prefixed with the host and port. + /// + /// @param port the server port + /// @return the backup checkpoint file name of the form @c ..check.b + /// std::string ecf_backup_checkpt_file(const std::string& port) const; - /// return ecf.list file. White list file used for authentication & authorisation + /// + /// @brief Get the white-list file name prefixed with the host and port. + /// + /// The white-list file is used for authentication and authorisation. + /// + /// @param port the server port + /// @return the white-list file name of the form @c ..ecf.lists + /// std::string ecf_lists_file(const std::string& port) const; - /// return ecf.passwd file. Used for authentication + /// + /// @brief Get the password file name prefixed with the host and port. + /// + /// The password file is used for authentication. + /// + /// @param port the server port + /// @return the password file name of the form @c ..ecf.passwd + /// std::string ecf_passwd_file(const std::string& port) const; - /// return ecf.custom_passwd file. Used for authentication + /// + /// @brief Get the custom password file name prefixed with the host and port. + /// + /// The custom password file is used for authentication. + /// + /// @param port the server port + /// @return the custom password file name of the form @c ..ecf.custom_passwd + /// std::string ecf_custom_passwd_file(const std::string& port) const; - /// Given a port and file name, will return ..file_name + /// + /// @brief Build a file name prefixed with the host and port. + /// + /// If @p file_name contains a path separator (@c /), it is returned unchanged. + /// Otherwise, returns a name of the form @c ... + /// + /// @param port the server port + /// @param file_name the base file name or absolute path + /// @return the prefixed file name, or @p file_name unchanged if it contains a path separator + /// std::string prefix_host_and_port(const std::string& port, std::string_view file_name) const; private: diff --git a/libs/core/src/ecflow/core/NodePath.hpp b/libs/core/src/ecflow/core/NodePath.hpp index 32a02c979..8f00d40a7 100644 --- a/libs/core/src/ecflow/core/NodePath.hpp +++ b/libs/core/src/ecflow/core/NodePath.hpp @@ -21,51 +21,55 @@ namespace node { /// /// @brief Split the given node path into its components, using '/' as separator. /// -/// The containers storing the path components will be cleared before storing any results. +/// The container storing the path components will be cleared before storing any results. /// -/// Multiple path separator '/' are treated as one separator, mimicing unix path conventions: -/// The path '/suite//family///task' will be split into [ 'suite', 'family', 'task' ] +/// Multiple consecutive '/' separators are treated as one, mimicking unix path conventions: +/// the path '/suite//family///task' will be split into [ 'suite', 'family', 'task' ] /// -/// @param path The input node path to split. -/// @param components The vector to store the extracted components of the path. +/// @param path the input node path to split +/// @param components the vector to store the extracted path components /// void split_path(const std::string& path, std::vector& components); /// -/// @brief Retrieve host and port values from the given path. +/// @brief Extract host and port values from the given path. /// -/// Considers the path has the following form, and extracts the and values: -/// :/suite/family/task +/// Expects the path to have the following form and extracts the @c and @c values: +/// @c :/suite/family/task /// -/// @param path The input node path to extract host and port from. -/// @param host The buffer to store the extracted host value. -/// @param port The buffer to store the extracted port value. -/// @return true if host and port were successfully extracted, false otherwise. +/// @param path the input node path to extract host and port from +/// @param host the buffer to store the extracted host value +/// @param port the buffer to store the extracted port value +/// @return true if host and port were successfully extracted; false otherwise /// bool extract_host_and_port_from_path(const std::string& path, std::string& host, std::string& port); -/// @brief Creates a node path based on the given a vector of strings /// -/// For the components [ "suite", "family", "task" ], returns the string "/suite/family/task". +/// @brief Create a node path string from the given vector of path components. /// -/// @param components The vector with the node path components to use to create the node path string. -/// @return The node path string created from the components. +/// For the components @c [ "suite", "family", "task" ], returns @c "/suite/family/task". +/// Returns an empty string if @p components is empty. +/// +/// @param components the path components to join +/// @return the node path string created from the components /// std::string create_node_path(const std::vector& components); /// -/// @brief Remove host and port information from the given path. +/// @brief Remove the host and port prefix from the given path. +/// +/// For the node path @c ":/suite/family/task", returns @c "/suite/family/task". /// -/// @param path The input node path to remove host and port from. -/// @return The node path with host and port removed. +/// @param path the input node path to remove the host and port prefix from +/// @return the node path with the host and port prefix removed /// -/// For the node path "/localhost:3141/suite/family/task", return the path "/suite/family/task" std::string remove_host_and_port_from_path(const std::string& path); /// /// @brief Check if the given path is an absolute path. /// -/// @return true if absolute path, false otherwise. +/// @param path the path to check +/// @return true if @p path is an absolute path; false otherwise /// bool is_absolute_path(const std::string& path); diff --git a/libs/core/src/ecflow/core/Str.hpp b/libs/core/src/ecflow/core/Str.hpp index a37e0cb58..d3bb69f58 100644 --- a/libs/core/src/ecflow/core/Str.hpp +++ b/libs/core/src/ecflow/core/Str.hpp @@ -25,29 +25,42 @@ namespace ecf { +/// +/// @brief Common string constants used throughout ecFlow. +/// +/// This namespace provides a centralised set of well-known literal strings to avoid +/// ad-hoc string construction and to make intent explicit at call sites. +/// namespace string_constants { +/// Command prefixes used to identify the origin of a client request. inline const std::string child_cmd = "chd:"; inline const std::string user_cmd = "--"; inline const std::string server_cmd = "svr:"; // Only for automatic check_pt +/// An empty string, provided as a named constant for use as a default reference return. inline const std::string empty = ""; +/// Node path strings. inline const std::string root_path = "/"; inline const std::string path_separator = "/"; inline const std::string colon = ":"; +/// Node type name strings, as used in definition files and wire protocol messages. inline const std::string task = "TASK"; inline const std::string family = "FAMILY"; inline const std::string family1 = "FAMILY1"; inline const std::string suite = "SUITE"; inline const std::string alias = "ALIAS"; +/// Server connection defaults. inline const std::string default_port_number = "3141"; inline const std::string localhost = "localhost"; +/// Name of the ecFlow access-control white-list file. inline const std::string white_list_file = "ecf.lists"; +/// Character sets used for name validation. inline const std::string alphanumeric_underscore_chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"; @@ -116,7 +129,8 @@ inline void replace_first(Sequence& input, const SearchSequence& search, const R /// 2) consecutive separators are collapsed into one, so no empty substrings are produced. /// 3) leading and trailing separators are ignored. /// -/// If the input string is empty, or contains only separator characters, the result be one empty sequence (i.e. `[ ]`). +/// If the input string is empty, or contains only separator characters, the result will be an empty sequence (i.e. +/// `[ ]`). /// /// @tparam ResultSequence A container type to hold the resulting substrings. /// @tparam Sequence1 A string-like type representing the input string. @@ -153,11 +167,10 @@ split_at(ResultSequence& buffer, const Sequence1& input, const Sequence2& separa /// /// A field separator is defined as any single character contained in the `separators` string. /// Consecutive separators are used to split the input string into empty field. -/// An empty field is included in result, when a separator is found at the beginning and or at end of -/// the input. +/// An empty field is included in result, when a separator is found at the beginning and or at end of the input. /// -/// Splitting the input `",,a,,b,c;d; ;e,;f;"` with the separators `",;"` would result -/// in a buffer containing `["", "", "a", "", "b", "c", "d", " ", "e", " ", "f", " "]`. +/// Splitting the input `",,a,,b,c;d; ;e,;f;"` with the separators `",;"` would result in a buffer containing `["", "", +/// "a", "", "b", "c", "d", " ", "e", "", "f", ""]`. /// /// If the input string is empty, the result be one empty sequence (i.e. `[ ]`). /// @@ -206,8 +219,7 @@ static ResultSequence& split_fields_at(ResultSequence& buffer, const Sequence1& /// /// @tparam ResultSequence A container type to hold the resulting substrings. /// @tparam Sequence1 A string-like type representing the input string. -/// @tparam Sequence2 A string-like type representing the pattern to split by (n.b. if the pattern contains -/// duplicated characters, they will be treated as a single separator). +/// @tparam Sequence2 A string-like type representing the pattern to split by. /// /// @param buffer A reference to the container where the resulting substrings will be stored. /// @param input The input string to be split. @@ -250,7 +262,7 @@ inline ResultSequence& split_by(ResultSequence& buffer, const Sequence1& input, /// - split_within_quotes("'a' x 'b' y 'c' zz ", "'") will return tokens ['a', x, 'b', y, 'c', zz]. /// Notice that any non-whitespace characters _outside_ the quotation marks are also collected (without quotes). /// -/// - split_within_quotes("'a \"b\" c' \"d 'e' f\" , "\"'") will return ['a \"b\" c', 'd \'e\' f'] +/// - split_within_quotes("'a \"b\" c' \"d 'e' f\"", "\"'") will return ['a \"b\" c', 'd \'e\' f'] /// Every quotation mark provided is considered, but only one level/quoted region is considered. /// This means that regions are detected by matching the first quote found. /// @@ -287,8 +299,7 @@ inline static bool starts_with(const Sequence1& input, const Sequence2& pattern) /// /// @param input The input string to check. /// @param pattern The pattern to check for at the end of the input. -/// @return true if the input ends with the pattern, false otherwise -/// . +/// @return true if the input ends with the pattern, false otherwise. template inline static bool ends_with(const Sequence1& input, const Sequence2& pattern) { std::string_view in(input); @@ -477,7 +488,8 @@ bool is_valid_name(const std::string& name, std::string& error); /// /// @brief Check if the \param name is valid according to the rules for node names in ecFlow. /// -/// This is used to verify the validity of nodes and attributes (Variable, Label, Event, ...) names in ecFlow. +/// This overload returns only a boolean. Use the two-argument overload to also +/// retrieve a description of any validation failure. /// /// @param name The name to be validated. /// @return true if the name is valid, false otherwise. @@ -633,10 +645,10 @@ inline int to_int(const std::string& input, int error_return = std::numeric_limi } /// -/// @brief Trim the string \param fileContents, so that \param max_lines at the tail of the file are kept. +/// @brief Trim `fileContents` so that `max_lines` at the tail of the file are kept. /// /// Lines are counted by the number of '\n' characters found when scanning from the end of the string towards the -/// beginning. Once \param max_lines newlines have been encountered, all content before (and including) the newline +/// beginning. Once `max_lines` newlines have been encountered, all content before (and including) the newline /// at the split point is erased. /// /// @param fileContents The string to truncate in-place. @@ -646,14 +658,14 @@ inline int to_int(const std::string& input, int error_return = std::numeric_limi bool tail(std::string& fileContents, size_t max_lines); /// -/// @brief Trim the string \param fileContents, so that \param max_lines at the head of the file are kept. +/// @brief Trim `fileContents` so that `max_lines` at the head of the file are kept. /// -/// Lines are counted by the number of '\n' characters found when scanning from the begining of the string towards -/// the end. Once \param max_lines newlines have been encountered, all content after (and including) the +/// Lines are counted by the number of '\n' characters found when scanning from the beginning of the string towards +/// the end. Once `max_lines` newlines have been encountered, all content after (and including) the /// newline at the split point is erased. /// /// @param fileContents The string to truncate in-place. -/// @param max_lines The maximum number of lines to retain (counted from the beginninf). +/// @param max_lines The maximum number of lines to retain (counted from the beginning). /// @return true if the string was truncated, false if it was empty or already within the limit. /// bool head(std::string& fileContents, size_t max_lines); diff --git a/libs/core/src/ecflow/core/TimeStamp.cpp b/libs/core/src/ecflow/core/TimeStamp.cpp index 551b81ab8..cd3f937f3 100644 --- a/libs/core/src/ecflow/core/TimeStamp.cpp +++ b/libs/core/src/ecflow/core/TimeStamp.cpp @@ -18,18 +18,40 @@ namespace TimeStamp { namespace { +/// +/// @brief Format policy for the regular timestamp, including the year. +/// +/// Format: `"[HH:MM:SS DD.MM.YYYY] "` (strftime pattern: `"[%H:%M:%S %d.%m.%Y] "`). +/// struct regular { + /// strftime format string. static constexpr char const* format = "[%H:%M:%S %d.%m.%Y] "; - static constexpr size_t size = 23; + /// Buffer size in bytes, including the null terminator. + static constexpr size_t size = 23; }; +/// +/// @brief Format policy for the brief timestamp, omitting the year. +/// +/// Format: `"[HH:MM:SS DD.MM] "` (strftime pattern: `"[%H:%M:%S %d.%m] "`). +/// struct brief { + /// strftime format string. static constexpr char const* format = "[%H:%M:%S %d.%m] "; - static constexpr size_t size = 18; + /// Buffer size in bytes, including the null terminator. + static constexpr size_t size = 18; }; +/// +/// @brief Format the current local time as a timestamp string. +/// +/// @tparam FMT A format policy providing a strftime-compatible `format` string and the +/// corresponding `size` (in bytes, including the null terminator). Defaults to `regular`. +/// +/// @return The current local time formatted according to `FMT::format`. +/// template std::string format_now() { std::time_t now = std::time(nullptr); @@ -44,12 +66,12 @@ std::string now() { return format_now(); } -void now(std::string& time_stamp) { - time_stamp = format_now(); +void now(std::string& buffer) { + buffer = format_now(); } -void now_in_brief(std::string& time_stamp) { - time_stamp = format_now(); +void now_in_brief(std::string& buffer) { + buffer = format_now(); } } // namespace TimeStamp diff --git a/libs/core/src/ecflow/core/TimeStamp.hpp b/libs/core/src/ecflow/core/TimeStamp.hpp index 072e57fac..28291b108 100644 --- a/libs/core/src/ecflow/core/TimeStamp.hpp +++ b/libs/core/src/ecflow/core/TimeStamp.hpp @@ -14,31 +14,42 @@ #include namespace ecf { + +/// +/// @brief Utilities for generating formatted wall-clock timestamps. +/// namespace TimeStamp { -/// Generate a 'regular' time stamp. /// -/// Format specified as -/// - "[%02d:%02d:%02d %d.%d.%d] ", considering "[hour:min:sec day.month.year] -/// " +/// @brief Return a timestamp string representing the current time. /// -/// Results in the following examples -/// - "[05:26:20 29.10.2014] " -/// - "[05:26:20 17.1.2023] " +/// Format: `"[HH:MM:SS D.M.YYYY] "` (e.g. `"[05:26:20 29.10.2014] "` or `"[05:26:20 17.1.2023] "`). +/// Note that day and month have no leading zeros. +/// +/// @return A formatted timestamp string. /// std::string now(); -void now(std::string&); -/// Generate a 'brief' time stamp. /// -/// Format specified as -/// - "[%02d:%02d:%02d %d.%d] ", considering "[hour:min:sec day.month] " +/// @brief Append a timestamp of the current time to \p buffer. +/// +/// Format: `"[HH:MM:SS D.M.YYYY] "` (e.g. `"[05:26:20 29.10.2014] "` or `"[05:26:20 17.1.2023] "`). +/// Note that day and month have no leading zeros. +/// +/// @param buffer The string to which the formatted timestamp is appended. +/// +void now(std::string& buffer); + +/// +/// @brief Append a brief timestamp of the current time to \p buffer. +/// +/// The brief format omits the year component. +/// Format: `"[HH:MM:SS D.M] "` (e.g. `"[05:26:20 29.10] "` or `"[05:26:20 17.1] "`). +/// Note that day and month have no leading zeros. /// -/// Results in the following examples -/// - "[05:26:20 29.10] " -/// - "[05:26:20 17.1] " +/// @param buffer The string to which the formatted timestamp is appended. /// -void now_in_brief(std::string&); +void now_in_brief(std::string& buffer); } // namespace TimeStamp } // namespace ecf diff --git a/libs/core/src/ecflow/core/Timer.hpp b/libs/core/src/ecflow/core/Timer.hpp index 6a0ee3d14..f42f1b266 100644 --- a/libs/core/src/ecflow/core/Timer.hpp +++ b/libs/core/src/ecflow/core/Timer.hpp @@ -16,6 +16,15 @@ #include #include +/// +/// @brief A simple elapsed-time utility that records the instant of its construction. +/// +/// The elapsed time is measured from the moment the object is constructed. `Resolution` +/// controls the granularity of the reported duration. +/// +/// @tparam Resolution The chrono duration type used to express elapsed time +/// (defaults to `std::chrono::milliseconds`). +/// template class Timer { public: @@ -29,25 +38,48 @@ class Timer { public: Timer() = default; ~Timer() = default; + + /// + /// @brief Print the elapsed time since construction to standard output, prefixed by \p msg. + /// + /// @param msg The label printed before the elapsed-time value. + /// void elapsed(const char* msg) const { const auto end = Clock::now(); std::cout << msg << " " << std::chrono::duration_cast(end - start_).count() << std::endl; } + /// + /// @brief Return the elapsed time since construction. + /// + /// @return The elapsed duration cast to `Resolution`. + /// Resolution elapsed() const { return std::chrono::duration_cast(Clock::now() - start_); } }; namespace ecf { -/** - * @brief A class to measure the duration of a function invocation - * - * @tparam Time the unit of time used to measure the duration of a function invocation - * @tparam Clock the clock type used to measure the duration of a function invocation - */ +/// +/// @brief Measures the duration of a single function invocation. +/// +/// @tparam Time The chrono duration type used to express the measured duration +/// (defaults to `std::chrono::microseconds`). +/// @tparam Clock The clock type used to take timestamps +/// (defaults to `std::chrono::high_resolution_clock`). +/// template struct FunctionPerformanceTimer { + /// + /// @brief Invoke \p f with \p args and return the elapsed time. + /// + /// @tparam F A callable type. + /// @tparam Args Argument types forwarded to \p f. + /// + /// @param f The callable to invoke. + /// @param args Arguments to forward to \p f. + /// @return The elapsed duration cast to `Time`. + /// template static Time duration(F&& f, Args... args) { auto start = Clock::now(); @@ -59,9 +91,9 @@ struct FunctionPerformanceTimer } }; -/** - * @brief A timer capable of measuring and reporting the elapsed wall clock time in microseconds. - */ +/// +/// @brief A timer that measures elapsed wall-clock time with microsecond precision. +/// class DurationTimer { public: using clock_t = std::chrono::system_clock; @@ -73,40 +105,48 @@ class DurationTimer { ~DurationTimer() = default; - /** - * @return the elapsed duration, in whole seconds, since the start (i.e. creation of this DurationTimer) - */ + /// + /// @brief Return the elapsed time since construction, in whole seconds. + /// + /// @return The elapsed duration, in whole seconds, since the start (i.e. creation of this DurationTimer). + /// [[nodiscard]] int duration() const { return std::chrono::duration_cast(elapsed()).count(); } - /** - * @return the elapsed duration, since the start (i.e. creation of this DurationTimer) - */ + /// + /// @brief Return the elapsed time since construction. + /// + /// @return The elapsed duration since the start (i.e. creation of this DurationTimer). + /// [[nodiscard]] duration_t elapsed() const { return std::chrono::duration_cast(clock_t::now() - start_time_); } - /** - * @return the elapsed duration since the start, in decimal seconds with microsecond precision - */ + /// + /// @brief Return the elapsed time since construction, in decimal seconds. + /// + /// @return The elapsed duration since the start, in decimal seconds with microsecond precision. + /// [[nodiscard]] double elapsed_seconds() const { auto us = std::chrono::duration_cast(elapsed()); return static_cast(us.count()) / 1000000.0; } - /** - * @return the elapsed duration since the start, in decimal milliseconds with microsecond precision - */ + /// + /// @brief Return the elapsed time since construction, in decimal milliseconds. + /// + /// @return The elapsed duration since the start, in decimal milliseconds with microsecond precision. + /// [[nodiscard]] double elapsed_milliseconds() const { auto us = std::chrono::duration_cast(elapsed()); return static_cast(us.count()) / 1000.0; } - /** - * Formats the given duration as a string - * - * @param d the elapsed duration to format - * @return the formatted string in the form "HH:MM:SS.mmmuuu" - */ + /// + /// @brief Format a duration as a human-readable string. + /// + /// @param d The elapsed duration to format. + /// @return The formatted string in the form `"HH:MM:SS.mmmuuu"`. + /// static std::string to_simple_string(duration_t d) { using namespace std::chrono; auto h = duration_cast(d); @@ -135,13 +175,18 @@ class DurationTimer { instant_t start_time_; }; -/** - * @brief A timer capable of measuring and reporting the elapsed wall clock time in microseconds. - * - * The elapsed time is reported to standard out when this object goes out of scope (i.e. in the dtor). - */ +/// +/// @brief A timer that reports the elapsed wall-clock time to standard output when it goes out of scope. +/// +/// The elapsed time is printed to standard output when this object is destroyed (i.e. in the destructor). +/// class ScopedDurationTimer : public DurationTimer { public: + /// + /// @brief Construct a scoped timer with the given label. + /// + /// @param msg The label printed alongside the elapsed time when the timer is destroyed. + /// explicit ScopedDurationTimer(std::string msg) : DurationTimer(), msg_(std::move(msg)) {} @@ -152,21 +197,39 @@ class ScopedDurationTimer : public DurationTimer { std::string msg_; }; +/// +/// @brief A snapshot of elapsed wall-clock, user-CPU, and system-CPU time. +/// struct PerformanceMeasure { - std::chrono::nanoseconds wall; - std::chrono::nanoseconds user; - std::chrono::nanoseconds system; + std::chrono::nanoseconds wall; ///< Elapsed wall-clock time. + std::chrono::nanoseconds user; ///< Elapsed user-CPU time. + std::chrono::nanoseconds system; ///< Elapsed system-CPU time. + /// + /// @brief Reset all durations to zero. + /// void clear() { wall = std::chrono::nanoseconds{0}; user = std::chrono::nanoseconds{0}; system = std::chrono::nanoseconds{0}; } + /// + /// @brief Capture the current wall-clock, user-CPU, and system-CPU times. + /// + /// @return A `PerformanceMeasure` snapshot representing the current instant. + /// static PerformanceMeasure current(); }; +/// +/// @brief Compute the difference between two `PerformanceMeasure` snapshots. +/// +/// @param lhs The later (end) snapshot. +/// @param rhs The earlier (start) snapshot. +/// @return A `PerformanceMeasure` whose fields hold the per-dimension elapsed time. +/// inline PerformanceMeasure operator-(const PerformanceMeasure& lhs, const PerformanceMeasure& rhs) { auto wall = lhs.wall - rhs.wall; auto user = lhs.user - rhs.user; @@ -174,18 +237,45 @@ inline PerformanceMeasure operator-(const PerformanceMeasure& lhs, const Perform return PerformanceMeasure{wall, user, system}; } +/// +/// @brief A timer that measures wall-clock, user-CPU, and system-CPU time. +/// +/// Call `start()` to reset the timer, and `elapsed()` to obtain the elapsed +/// `PerformanceMeasure` since the last reset (or construction). +/// class PerformanceTimer { public: + /// + /// @brief Construct a performance timer, recording the start instant. + /// PerformanceTimer() : times_{PerformanceMeasure::current()} {} + /// + /// @brief Reset the timer to the current instant. + /// void start() noexcept { times_ = PerformanceMeasure::current(); } + + /// + /// @brief Return the elapsed time since the last reset (or construction). + /// + /// @return A `PerformanceMeasure` snapshot of the elapsed times. + /// PerformanceMeasure elapsed() const { return PerformanceMeasure::current() - times_; } private: PerformanceMeasure times_; }; +/// +/// @brief Write a human-readable performance summary of \p timer to \p os. +/// +/// The output format is: `s wall, (s user + s system = s) CPU (%)` +/// +/// @param os The output stream to write to. +/// @param timer The timer whose elapsed performance to format. +/// @return A reference to \p os. +/// inline std::ostream& operator<<(std::ostream& os, const PerformanceTimer& timer) { auto elapsed = timer.elapsed(); auto w = static_cast(elapsed.wall.count()) / 1000000000.0; @@ -197,6 +287,9 @@ inline std::ostream& operator<<(std::ostream& os, const PerformanceTimer& timer) return os; } +/// +/// @brief A performance timer that reports elapsed time to standard output when it goes out of scope. +/// class ScopedPerformanceTimer : public PerformanceTimer { public: ~ScopedPerformanceTimer() noexcept { std::cout << *this << std::endl; }; diff --git a/libs/core/src/ecflow/core/User.hpp b/libs/core/src/ecflow/core/User.hpp index e21c8ff8a..c95ea040b 100644 --- a/libs/core/src/ecflow/core/User.hpp +++ b/libs/core/src/ecflow/core/User.hpp @@ -15,6 +15,11 @@ namespace ecf { +/// +/// @brief Return the login name of the current user. +/// +/// @return The login name of the current user as a string. +/// std::string get_login_name(); } // namespace ecf diff --git a/libs/core/src/ecflow/core/Version.hpp b/libs/core/src/ecflow/core/Version.hpp index f3611c294..249dcebb7 100644 --- a/libs/core/src/ecflow/core/Version.hpp +++ b/libs/core/src/ecflow/core/Version.hpp @@ -16,7 +16,7 @@ namespace ecf { /// -/// \brief Provides information regarding the version of ecFlow. +/// @brief Provides information regarding the version of ecFlow. /// /// The ecFlow version format is of the form `..[]`, /// following the semantic versioning scheme. @@ -28,41 +28,89 @@ class Version { // Disable default construction Version() = delete; + /// + /// @brief Return the major version component of ecFlow. + /// + /// @return The major version component as a string. + /// static std::string major(); + + /// + /// @brief Return the minor version component of ecFlow. + /// + /// @return The minor version component as a string. + /// static std::string minor(); + + /// + /// @brief Return the patch version component of ecFlow. + /// + /// @return The patch version component as a string. + /// static std::string patch(); + + /// + /// @brief Return the version suffix of ecFlow, if any. + /// + /// @return The version suffix as a string, or an empty string if no suffix is defined. + /// static std::string suffix(); /// - /// Creates a string with a descriptive version information, - /// including the version of ecFlow and relevant dependencies. + /// @brief Return a descriptive version string, including ecFlow and relevant dependency versions. /// - /// This provides user facing version information + /// This provides user-facing version information /// (shown by ecflow_client --help, and in the server info panel on ecflow_ui). /// + /// @return A formatted version description string. + /// static std::string description(); /// - /// Creates the ecFlow version, following the template: `..` + /// @brief Return the ecFlow version string, without any suffix. + /// + /// Format: `..` + /// + /// @return The base version string. /// static std::string base(); /// - /// Creates the ecFlow version, following the template: `..[]` + /// @brief Return the complete ecFlow version string, including any suffix. + /// + /// Format: `..[]` + /// + /// @return The full version string. /// static std::string full(); private: - /// Create a string containing the version of the Boost library + /// + /// @brief Return the version of the Boost library. + /// + /// @return The Boost library version as a string. + /// static std::string boost(); - /// Create a string containing the version of the Cereal library + /// + /// @brief Return the version of the Cereal library. + /// + /// @return The Cereal library version as a string. + /// static std::string cereal(); - /// Create a string containing the version of the Compiler used to build ecFlow + /// + /// @brief Return the version of the compiler used to build ecFlow. + /// + /// @return The compiler version as a string. + /// static std::string compiler(); - /// Create a string containing the version of the OpenSSL library + /// + /// @brief Return the version of the OpenSSL library. + /// + /// @return The OpenSSL library version as a string. + /// static std::string openssl(); }; From 56215868a024a0e2d18aaa0a6fd5554780fa8bda Mon Sep 17 00:00:00 2001 From: Marcos Bento Date: Mon, 20 Apr 2026 13:08:48 +0100 Subject: [PATCH 28/90] refactor: replace calls to [[deprecated]] Defs::suiteVec Re ECFLOW-2079 --- Viewer/ecflowUI/src/ServerComThread.cpp | 6 +- Viewer/ecflowUI/src/VNode.cpp | 4 +- .../src/ecflow/base/cts/user/BeginCmd.cpp | 8 +-- libs/base/src/ecflow/base/cts/user/CtsCmd.cpp | 2 +- .../src/ecflow/base/cts/user/LoadDefsCmd.cpp | 2 +- libs/base/src/ecflow/base/stc/SStatsCmd.cpp | 2 +- libs/base/src/ecflow/base/stc/SSuitesCmd.cpp | 10 ++-- libs/base/src/ecflow/base/stc/SSyncCmd.cpp | 2 +- libs/base/test/TestClientHandleCmd.cpp | 10 ++-- libs/base/test/TestDeleteNodeCmd.cpp | 34 +++++------ libs/base/test/TestSSyncCmd.cpp | 56 +++++++++---------- libs/base/test/TestSSyncCmdOrder.cpp | 10 ++-- libs/base/test/TestSSyncCmd_CH1.cpp | 12 ++-- libs/client/test/TestCheckPtDefsCmd.cpp | 6 +- libs/client/test/TestClientHandleCmd.cpp | 12 ++-- libs/client/test/TestGroupCmd.cpp | 10 ++-- libs/client/test/TestLifeCycle.cpp | 4 +- libs/client/test/TestLoadDefsCmd.cpp | 12 ++-- libs/client/test/TestPlugCmd.cpp | 10 ++-- libs/client/test/TestServer.cpp | 8 +-- libs/client/test/TestServerAndLifeCycle.cpp | 6 +- libs/node/src/ecflow/node/ClientSuites.cpp | 12 ++-- libs/node/src/ecflow/node/Defs.cpp | 8 +-- libs/node/src/ecflow/node/DefsTreeVisitor.hpp | 2 +- libs/node/src/ecflow/node/Jobs.cpp | 2 +- libs/node/src/ecflow/node/Memento.cpp | 2 +- libs/node/src/ecflow/node/Operations.hpp | 2 +- .../src/ecflow/node/ResolveExternsVisitor.cpp | 2 +- .../src/ecflow/node/formatter/DefsWriter.hpp | 2 +- libs/node/test/TestAvisoAttr.cpp | 2 +- libs/node/test/TestClientSuiteMgr.cpp | 30 +++++----- libs/node/test/TestDefs.cpp | 6 +- libs/node/test/TestMovePeer.cpp | 2 +- libs/node/test/TestOrder.cpp | 22 ++++---- libs/node/test/parser/ParseTimer.cpp | 20 +++---- libs/node/test/parser/TestAvisoAttr.cpp | 4 +- .../TestDefsStructurePersistAndReload.cpp | 6 +- libs/node/test/parser/TestMirrorAttr.cpp | 4 +- libs/node/test/parser/TestSingleDefsFile.cpp | 14 ++--- libs/pyext/src/ecflow/python/ExportDefs.cpp | 6 +- libs/rest/src/ecflow/http/ApiV1Impl.cpp | 11 ++-- libs/server/src/ecflow/server/BaseServer.cpp | 4 +- libs/test/overall/TestClkSync.cpp | 6 +- libs/test/overall/TestHandle.cpp | 8 +-- libs/test/overall/TestOrderCmd.cpp | 22 ++++---- libs/test/overall/TestServer.cpp | 8 +-- .../overall/harness/ServerTestHarness.cpp | 24 ++++---- .../ecflow/simulator/DefsAnalyserVisitor.cpp | 4 +- .../ecflow/simulator/FlatAnalyserVisitor.cpp | 4 +- .../src/ecflow/simulator/Simulator.cpp | 26 ++++----- .../src/ecflow/simulator/SimulatorVisitor.cpp | 4 +- libs/test/simulator/test/TestAutoArchive.cpp | 3 +- libs/test/simulator/test/TestAutoCancel.cpp | 8 +-- 53 files changed, 249 insertions(+), 257 deletions(-) diff --git a/Viewer/ecflowUI/src/ServerComThread.cpp b/Viewer/ecflowUI/src/ServerComThread.cpp index c36c84b04..c919116d9 100644 --- a/Viewer/ecflowUI/src/ServerComThread.cpp +++ b/Viewer/ecflowUI/src/ServerComThread.cpp @@ -547,8 +547,7 @@ void ServerComThread::attach(defs_ptr d) { d->attach(this); - const std::vector& suites = d->suiteVec(); - for (const auto& suite : suites) { + for (const auto& suite : d->suites()) { attach(suite.get()); } } @@ -585,8 +584,7 @@ void ServerComThread::detach(defs_ptr d) { d->detach(this); - const std::vector& suites = d->suiteVec(); - for (const auto& suite : suites) { + for (const auto& suite : d->suites()) { detach(suite.get()); } } diff --git a/Viewer/ecflowUI/src/VNode.cpp b/Viewer/ecflowUI/src/VNode.cpp index 61d69728e..3de084fbc 100644 --- a/Viewer/ecflowUI/src/VNode.cpp +++ b/Viewer/ecflowUI/src/VNode.cpp @@ -1386,9 +1386,7 @@ void VServer::endScan() { bool hasNotifications = server_->conf()->notificationsEnabled(); // Scan the suits.This will recursively scan all nodes in the tree. - const std::vector& suites = defs->suiteVec(); - - for (const auto& suite : suites) { + for (const auto& suite : defs->suites()) { VNode* vn = new VSuiteNode(this, suite); totalNum_++; scan(vn, hasNotifications); diff --git a/libs/base/src/ecflow/base/cts/user/BeginCmd.cpp b/libs/base/src/ecflow/base/cts/user/BeginCmd.cpp index bddcc583e..746597a76 100644 --- a/libs/base/src/ecflow/base/cts/user/BeginCmd.cpp +++ b/libs/base/src/ecflow/base/cts/user/BeginCmd.cpp @@ -73,12 +73,12 @@ STC_Cmd_ptr BeginCmd::doHandleRequest(AbstractServer* as) const { // If no suite name begin all suites, else begin the the specific suite if (suiteName_.empty()) { - const std::vector& suiteVec = defs->suiteVec(); - size_t theSuiteVecSize = suiteVec.size(); + const auto& suites = defs->suites(); if (!force_) { - for (size_t s = 0; s < theSuiteVecSize; s++) { + size_t size = suites.size(); + for (size_t s = 0; s < size; s++) { /// check_suite_can_begin will throw if suite can't begin - defs->check_suite_can_begin(suiteVec[s]); + defs->check_suite_can_begin(suites[s]); } } else { diff --git a/libs/base/src/ecflow/base/cts/user/CtsCmd.cpp b/libs/base/src/ecflow/base/cts/user/CtsCmd.cpp index 548975fe2..052739ef1 100644 --- a/libs/base/src/ecflow/base/cts/user/CtsCmd.cpp +++ b/libs/base/src/ecflow/base/cts/user/CtsCmd.cpp @@ -404,7 +404,7 @@ STC_Cmd_ptr CtsCmd::doHandleRequest(AbstractServer* as) const { case CtsCmd::STATS: { as->update_stats().stats_++; as->stats().update_for_serialisation(); - as->stats().no_of_suites_ = as->defs()->suiteVec().size(); + as->stats().no_of_suites_ = as->defs()->suites().size(); std::ostringstream ss; as->stats().show(ss); // ECFLOW-880, allow stats to be changed in server, by only returning string return PreAllocatedReply::string_cmd(ss.str()); diff --git a/libs/base/src/ecflow/base/cts/user/LoadDefsCmd.cpp b/libs/base/src/ecflow/base/cts/user/LoadDefsCmd.cpp index b5b948275..0bd824c8d 100644 --- a/libs/base/src/ecflow/base/cts/user/LoadDefsCmd.cpp +++ b/libs/base/src/ecflow/base/cts/user/LoadDefsCmd.cpp @@ -136,7 +136,7 @@ STC_Cmd_ptr LoadDefsCmd::doHandleRequest(AbstractServer* as) const { // all errors, references are not resolved. as->updateDefs(defs, force_); - LOG_ASSERT(defs->suiteVec().size() == 0, "Expected suites to be transferred to server defs"); + LOG_ASSERT(defs->suites().size() == 0, "Expected suites to be transferred to server defs"); } LOG_ASSERT(as->defs()->externs().size() == 0, "Expected server to have no externs"); diff --git a/libs/base/src/ecflow/base/stc/SStatsCmd.cpp b/libs/base/src/ecflow/base/stc/SStatsCmd.cpp index 47fb089d4..f53ae716b 100644 --- a/libs/base/src/ecflow/base/stc/SStatsCmd.cpp +++ b/libs/base/src/ecflow/base/stc/SStatsCmd.cpp @@ -22,7 +22,7 @@ SStatsCmd::SStatsCmd(AbstractServer* as) { void SStatsCmd::init(AbstractServer* as) { as->stats().update_for_serialisation(); stats_ = as->stats(); - stats_.no_of_suites_ = as->defs()->suiteVec().size(); + stats_.no_of_suites_ = as->defs()->suites().size(); } bool SStatsCmd::equals(ServerToClientCmd* rhs) const { diff --git a/libs/base/src/ecflow/base/stc/SSuitesCmd.cpp b/libs/base/src/ecflow/base/stc/SSuitesCmd.cpp index f3450d961..df0394c5b 100644 --- a/libs/base/src/ecflow/base/stc/SSuitesCmd.cpp +++ b/libs/base/src/ecflow/base/stc/SSuitesCmd.cpp @@ -25,11 +25,11 @@ void SSuitesCmd::init(AbstractServer* as) { // This command can be re-used hence clear existing data members suites_.clear(); - const std::vector& suiteVec = as->defs()->suiteVec(); - size_t suite_vec_size = suiteVec.size(); - suites_.reserve(suite_vec_size); - for (size_t i = 0; i < suite_vec_size; i++) { - suites_.push_back(suiteVec[i]->name()); + const auto& suites = as->defs()->suites(); + size_t size = suites.size(); + suites_.reserve(size); + for (size_t i = 0; i < size; i++) { + suites_.push_back(suites[i]->name()); } } diff --git a/libs/base/src/ecflow/base/stc/SSyncCmd.cpp b/libs/base/src/ecflow/base/stc/SSyncCmd.cpp index ff9e407ec..4301744b6 100644 --- a/libs/base/src/ecflow/base/stc/SSyncCmd.cpp +++ b/libs/base/src/ecflow/base/stc/SSyncCmd.cpp @@ -237,7 +237,7 @@ void SSyncCmd::full_sync(unsigned int client_handle, AbstractServer* as) { #ifdef DEBUG_SERVER_SYNC if (the_server_defs) { - cout << ": no of suites(" << the_server_defs->suiteVec().size() << ")" << endl; + cout << ": no of suites(" << the_server_defs->suites().size() << ")" << endl; } else { cout << ": NULL defs!" << endl; diff --git a/libs/base/test/TestClientHandleCmd.cpp b/libs/base/test/TestClientHandleCmd.cpp index d0f81370e..80acbbe45 100644 --- a/libs/base/test/TestClientHandleCmd.cpp +++ b/libs/base/test/TestClientHandleCmd.cpp @@ -258,7 +258,7 @@ BOOST_AUTO_TEST_CASE(is_able_to_handle_cmd_add_remove) { defs_ptr created_defs = defs->client_suite_mgr().create_defs(1, defs); // clear the handle change BOOST_CHECK_MESSAGE(created_defs, "Expected defs to be created"); - BOOST_CHECK_MESSAGE(created_defs->suiteVec().empty(), "Expected no suites"); + BOOST_CHECK_MESSAGE(created_defs->suites().empty(), "Expected no suites"); BOOST_CHECK_MESSAGE(!defs->client_suite_mgr().handle_changed(1), "Expected handle changed to be cleared after create_defs()"); } @@ -280,7 +280,7 @@ BOOST_AUTO_TEST_CASE(is_able_to_handle_cmd_add_remove) { BOOST_CHECK_MESSAGE(created_defs, "Expected defs to be created"); BOOST_CHECK_MESSAGE(created_defs.get() == defs.get(), "When *ALL* suites registered, the returned defs should be the same"); - BOOST_CHECK_MESSAGE(created_defs->suiteVec().size() == suite_names.size(), "Not all suites created"); + BOOST_CHECK_MESSAGE(created_defs->suites().size() == suite_names.size(), "Not all suites created"); } // Now remove the suites from the handle @@ -299,7 +299,7 @@ BOOST_AUTO_TEST_CASE(is_able_to_handle_cmd_add_remove) { defs_ptr created_defs = defs->client_suite_mgr().create_defs(1, defs); // clear the handle change BOOST_CHECK_MESSAGE(created_defs, "Expected defs to be created"); - BOOST_CHECK_MESSAGE(created_defs->suiteVec().empty(), "Expected no suites"); + BOOST_CHECK_MESSAGE(created_defs->suites().empty(), "Expected no suites"); BOOST_CHECK_MESSAGE(!defs->client_suite_mgr().handle_changed(1), "Expected handle changed to be cleared after create_defs()"); } @@ -307,9 +307,9 @@ BOOST_AUTO_TEST_CASE(is_able_to_handle_cmd_add_remove) { static bool check_ordering(Defs& defs) { // make sure order of suites in handles is the same as server order - const std::vector& suite_vec = defs.suiteVec(); + const auto& suites = defs.suites(); std::vector suite_names; - for (const auto& i : suite_vec) { + for (const auto& i : suites) { suite_names.push_back(i->name()); } diff --git a/libs/base/test/TestDeleteNodeCmd.cpp b/libs/base/test/TestDeleteNodeCmd.cpp index b43afbc99..69d220335 100644 --- a/libs/base/test/TestDeleteNodeCmd.cpp +++ b/libs/base/test/TestDeleteNodeCmd.cpp @@ -119,9 +119,9 @@ BOOST_AUTO_TEST_CASE(test_delete_node_cmd) { // Delete all Suites { - std::vector vec = fixtureDef.defsfile_.suiteVec(); - BOOST_CHECK_MESSAGE(vec.size() > 0, "Expected > 0 Suites but found " << vec.size()); - for (suite_ptr s : vec) { + auto suites = fixtureDef.defsfile_.suites(); + BOOST_CHECK_MESSAGE(suites.size() > 0, "Expected > 0 Suites but found " << suites.size()); + for (auto s : suites) { { // Delete all Families // ********************************************************************************************* @@ -133,7 +133,7 @@ BOOST_AUTO_TEST_CASE(test_delete_node_cmd) { // for(family_ptr f: s->familyVec()) { // As with this will invalidate the iterators. std::vector paths; - paths.reserve(vec.size()); + paths.reserve(suites.size()); for (family_ptr f : familyVec) { paths.push_back(f->absNodePath()); } @@ -182,8 +182,8 @@ BOOST_AUTO_TEST_CASE(test_delete_node_cmd) { } } - BOOST_REQUIRE_MESSAGE(fixtureDef.defsfile_.suiteVec().empty(), - "Expected all Suites to be deleted but found " << fixtureDef.defsfile_.suiteVec().size()); + BOOST_REQUIRE_MESSAGE(fixtureDef.defsfile_.suites().empty(), + "Expected all Suites to be deleted but found " << fixtureDef.defsfile_.suites().size()); } { @@ -224,9 +224,9 @@ BOOST_AUTO_TEST_CASE(test_delete_node_edit_history_ECFLOW_1684) { } BOOST_CHECK_MESSAGE(!paths.empty(), "Expected paths to be specified, *OTHERWISE* we delete all nodes"); - std::vector suite_vec = fixtureDef.defsfile_.suiteVec(); - for (suite_ptr s : suite_vec) { - paths.push_back(s->absNodePath()); + auto suites = fixtureDef.defsfile_.suites(); + for (suite_ptr suite : suites) { + paths.push_back(suite->absNodePath()); } PathsCmd cmd(PathsCmd::SUSPEND, paths); @@ -249,25 +249,25 @@ BOOST_AUTO_TEST_CASE(test_delete_node_edit_history_ECFLOW_1684) { // Delete all Suites, this should delete edit history associated with suites and child tasks { - std::vector vec = fixtureDef.defsfile_.suiteVec(); - BOOST_CHECK_MESSAGE(vec.size() > 0, "Expected > 0 Suites but found " << vec.size()); - for (suite_ptr s : vec) { - std::string absNodePath = s->absNodePath(); + auto suites = fixtureDef.defsfile_.suites(); + BOOST_CHECK_MESSAGE(suites.size() > 0, "Expected > 0 Suites but found " << suites.size()); + for (suite_ptr suite : suites) { + std::string absNodePath = suite->absNodePath(); DeleteCmd cmd(absNodePath); cmd.setup_user_authentification(); STC_Cmd_ptr returnCmd = cmd.handleRequest(&mockServer); BOOST_CHECK_MESSAGE(returnCmd->ok(), "Failed to delete suite at path " << absNodePath); } - BOOST_REQUIRE_MESSAGE(fixtureDef.defsfile_.suiteVec().empty(), - "Expected all Suites to be deleted but found " << fixtureDef.defsfile_.suiteVec().size()); + BOOST_REQUIRE_MESSAGE(fixtureDef.defsfile_.suites().empty(), + "Expected all Suites to be deleted but found " << fixtureDef.defsfile_.suites().size()); size_t edit_history_root_size_after = fixtureDef.defsfile_.get_edit_history(ecf::string_constants::root_path).size(); size_t edit_history_after = fixtureDef.defsfile_.get_edit_history().size(); - BOOST_CHECK_MESSAGE(edit_history_root_size_after == vec.size(), + BOOST_CHECK_MESSAGE(edit_history_root_size_after == suites.size(), "Expected edit_history size of root node to be same as number of deleted suites(" - << vec.size() << ") but found " << edit_history_root_size_after); + << suites.size() << ") but found " << edit_history_root_size_after); BOOST_CHECK_MESSAGE(edit_history_after == 1, "Expected edit_history_after == 1 to *ONLY* have root node, but found " << edit_history_after << "\n" diff --git a/libs/base/test/TestSSyncCmd.cpp b/libs/base/test/TestSSyncCmd.cpp index 98ed1ddd0..5f96243d8 100644 --- a/libs/base/test/TestSSyncCmd.cpp +++ b/libs/base/test/TestSSyncCmd.cpp @@ -265,7 +265,7 @@ void remove_a_family(defs_ptr defs) { } void change_clock_gain(defs_ptr defs) { - for (suite_ptr suite : defs->suiteVec()) { + for (suite_ptr suite : defs->suites()) { if (suite->clockAttr().get()) { SuiteChanged changed(suite); suite->changeClockGain("100001"); @@ -274,7 +274,7 @@ void change_clock_gain(defs_ptr defs) { } void change_clock_type_to_real(defs_ptr defs) { - for (suite_ptr suite : defs->suiteVec()) { + for (suite_ptr suite : defs->suites()) { if (suite->clockAttr().get()) { SuiteChanged changed(suite); suite->changeClockType("real"); @@ -283,7 +283,7 @@ void change_clock_type_to_real(defs_ptr defs) { } void change_clock_type_to_hybrid(defs_ptr defs) { - for (suite_ptr suite : defs->suiteVec()) { + for (suite_ptr suite : defs->suites()) { if (suite->clockAttr().get()) { SuiteChanged changed(suite); suite->changeClockType("hybrid"); @@ -292,7 +292,7 @@ void change_clock_type_to_hybrid(defs_ptr defs) { } void change_clock_date(defs_ptr defs) { - for (suite_ptr suite : defs->suiteVec()) { + for (suite_ptr suite : defs->suites()) { if (suite->clockAttr().get()) { SuiteChanged changed(suite); suite->changeClockDate("1.1.2001"); @@ -301,7 +301,7 @@ void change_clock_date(defs_ptr defs) { } void change_clock_sync(defs_ptr defs) { - for (suite_ptr suite : defs->suiteVec()) { + for (suite_ptr suite : defs->suites()) { if (suite->clockAttr().get()) { SuiteChanged changed(suite); suite->changeClockSync(); @@ -314,25 +314,25 @@ void change_clock_sync(defs_ptr defs) { /// max value, however because we had, changed value as well it got masked. void change_limit_max(defs_ptr defs) { - for (suite_ptr s : defs->suiteVec()) { - std::vector theLimits = s->limits(); + for (suite_ptr suite : defs->suites()) { + std::vector theLimits = suite->limits(); for (limit_ptr l : theLimits) { // std::cout << "found " << l->toString() << "\n"; - TestHelper::invokeRequest(defs.get(), - Cmd_ptr(new AlterCmd(s->absNodePath(), AlterCmd::LIMIT_MAX, l->name(), "90"))); - limit_ptr v = s->find_limit(l->name()); + TestHelper::invokeRequest( + defs.get(), Cmd_ptr(new AlterCmd(suite->absNodePath(), AlterCmd::LIMIT_MAX, l->name(), "90"))); + limit_ptr v = suite->find_limit(l->name()); BOOST_CHECK_MESSAGE(v.get() && v->theLimit() == 90, "expected to find limit with max value of 90"); } } } void change_limit_value(defs_ptr defs) { - for (suite_ptr s : defs->suiteVec()) { - std::vector theLimits = s->limits(); + for (suite_ptr suite : defs->suites()) { + std::vector theLimits = suite->limits(); for (limit_ptr l : theLimits) { - TestHelper::invokeRequest(defs.get(), - Cmd_ptr(new AlterCmd(s->absNodePath(), AlterCmd::LIMIT_VAL, l->name(), "33"))); - limit_ptr v = s->find_limit(l->name()); + TestHelper::invokeRequest( + defs.get(), Cmd_ptr(new AlterCmd(suite->absNodePath(), AlterCmd::LIMIT_VAL, l->name(), "33"))); + limit_ptr v = suite->find_limit(l->name()); BOOST_CHECK_MESSAGE(v.get() && v->value() == 33, "expected to find limit with value of 33"); } } @@ -359,7 +359,7 @@ void update_calendar(defs_ptr defs) { defs->updateCalendar(p); // Currently updating the calendar, does not cause change, Hence force a change - for (suite_ptr suite : defs->suiteVec()) { + for (suite_ptr suite : defs->suites()) { SuiteChanged changed(suite); suite->add_variable("name", "value"); } @@ -371,9 +371,9 @@ void update_calendar(defs_ptr defs) { } void delete_suite(defs_ptr defs) { - std::vector vec = defs->suiteVec(); - BOOST_REQUIRE_MESSAGE(!vec.empty(), "Expected suites"); - vec[0]->remove(); + auto suites = defs->suites(); + BOOST_REQUIRE_MESSAGE(!suites.empty(), "Expected suites"); + suites[0]->remove(); } void set_server_state_shutdown(defs_ptr defs) { @@ -401,21 +401,21 @@ void delete_server_variable(defs_ptr defs) { } void reorder_suites(defs_ptr defs) { - std::vector suiteVec = defs->suiteVec(); - BOOST_REQUIRE_MESSAGE(!suiteVec.empty(), "Expected suites"); - std::string path = "/" + suiteVec[0]->name(); + auto suites = defs->suites(); + BOOST_REQUIRE_MESSAGE(!suites.empty(), "Expected suites"); + std::string path = "/" + suites[0]->name(); TestHelper::invokeRequest(defs.get(), Cmd_ptr(new OrderNodeCmd(path, NOrder::ALPHA))); } void move_peers(defs_ptr defs) { - std::vector suiteVec = defs->suiteVec(); - BOOST_REQUIRE_MESSAGE(suiteVec.size() >= 2, "Expected suites"); - TestHelper::invokeRequest(defs.get(), Cmd_ptr(new PlugCmd(suiteVec[0]->absNodePath(), suiteVec[1]->absNodePath()))); + auto suites = defs->suites(); + BOOST_REQUIRE_MESSAGE(suites.size() >= 2, "Expected suites"); + TestHelper::invokeRequest(defs.get(), Cmd_ptr(new PlugCmd(suites[0]->absNodePath(), suites[1]->absNodePath()))); } void move_peers1(defs_ptr defs) { - std::vector suiteVec = defs->suiteVec(); - BOOST_REQUIRE_MESSAGE(suiteVec.size() >= 2, "Expected suites"); - TestHelper::invokeRequest(defs.get(), Cmd_ptr(new PlugCmd(suiteVec[1]->absNodePath(), suiteVec[0]->absNodePath()))); + auto suites = defs->suites(); + BOOST_REQUIRE_MESSAGE(suites.size() >= 2, "Expected suites"); + TestHelper::invokeRequest(defs.get(), Cmd_ptr(new PlugCmd(suites[1]->absNodePath(), suites[0]->absNodePath()))); } void set_defs_flag(defs_ptr defs) { diff --git a/libs/base/test/TestSSyncCmdOrder.cpp b/libs/base/test/TestSSyncCmdOrder.cpp index 553d1e72f..fc8f39204 100644 --- a/libs/base/test/TestSSyncCmdOrder.cpp +++ b/libs/base/test/TestSSyncCmdOrder.cpp @@ -111,16 +111,16 @@ static void test_sync_scaffold(defs_change_cmd the_defs_change_command, BOOST_CHECK_MESSAGE(*server_defs == *server_reply.client_defs(), "Server and client should be same after sync"); } else { - BOOST_CHECK_MESSAGE(server_reply.client_defs()->suiteVec().size() == test_equality, + BOOST_CHECK_MESSAGE(server_reply.client_defs()->suites().size() == test_equality, "Expected suite of size " << test_equality << " but found " - << server_reply.client_defs()->suiteVec().size()); + << server_reply.client_defs()->suites().size()); } } static void reorder_suites(defs_ptr theDefs) { TestHelper::invokeRequest(theDefs.get(), Cmd_ptr(new OrderNodeCmd("/a", NOrder::ALPHA))); - auto names = ecf::algorithm::transform_to_vector(theDefs->suiteVec(), to_name); + auto names = ecf::algorithm::transform_to_vector(theDefs->suites(), to_name); BOOST_REQUIRE_MESSAGE(names == vector_abcd(), "NOrder::ALPHA expected " << ecf::algorithm::join(vector_abcd()) << " but found: " << ecf::algorithm::join(names)); @@ -164,7 +164,7 @@ static void reorder_suites_using_handles(defs_ptr theDefs) { "Expected 1 Client suites but found " << theDefs->client_suite_mgr().clientSuites().size()); TestHelper::invokeRequest(theDefs.get(), Cmd_ptr(new OrderNodeCmd("/a", NOrder::ALPHA))); - auto names = ecf::algorithm::transform_to_vector(theDefs->suiteVec(), to_name); + auto names = ecf::algorithm::transform_to_vector(theDefs->suites(), to_name); BOOST_REQUIRE_MESSAGE(names == vector_abcd(), "NOrder::ALPHA expected " << ecf::algorithm::join(vector_abcd()) << " but found: " << ecf::algorithm::join(names)); @@ -189,7 +189,7 @@ static void reorder_family_using_handles(defs_ptr theDefs) { /// Don't call, data model function directly, since Ecf::server_ is false. *here* /// The suite should stay the same, only suite d's family should change TestHelper::invokeRequest(theDefs.get(), Cmd_ptr(new OrderNodeCmd("/d/d", NOrder::ALPHA))); - auto names = ecf::algorithm::transform_to_vector(theDefs->suiteVec(), to_name); + auto names = ecf::algorithm::transform_to_vector(theDefs->suites(), to_name); BOOST_REQUIRE_MESSAGE(names == vector_dcba(), "expected " << ecf::algorithm::join(vector_dcba()) << " but found: " << ecf::algorithm::join(names)); diff --git a/libs/base/test/TestSSyncCmd_CH1.cpp b/libs/base/test/TestSSyncCmd_CH1.cpp index dd5b3089b..fc6048820 100644 --- a/libs/base/test/TestSSyncCmd_CH1.cpp +++ b/libs/base/test/TestSSyncCmd_CH1.cpp @@ -125,7 +125,7 @@ void test_sync_scaffold(defs_change_cmd the_defs_change_command, const std::stri /// called create Defs should clear the flag. make sure server state, is synced defs_ptr the_client_defs = server_defs->client_suite_mgr().create_defs(client_handle, server_defs); - BOOST_CHECK_MESSAGE(the_client_defs->suiteVec().size() == 2, test_name << ": Expected 2 suites"); + BOOST_CHECK_MESSAGE(the_client_defs->suites().size() == 2, test_name << ": Expected 2 suites"); BOOST_CHECK_MESSAGE(server_defs->client_suite_mgr().handle_changed(client_handle) == false, test_name << ": Expected handle_changed to be cleared after create_defs()"); BOOST_CHECK_MESSAGE(server_defs->server_state().compare(the_client_defs->server_state()), @@ -582,11 +582,11 @@ BOOST_AUTO_TEST_CASE(test_ssync_using_handle) { // ============================================================================================= static defs_ptr create_the_server_defs() { - defs_ptr defs = create_server_defs(); - std::vector suite_vec = defs->suiteVec(); - for (size_t j = 0; j < suite_vec.size(); j++) { - suite_vec[j]->set_state_change_no(j); - suite_vec[j]->set_modify_change_no(j); + defs_ptr defs = create_server_defs(); + auto suites = defs->suites(); + for (size_t j = 0; j < suites.size(); j++) { + suites[j]->set_state_change_no(j); + suites[j]->set_modify_change_no(j); } return defs; } diff --git a/libs/client/test/TestCheckPtDefsCmd.cpp b/libs/client/test/TestCheckPtDefsCmd.cpp index 5fbdd2eb2..2213572ec 100644 --- a/libs/client/test/TestCheckPtDefsCmd.cpp +++ b/libs/client/test/TestCheckPtDefsCmd.cpp @@ -222,9 +222,9 @@ BOOST_AUTO_TEST_CASE(test_restore_from_check_pt) { BOOST_REQUIRE_MESSAGE(theClient.haltServer() == 0, "Expected halt server to succeed\n" << theClient.errorMsg()); BOOST_REQUIRE_MESSAGE(theClient.restoreDefsFromCheckPt() == 0, "Expected restoreDefsFromCheckPt succeed\n"); BOOST_REQUIRE_MESSAGE(theClient.getDefs() == 0, "Expected getDefs() to succeed, i.e expected empty defs\n"); - BOOST_REQUIRE_MESSAGE(theClient.defs()->suiteVec().size() == expected_no_of_suites, + BOOST_REQUIRE_MESSAGE(theClient.defs()->suites().size() == expected_no_of_suites, "Expected " << expected_no_of_suites << " suites, after restoreDefsFromCheckPt but found " - << theClient.defs()->suiteVec().size() << "\n"); + << theClient.defs()->suites().size() << "\n"); std::string suite = "/s" + ecf::convert_to(i); BOOST_REQUIRE_MESSAGE(theClient.delete_node(suite) == 0, @@ -250,7 +250,7 @@ BOOST_AUTO_TEST_CASE(test_restore_from_check_pt_using_new_server) { MyDefsFixture theDefsFixture; // make sure generated server variable use this port. defs_ptr defs_to_be_check_pointed = theDefsFixture.create_defs(port); - BOOST_REQUIRE_MESSAGE(defs_to_be_check_pointed->suiteVec().size() >= 2, "expected at least 2 suites"); + BOOST_REQUIRE_MESSAGE(defs_to_be_check_pointed->suites().size() >= 2, "expected at least 2 suites"); { // Start a new server. However make sure that on server exit, we not delete check pt files diff --git a/libs/client/test/TestClientHandleCmd.cpp b/libs/client/test/TestClientHandleCmd.cpp index 0aa05b11b..369cce698 100644 --- a/libs/client/test/TestClientHandleCmd.cpp +++ b/libs/client/test/TestClientHandleCmd.cpp @@ -41,14 +41,14 @@ BOOST_AUTO_TEST_CASE(test_client_handle_cmd) { } BOOST_REQUIRE_MESSAGE(theClient.load(defs) == 0, "load defs failed \n" << theClient.errorMsg()); - BOOST_REQUIRE_MESSAGE(theClient.defs()->suiteVec().size() == suites.size(), "load failed"); + BOOST_REQUIRE_MESSAGE(theClient.defs()->suites().size() == suites.size(), "load failed"); BOOST_REQUIRE_MESSAGE(theClient.ch1_register(true, suites) == 0, "ch1_register failed\n" << theClient.errorMsg()); - BOOST_REQUIRE_MESSAGE(theClient.defs()->suiteVec().size() == suites.size(), "register group sync failed"); + BOOST_REQUIRE_MESSAGE(theClient.defs()->suites().size() == suites.size(), "register group sync failed"); BOOST_REQUIRE_MESSAGE(theClient.server_reply().client_handle() == 1, " ch1_register failed expected handle 1"); BOOST_REQUIRE_MESSAGE(theClient.delete_all() == 0, "delete_all failed\n" << theClient.errorMsg()); - BOOST_REQUIRE_MESSAGE(theClient.defs()->suiteVec().size() == 0, "delete all failed"); + BOOST_REQUIRE_MESSAGE(theClient.defs()->suites().size() == 0, "delete all failed"); BOOST_REQUIRE_MESSAGE(theClient.server_reply().client_handle() == 0, "delete all failed expected handle 0 but found " << theClient.server_reply().client_handle()); @@ -56,16 +56,16 @@ BOOST_AUTO_TEST_CASE(test_client_handle_cmd) { theClient.set_auto_sync(false); BOOST_REQUIRE_MESSAGE(theClient.load(defs) == 0, "load defs failed \n" << theClient.errorMsg()); BOOST_REQUIRE_MESSAGE(theClient.sync_local() == 0, "sync_local failed \n" << theClient.errorMsg()); - BOOST_REQUIRE_MESSAGE(theClient.defs()->suiteVec().size() == suites.size(), "load failed"); + BOOST_REQUIRE_MESSAGE(theClient.defs()->suites().size() == suites.size(), "load failed"); BOOST_REQUIRE_MESSAGE(theClient.ch1_register(true, suites) == 0, "ch1_register failed\n" << theClient.errorMsg()); BOOST_REQUIRE_MESSAGE(theClient.sync_local() == 0, "sync_local failed \n" << theClient.errorMsg()); - BOOST_REQUIRE_MESSAGE(theClient.defs()->suiteVec().size() == suites.size(), "register group sync failed"); + BOOST_REQUIRE_MESSAGE(theClient.defs()->suites().size() == suites.size(), "register group sync failed"); BOOST_REQUIRE_MESSAGE(theClient.server_reply().client_handle() == 1, " ch1_register failed expected handle 1"); BOOST_REQUIRE_MESSAGE(theClient.delete_all() == 0, "delete_all failed\n" << theClient.errorMsg()); BOOST_REQUIRE_MESSAGE(theClient.sync_local() == 0, "sync_local failed \n" << theClient.errorMsg()); - BOOST_REQUIRE_MESSAGE(theClient.defs()->suiteVec().size() == 0, "delete all failed"); + BOOST_REQUIRE_MESSAGE(theClient.defs()->suites().size() == 0, "delete all failed"); BOOST_REQUIRE_MESSAGE(theClient.server_reply().client_handle() == 0, "delete all failed expected handle 0 but found " << theClient.server_reply().client_handle()); diff --git a/libs/client/test/TestGroupCmd.cpp b/libs/client/test/TestGroupCmd.cpp index 87eb0cd44..7f4d3b548 100644 --- a/libs/client/test/TestGroupCmd.cpp +++ b/libs/client/test/TestGroupCmd.cpp @@ -76,8 +76,8 @@ BOOST_AUTO_TEST_CASE(test_client_group_lifecyle) { << theClient.errorMsg()); defs_ptr serverDefs = theClient.defs(); BOOST_REQUIRE_MESSAGE(serverDefs.get(), "Server returned a NULL defs"); - BOOST_REQUIRE_MESSAGE(serverDefs->suiteVec().size() >= 1, " no suite ?"); - const std::vector& suites = serverDefs->suiteVec(); + BOOST_REQUIRE_MESSAGE(serverDefs->suites().size() >= 1, " no suite ?"); + const auto& suites = serverDefs->suites(); const std::vector& variables = suites[0]->variables(); expected_var = variables.size(); } @@ -92,7 +92,7 @@ BOOST_AUTO_TEST_CASE(test_client_group_lifecyle) { << theClient.errorMsg()); defs_ptr serverDefs = theClient.defs(); BOOST_REQUIRE_MESSAGE(serverDefs.get(), "Server returned a NULL defs"); - const std::vector& suites = serverDefs->suiteVec(); + const auto& suites = serverDefs->suites(); BOOST_REQUIRE_MESSAGE(suites.size() >= 1, " no suite ?"); const std::vector& variables = suites[0]->variables(); BOOST_REQUIRE_MESSAGE(variables.size() == expected_var, @@ -245,8 +245,8 @@ BOOST_AUTO_TEST_CASE(test_client_group_lifecyle) { BOOST_REQUIRE_MESSAGE(!theEvent.empty(), "Could not find the event myEvent"); BOOST_REQUIRE_MESSAGE(theEvent.value(), "The event was not set"); - const std::vector& suiteVec = serverDefs->suiteVec(); - suite_ptr suite = suiteVec.back(); + const auto& suites = serverDefs->suites(); + suite_ptr suite = suites.back(); BOOST_REQUIRE_MESSAGE(suite->state() == NState::COMPLETE, "Suite expected NState::COMPLETE, but found to be " << NState::toString(suite->state())); } diff --git a/libs/client/test/TestLifeCycle.cpp b/libs/client/test/TestLifeCycle.cpp index 62fed268c..537f67065 100644 --- a/libs/client/test/TestLifeCycle.cpp +++ b/libs/client/test/TestLifeCycle.cpp @@ -68,8 +68,8 @@ BOOST_AUTO_TEST_CASE(test_node_tree_lifecycle) { // endsuite // get the suite, before we do anything initial state should be UNKNOWN - const std::vector& suiteVec = defs.suiteVec(); - suite_ptr suite = suiteVec.back(); + const auto& suites = defs.suites(); + auto suite = suites.back(); BOOST_CHECK_MESSAGE(suite->state() == NState::UNKNOWN, " Initial suite state should be NState::UNKNOWN"); std::string suite1_family1_a = "suite1/family1/a"; diff --git a/libs/client/test/TestLoadDefsCmd.cpp b/libs/client/test/TestLoadDefsCmd.cpp index 35b059b30..e459bb34f 100644 --- a/libs/client/test/TestLoadDefsCmd.cpp +++ b/libs/client/test/TestLoadDefsCmd.cpp @@ -54,7 +54,7 @@ BOOST_AUTO_TEST_CASE(test_load_defs_cmd_handleRequest) { BOOST_CHECK_MESSAGE(parse, "Parse failed. " << errorMsg); firstDefs->clear_externs(); // server defs should not have externs. } - size_t noOfSuites = firstDefs->suiteVec().size(); + size_t noOfSuites = firstDefs->suites().size(); // load the SECOND file, which should resolve the externs std::string secondDef = File::test_data("libs/client/test/data/second.def", "libs/client"); @@ -64,7 +64,7 @@ BOOST_AUTO_TEST_CASE(test_load_defs_cmd_handleRequest) { bool parse = secondDefs.restore(secondDef, errorMsg, warningMsg); BOOST_CHECK_MESSAGE(parse, "Parse failed. " << errorMsg); } - noOfSuites += secondDefs.suiteVec().size(); + noOfSuites += secondDefs.suites().size(); // Create a LoadDefsCmd. This capable of merging defs files // Externs are NOT loaded into the server. @@ -80,13 +80,13 @@ BOOST_AUTO_TEST_CASE(test_load_defs_cmd_handleRequest) { STC_Cmd_ptr requestStatus = cmd.handleRequest(&mockServer); BOOST_CHECK_MESSAGE(requestStatus, "Handle Request " << cmd << " returned NULL\n"); BOOST_CHECK_MESSAGE(requestStatus->error().empty(), requestStatus->error()); - BOOST_CHECK_MESSAGE(secondDefs.suiteVec().size() == noOfSuites, "Merge failed to add suites"); + BOOST_CHECK_MESSAGE(secondDefs.suites().size() == noOfSuites, "Merge failed to add suites"); // Modify the Defs file to add a task/trigger that references the undefined // extern path defined in file 'first.def' This should fail. task_ptr task = Task::create("AMadeUpName"); task->add_trigger("/a/b/c/d/e/f/g/h/j == complete"); - secondDefs.suiteVec().back()->familyVec().back()->addTask(task); + secondDefs.suites().back()->familyVec().back()->addTask(task); // we just added an expression, re-parse to create AST // This should also attempt to resolve the extern node path /a/b/c/d/e/f/g/h/j @@ -115,7 +115,7 @@ BOOST_AUTO_TEST_CASE(test_load_defs_check_only) { BOOST_REQUIRE_MESSAGE(theClient.sync_local() == 0, "Expected sync to succeed\n" << theClient.errorMsg()); // Note: when running with ECF_HOST=localhost the defs may exist, but the number of suites should be empty - BOOST_REQUIRE_MESSAGE(!theClient.defs() || theClient.defs()->suiteVec().empty(), + BOOST_REQUIRE_MESSAGE(!theClient.defs() || theClient.defs()->suites().empty(), "Expected no defs, since nothing should have been loaded\n" << theClient.errorMsg()); } @@ -124,7 +124,7 @@ BOOST_AUTO_TEST_CASE(test_load_defs_check_only) { BOOST_REQUIRE_MESSAGE(theClient.loadDefs(path, false, true /* check only*/) == 0, "Expected load to succeed\n" << theClient.errorMsg()); - BOOST_REQUIRE_MESSAGE(!theClient.defs() || theClient.defs()->suiteVec().empty(), + BOOST_REQUIRE_MESSAGE(!theClient.defs() || theClient.defs()->suites().empty(), "Expected no defs, since nothing should have been loaded\n" << theClient.errorMsg()); } diff --git a/libs/client/test/TestPlugCmd.cpp b/libs/client/test/TestPlugCmd.cpp index b86a8fbd1..9a8b2cbe2 100644 --- a/libs/client/test/TestPlugCmd.cpp +++ b/libs/client/test/TestPlugCmd.cpp @@ -299,7 +299,7 @@ static void test_plug_on_multiple_server(const std::string& host1, CtsApi::get() << " failed should return 0\n" << server2Client.errorMsg()); BOOST_REQUIRE_MESSAGE(server2Client.defs().get(), "Server returned a NULL defs"); - BOOST_REQUIRE_MESSAGE(server2Client.defs()->suiteVec().size() == 0, " Expected server2 to have zero suite"); + BOOST_REQUIRE_MESSAGE(server2Client.defs()->suites().size() == 0, " Expected server2 to have zero suite"); // Move the suite FROM the FIRST server TO the SECOND server and check that it worked destPath = secondServerHostPort; @@ -310,13 +310,13 @@ static void test_plug_on_multiple_server(const std::string& host1, CtsApi::get() << " failed should return 0\n" << server1Client.errorMsg()); BOOST_REQUIRE_MESSAGE(server1Client.defs().get(), "Server returned a NULL defs"); - BOOST_REQUIRE_MESSAGE(server1Client.defs()->suiteVec().size() == 0, " Expected server1 to have no suites"); + BOOST_REQUIRE_MESSAGE(server1Client.defs()->suites().size() == 0, " Expected server1 to have no suites"); BOOST_REQUIRE_MESSAGE(server2Client.getDefs() == 0, CtsApi::get() << " failed should return 0\n" << server2Client.errorMsg()); BOOST_REQUIRE_MESSAGE(server2Client.defs().get(), "Server returned a NULL defs"); - BOOST_REQUIRE_MESSAGE(server2Client.defs()->suiteVec().size() == 1, " Expected server2 to have one suite"); + BOOST_REQUIRE_MESSAGE(server2Client.defs()->suites().size() == 1, " Expected server2 to have one suite"); // ========================================================================== // Do it again, but with no defs file in second server. reload defs into server1 @@ -336,13 +336,13 @@ static void test_plug_on_multiple_server(const std::string& host1, CtsApi::get() << " failed should return 0\n" << server1Client.errorMsg()); BOOST_REQUIRE_MESSAGE(server1Client.defs().get(), "Server returned a NULL defs"); - BOOST_REQUIRE_MESSAGE(server1Client.defs()->suiteVec().size() == 0, " Expected server1 to have no suites"); + BOOST_REQUIRE_MESSAGE(server1Client.defs()->suites().size() == 0, " Expected server1 to have no suites"); BOOST_REQUIRE_MESSAGE(server2Client.getDefs() == 0, CtsApi::get() << " failed should return 0\n" << server2Client.errorMsg()); BOOST_REQUIRE_MESSAGE(server2Client.defs().get(), "Server returned a NULL defs"); - BOOST_REQUIRE_MESSAGE(server2Client.defs()->suiteVec().size() == 1, " Expected server2 to have one suite"); + BOOST_REQUIRE_MESSAGE(server2Client.defs()->suites().size() == 1, " Expected server2 to have one suite"); } BOOST_AUTO_TEST_CASE(test_server_plug_cmd) { diff --git a/libs/client/test/TestServer.cpp b/libs/client/test/TestServer.cpp index e663e6047..eabbeeaca 100644 --- a/libs/client/test/TestServer.cpp +++ b/libs/client/test/TestServer.cpp @@ -294,7 +294,7 @@ BOOST_AUTO_TEST_CASE(test_server_stress_test) { BOOST_REQUIRE_MESSAGE(theClient.sync_local() == 0, "failed should return 0\n" << theClient.errorMsg()); BOOST_REQUIRE_MESSAGE(theClient.defs().get(), "Server returned a NULL defs"); - BOOST_REQUIRE_MESSAGE(theClient.defs()->suiteVec().size() >= 1, " no suite ?"); + BOOST_REQUIRE_MESSAGE(theClient.defs()->suites().size() >= 1, " no suite ?"); } std::cout << " Server handled " << load * 16 << " requests in CPU timer(" << timer << ")" << " Duration timer(" << DurationTimer::to_simple_string(duration_timer.elapsed()) << ")" << " Chrono timer(" @@ -328,7 +328,7 @@ BOOST_AUTO_TEST_CASE(test_server_stress_test) { << theClient.errorMsg()); BOOST_REQUIRE_MESSAGE(theClient.defs().get(), "Server returned a NULL defs"); - BOOST_REQUIRE_MESSAGE(theClient.defs()->suiteVec().size() >= 1, " no suite ?"); + BOOST_REQUIRE_MESSAGE(theClient.defs()->suites().size() >= 1, " no suite ?"); } std::cout << " Server handled " << load * 8 << " requests in boost_timer(" << timer << ")" << " DurationTimer(" << DurationTimer::to_simple_string(duration_timer.elapsed()) << ")" << " Chrono_timer(" @@ -382,7 +382,7 @@ BOOST_AUTO_TEST_CASE(test_server_group_stress_test) { "Group request " << CtsApi::group(groupRequest) << " failed should return 0\n" << theClient.errorMsg()); BOOST_REQUIRE_MESSAGE(theClient.defs().get(), "Server returned a NULL defs"); - BOOST_REQUIRE_MESSAGE(theClient.defs()->suiteVec().size() >= 1, " no suite ?"); + BOOST_REQUIRE_MESSAGE(theClient.defs()->suites().size() >= 1, " no suite ?"); } std::cout << " Server handled " << load * 8 << " commands using " << load << " group requests in boost_timer(" << timer << ") DurationTimer(" << DurationTimer::to_simple_string(duration_timer.elapsed()) << ")" @@ -608,7 +608,7 @@ BOOST_AUTO_TEST_CASE(test_server_stress_test_2) { CtsApi::get() << " failed should return 0\n" << theClient.errorMsg()); // 60 BOOST_REQUIRE_MESSAGE(theClient.defs().get(), "Server returned a NULL defs"); - BOOST_REQUIRE_MESSAGE(theClient.defs()->suiteVec().size() >= 1, " no suite ?"); + BOOST_REQUIRE_MESSAGE(theClient.defs()->suites().size() >= 1, " no suite ?"); } int no_of_client_calls = 74; diff --git a/libs/client/test/TestServerAndLifeCycle.cpp b/libs/client/test/TestServerAndLifeCycle.cpp index 48560c94f..2e12249bf 100644 --- a/libs/client/test/TestServerAndLifeCycle.cpp +++ b/libs/client/test/TestServerAndLifeCycle.cpp @@ -77,7 +77,7 @@ BOOST_AUTO_TEST_CASE(test_client_lifecyle) { << theClient.errorMsg()); defs_ptr serverDefs = theClient.defs(); BOOST_REQUIRE_MESSAGE(serverDefs.get(), "Server returned a NULL defs"); - BOOST_REQUIRE_MESSAGE(serverDefs->suiteVec().size() >= 1, " no suite ?"); + BOOST_REQUIRE_MESSAGE(serverDefs->suites().size() >= 1, " no suite ?"); } // Now go through and simulate client request to change Node tree state. @@ -267,8 +267,8 @@ BOOST_AUTO_TEST_CASE(test_client_lifecyle) { BOOST_REQUIRE_MESSAGE(!theEvent.empty(), "Could not find the event myEvent"); BOOST_REQUIRE_MESSAGE(theEvent.value(), "The event was not set"); - const std::vector& suiteVec = serverDefs->suiteVec(); - suite_ptr suite = suiteVec.back(); + const auto& suites = serverDefs->suites(); + suite_ptr suite = suites.back(); BOOST_REQUIRE_MESSAGE(suite->state() == NState::COMPLETE, "Suite expected NState::COMPLETE, but found to be " << NState::toString(suite->state())); } diff --git a/libs/node/src/ecflow/node/ClientSuites.cpp b/libs/node/src/ecflow/node/ClientSuites.cpp index 0eeb6f0e6..55843cad8 100644 --- a/libs/node/src/ecflow/node/ClientSuites.cpp +++ b/libs/node/src/ecflow/node/ClientSuites.cpp @@ -143,14 +143,14 @@ defs_ptr ClientSuites::create_defs(defs_ptr server_defs) const { // If the user has registered *ALL* the suites just return the server defs auto suites_end = suites_.end(); - if (suites_.size() == server_defs->suiteVec().size()) { + if (suites_.size() == server_defs->suites().size()) { size_t real_suite_count = 0; for (auto i = suites_.begin(); i != suites_end; ++i) { if (suite_ptr suite = (*i).weak_suite_ptr_.lock(); suite.get()) { real_suite_count++; } } - if (real_suite_count == server_defs->suiteVec().size()) { + if (real_suite_count == server_defs->suites().size()) { server_defs->set_state_change_no(Ecf::state_change_no()); server_defs->set_modify_change_no(Ecf::modify_change_no()); @@ -277,13 +277,13 @@ std::string ClientSuites::dump() const { } void ClientSuites::update_suite_order() { - const std::vector& server_suite_vec = defs_->suiteVec(); - size_t server_suite_vec_size = server_suite_vec.size(); + const auto& suites = defs_->suites(); + size_t size = suites.size(); auto suites_end = suites_.end(); for (auto i = suites_.begin(); i != suites_end; ++i) { - for (size_t s = 0; s < server_suite_vec_size; s++) { - if ((*i).name_ == server_suite_vec[s]->name()) { + for (size_t s = 0; s < size; s++) { + if ((*i).name_ == suites[s]->name()) { (*i).index_ = static_cast(s); break; } diff --git a/libs/node/src/ecflow/node/Defs.cpp b/libs/node/src/ecflow/node/Defs.cpp index 734d6f958..d6eebfe18 100644 --- a/libs/node/src/ecflow/node/Defs.cpp +++ b/libs/node/src/ecflow/node/Defs.cpp @@ -354,12 +354,12 @@ void Defs::absorb(Defs* input_defs, bool force) { updateCalendarCount_ = 0; // We must make a copy, otherwise we are iterating over a vector that is being deleted - std::vector suiteVecCopy = input_defs->suiteVec(); - size_t theSize = suiteVecCopy.size(); + auto suitesCopy = input_defs->suites(); + size_t theSize = suitesCopy.size(); for (size_t s = 0; s < theSize; s++) { /// regardless remove the suite from the input defs - suite_ptr the_input_suite = input_defs->removeSuite(suiteVecCopy[s]); + suite_ptr the_input_suite = input_defs->removeSuite(suitesCopy[s]); if (force) { /// The suite of the same name exists. remove it from *existing* defs @@ -373,7 +373,7 @@ void Defs::absorb(Defs* input_defs, bool force) { /// This stops accidental overwrite addSuite(the_input_suite); } - LOG_ASSERT(input_defs->suiteVec().empty(), "Defs::absorb"); + LOG_ASSERT(input_defs->suites().empty(), "Defs::absorb"); // Copy over server user variables server_state().add_or_update_user_variables(input_defs->server_state().user_variables()); diff --git a/libs/node/src/ecflow/node/DefsTreeVisitor.hpp b/libs/node/src/ecflow/node/DefsTreeVisitor.hpp index b29ef9eee..8046e871e 100644 --- a/libs/node/src/ecflow/node/DefsTreeVisitor.hpp +++ b/libs/node/src/ecflow/node/DefsTreeVisitor.hpp @@ -58,7 +58,7 @@ struct DefsTreeVisitor nodes_t nodes_at(const path_t& path) const { nodes_t nodes; if (path == "/") { - std::vector suites = defs_.suiteVec(); + auto suites = defs_.suites(); std::transform(std::begin(suites), std::end(suites), std::back_inserter(nodes), [](const suite_ptr& ptr) { return static_cast(ptr.get()); }); diff --git a/libs/node/src/ecflow/node/Jobs.cpp b/libs/node/src/ecflow/node/Jobs.cpp index 3bb8cd3f7..d5bb713e4 100644 --- a/libs/node/src/ecflow/node/Jobs.cpp +++ b/libs/node/src/ecflow/node/Jobs.cpp @@ -55,7 +55,7 @@ bool Jobs::generate(JobsParam& jobsParam) const { if (defs_) { if (defs_->server_state().get_state() == SState::RUNNING) { - const std::vector& suites = defs_->suiteVec(); + const auto& suites = defs_->suites(); for (const suite_ptr& suite : suites) { // SuiteChanged moved into Suite::resolveDependencies. // This ensures the fast path and when suite are not begun we save a ctor/dtor call diff --git a/libs/node/src/ecflow/node/Memento.cpp b/libs/node/src/ecflow/node/Memento.cpp index 1a724ad1d..2d64912f6 100644 --- a/libs/node/src/ecflow/node/Memento.cpp +++ b/libs/node/src/ecflow/node/Memento.cpp @@ -38,7 +38,7 @@ void CompoundMemento::incremental_sync(defs_ptr client_def) const { std::string error_msg = "CompoundMemento::incremental_sync: could not find path "; error_msg += absNodePath_; error_msg += "\nClient has the following suites: "; - for (const auto& suite : client_def->suiteVec()) { + for (const auto& suite : client_def->suites()) { error_msg += suite->name(); error_msg += ","; } diff --git a/libs/node/src/ecflow/node/Operations.hpp b/libs/node/src/ecflow/node/Operations.hpp index 6c13c66e4..03f49708e 100644 --- a/libs/node/src/ecflow/node/Operations.hpp +++ b/libs/node/src/ecflow/node/Operations.hpp @@ -153,7 +153,7 @@ struct VisitorAll void operator()(V&& v) { v(*this); - visit_all(defs_.suiteVec(), std::forward(v)); + visit_all(defs_.suites(), std::forward(v)); } Defs& defs_; diff --git a/libs/node/src/ecflow/node/ResolveExternsVisitor.cpp b/libs/node/src/ecflow/node/ResolveExternsVisitor.cpp index d82c4e2ec..87abd5e2b 100644 --- a/libs/node/src/ecflow/node/ResolveExternsVisitor.cpp +++ b/libs/node/src/ecflow/node/ResolveExternsVisitor.cpp @@ -27,7 +27,7 @@ ResolveExternsVisitor::ResolveExternsVisitor(Defs* defs) } void ResolveExternsVisitor::visitDefs(Defs* d) { - for (suite_ptr s : d->suiteVec()) { + for (auto s : d->suites()) { s->acceptVisitTraversor(*this); } } diff --git a/libs/node/src/ecflow/node/formatter/DefsWriter.hpp b/libs/node/src/ecflow/node/formatter/DefsWriter.hpp index f229a330c..915a56c31 100644 --- a/libs/node/src/ecflow/node/formatter/DefsWriter.hpp +++ b/libs/node/src/ecflow/node/formatter/DefsWriter.hpp @@ -2179,7 +2179,7 @@ struct Writer } // Write the children (i.e. suites) - for (const auto& s : item.suiteVec()) { + for (const auto& s : item.suites()) { Writer::write(output, *s, ctx); } diff --git a/libs/node/test/TestAvisoAttr.cpp b/libs/node/test/TestAvisoAttr.cpp index 4a2f5a6a6..dc600cdbb 100644 --- a/libs/node/test/TestAvisoAttr.cpp +++ b/libs/node/test/TestAvisoAttr.cpp @@ -75,7 +75,7 @@ BOOST_AUTO_TEST_CASE(can_run_aviso_attribute_with_variable_substitution) { bool parsedOK = parser.doParse(errorMsg, warningMsg); BOOST_CHECK_MESSAGE(parsedOK, "Failed to parse definition: " << errorMsg); - const auto& suites = defs.suiteVec(); + const auto& suites = defs.suites(); BOOST_CHECK_EQUAL(suites.size(), static_cast(1)); const auto& families = suites[0]->familyVec(); diff --git a/libs/node/test/TestClientSuiteMgr.cpp b/libs/node/test/TestClientSuiteMgr.cpp index c5bae5930..d7ffcb2f5 100644 --- a/libs/node/test/TestClientSuiteMgr.cpp +++ b/libs/node/test/TestClientSuiteMgr.cpp @@ -163,13 +163,13 @@ BOOST_AUTO_TEST_CASE(test_suite_filter_replace_filtered_suite_with_auto_add) { ECF_NAME_THIS_TEST(); auto defs = load_defs(path); - BOOST_CHECK_EQUAL(defs->suiteVec().size(), std::size_t{3}); + BOOST_CHECK_EQUAL(defs->suites().size(), std::size_t{3}); auto handle = defs->client_suite_mgr().create_client_suite(true, suites, user); { // access filtered suites, before replace auto filtered = defs->client_suite_mgr().create_defs(handle, defs); - BOOST_CHECK_EQUAL(filtered->suiteVec().size(), std::size_t{2}); + BOOST_CHECK_EQUAL(filtered->suites().size(), std::size_t{2}); } { // replace suite @@ -179,7 +179,7 @@ BOOST_AUTO_TEST_CASE(test_suite_filter_replace_filtered_suite_with_auto_add) { { // access filtered suites, after replace auto filtered = defs->client_suite_mgr().create_defs(handle, defs); - BOOST_CHECK_EQUAL(filtered->suiteVec().size(), std::size_t{2}); + BOOST_CHECK_EQUAL(filtered->suites().size(), std::size_t{2}); } } @@ -187,13 +187,13 @@ BOOST_AUTO_TEST_CASE(test_suite_filter_replace_selected_suite_with_auto_add) { ECF_NAME_THIS_TEST(); auto defs = load_defs(path); - BOOST_CHECK_EQUAL(defs->suiteVec().size(), std::size_t{3}); + BOOST_CHECK_EQUAL(defs->suites().size(), std::size_t{3}); auto handle = defs->client_suite_mgr().create_client_suite(true, suites, user); { // access filtered suites, before replace auto filtered = defs->client_suite_mgr().create_defs(handle, defs); - BOOST_CHECK_EQUAL(filtered->suiteVec().size(), std::size_t{2}); + BOOST_CHECK_EQUAL(filtered->suites().size(), std::size_t{2}); } { // replace suite @@ -204,7 +204,7 @@ BOOST_AUTO_TEST_CASE(test_suite_filter_replace_selected_suite_with_auto_add) { { // access filtered suites, after replace auto& mng = defs->client_suite_mgr(); auto filtered = mng.create_defs(handle, defs); - BOOST_CHECK_EQUAL(filtered->suiteVec().size(), std::size_t{2}); + BOOST_CHECK_EQUAL(filtered->suites().size(), std::size_t{2}); } } @@ -212,13 +212,13 @@ BOOST_AUTO_TEST_CASE(test_suite_filter_replace_filtered_suite_without_auto_add) ECF_NAME_THIS_TEST(); auto defs = load_defs(path); - BOOST_CHECK_EQUAL(defs->suiteVec().size(), std::size_t{3}); + BOOST_CHECK_EQUAL(defs->suites().size(), std::size_t{3}); auto handle = defs->client_suite_mgr().create_client_suite(false, suites, user); { // access filtered suites, before replace auto filtered = defs->client_suite_mgr().create_defs(handle, defs); - BOOST_CHECK_EQUAL(filtered->suiteVec().size(), std::size_t{2}); + BOOST_CHECK_EQUAL(filtered->suites().size(), std::size_t{2}); } { // replace suite @@ -228,7 +228,7 @@ BOOST_AUTO_TEST_CASE(test_suite_filter_replace_filtered_suite_without_auto_add) { // access filtered suites, after replace auto filtered = defs->client_suite_mgr().create_defs(handle, defs); - BOOST_CHECK_EQUAL(filtered->suiteVec().size(), std::size_t{2}); + BOOST_CHECK_EQUAL(filtered->suites().size(), std::size_t{2}); } } @@ -236,13 +236,13 @@ BOOST_AUTO_TEST_CASE(test_suite_filter_replace_selected_suite_without_auto_add) ECF_NAME_THIS_TEST(); auto defs = load_defs(path); - BOOST_CHECK_EQUAL(defs->suiteVec().size(), std::size_t{3}); + BOOST_CHECK_EQUAL(defs->suites().size(), std::size_t{3}); auto handle = defs->client_suite_mgr().create_client_suite(false, suites, user); { // access filtered suites, before replace auto filtered = defs->client_suite_mgr().create_defs(handle, defs); - BOOST_CHECK_EQUAL(filtered->suiteVec().size(), std::size_t{2}); + BOOST_CHECK_EQUAL(filtered->suites().size(), std::size_t{2}); } { // replace suite @@ -253,7 +253,7 @@ BOOST_AUTO_TEST_CASE(test_suite_filter_replace_selected_suite_without_auto_add) { // access filtered suites, after replace auto& mng = defs->client_suite_mgr(); auto filtered = mng.create_defs(handle, defs); - BOOST_CHECK_EQUAL(filtered->suiteVec().size(), std::size_t{2}); + BOOST_CHECK_EQUAL(filtered->suites().size(), std::size_t{2}); } } @@ -261,13 +261,13 @@ BOOST_AUTO_TEST_CASE(test_suite_filter_delete_and_add_filtered_suite_with_auto_a ECF_NAME_THIS_TEST(); auto defs = load_defs(path); - BOOST_CHECK_EQUAL(defs->suiteVec().size(), std::size_t{3}); + BOOST_CHECK_EQUAL(defs->suites().size(), std::size_t{3}); auto handle = defs->client_suite_mgr().create_client_suite(true, suites, user); { // access filtered suites, before replace auto filtered = defs->client_suite_mgr().create_defs(handle, defs); - BOOST_CHECK_EQUAL(filtered->suiteVec().size(), std::size_t{2}); + BOOST_CHECK_EQUAL(filtered->suites().size(), std::size_t{2}); } { // delete suite @@ -284,7 +284,7 @@ BOOST_AUTO_TEST_CASE(test_suite_filter_delete_and_add_filtered_suite_with_auto_a { // access filtered suites, after replace auto filtered = defs->client_suite_mgr().create_defs(handle, defs); - BOOST_CHECK_EQUAL(filtered->suiteVec().size(), std::size_t{3}); + BOOST_CHECK_EQUAL(filtered->suites().size(), std::size_t{3}); } } diff --git a/libs/node/test/TestDefs.cpp b/libs/node/test/TestDefs.cpp index 74a7d9c8e..72403846d 100644 --- a/libs/node/test/TestDefs.cpp +++ b/libs/node/test/TestDefs.cpp @@ -55,7 +55,7 @@ BOOST_AUTO_TEST_CASE(test_defs_absorb) { } theDefs.absorb(&otherDefs, true); - BOOST_CHECK_MESSAGE(otherDefs.suiteVec().empty(), "absorb failed"); + BOOST_CHECK_MESSAGE(otherDefs.suites().empty(), "absorb failed"); } BOOST_AUTO_TEST_CASE(test_defs_absorb_server_user_variables) { @@ -74,8 +74,8 @@ BOOST_AUTO_TEST_CASE(test_defs_absorb_server_user_variables) { theDefs.absorb(&otherDefs, true); - BOOST_CHECK_MESSAGE(otherDefs.suiteVec().empty(), "absorb failed"); - BOOST_CHECK_MESSAGE(theDefs.suiteVec().size() == 1, "absorb failed"); + BOOST_CHECK_MESSAGE(otherDefs.suites().empty(), "absorb failed"); + BOOST_CHECK_MESSAGE(theDefs.suites().size() == 1, "absorb failed"); BOOST_CHECK_MESSAGE(theDefs.server_state().user_variables().size() == 3, "Expected 3 server user variables"); } diff --git a/libs/node/test/TestMovePeer.cpp b/libs/node/test/TestMovePeer.cpp index 3e7acac9c..bb90d6156 100644 --- a/libs/node/test/TestMovePeer.cpp +++ b/libs/node/test/TestMovePeer.cpp @@ -84,7 +84,7 @@ BOOST_AUTO_TEST_CASE(test_move_peer) { s3->move_peer(s3_f1.get(), s3_f3.get()); // order is f1,f3,f2 s3->move_peer(s3_f2.get(), s3_f3.get()); // order is f1,f3,f2 - for (auto suite : defs.suiteVec()) { + for (auto suite : defs.suites()) { for (auto family : suite->familyVec()) { node_ptr t1 = defs.findAbsNode(family->absNodePath() + "/t1"); diff --git a/libs/node/test/TestOrder.cpp b/libs/node/test/TestOrder.cpp index 368c8ee0f..505182000 100644 --- a/libs/node/test/TestOrder.cpp +++ b/libs/node/test/TestOrder.cpp @@ -66,7 +66,7 @@ BOOST_AUTO_TEST_CASE(test_order) { // In init state all suite should be in alpha order theDefs.order(theDefs.findAbsNode("/A").get(), NOrder::ALPHA); test_invariants(theDefs, __LINE__); - auto names = ecf::algorithm::transform_to_vector(theDefs.suiteVec(), to_name); + auto names = ecf::algorithm::transform_to_vector(theDefs.suites(), to_name); BOOST_REQUIRE_MESSAGE(names == alpha, "NOrder::ALPHA expected " << ecf::algorithm::join(alpha) << " but found " << ecf::algorithm::join(names)); @@ -77,7 +77,7 @@ BOOST_AUTO_TEST_CASE(test_order) { theDefs.order(theDefs.findAbsNode("/a").get(), NOrder::ORDER); test_invariants(theDefs, __LINE__); - auto names = ecf::algorithm::transform_to_vector(theDefs.suiteVec(), to_name); + auto names = ecf::algorithm::transform_to_vector(theDefs.suites(), to_name); BOOST_REQUIRE_MESSAGE(names == order, "NOrder::ORDER expected " << ecf::algorithm::join(order) << " but found " << ecf::algorithm::join(names)); @@ -86,7 +86,7 @@ BOOST_AUTO_TEST_CASE(test_order) { { // Change back to alpha, then move suite 'c' to the top theDefs.order(theDefs.findAbsNode("/A").get(), NOrder::ALPHA); - auto names = ecf::algorithm::transform_to_vector(theDefs.suiteVec(), to_name); + auto names = ecf::algorithm::transform_to_vector(theDefs.suites(), to_name); BOOST_REQUIRE_MESSAGE(names == alpha, "NOrder::ALPHA expected " << ecf::algorithm::join(alpha) << " but found " << ecf::algorithm::join(names)); @@ -102,7 +102,7 @@ BOOST_AUTO_TEST_CASE(test_order) { expected.emplace_back("B"); theDefs.order(theDefs.findAbsNode("/c").get(), NOrder::TOP); test_invariants(theDefs, __LINE__); - auto names = ecf::algorithm::transform_to_vector(theDefs.suiteVec(), to_name); + auto names = ecf::algorithm::transform_to_vector(theDefs.suites(), to_name); BOOST_REQUIRE_MESSAGE(names == expected, "NOrder::TOP expected " << ecf::algorithm::join(expected) << " but found " << ecf::algorithm::join(names)); @@ -112,7 +112,7 @@ BOOST_AUTO_TEST_CASE(test_order) { // move suite 'c' back to the bottom theDefs.order(theDefs.findAbsNode("/c").get(), NOrder::BOTTOM); test_invariants(theDefs, __LINE__); - auto names = ecf::algorithm::transform_to_vector(theDefs.suiteVec(), to_name); + auto names = ecf::algorithm::transform_to_vector(theDefs.suites(), to_name); BOOST_REQUIRE_MESSAGE(names == alpha, "NOrder::BOTTOM order not as expected"); } @@ -120,7 +120,7 @@ BOOST_AUTO_TEST_CASE(test_order) { // move suite 'a' up one place. Should be no change, since its already at the top theDefs.order(theDefs.findAbsNode("/a").get(), NOrder::UP); test_invariants(theDefs, __LINE__); - auto names = ecf::algorithm::transform_to_vector(theDefs.suiteVec(), to_name); + auto names = ecf::algorithm::transform_to_vector(theDefs.suites(), to_name); BOOST_REQUIRE_MESSAGE(names == alpha, "NOrder::UP order not as expected"); } @@ -128,7 +128,7 @@ BOOST_AUTO_TEST_CASE(test_order) { // move suite 'c' down one place. Should be no change, since its already at the bottom theDefs.order(theDefs.findAbsNode("/c").get(), NOrder::DOWN); test_invariants(theDefs, __LINE__); - auto names = ecf::algorithm::transform_to_vector(theDefs.suiteVec(), to_name); + auto names = ecf::algorithm::transform_to_vector(theDefs.suites(), to_name); BOOST_REQUIRE_MESSAGE(names == alpha, "NOrder::DOWN order not as expected"); } @@ -138,7 +138,7 @@ BOOST_AUTO_TEST_CASE(test_order) { theDefs.order(theDefs.findAbsNode("/a").get(), NOrder::DOWN); test_invariants(theDefs, __LINE__); - auto names = ecf::algorithm::transform_to_vector(theDefs.suiteVec(), to_name); + auto names = ecf::algorithm::transform_to_vector(theDefs.suites(), to_name); BOOST_REQUIRE_MESSAGE(names == expected, "NOrder::DOWN order not as expected"); } @@ -148,7 +148,7 @@ BOOST_AUTO_TEST_CASE(test_order) { // Move suite 'b' up by one place theDefs.order(theDefs.findAbsNode("/b").get(), NOrder::UP); test_invariants(theDefs, __LINE__); - auto names = ecf::algorithm::transform_to_vector(theDefs.suiteVec(), to_name); + auto names = ecf::algorithm::transform_to_vector(theDefs.suites(), to_name); BOOST_REQUIRE_MESSAGE(names == expected, "NOrder::UP order not as expected"); } @@ -160,7 +160,7 @@ BOOST_AUTO_TEST_CASE(test_order) { // In init state all suite should be in alpha order theDefs.order(theDefs.findAbsNode("/a/a").get(), NOrder::ALPHA); test_invariants(theDefs, __LINE__); - auto names = ecf::algorithm::transform_to_vector(theDefs.suiteVec(), to_name); + auto names = ecf::algorithm::transform_to_vector(theDefs.suites(), to_name); BOOST_REQUIRE_MESSAGE(names == alpha, "NOrder::ALPHA Init order " << ecf::algorithm::join(names) << " not as expected " << ecf::algorithm::join(alpha)); @@ -471,7 +471,7 @@ BOOST_AUTO_TEST_CASE(test_order_by_runtime) { } defs.order(nullptr, NOrder::RUNTIME); // moot when you only have one suite - for (auto suite : defs.suiteVec()) { + for (auto suite : defs.suites()) { suite->order(nullptr, NOrder::RUNTIME); for (auto family : suite->familyVec()) { family->order(nullptr, NOrder::RUNTIME); diff --git a/libs/node/test/parser/ParseTimer.cpp b/libs/node/test/parser/ParseTimer.cpp index 67990e537..a3228fe9e 100644 --- a/libs/node/test/parser/ParseTimer.cpp +++ b/libs/node/test/parser/ParseTimer.cpp @@ -179,8 +179,8 @@ int main(int argc, char* argv[]) { { timer.start(); - for (suite_ptr s : defs.suiteVec()) { - test_find_task_using_path(s.get(), defs); + for (suite_ptr suite : defs.suites()) { + test_find_task_using_path(suite.get(), defs); } std::cout << " Test all paths can be found. time taken = " << timer << std::endl; } @@ -219,23 +219,23 @@ int main(int argc, char* argv[]) { std::cout << "Expected all tasks to be deleted but found " << tasks.size() << "\n"; } - std::vector vec = defs.suiteVec(); // make a copy, to avoid invalidating iterators - for (suite_ptr s : vec) { - std::vector familyVec = s->nodeVec(); // make a copy, to avoid invalidating iterators + auto suites = defs.suites(); // make a copy, to avoid invalidating iterators + for (suite_ptr suite : suites) { + std::vector familyVec = suite->nodeVec(); // make a copy, to avoid invalidating iterators for (node_ptr f : familyVec) { if (!defs.deleteChild(f.get())) { std::cout << "Failed to delete family\n"; } } - if (!s->nodeVec().empty()) { - std::cout << "Expected all Families to be deleted but found " << s->nodeVec().size() << "\n"; + if (!suite->nodeVec().empty()) { + std::cout << "Expected all Families to be deleted but found " << suite->nodeVec().size() << "\n"; } - if (!defs.deleteChild(s.get())) { + if (!defs.deleteChild(suite.get())) { std::cout << "Failed to delete suite\n"; } } - if (!defs.suiteVec().empty()) { - std::cout << "Expected all Suites to be deleted but found " << defs.suiteVec().size() << "\n"; + if (!defs.suites().empty()) { + std::cout << "Expected all Suites to be deleted but found " << defs.suites().size() << "\n"; } std::cout << " time for deleting all nodes = " << timer << std::endl; diff --git a/libs/node/test/parser/TestAvisoAttr.cpp b/libs/node/test/parser/TestAvisoAttr.cpp index 1327d024b..d050afa55 100644 --- a/libs/node/test/parser/TestAvisoAttr.cpp +++ b/libs/node/test/parser/TestAvisoAttr.cpp @@ -44,7 +44,7 @@ BOOST_AUTO_TEST_CASE(can_parse_aviso_attribute_on_task_with_default_parameters) bool parsedOK = parser.doParse(errorMsg, warningMsg); BOOST_CHECK_MESSAGE(parsedOK, "Failed to parse definition: " << errorMsg); - const auto& suites = defs.suiteVec(); + const auto& suites = defs.suites(); BOOST_CHECK_EQUAL(suites.size(), static_cast(1)); const auto& families = suites[0]->familyVec(); @@ -85,7 +85,7 @@ BOOST_AUTO_TEST_CASE(can_parse_aviso_attribute_on_task_with_all_parameters) { bool parsedOK = parser.doParse(errorMsg, warningMsg); BOOST_CHECK_MESSAGE(parsedOK, "Failed to parse definition: " << errorMsg); - const auto& suites = defs.suiteVec(); + const auto& suites = defs.suites(); BOOST_CHECK_EQUAL(suites.size(), static_cast(1)); const auto& families = suites[0]->familyVec(); diff --git a/libs/node/test/parser/TestDefsStructurePersistAndReload.cpp b/libs/node/test/parser/TestDefsStructurePersistAndReload.cpp index e3400483c..74b2eabf0 100644 --- a/libs/node/test/parser/TestDefsStructurePersistAndReload.cpp +++ b/libs/node/test/parser/TestDefsStructurePersistAndReload.cpp @@ -80,9 +80,9 @@ BOOST_AUTO_TEST_CASE(test_find_task_using_paths) { MyDefsFixture theDefsFixture; - const std::vector& suiteVec = theDefsFixture.defsfile_.suiteVec(); - for (suite_ptr s : suiteVec) { - test_find_task_using_path(s.get(), theDefsFixture.defsfile_); + const auto& suites = theDefsFixture.defsfile_.suites(); + for (suite_ptr suite : suites) { + test_find_task_using_path(suite.get(), theDefsFixture.defsfile_); } } diff --git a/libs/node/test/parser/TestMirrorAttr.cpp b/libs/node/test/parser/TestMirrorAttr.cpp index c56120afd..02802640d 100644 --- a/libs/node/test/parser/TestMirrorAttr.cpp +++ b/libs/node/test/parser/TestMirrorAttr.cpp @@ -44,7 +44,7 @@ BOOST_AUTO_TEST_CASE(can_parse_mirror_attribute_on_task_with_default_parameters) bool parsedOK = parser.doParse(errorMsg, warningMsg); BOOST_CHECK_MESSAGE(parsedOK, "Failed to parse definition: " << errorMsg); - const auto& suites = defs.suiteVec(); + const auto& suites = defs.suites(); BOOST_CHECK_EQUAL(suites.size(), static_cast(1)); const auto& families = suites[0]->familyVec(); @@ -85,7 +85,7 @@ BOOST_AUTO_TEST_CASE(can_parse_mirror_attribute_on_task_with_all_attributes) { bool parsedOK = parser.doParse(errorMsg, warningMsg); BOOST_CHECK_MESSAGE(parsedOK, "Failed to parse definition: " << errorMsg); - const auto& suites = defs.suiteVec(); + const auto& suites = defs.suites(); BOOST_CHECK_EQUAL(suites.size(), static_cast(1)); const auto& families = suites[0]->familyVec(); diff --git a/libs/node/test/parser/TestSingleDefsFile.cpp b/libs/node/test/parser/TestSingleDefsFile.cpp index c0e800ba8..bc6aac90c 100644 --- a/libs/node/test/parser/TestSingleDefsFile.cpp +++ b/libs/node/test/parser/TestSingleDefsFile.cpp @@ -151,8 +151,8 @@ BOOST_AUTO_TEST_CASE(test_single_defs) { } { timer.start(); - for (suite_ptr s : defs.suiteVec()) { - test_find_task_using_path(s.get(), defs); + for (suite_ptr suites : defs.suites()) { + test_find_task_using_path(suites.get(), defs); } BOOST_CHECK_MESSAGE(get_seconds(timer.elapsed().user) < expectedTimeForFindAllPaths, "Performance regression, expected < " << expectedTimeForFindAllPaths @@ -286,9 +286,9 @@ BOOST_AUTO_TEST_CASE(test_single_defs) { tasks = ecf::get_all_tasks(defs); BOOST_REQUIRE_MESSAGE(tasks.empty(), "Expected all tasks to be deleted but found " << tasks.size()); - std::vector vec = defs.suiteVec(); // make a copy, to avoid invalidating iterators - BOOST_CHECK_MESSAGE(vec.size() > 0, "Expected > 0 Suites but found " << vec.size()); - for (suite_ptr s : vec) { + auto suites = defs.suites(); // make a copy, to avoid invalidating iterators + BOOST_CHECK_MESSAGE(suites.size() > 0, "Expected > 0 Suites but found " << suites.size()); + for (suite_ptr s : suites) { std::vector familyVec = s->nodeVec(); // make a copy, to avoid invalidating iterators for (node_ptr f : familyVec) { BOOST_REQUIRE_MESSAGE(defs.deleteChild(f.get()), " Failed to delete family"); @@ -297,8 +297,8 @@ BOOST_AUTO_TEST_CASE(test_single_defs) { "Expected all Families to be deleted but found " << s->nodeVec().size()); BOOST_REQUIRE_MESSAGE(defs.deleteChild(s.get()), " Failed to delete suite"); } - BOOST_REQUIRE_MESSAGE(defs.suiteVec().empty(), - "Expected all Suites to be deleted but found " << defs.suiteVec().size()); + BOOST_REQUIRE_MESSAGE(defs.suites().empty(), + "Expected all Suites to be deleted but found " << defs.suites().size()); std::cout << " time for deleting all nodes = " << timer << std::endl; } diff --git a/libs/pyext/src/ecflow/python/ExportDefs.cpp b/libs/pyext/src/ecflow/python/ExportDefs.cpp index e485e2406..5c5d71957 100644 --- a/libs/pyext/src/ecflow/python/ExportDefs.cpp +++ b/libs/pyext/src/ecflow/python/ExportDefs.cpp @@ -78,8 +78,8 @@ std::string simulate(defs_ptr defs) { if (defs.get()) { // name output file after name of the first suite std::string defs_filename = "pyext.def"; - if (!defs->suiteVec().empty()) { - defs_filename = (*defs->suiteVec().begin())->name() + ".def"; + if (!defs->suites().empty()) { + defs_filename = (*defs->suites().begin())->name() + ".def"; } ecf::Simulator simulator; @@ -182,7 +182,7 @@ void sort_attributes3(defs_ptr self, const std::string& attribute_name, bool rec // Support sized and Container protocol size_t defs_len(defs_ptr self) { - return self->suiteVec().size(); + return self->suites().size(); } bool defs_container(defs_ptr self, const std::string& name) { return (self->findSuite(name)) ? true : false; diff --git a/libs/rest/src/ecflow/http/ApiV1Impl.cpp b/libs/rest/src/ecflow/http/ApiV1Impl.cpp index 146463794..6bc7e0963 100644 --- a/libs/rest/src/ecflow/http/ApiV1Impl.cpp +++ b/libs/rest/src/ecflow/http/ApiV1Impl.cpp @@ -140,8 +140,7 @@ ojson get_sparser_node_tree(const std::string& path) { ojson j; if (path == "/") { - const std::vector suites = get_defs()->suiteVec(); - for (const auto& suite : suites) { + for (const auto& suite : get_defs()->suites()) { j[suite->name()] = ojson::object({}); print_sparser_node(j[suite->name()], suite->nodeVec()); @@ -171,8 +170,8 @@ ojson get_node_tree(const std::string& path, bool add_id = false) { ojson j; if (path == "/") { - const std::vector suites = get_defs()->suiteVec(); - j["suites"] = ojson::array(); + const auto& suites = get_defs()->suites(); + j["suites"] = ojson::array(); j["suites"].get_ptr()->reserve(suites.size()); for (const auto& suite : suites) { @@ -206,10 +205,8 @@ ojson get_node_tree(const std::string& path, bool add_id = false) { } ojson get_suites() { - auto suites = get_defs()->suiteVec(); - ojson j = ojson::array(); - for (const auto& s : suites) { + for (const auto& s : get_defs()->suites()) { j.push_back(s->name()); } return j; diff --git a/libs/server/src/ecflow/server/BaseServer.cpp b/libs/server/src/ecflow/server/BaseServer.cpp index b956feabb..b2f73b925 100644 --- a/libs/server/src/ecflow/server/BaseServer.cpp +++ b/libs/server/src/ecflow/server/BaseServer.cpp @@ -179,7 +179,7 @@ bool BaseServer::restore_from_checkpt(const std::string& filename, bool& failed) defs_->handle_migration(); // handle any migration of checkpt file. update_defs_server_state(); // works on def_ LOG(Log::MSG, - "Loading of *DEFS* check point file SUCCEDED. Loaded " << defs_->suiteVec().size() << " suites"); + "Loading of *DEFS* check point file SUCCEDED. Loaded " << defs_->suites().size() << " suites"); // fast forward any time attributes, if suite clock is *ONLY* 1 hour behind real time // This may remove autocancelled nodes, may autoarchive, may set late attribute, @@ -304,7 +304,7 @@ void BaseServer::restore_defs_from_checkpt() { throw std::runtime_error("Cannot restore from checkpt the server must be halted first"); } - if (!defs_->suiteVec().empty()) { + if (!defs_->suites().empty()) { // suites must be deleted manually first throw std::runtime_error("Cannot restore from checkpt the server suites must be deleted first"); } diff --git a/libs/test/overall/TestClkSync.cpp b/libs/test/overall/TestClkSync.cpp index 4a82963c0..513365cf7 100644 --- a/libs/test/overall/TestClkSync.cpp +++ b/libs/test/overall/TestClkSync.cpp @@ -117,7 +117,7 @@ BOOST_AUTO_TEST_CASE(test_suite_calendar_sync) { CtsApi::get() << " failed should return 0 " << TestFixture::client().errorMsg()); std::ostringstream ss; ss << "\nStart time" - << "\n sync_full: suite time : " << TestFixture::client().defs()->suiteVec()[0]->calendar().toString() + << "\n sync_full: suite time : " << TestFixture::client().defs()->suites()[0]->calendar().toString() << " cal_count(" << TestFixture::client().defs()->updateCalendarCount() << ")\n"; for (size_t i = 0; i < 3; i++) { @@ -136,7 +136,7 @@ BOOST_AUTO_TEST_CASE(test_suite_calendar_sync) { BOOST_REQUIRE_MESSAGE(TestFixture::client().sync_local(true /*sync suite clock*/) == 0, "sync_local failed should return 0\n" << TestFixture::client().errorMsg()); - auto sync_clock_suiteTime = TestFixture::client().defs()->suiteVec()[0]->calendar().suiteTime(); + auto sync_clock_suiteTime = TestFixture::client().defs()->suites()[0]->calendar().suiteTime(); ss << " Sync clock suite time:" << to_simple_string(sync_clock_suiteTime) << " full_sync(" << TestFixture::client().server_reply().full_sync() << ")" << " in_sync(" << TestFixture::client().server_reply().in_sync() << ") cal_count(" @@ -145,7 +145,7 @@ BOOST_AUTO_TEST_CASE(test_suite_calendar_sync) { // suiteVec is now invalidated BOOST_REQUIRE_MESSAGE(TestFixture::client().getDefs() == 0, CtsApi::get() << " failed should return 0 " << TestFixture::client().errorMsg()); - auto sync_full_suiteTime = TestFixture::client().defs()->suiteVec()[0]->calendar().suiteTime(); + auto sync_full_suiteTime = TestFixture::client().defs()->suites()[0]->calendar().suiteTime(); ss << " Sync full suite time :" << to_simple_string(sync_full_suiteTime) << " full_sync(" << TestFixture::client().server_reply().full_sync() << ")" << " in_sync(" << TestFixture::client().server_reply().in_sync() << ") cal_count(" diff --git a/libs/test/overall/TestHandle.cpp b/libs/test/overall/TestHandle.cpp index c134aef30..d93379f76 100644 --- a/libs/test/overall/TestHandle.cpp +++ b/libs/test/overall/TestHandle.cpp @@ -234,7 +234,7 @@ BOOST_AUTO_TEST_CASE(test_handle_sync) { TestFixture::client().sync_local(); // sync for any changes to get full update before test starts BOOST_CHECK_MESSAGE(TestFixture::client().server_reply().full_sync(), "Expected a full_sync() after registering"); - BOOST_CHECK_MESSAGE(TestFixture::client().defs()->suiteVec().size() == 3, + BOOST_CHECK_MESSAGE(TestFixture::client().defs()->suites().size() == 3, "Expected 3 suites back from sync, after registering 3 suites " << ecf::as_string(*TestFixture::client().defs(), PrintStyle::DEFS)); @@ -382,7 +382,7 @@ BOOST_AUTO_TEST_CASE(test_handle_add_remove_add) { BOOST_CHECK_MESSAGE(TestFixture::client().server_reply().in_sync(), "Expected to be in sync after syn_local()"); BOOST_CHECK_MESSAGE(TestFixture::client().server_reply().full_sync(), "Expected a full_sync() after registering"); - BOOST_CHECK_MESSAGE(TestFixture::client().defs()->suiteVec().size() == 3, + BOOST_CHECK_MESSAGE(TestFixture::client().defs()->suites().size() == 3, "Expected 3 suites back from sync " << ecf::as_string(*TestFixture::client().defs(), PrintStyle::DEFS)); @@ -393,7 +393,7 @@ BOOST_AUTO_TEST_CASE(test_handle_add_remove_add) { TestFixture::client().sync_local(); BOOST_CHECK_MESSAGE(TestFixture::client().server_reply().full_sync(), "Expected a full_sync() after deleting suite"); - BOOST_CHECK_MESSAGE(TestFixture::client().defs()->suiteVec().size() == 0, + BOOST_CHECK_MESSAGE(TestFixture::client().defs()->suites().size() == 0, "Expected 0 suites back from sync " << ecf::as_string(*TestFixture::client().defs(), PrintStyle::DEFS)); @@ -408,7 +408,7 @@ BOOST_AUTO_TEST_CASE(test_handle_add_remove_add) { TestFixture::client().sync_local(); BOOST_CHECK_MESSAGE(TestFixture::client().server_reply().full_sync(), "Expected a full_sync() since client handle should be refreshed with new suite_pts"); - BOOST_CHECK_MESSAGE(TestFixture::client().defs()->suiteVec().size() == 3, + BOOST_CHECK_MESSAGE(TestFixture::client().defs()->suites().size() == 3, "Expected 3 suites back from sync " << ecf::as_string(*TestFixture::client().defs(), PrintStyle::DEFS)); diff --git a/libs/test/overall/TestOrderCmd.cpp b/libs/test/overall/TestOrderCmd.cpp index de2d9bc5f..c0d9f854b 100644 --- a/libs/test/overall/TestOrderCmd.cpp +++ b/libs/test/overall/TestOrderCmd.cpp @@ -82,33 +82,33 @@ void test_ordering() { TestFixture::client().order("/a", NOrder::toString(NOrder::ORDER)); TestFixture::client().sync_local(); BOOST_CHECK_MESSAGE(!TestFixture::client().server_reply().full_sync(), "Expected incremental sync"); - BOOST_CHECK_MESSAGE(to_string_vec(TestFixture::client().defs()->suiteVec()) == str_c_b_a, + BOOST_CHECK_MESSAGE(to_string_vec(TestFixture::client().defs()->suites()) == str_c_b_a, "Order not as expected"); TestFixture::client().order("/a", NOrder::toString(NOrder::ALPHA)); TestFixture::client().sync_local(); BOOST_CHECK_MESSAGE(!TestFixture::client().server_reply().full_sync(), "Expected incremental sync"); - BOOST_CHECK_MESSAGE(to_string_vec(TestFixture::client().defs()->suiteVec()) == str_a_b_c, + BOOST_CHECK_MESSAGE(to_string_vec(TestFixture::client().defs()->suites()) == str_a_b_c, "Order not as expected"); TestFixture::client().order("/a", NOrder::toString(NOrder::BOTTOM)); TestFixture::client().sync_local(); BOOST_CHECK_MESSAGE(!TestFixture::client().server_reply().full_sync(), "Expected incremental sync"); - BOOST_CHECK_MESSAGE(to_string_vec(TestFixture::client().defs()->suiteVec()) == str_b_c_a, + BOOST_CHECK_MESSAGE(to_string_vec(TestFixture::client().defs()->suites()) == str_b_c_a, "Order not as expected"); TestFixture::client().order("/a", NOrder::toString(NOrder::ALPHA)); TestFixture::client().order("/a", NOrder::toString(NOrder::DOWN)); TestFixture::client().sync_local(); BOOST_CHECK_MESSAGE(!TestFixture::client().server_reply().full_sync(), "Expected incremental sync"); - BOOST_CHECK_MESSAGE(to_string_vec(TestFixture::client().defs()->suiteVec()) == str_b_a_c, + BOOST_CHECK_MESSAGE(to_string_vec(TestFixture::client().defs()->suites()) == str_b_a_c, "Order not as expected"); TestFixture::client().order("/a", NOrder::toString(NOrder::ALPHA)); TestFixture::client().order("/c", NOrder::toString(NOrder::UP)); TestFixture::client().sync_local(); BOOST_CHECK_MESSAGE(!TestFixture::client().server_reply().full_sync(), "Expected incremental sync"); - BOOST_CHECK_MESSAGE(to_string_vec(TestFixture::client().defs()->suiteVec()) == str_a_c_b, + BOOST_CHECK_MESSAGE(to_string_vec(TestFixture::client().defs()->suites()) == str_a_c_b, "Order not as expected"); } { @@ -116,33 +116,33 @@ void test_ordering() { TestFixture::client().order("/a/a", NOrder::toString(NOrder::ORDER)); TestFixture::client().sync_local(); BOOST_CHECK_MESSAGE(!TestFixture::client().server_reply().full_sync(), "Expected incremental sync"); - BOOST_CHECK_MESSAGE(to_string_vec(TestFixture::client().defs()->suiteVec()[0]->nodeVec()) == str_c_b_a, + BOOST_CHECK_MESSAGE(to_string_vec(TestFixture::client().defs()->suites()[0]->nodeVec()) == str_c_b_a, "Order not as expected"); TestFixture::client().order("/a/a", NOrder::toString(NOrder::ALPHA)); TestFixture::client().sync_local(); BOOST_CHECK_MESSAGE(!TestFixture::client().server_reply().full_sync(), "Expected incremental sync"); - BOOST_CHECK_MESSAGE(to_string_vec(TestFixture::client().defs()->suiteVec()[0]->nodeVec()) == str_a_b_c, + BOOST_CHECK_MESSAGE(to_string_vec(TestFixture::client().defs()->suites()[0]->nodeVec()) == str_a_b_c, "Order not as expected"); TestFixture::client().order("/a/a", NOrder::toString(NOrder::BOTTOM)); TestFixture::client().sync_local(); BOOST_CHECK_MESSAGE(!TestFixture::client().server_reply().full_sync(), "Expected incremental sync"); - BOOST_CHECK_MESSAGE(to_string_vec(TestFixture::client().defs()->suiteVec()[0]->nodeVec()) == str_b_c_a, + BOOST_CHECK_MESSAGE(to_string_vec(TestFixture::client().defs()->suites()[0]->nodeVec()) == str_b_c_a, "Order not as expected"); TestFixture::client().order("/a/a", NOrder::toString(NOrder::ALPHA)); TestFixture::client().order("/a/a", NOrder::toString(NOrder::DOWN)); TestFixture::client().sync_local(); BOOST_CHECK_MESSAGE(!TestFixture::client().server_reply().full_sync(), "Expected incremental sync"); - BOOST_CHECK_MESSAGE(to_string_vec(TestFixture::client().defs()->suiteVec()[0]->nodeVec()) == str_b_a_c, + BOOST_CHECK_MESSAGE(to_string_vec(TestFixture::client().defs()->suites()[0]->nodeVec()) == str_b_a_c, "Order not as expected"); TestFixture::client().order("/a/a", NOrder::toString(NOrder::ALPHA)); TestFixture::client().order("/a/c", NOrder::toString(NOrder::UP)); TestFixture::client().sync_local(); BOOST_CHECK_MESSAGE(!TestFixture::client().server_reply().full_sync(), "Expected incremental sync"); - BOOST_CHECK_MESSAGE(to_string_vec(TestFixture::client().defs()->suiteVec()[0]->nodeVec()) == str_a_c_b, + BOOST_CHECK_MESSAGE(to_string_vec(TestFixture::client().defs()->suites()[0]->nodeVec()) == str_a_c_b, "Order not as expected"); } } @@ -237,7 +237,7 @@ BOOST_AUTO_TEST_CASE(test_handle_change_order) { BOOST_CHECK_MESSAGE(TestFixture::client().server_reply().in_sync(), "Expected to be in sync after syn_local()"); BOOST_CHECK_MESSAGE(TestFixture::client().server_reply().full_sync(), "Expected a full_sync() after registering"); - BOOST_CHECK_MESSAGE(TestFixture::client().defs()->suiteVec().size() == 3, "Expected sync to return 3 suites."); + BOOST_CHECK_MESSAGE(TestFixture::client().defs()->suites().size() == 3, "Expected sync to return 3 suites."); } // Do same test, this time sync_local will only return suite a,b,c (i.e suite d,e not registered and hence left out) diff --git a/libs/test/overall/TestServer.cpp b/libs/test/overall/TestServer.cpp index a7af33576..f67a11076 100644 --- a/libs/test/overall/TestServer.cpp +++ b/libs/test/overall/TestServer.cpp @@ -102,7 +102,7 @@ BOOST_AUTO_TEST_CASE(test_restore_defs_from_check_pt) { // make sure we have a valid definition with at least one suite before start BOOST_REQUIRE_MESSAGE(TestFixture::client().getDefs() == 0, "Expected getDefs() to succeed\n"); - BOOST_REQUIRE_MESSAGE(TestFixture::client().defs()->suiteVec().size() == 1, "Expected 1 suite at the start\n"); + BOOST_REQUIRE_MESSAGE(TestFixture::client().defs()->suites().size() == 1, "Expected 1 suite at the start\n"); // Check pt, make sure file exist on disk and has a non zero file size\n"; BOOST_REQUIRE_MESSAGE(TestFixture::client().checkPtDefs() == 0, @@ -126,7 +126,7 @@ BOOST_AUTO_TEST_CASE(test_restore_defs_from_check_pt) { << TestFixture::client().errorMsg()); BOOST_REQUIRE_MESSAGE(TestFixture::client().getDefs() == 0, "Expected getDefs() to succeed, i.e expected empty defs\n"); - BOOST_REQUIRE_MESSAGE(TestFixture::client().defs()->suiteVec().empty(), "Expected no suites, after delete_all()\n"); + BOOST_REQUIRE_MESSAGE(TestFixture::client().defs()->suites().empty(), "Expected no suites, after delete_all()\n"); // make sure check pt file still exists, sanity test\n"; BOOST_REQUIRE_MESSAGE(fs::exists(check_pt_file_name), @@ -141,9 +141,9 @@ BOOST_AUTO_TEST_CASE(test_restore_defs_from_check_pt) { BOOST_REQUIRE_MESSAGE(TestFixture::client().getDefs() == 0, "Get defs failed should return 0\n" << TestFixture::client().errorMsg()); - BOOST_REQUIRE_MESSAGE(TestFixture::client().defs()->suiteVec().size() == 1, + BOOST_REQUIRE_MESSAGE(TestFixture::client().defs()->suites().size() == 1, "Restore from check pt did not work. expected 1 suite but found " - << TestFixture::client().defs()->suiteVec().size()); + << TestFixture::client().defs()->suites().size()); // restore default check pointing, for test that follow BOOST_REQUIRE_MESSAGE(TestFixture::client().checkPtDefs(ecf::CheckPt::ON_TIME, 120) == 0, diff --git a/libs/test/overall/harness/ServerTestHarness.cpp b/libs/test/overall/harness/ServerTestHarness.cpp index e5d32f506..0e8ae1ff1 100644 --- a/libs/test/overall/harness/ServerTestHarness.cpp +++ b/libs/test/overall/harness/ServerTestHarness.cpp @@ -66,7 +66,7 @@ defs_ptr ServerTestHarness::doRun(Defs& theClientDefs, cout << "ServerTestHarness::doRun " << defs_filename_ << " timeout=" << timeout << " waitForTestCompletion = " << waitForTestCompletion << "\n"; #endif - BOOST_REQUIRE_MESSAGE(!theClientDefs.suiteVec().empty(), "No suite defined"); + BOOST_REQUIRE_MESSAGE(!theClientDefs.suites().empty(), "No suite defined"); #ifdef DEBUG_TEST_HARNESS cout << " ServerTestHarness::doRun: Get the client exe. This can be client exe on another platform hence cant " @@ -90,7 +90,7 @@ defs_ptr ServerTestHarness::doRun(Defs& theClientDefs, // Allow user to add SLEEPTIME, otherwise add a default int customSmsCnt = 0; auto taskSmsMapSize = static_cast(customTaskSmsMap.size()); - for (suite_ptr s : theClientDefs.suiteVec()) { + for (auto s : theClientDefs.suites()) { // Always override these to correctly locate files. s->addVariable(Variable(ecf::environment::ECF_HOME, ecf_home)); @@ -117,10 +117,10 @@ defs_ptr ServerTestHarness::doRun(Defs& theClientDefs, << " createDirAndEcfFiles did not create all sms file corresponding to tasks"); // If the defs has more than one suite, then start them all. - if (theClientDefs.suiteVec().size() != 1) { + if (theClientDefs.suites().size() != 1) { suiteName.clear(); #ifdef DEBUG_TEST_HARNESS - cout << " ServerTestHarness::doRun: defs has " << theClientDefs.suiteVec().size() + cout << " ServerTestHarness::doRun: defs has " << theClientDefs.suites().size() << " suites hence will begin all of them\n"; #endif } @@ -236,7 +236,7 @@ defs_ptr ServerTestHarness::testWaiter(const Defs& theClientDefs, int timeout, b defs_ptr incremental_defs; defs_ptr full_defs; - int sleepTime = (theClientDefs.suiteVec().size() == 1) ? TestFixture::job_submission_interval() : 10; + int sleepTime = (theClientDefs.suites().size() == 1) ? TestFixture::job_submission_interval() : 10; int sleep_fudgeFactor = TestFixture::job_submission_interval(); // How do we terminate this test? @@ -263,9 +263,9 @@ defs_ptr ServerTestHarness::testWaiter(const Defs& theClientDefs, int timeout, b BOOST_REQUIRE_MESSAGE(incremental_defs.get(), "get command failed to get node tree from server"); // Ensure that when suite was loaded in server, that others suites were discarded - BOOST_CHECK_MESSAGE(theClientDefs.suiteVec().size() == full_defs->suiteVec().size(), - "mismatch in client suite count " << theClientDefs.suiteVec().size() << " and server " - << full_defs->suiteVec().size()); + BOOST_CHECK_MESSAGE(theClientDefs.suites().size() == full_defs->suites().size(), + "mismatch in client suite count " << theClientDefs.suites().size() << " and server " + << full_defs->suites().size()); test_invariants(full_defs, "First time for getting full defs"); } else { @@ -374,7 +374,7 @@ defs_ptr ServerTestHarness::testWaiter(const Defs& theClientDefs, int timeout, b // record the number of times that the server updated the calendar. Allow debug of time dependencies serverUpdateCalendarCount_ = full_defs->updateCalendarCount(); - for (suite_ptr s : full_defs->suiteVec()) { + for (auto s : full_defs->suites()) { if (s->state() == NState::COMPLETE) { completeSuiteCnt++; } @@ -388,10 +388,10 @@ defs_ptr ServerTestHarness::testWaiter(const Defs& theClientDefs, int timeout, b "==================================================================================\n"; std::cout << *full_defs.get(); cout << "completeSuiteCnt = " << completeSuiteCnt - << " full_defs->suiteVec().size() = " << full_defs->suiteVec().size() + << " full_defs->suites().size() = " << full_defs->suites().size() << " hasAutoCancel = " << hasAutoCancel << "\n"; #endif - if ((full_defs->suiteVec().size() == completeSuiteCnt) && (hasAutoCancel == 0)) { + if ((full_defs->suites().size() == completeSuiteCnt) && (hasAutoCancel == 0)) { if (verifyAttr && verify_attribute_verification()) { // Do verification of expected state changes @@ -410,7 +410,7 @@ defs_ptr ServerTestHarness::testWaiter(const Defs& theClientDefs, int timeout, b std::cout << "Test time " << assertTimer.duration() << " taking longer than time constraint of " << assertTimer.timeConstraint() << " aborting\n"; std::cout << " completeSuiteCnt = " << completeSuiteCnt << "\n"; - std::cout << " full_defs->suiteVec().size() = " << full_defs->suiteVec().size() << "\n"; + std::cout << " full_defs->suites().size() = " << full_defs->suites().size() << "\n"; std::cout << " hasAutoCancel = " << hasAutoCancel << "\n"; std::cout << "update-calendar-count(" << serverUpdateCalendarCount_ << ")\n"; std::cout << "WHY:\n"; diff --git a/libs/test/simulator/src/ecflow/simulator/DefsAnalyserVisitor.cpp b/libs/test/simulator/src/ecflow/simulator/DefsAnalyserVisitor.cpp index 3b3f61419..0f140c143 100644 --- a/libs/test/simulator/src/ecflow/simulator/DefsAnalyserVisitor.cpp +++ b/libs/test/simulator/src/ecflow/simulator/DefsAnalyserVisitor.cpp @@ -24,8 +24,8 @@ namespace ecf { DefsAnalyserVisitor::DefsAnalyserVisitor() = default; void DefsAnalyserVisitor::visitDefs(Defs* d) { - for (suite_ptr s : d->suiteVec()) { - s->acceptVisitTraversor(*this); + for (auto suite : d->suites()) { + suite->acceptVisitTraversor(*this); } } diff --git a/libs/test/simulator/src/ecflow/simulator/FlatAnalyserVisitor.cpp b/libs/test/simulator/src/ecflow/simulator/FlatAnalyserVisitor.cpp index 260d9ccea..2b809aad7 100644 --- a/libs/test/simulator/src/ecflow/simulator/FlatAnalyserVisitor.cpp +++ b/libs/test/simulator/src/ecflow/simulator/FlatAnalyserVisitor.cpp @@ -25,8 +25,8 @@ namespace ecf { FlatAnalyserVisitor::FlatAnalyserVisitor() = default; void FlatAnalyserVisitor::visitDefs(Defs* d) { - for (suite_ptr s : d->suiteVec()) { - s->acceptVisitTraversor(*this); + for (auto suite : d->suites()) { + suite->acceptVisitTraversor(*this); } } diff --git a/libs/test/simulator/src/ecflow/simulator/Simulator.cpp b/libs/test/simulator/src/ecflow/simulator/Simulator.cpp index 0d40eb020..f6f019240 100644 --- a/libs/test/simulator/src/ecflow/simulator/Simulator.cpp +++ b/libs/test/simulator/src/ecflow/simulator/Simulator.cpp @@ -52,7 +52,7 @@ bool Simulator::run(const std::string& theDefsFile, std::string& errorMsg) const bool Simulator::run(Defs& theDefs, const std::string& defs_filename, std::string& errorMsg, bool do_checks) const { #ifdef DEBUG_LONG_RUNNING_SUITES - std::cout << "Simulator::run " << defs_filename << " no of suites " << theDefs.suiteVec().size() << endl; + std::cout << "Simulator::run " << defs_filename << " no of suites " << theDefs.suites().size() << endl; #endif // ****:NOTE:****** // ** This simulator relies on the defs checking to set event and meter usedInTrigger() @@ -119,8 +119,8 @@ bool Simulator::run(Defs& theDefs, const std::string& defs_filename, std::string // Do we have autocancel, must be done before. int hasAutoCancel = 0; - for (suite_ptr s : theDefs.suiteVec()) { - if (s->hasAutoCancel()) { + for (auto suite : theDefs.suites()) { + if (suite->hasAutoCancel()) { hasAutoCancel++; } } @@ -134,8 +134,8 @@ bool Simulator::run(Defs& theDefs, const std::string& defs_filename, std::string while (duration <= max_simulation_period) { #ifdef DEBUG_LONG_RUNNING_SUITES - for (suite_ptr my_suite : theDefs.suiteVec()) { - cout << "duration: " << to_simple_string(duration) << " " << my_suite->calendar().toString() + for (auto suite : theDefs.suites()) { + cout << "duration: " << to_simple_string(duration) << " " << suite->calendar().toString() << " +++++++++++++++++++++++++++++++++++ " << endl; // use following to snapshot definition state at a given point in time @@ -155,8 +155,8 @@ bool Simulator::run(Defs& theDefs, const std::string& defs_filename, std::string } // Increment calendar per suite. *MUST* use *COPY* as update_calendar() can remove suites (auto-cancel) - std::vector suiteVec = theDefs.suiteVec(); - for (suite_ptr suite : suiteVec) { + auto suites = theDefs.suites(); + for (suite_ptr suite : suites) { boost::posix_time::time_duration max_duration_for_suite = simiVisitor.max_simulation_period(suite.get()); if (duration < max_duration_for_suite) { theDefs.update_calendar(suite.get(), calUpdateParams); @@ -177,18 +177,18 @@ bool Simulator::run(Defs& theDefs, const std::string& defs_filename, std::string // Ignore suites with autocancel, as suite may get deleted if (!simiVisitor.foundCrons() && (hasAutoCancel == 0)) { size_t completeSuiteCnt = 0; - for (suite_ptr s : theDefs.suiteVec()) { - if (s->state() == NState::COMPLETE) { + for (auto suite : theDefs.suites()) { + if (suite->state() == NState::COMPLETE) { completeSuiteCnt++; } } - if ((theDefs.suiteVec().size() != completeSuiteCnt)) { + if ((theDefs.suites().size() != completeSuiteCnt)) { std::ostringstream iss; // inner stringstream, to avoid name clash with outer ostringstream ss iss << "\nDefs file " << defs_filename << "\n"; - for (suite_ptr s : theDefs.suiteVec()) { - if (s->state() != NState::COMPLETE) { - iss << " suite '/" << s->name() << "' has not completed\n"; + for (auto suite : theDefs.suites()) { + if (suite->state() != NState::COMPLETE) { + iss << " suite '/" << suite->name() << "' has not completed\n"; } } errorMsg += iss.str(); diff --git a/libs/test/simulator/src/ecflow/simulator/SimulatorVisitor.cpp b/libs/test/simulator/src/ecflow/simulator/SimulatorVisitor.cpp index a4e80d31c..ff4424247 100644 --- a/libs/test/simulator/src/ecflow/simulator/SimulatorVisitor.cpp +++ b/libs/test/simulator/src/ecflow/simulator/SimulatorVisitor.cpp @@ -40,8 +40,8 @@ SimulatorVisitor::SimulatorVisitor(const std::string& defs_filename) } void SimulatorVisitor::visitDefs(Defs* d) { - for (suite_ptr s : d->suiteVec()) { - s->acceptVisitTraversor(*this); + for (suite_ptr suite : d->suites()) { + suite->acceptVisitTraversor(*this); } } diff --git a/libs/test/simulator/test/TestAutoArchive.cpp b/libs/test/simulator/test/TestAutoArchive.cpp index d4b5c4c84..af3af879b 100644 --- a/libs/test/simulator/test/TestAutoArchive.cpp +++ b/libs/test/simulator/test/TestAutoArchive.cpp @@ -173,8 +173,7 @@ BOOST_AUTO_TEST_CASE(test_autoarchive_ast_node_reset) { errorMsg); // Auto archive should archive suite s2 and s3, leaving one suite i.e s1 - const std::vector& suites = theDefs.suiteVec(); - for (const auto& suite : suites) { + for (const auto& suite : theDefs.suites()) { if (suite->name() == "s2" || suite->name() == "s3") { BOOST_CHECK_MESSAGE(suite->get_flag().is_set(ecf::Flag::ARCHIVED), "Expected suite " << suite->absNodePath() << " to be archived"); diff --git a/libs/test/simulator/test/TestAutoCancel.cpp b/libs/test/simulator/test/TestAutoCancel.cpp index 0b8a2d440..72ca1958a 100644 --- a/libs/test/simulator/test/TestAutoCancel.cpp +++ b/libs/test/simulator/test/TestAutoCancel.cpp @@ -90,8 +90,8 @@ BOOST_AUTO_TEST_CASE(test_autocancel_ast_node_reset) { errorMsg); // Auto cancel should delete suite s2 and s3, leaving one suite i.e s1 - BOOST_CHECK_MESSAGE(theDefs.suiteVec().size() == 1, - "Expected to have 1 suites but found " << theDefs.suiteVec().size()); + BOOST_CHECK_MESSAGE(theDefs.suites().size() == 1, + "Expected to have 1 suites but found " << theDefs.suites().size()); // The references to nodes in suites s2, s3 should have been cleared in suite s1 { @@ -144,8 +144,8 @@ BOOST_AUTO_TEST_CASE(test_autocancel_suite) { BOOST_CHECK_MESSAGE(simulator.run(theDefs, findTestDataLocation("test_autocancel_suite.def"), errorMsg), errorMsg); // make sure autocancel deletes the suite. - BOOST_CHECK_MESSAGE(theDefs.suiteVec().size() == 0, - "Expected to have 0 suites but found " << theDefs.suiteVec().size()); + BOOST_CHECK_MESSAGE(theDefs.suites().size() == 0, + "Expected to have 0 suites but found " << theDefs.suites().size()); // remove generated log file. Comment out to debug std::string logFileName = findTestDataLocation("test_autocancel_suite.def") + ".log"; From 64ff902abb17ab90caacb7cb1e28cce9f3c9e556 Mon Sep 17 00:00:00 2001 From: Marcos Bento Date: Mon, 20 Apr 2026 14:30:13 +0100 Subject: [PATCH 29/90] refactor: replace calls to [[deprecated]] NodeContainer::nodeVec Re ECFLOW-2079 --- cmake/CompilerOptions.cmake | 5 --- libs/base/test/TestArchiveAndRestoreCmd.cpp | 32 +++++++++---------- libs/node/src/ecflow/node/DefsTreeVisitor.hpp | 8 ++--- .../src/ecflow/node/ResolveExternsVisitor.cpp | 4 +-- .../src/ecflow/node/formatter/DefsWriter.hpp | 2 +- libs/node/test/TestOrder.cpp | 32 +++++++++---------- libs/node/test/parser/ParseTimer.cpp | 18 +++++------ .../TestDefsStructurePersistAndReload.cpp | 8 ++--- libs/node/test/parser/TestSingleDefsFile.cpp | 16 +++++----- .../ecflow/python/ExportSuiteAndFamily.cpp | 4 +-- libs/rest/src/ecflow/http/ApiV1Impl.cpp | 16 +++++----- libs/test/overall/TestOrderCmd.cpp | 10 +++--- .../overall/harness/ServerTestHarness.cpp | 6 ++-- .../ecflow/simulator/DefsAnalyserVisitor.cpp | 12 +++---- .../ecflow/simulator/FlatAnalyserVisitor.cpp | 4 +-- .../src/ecflow/simulator/SimulatorVisitor.cpp | 4 +-- libs/test/simulator/test/TestAutoArchive.cpp | 18 +++++------ libs/test/simulator/test/TestAutoRestore.cpp | 4 +-- 18 files changed, 99 insertions(+), 104 deletions(-) diff --git a/cmake/CompilerOptions.cmake b/cmake/CompilerOptions.cmake index 89e3066e2..e2570d78a 100644 --- a/cmake/CompilerOptions.cmake +++ b/cmake/CompilerOptions.cmake @@ -81,20 +81,16 @@ if (HAVE_WARNINGS) ## GCC $<$,$>:-Wno-array-bounds> $<$,$,$,9.0.0>>:-Wno-deprecated-copy> # silence warnings in Qt5 related headers - $<$,$>:-Wno-deprecated-declarations> $<$,$>:-Wno-unused-result> $<$,$>:-Wno-unused-parameter> ## Clang (MacOS Homebrew, AMD Clang-base) $<$,$>:-Wno-deprecated-copy-with-user-provided-copy> # silence warnings in Qt5 related headers - $<$,$>:-Wno-deprecated-declarations> $<$,$>:-Wno-missing-field-initializers> # silence warning in Boost.Python related headers - $<$,$>:-Wno-overloaded-virtual> $<$,$>:-Wno-unused-parameter> $<$,$,$,14.0.0>>:-Wno-c++20-attribute-extensions> # silence warning in Qt6 related headers $<$,$,$,21.0.0>>:-Wno-character-conversion> # silence warning in Qt6 related headers ## Clang (MacOS AppleClang) $<$,$>:-Wno-deprecated-copy-with-user-provided-copy> # silence warnings in Qt5 related headers - $<$,$>:-Wno-deprecated-declarations> $<$,$>:-Wno-missing-field-initializers> # silence warning in Boost.Python related headers $<$,$>:-Wno-overloaded-virtual> $<$,$>:-Wno-unused-parameter> @@ -102,7 +98,6 @@ if (HAVE_WARNINGS) $<$,$,$,21.0.0>>:-Wno-character-conversion> # silence warning in Qt6 related headers ## Clang (Intel Clang-based) $<$,$>:-Wno-deprecated-copy-with-user-provided-copy> # silence warnings in Qt5 related headers - $<$,$>:-Wno-deprecated-declarations> $<$,$>:-Wno-missing-field-initializers> # silence warning in Boost.Python related headers $<$,$>:-Wno-overloaded-virtual> $<$,$>:-Wno-unused-parameter> diff --git a/libs/base/test/TestArchiveAndRestoreCmd.cpp b/libs/base/test/TestArchiveAndRestoreCmd.cpp index a20bcca47..d6f764f73 100644 --- a/libs/base/test/TestArchiveAndRestoreCmd.cpp +++ b/libs/base/test/TestArchiveAndRestoreCmd.cpp @@ -60,26 +60,26 @@ BOOST_AUTO_TEST_CASE(test_archive_and_restore_suite) { TestHelper::invokeRequest(&theDefs, Cmd_ptr(new PathsCmd(PathsCmd::ARCHIVE, suite->absNodePath()))); BOOST_CHECK_MESSAGE(suite->get_flag().is_set(ecf::Flag::ARCHIVED), "Archived flag not set"); BOOST_CHECK_MESSAGE(fs::exists(suite->archive_path()), "Archive path" << suite->archive_path() << " not created"); - BOOST_CHECK_MESSAGE(suite->nodeVec().empty(), "Children not removed"); + BOOST_CHECK_MESSAGE(suite->children().empty(), "Children not removed"); // cout << theDefs << "\n"; TestHelper::invokeRequest(&theDefs, Cmd_ptr(new PathsCmd(PathsCmd::RESTORE, suite->absNodePath()))); BOOST_CHECK_MESSAGE(suite->get_flag().is_set(ecf::Flag::RESTORED), "Restored flag not set"); BOOST_CHECK_MESSAGE(!suite->get_flag().is_set(ecf::Flag::ARCHIVED), "Archived flag not *cleared"); BOOST_CHECK_MESSAGE(!fs::exists(suite->archive_path()), "Archived file has not been deleted after restore"); - BOOST_CHECK_MESSAGE(!suite->nodeVec().empty(), "Children are not restored"); + BOOST_CHECK_MESSAGE(!suite->children().empty(), "Children are not restored"); // Archive again but restore via begin TestHelper::invokeRequest(&theDefs, Cmd_ptr(new PathsCmd(PathsCmd::ARCHIVE, suite->absNodePath()))); BOOST_CHECK_MESSAGE(suite->get_flag().is_set(ecf::Flag::ARCHIVED), "Archived flag not set"); BOOST_CHECK_MESSAGE(fs::exists(suite->archive_path()), "Archive path" << suite->archive_path() << " not created"); - BOOST_CHECK_MESSAGE(suite->nodeVec().empty(), "Children not removed"); + BOOST_CHECK_MESSAGE(suite->children().empty(), "Children not removed"); TestHelper::invokeRequest(&theDefs, Cmd_ptr(new BeginCmd(suite->absNodePath(), true))); // nodes will be active BOOST_CHECK_MESSAGE(!suite->get_flag().is_set(ecf::Flag::ARCHIVED), "Archived flag not *cleared"); BOOST_CHECK_MESSAGE(!suite->get_flag().is_set(ecf::Flag::RESTORED), "restored flag not *cleared"); BOOST_CHECK_MESSAGE(!fs::exists(suite->archive_path()), "Archived file has not been deleted after restore"); - BOOST_CHECK_MESSAGE(!suite->nodeVec().empty(), "Children are not restored"); + BOOST_CHECK_MESSAGE(!suite->children().empty(), "Children are not restored"); // PrintStyle::setStyle(PrintStyle::MIGRATE); // cout << theDefs << "\n"; @@ -106,26 +106,26 @@ BOOST_AUTO_TEST_CASE(test_archive_and_restore_family) { TestHelper::invokeRequest(&theDefs, Cmd_ptr(new PathsCmd(PathsCmd::ARCHIVE, f3->absNodePath()))); BOOST_CHECK_MESSAGE(f3->get_flag().is_set(ecf::Flag::ARCHIVED), "Archived flag not set"); BOOST_CHECK_MESSAGE(fs::exists(f3->archive_path()), "Archive path" << f3->archive_path() << " not created"); - BOOST_CHECK_MESSAGE(f3->nodeVec().empty(), "Children not removed"); + BOOST_CHECK_MESSAGE(f3->children().empty(), "Children not removed"); TestHelper::invokeRequest(&theDefs, Cmd_ptr(new PathsCmd(PathsCmd::RESTORE, f3->absNodePath()))); BOOST_CHECK_MESSAGE(f3->get_flag().is_set(ecf::Flag::RESTORED), "restored flag not set"); BOOST_CHECK_MESSAGE(!f3->get_flag().is_set(ecf::Flag::ARCHIVED), "Archived flag not *cleared"); BOOST_CHECK_MESSAGE(!fs::exists(f3->archive_path()), "Archived file has not been deleted after restore"); - BOOST_CHECK_MESSAGE(!f3->nodeVec().empty(), "Children are not restored"); + BOOST_CHECK_MESSAGE(!f3->children().empty(), "Children are not restored"); // Archive again but restore via begin TestHelper::invokeRequest(&theDefs, Cmd_ptr(new PathsCmd(PathsCmd::ARCHIVE, f3->absNodePath()))); BOOST_CHECK_MESSAGE(f3->get_flag().is_set(ecf::Flag::ARCHIVED), "Archived flag not set"); BOOST_CHECK_MESSAGE(!f3->get_flag().is_set(ecf::Flag::RESTORED), "Restored flag not cleared"); BOOST_CHECK_MESSAGE(fs::exists(f3->archive_path()), "Archive path" << suite->archive_path() << " not created"); - BOOST_CHECK_MESSAGE(f3->nodeVec().empty(), "Children not removed"); + BOOST_CHECK_MESSAGE(f3->children().empty(), "Children not removed"); TestHelper::invokeRequest(&theDefs, Cmd_ptr(new BeginCmd(suite->absNodePath(), true))); // nodes will be active BOOST_CHECK_MESSAGE(!f3->get_flag().is_set(ecf::Flag::ARCHIVED), "Archived flag not *cleared"); BOOST_CHECK_MESSAGE(!f3->get_flag().is_set(ecf::Flag::RESTORED), "Restored flag not *cleared"); BOOST_CHECK_MESSAGE(!fs::exists(f3->archive_path()), "Archived file has not been deleted after restore"); - BOOST_CHECK_MESSAGE(!f3->nodeVec().empty(), "Children are not restored"); + BOOST_CHECK_MESSAGE(!f3->children().empty(), "Children are not restored"); // PrintStyle::setStyle(PrintStyle::MIGRATE); // cout << theDefs << "\n"; @@ -190,7 +190,7 @@ BOOST_AUTO_TEST_CASE(test_archive_and_restore_all) { BOOST_CHECK_MESSAGE(nc->get_flag().is_set(ecf::Flag::ARCHIVED), "Archived flag not set " << nc->absNodePath()); BOOST_CHECK_MESSAGE(fs::exists(nc->archive_path()), "Archive path" << nc->archive_path() << " not created"); - BOOST_CHECK_MESSAGE(nc->nodeVec().empty(), "Children not removed " << nc->absNodePath()); + BOOST_CHECK_MESSAGE(nc->children().empty(), "Children not removed " << nc->absNodePath()); TestHelper::invokeRequest(&theDefs, Cmd_ptr(new PathsCmd(PathsCmd::RESTORE, i))); node = theDefs.findAbsNode(i); @@ -202,7 +202,7 @@ BOOST_AUTO_TEST_CASE(test_archive_and_restore_all) { "Archived flag not *cleared " << nc->absNodePath()); BOOST_CHECK_MESSAGE(!fs::exists(nc->archive_path()), "Archived file has not been deleted after restore " << nc->absNodePath()); - BOOST_CHECK_MESSAGE(!nc->nodeVec().empty(), "Children are not restored " << nc->absNodePath()); + BOOST_CHECK_MESSAGE(!nc->children().empty(), "Children are not restored " << nc->absNodePath()); } { // Archive again but restore via re-queue @@ -215,7 +215,7 @@ BOOST_AUTO_TEST_CASE(test_archive_and_restore_all) { BOOST_CHECK_MESSAGE(!nc->get_flag().is_set(ecf::Flag::RESTORED), "Restored flag should be clear " << nc->absNodePath()); BOOST_CHECK_MESSAGE(fs::exists(nc->archive_path()), "Archive path" << nc->archive_path() << " not created"); - BOOST_CHECK_MESSAGE(nc->nodeVec().empty(), "Children not removed " << nc->absNodePath()); + BOOST_CHECK_MESSAGE(nc->children().empty(), "Children not removed " << nc->absNodePath()); TestHelper::invokeRequest(&theDefs, Cmd_ptr(new RequeueNodeCmd(i, RequeueNodeCmd::FORCE))); node = theDefs.findAbsNode(i); @@ -227,7 +227,7 @@ BOOST_AUTO_TEST_CASE(test_archive_and_restore_all) { "Archived flag not *cleared " << nc->absNodePath()); BOOST_CHECK_MESSAGE(!fs::exists(nc->archive_path()), "Archived file has not been deleted after restore " << nc->absNodePath()); - BOOST_CHECK_MESSAGE(!nc->nodeVec().empty(), "Children are not restored " << nc->absNodePath()); + BOOST_CHECK_MESSAGE(!nc->children().empty(), "Children are not restored " << nc->absNodePath()); } } // cout << theDefs << "\n"; @@ -263,14 +263,14 @@ BOOST_AUTO_TEST_CASE(test_archive_and_restore_overlap) { Cmd_ptr(new PathsCmd(PathsCmd::ARCHIVE, paths))); // family_ptr f1 removed from the suite BOOST_CHECK_MESSAGE(suite->get_flag().is_set(ecf::Flag::ARCHIVED), "Archived flag not set"); BOOST_CHECK_MESSAGE(fs::exists(suite->archive_path()), "Archive path " << suite->archive_path() << " not created"); - BOOST_CHECK_MESSAGE(suite->nodeVec().empty(), "Children not removed"); + BOOST_CHECK_MESSAGE(suite->children().empty(), "Children not removed"); BOOST_CHECK_MESSAGE(!theDefs.findAbsNode(f1_abs_node_path), "f1 should have been removed"); TestHelper::invokeRequest(&theDefs, Cmd_ptr(new PathsCmd(PathsCmd::RESTORE, suite->absNodePath()))); BOOST_CHECK_MESSAGE(suite->get_flag().is_set(ecf::Flag::RESTORED), "Restored flag not set"); BOOST_CHECK_MESSAGE(!suite->get_flag().is_set(ecf::Flag::ARCHIVED), "Archived flag not *cleared"); BOOST_CHECK_MESSAGE(!fs::exists(suite->archive_path()), "Archived file has not been deleted after restore"); - BOOST_CHECK_MESSAGE(!suite->nodeVec().empty(), "Children are not restored"); + BOOST_CHECK_MESSAGE(!suite->children().empty(), "Children are not restored"); node_ptr f1 = theDefs.findAbsNode(f1_abs_node_path); BOOST_CHECK_MESSAGE(f1, "f1 should have been restored"); BOOST_CHECK_MESSAGE(!f1->get_flag().is_set(ecf::Flag::RESTORED), "Family f1 should not have restored flag set"); @@ -301,14 +301,14 @@ BOOST_AUTO_TEST_CASE(test_archive_and_delete_suite) { BOOST_CHECK_MESSAGE(family->has_archive(), "Archived flag not set on family"); std::string family_archive_path = family->archive_path(); BOOST_CHECK_MESSAGE(fs::exists(family_archive_path), "Archive path" << family->archive_path() << " not created"); - BOOST_CHECK_MESSAGE(family->nodeVec().empty(), "Children of family not removed after archive"); + BOOST_CHECK_MESSAGE(family->children().empty(), "Children of family not removed after archive"); TestHelper::invokeRequest(&theDefs, Cmd_ptr(new PathsCmd(PathsCmd::ARCHIVE, suite->absNodePath()))); BOOST_CHECK_MESSAGE(suite->get_flag().is_set(ecf::Flag::ARCHIVED), "Archived flag not set on suite"); BOOST_CHECK_MESSAGE(suite->has_archive(), "Archived flag not set on family"); std::string suite_archive_path = suite->archive_path(); BOOST_CHECK_MESSAGE(fs::exists(suite_archive_path), "Archive path" << suite->archive_path() << " not created"); - BOOST_CHECK_MESSAGE(suite->nodeVec().empty(), "Children of suite not removed after archive"); + BOOST_CHECK_MESSAGE(suite->children().empty(), "Children of suite not removed after archive"); TestHelper::invokeRequest(&theDefs, Cmd_ptr(new DeleteCmd(suite->absNodePath()))); BOOST_CHECK_MESSAGE(!fs::exists(suite_archive_path), "Suite Archived file not removed after DeleteCmd"); diff --git a/libs/node/src/ecflow/node/DefsTreeVisitor.hpp b/libs/node/src/ecflow/node/DefsTreeVisitor.hpp index 8046e871e..4982221e8 100644 --- a/libs/node/src/ecflow/node/DefsTreeVisitor.hpp +++ b/libs/node/src/ecflow/node/DefsTreeVisitor.hpp @@ -83,8 +83,8 @@ struct DefsTreeVisitor // Visit suite itself v_.begin_visit(*found); // Visit suite children - for (auto&& entry : found->nodeVec()) { - visit(*entry.get()); + for (auto&& child : found->children()) { + visit(*child.get()); } v_.end_visit(*found); return; @@ -94,8 +94,8 @@ struct DefsTreeVisitor // Visit family itself v_.begin_visit(*found); // Visit family children - for (auto&& entry : found->nodeVec()) { - visit(*entry.get()); + for (auto&& child : found->children()) { + visit(*child.get()); } v_.end_visit(*found); return; diff --git a/libs/node/src/ecflow/node/ResolveExternsVisitor.cpp b/libs/node/src/ecflow/node/ResolveExternsVisitor.cpp index 87abd5e2b..3d3318ad8 100644 --- a/libs/node/src/ecflow/node/ResolveExternsVisitor.cpp +++ b/libs/node/src/ecflow/node/ResolveExternsVisitor.cpp @@ -43,8 +43,8 @@ void ResolveExternsVisitor::visitNodeContainer(NodeContainer* nc) { setup(nc); - for (node_ptr t : nc->nodeVec()) { - t->acceptVisitTraversor(*this); + for (auto node : nc->children()) { + node->acceptVisitTraversor(*this); } } diff --git a/libs/node/src/ecflow/node/formatter/DefsWriter.hpp b/libs/node/src/ecflow/node/formatter/DefsWriter.hpp index 915a56c31..903bdeb96 100644 --- a/libs/node/src/ecflow/node/formatter/DefsWriter.hpp +++ b/libs/node/src/ecflow/node/formatter/DefsWriter.hpp @@ -2070,7 +2070,7 @@ struct Writer static void write(Stream& output, const NodeContainer& item, Context& ctx) { // taken from NodeContainer::print(std::string& os) const - for (const auto& node : item.nodeVec()) { + for (const auto& node : item.children()) { // Note: here we must use dynamic_cast to determine the type of the node // This is because we are using a templated Writer, and we need to call the correct diff --git a/libs/node/test/TestOrder.cpp b/libs/node/test/TestOrder.cpp index 505182000..548bb8f07 100644 --- a/libs/node/test/TestOrder.cpp +++ b/libs/node/test/TestOrder.cpp @@ -173,7 +173,7 @@ BOOST_AUTO_TEST_CASE(test_order) { std::sort(expected.begin(), expected.end(), std::greater()); suite->order(theDefs.findAbsNode("/a/a").get(), NOrder::ORDER); test_invariants(theDefs, __LINE__); - auto names = ecf::algorithm::transform_to_vector(suite->nodeVec(), to_name); + auto names = ecf::algorithm::transform_to_vector(suite->children(), to_name); BOOST_REQUIRE_MESSAGE(names == order, "NOrder::ORDER order " << ecf::algorithm::join(names) << " not as expected " << ecf::algorithm::join(order)); @@ -182,7 +182,7 @@ BOOST_AUTO_TEST_CASE(test_order) { { // Change back to alpha, then move family 'e' to the top suite->order(theDefs.findAbsNode("/a/a").get(), NOrder::ALPHA); - auto names = ecf::algorithm::transform_to_vector(suite->nodeVec(), to_name); + auto names = ecf::algorithm::transform_to_vector(suite->children(), to_name); BOOST_REQUIRE_MESSAGE(names == alpha, "NOrder::ALPHA expected " << ecf::algorithm::join(alpha) << " but found " << ecf::algorithm::join(names)); @@ -191,7 +191,7 @@ BOOST_AUTO_TEST_CASE(test_order) { std::vector expected = {"c", "a", "A", "b", "B"}; suite->order(theDefs.findAbsNode("/a/c").get(), NOrder::TOP); - names = ecf::algorithm::transform_to_vector(suite->nodeVec(), to_name); + names = ecf::algorithm::transform_to_vector(suite->children(), to_name); BOOST_REQUIRE_MESSAGE(names == expected, "NOrder::TOP order " << ecf::algorithm::join(names) << " not as expected " << ecf::algorithm::join(expected)); @@ -201,7 +201,7 @@ BOOST_AUTO_TEST_CASE(test_order) { // move family 'c' back to the bottom suite->order(theDefs.findAbsNode("/a/c").get(), NOrder::BOTTOM); test_invariants(theDefs, __LINE__); - auto names = ecf::algorithm::transform_to_vector(suite->nodeVec(), to_name); + auto names = ecf::algorithm::transform_to_vector(suite->children(), to_name); BOOST_REQUIRE_MESSAGE(names == alpha, "NOrder::BOTTOM order " << ecf::algorithm::join(names) << " not as expected " << ecf::algorithm::join(alpha)); @@ -211,7 +211,7 @@ BOOST_AUTO_TEST_CASE(test_order) { // move family 'a' up one place. Should be no change, since its already at the top suite->order(theDefs.findAbsNode("/a/a").get(), NOrder::UP); test_invariants(theDefs, __LINE__); - auto names = ecf::algorithm::transform_to_vector(suite->nodeVec(), to_name); + auto names = ecf::algorithm::transform_to_vector(suite->children(), to_name); BOOST_REQUIRE_MESSAGE(names == alpha, "NOrder::UP order " << ecf::algorithm::join(names) << " not as expected " << ecf::algorithm::join(alpha)); @@ -221,7 +221,7 @@ BOOST_AUTO_TEST_CASE(test_order) { // move family 'c' down one place. Should be no change, since its already at the bottom suite->order(theDefs.findAbsNode("/a/c").get(), NOrder::DOWN); test_invariants(theDefs, __LINE__); - auto names = ecf::algorithm::transform_to_vector(suite->nodeVec(), to_name); + auto names = ecf::algorithm::transform_to_vector(suite->children(), to_name); BOOST_REQUIRE_MESSAGE(names == alpha, "NOrder::DOWN order " << ecf::algorithm::join(names) << " not as expected " << ecf::algorithm::join(alpha)); @@ -233,7 +233,7 @@ BOOST_AUTO_TEST_CASE(test_order) { suite->order(theDefs.findAbsNode("/a/a").get(), NOrder::DOWN); test_invariants(theDefs, __LINE__); - auto names = ecf::algorithm::transform_to_vector(suite->nodeVec(), to_name); + auto names = ecf::algorithm::transform_to_vector(suite->children(), to_name); BOOST_REQUIRE_MESSAGE(names == expected, "NOrder::DOWN order " << ecf::algorithm::join(names) << " not as expected " << ecf::algorithm::join(expected)); @@ -250,7 +250,7 @@ BOOST_AUTO_TEST_CASE(test_order) { expected.emplace_back("B"); expected.emplace_back("c"); suite->order(theDefs.findAbsNode("/a/b").get(), NOrder::UP); - auto names = ecf::algorithm::transform_to_vector(suite->nodeVec(), to_name); + auto names = ecf::algorithm::transform_to_vector(suite->children(), to_name); BOOST_REQUIRE_MESSAGE(names == expected, "NOrder::UP order " << ecf::algorithm::join(names) << " not as expected " << ecf::algorithm::join(expected)); @@ -264,7 +264,7 @@ BOOST_AUTO_TEST_CASE(test_order) { BOOST_REQUIRE_MESSAGE(family, "Expected family /a/a to exist "); family->order(theDefs.findAbsNode("/a/a/a").get(), NOrder::ALPHA); test_invariants(theDefs, __LINE__); - auto names = ecf::algorithm::transform_to_vector(family->nodeVec(), to_name); + auto names = ecf::algorithm::transform_to_vector(family->children(), to_name); BOOST_REQUIRE_MESSAGE(names == alpha, "NOrder::ALPHA Init state " << ecf::algorithm::join(names) << " not as expected " << ecf::algorithm::join(alpha)); @@ -274,7 +274,7 @@ BOOST_AUTO_TEST_CASE(test_order) { // sort in reverse order family->order(theDefs.findAbsNode("/a/a/a").get(), NOrder::ORDER); test_invariants(theDefs, __LINE__); - auto names = ecf::algorithm::transform_to_vector(family->nodeVec(), to_name); + auto names = ecf::algorithm::transform_to_vector(family->children(), to_name); BOOST_REQUIRE_MESSAGE(names == order, "NOrder::ORDER " << ecf::algorithm::join(names) << " not as expected " << ecf::algorithm::join(order)); @@ -288,7 +288,7 @@ BOOST_AUTO_TEST_CASE(test_order) { std::vector expected = {"c", "a", "A", "b", "B"}; - auto names = ecf::algorithm::transform_to_vector(family->nodeVec(), to_name); + auto names = ecf::algorithm::transform_to_vector(family->children(), to_name); BOOST_REQUIRE_MESSAGE(names == expected, "NOrder::TOP order " << ecf::algorithm::join(names) << " not as expected " << ecf::algorithm::join(expected)); @@ -298,7 +298,7 @@ BOOST_AUTO_TEST_CASE(test_order) { // move task 'c' back to the bottom family->order(theDefs.findAbsNode("/a/a/c").get(), NOrder::BOTTOM); test_invariants(theDefs, __LINE__); - auto names = ecf::algorithm::transform_to_vector(family->nodeVec(), to_name); + auto names = ecf::algorithm::transform_to_vector(family->children(), to_name); BOOST_REQUIRE_MESSAGE(names == alpha, "NOrder::BOTTOM order " << ecf::algorithm::join(names) << " not as expected " << ecf::algorithm::join(alpha)); @@ -308,7 +308,7 @@ BOOST_AUTO_TEST_CASE(test_order) { // move task 'a' up one place. Should be no change, since its already at the top family->order(theDefs.findAbsNode("/a/a/a").get(), NOrder::UP); test_invariants(theDefs, __LINE__); - auto names = ecf::algorithm::transform_to_vector(family->nodeVec(), to_name); + auto names = ecf::algorithm::transform_to_vector(family->children(), to_name); BOOST_REQUIRE_MESSAGE(names == alpha, "NOrder::UP order " << ecf::algorithm::join(names) << " not as expected " << ecf::algorithm::join(alpha)); @@ -318,7 +318,7 @@ BOOST_AUTO_TEST_CASE(test_order) { // move task 'e' down one place. Should be no change, since its already at the bottom family->order(theDefs.findAbsNode("/a/a/c").get(), NOrder::DOWN); test_invariants(theDefs, __LINE__); - auto names = ecf::algorithm::transform_to_vector(family->nodeVec(), to_name); + auto names = ecf::algorithm::transform_to_vector(family->children(), to_name); BOOST_REQUIRE_MESSAGE(names == alpha, "NOrder::DOWN order " << ecf::algorithm::join(names) << " not as expected " << ecf::algorithm::join(alpha)); @@ -330,7 +330,7 @@ BOOST_AUTO_TEST_CASE(test_order) { family->order(theDefs.findAbsNode("/a/a/a").get(), NOrder::DOWN); test_invariants(theDefs, __LINE__); - auto names = ecf::algorithm::transform_to_vector(family->nodeVec(), to_name); + auto names = ecf::algorithm::transform_to_vector(family->children(), to_name); BOOST_REQUIRE_MESSAGE(names == expected, "NOrder::DOWN order " << ecf::algorithm::join(names) << " not as expected " << ecf::algorithm::join(expected)); @@ -342,7 +342,7 @@ BOOST_AUTO_TEST_CASE(test_order) { family->order(theDefs.findAbsNode("/a/a/b").get(), NOrder::DOWN); test_invariants(theDefs, __LINE__); - auto names = ecf::algorithm::transform_to_vector(family->nodeVec(), to_name); + auto names = ecf::algorithm::transform_to_vector(family->children(), to_name); BOOST_REQUIRE_MESSAGE(names == expected, "NOrder::UP order " << ecf::algorithm::join(names) << " not as expected " << ecf::algorithm::join(expected)); diff --git a/libs/node/test/parser/ParseTimer.cpp b/libs/node/test/parser/ParseTimer.cpp index a3228fe9e..e74a7b0fb 100644 --- a/libs/node/test/parser/ParseTimer.cpp +++ b/libs/node/test/parser/ParseTimer.cpp @@ -38,11 +38,11 @@ void test_find_task_using_path(NodeContainer* f, const Defs& defs) { std::cout << "Could not find path " << f->absNodePath() << "\n"; } - for (node_ptr t : f->nodeVec()) { - if (t.get() != defs.findAbsNode(t->absNodePath()).get()) { - std::cout << "Could not find path " << t->absNodePath() << "\n"; + for (auto node : f->children()) { + if (node.get() != defs.findAbsNode(node->absNodePath()).get()) { + std::cout << "Could not find path " << node->absNodePath() << "\n"; } - Family* family = t->isFamily(); + Family* family = node->isFamily(); if (family) { test_find_task_using_path(family, defs); } @@ -221,14 +221,14 @@ int main(int argc, char* argv[]) { auto suites = defs.suites(); // make a copy, to avoid invalidating iterators for (suite_ptr suite : suites) { - std::vector familyVec = suite->nodeVec(); // make a copy, to avoid invalidating iterators - for (node_ptr f : familyVec) { - if (!defs.deleteChild(f.get())) { + auto nodes = suite->children(); // make a copy, to avoid invalidating iterators + for (auto node : nodes) { + if (!defs.deleteChild(node.get())) { std::cout << "Failed to delete family\n"; } } - if (!suite->nodeVec().empty()) { - std::cout << "Expected all Families to be deleted but found " << suite->nodeVec().size() << "\n"; + if (!suite->children().empty()) { + std::cout << "Expected all Families to be deleted but found " << suite->children().size() << "\n"; } if (!defs.deleteChild(suite.get())) { std::cout << "Failed to delete suite\n"; diff --git a/libs/node/test/parser/TestDefsStructurePersistAndReload.cpp b/libs/node/test/parser/TestDefsStructurePersistAndReload.cpp index 74b2eabf0..9a55668a7 100644 --- a/libs/node/test/parser/TestDefsStructurePersistAndReload.cpp +++ b/libs/node/test/parser/TestDefsStructurePersistAndReload.cpp @@ -65,10 +65,10 @@ void test_find_task_using_path(NodeContainer* f, const Defs& defs) { BOOST_CHECK_MESSAGE(f == defs.findAbsNode(f->absNodePath()).get(), "Could not find path " << f->absNodePath() << "\n"); - for (node_ptr t : f->nodeVec()) { - BOOST_CHECK_MESSAGE(t.get() == defs.findAbsNode(t->absNodePath()).get(), - "Could not find path " << t->absNodePath() << "\n"); - Family* family = t->isFamily(); + for (auto node : f->children()) { + BOOST_CHECK_MESSAGE(node.get() == defs.findAbsNode(node->absNodePath()).get(), + "Could not find path " << node->absNodePath() << "\n"); + Family* family = node->isFamily(); if (family) { test_find_task_using_path(family, defs); } diff --git a/libs/node/test/parser/TestSingleDefsFile.cpp b/libs/node/test/parser/TestSingleDefsFile.cpp index bc6aac90c..3a0334de9 100644 --- a/libs/node/test/parser/TestSingleDefsFile.cpp +++ b/libs/node/test/parser/TestSingleDefsFile.cpp @@ -47,10 +47,10 @@ void test_find_task_using_path(NodeContainer* f, const Defs& defs) { BOOST_CHECK_MESSAGE(f == defs.findAbsNode(f->absNodePath()).get(), "Could not find path " << f->absNodePath() << "\n"); - for (node_ptr t : f->nodeVec()) { - BOOST_CHECK_MESSAGE(t.get() == defs.findAbsNode(t->absNodePath()).get(), - "Could not find path " << t->absNodePath() << "\n"); - Family* family = t->isFamily(); + for (node_ptr node : f->children()) { + BOOST_CHECK_MESSAGE(node.get() == defs.findAbsNode(node->absNodePath()).get(), + "Could not find path " << node->absNodePath() << "\n"); + Family* family = node->isFamily(); if (family) { test_find_task_using_path(family, defs); } @@ -289,12 +289,12 @@ BOOST_AUTO_TEST_CASE(test_single_defs) { auto suites = defs.suites(); // make a copy, to avoid invalidating iterators BOOST_CHECK_MESSAGE(suites.size() > 0, "Expected > 0 Suites but found " << suites.size()); for (suite_ptr s : suites) { - std::vector familyVec = s->nodeVec(); // make a copy, to avoid invalidating iterators - for (node_ptr f : familyVec) { + auto nodes = s->children(); // make a copy, to avoid invalidating iterators + for (node_ptr f : nodes) { BOOST_REQUIRE_MESSAGE(defs.deleteChild(f.get()), " Failed to delete family"); } - BOOST_REQUIRE_MESSAGE(s->nodeVec().empty(), - "Expected all Families to be deleted but found " << s->nodeVec().size()); + BOOST_REQUIRE_MESSAGE(s->children().empty(), + "Expected all Families to be deleted but found " << s->children().size()); BOOST_REQUIRE_MESSAGE(defs.deleteChild(s.get()), " Failed to delete suite"); } BOOST_REQUIRE_MESSAGE(defs.suites().empty(), diff --git a/libs/pyext/src/ecflow/python/ExportSuiteAndFamily.cpp b/libs/pyext/src/ecflow/python/ExportSuiteAndFamily.cpp index a7806f086..bf70d297e 100644 --- a/libs/pyext/src/ecflow/python/ExportSuiteAndFamily.cpp +++ b/libs/pyext/src/ecflow/python/ExportSuiteAndFamily.cpp @@ -41,10 +41,10 @@ suite_ptr add_end_clock(suite_ptr self, const ClockAttr& clk) { // Sized and Container protocol size_t family_len(family_ptr self) { - return self->nodeVec().size(); + return self->children().size(); } size_t suite_len(suite_ptr self) { - return self->nodeVec().size(); + return self->children().size(); } bool family_container(family_ptr self, const std::string& name) { size_t pos; diff --git a/libs/rest/src/ecflow/http/ApiV1Impl.cpp b/libs/rest/src/ecflow/http/ApiV1Impl.cpp index 6bc7e0963..0dbf3158f 100644 --- a/libs/rest/src/ecflow/http/ApiV1Impl.cpp +++ b/libs/rest/src/ecflow/http/ApiV1Impl.cpp @@ -65,7 +65,7 @@ void print_sparser_node(ojson& j, const std::vector& nodes) { j[node->name()] = ojson::object({}); if (type == "family") { - print_sparser_node(j[node->name()], std::dynamic_pointer_cast(node)->nodeVec()); + print_sparser_node(j[node->name()], std::dynamic_pointer_cast(node)->children()); } } } @@ -78,7 +78,7 @@ void print_node(ojson& j, const std::vector& nodes, bool add_id = fals j["children"].push_back(jnode); if (type == "family") { - print_node(j["children"].back(), std::dynamic_pointer_cast(node)->nodeVec(), add_id); + print_node(j["children"].back(), std::dynamic_pointer_cast(node)->children(), add_id); } } } @@ -143,7 +143,7 @@ ojson get_sparser_node_tree(const std::string& path) { for (const auto& suite : get_defs()->suites()) { j[suite->name()] = ojson::object({}); - print_sparser_node(j[suite->name()], suite->nodeVec()); + print_sparser_node(j[suite->name()], suite->children()); } } else { @@ -156,10 +156,10 @@ ojson get_sparser_node_tree(const std::string& path) { const std::string type = ecf::algorithm::tolower(node->debugType()); if (type == "family") { - print_sparser_node(j, std::dynamic_pointer_cast(node)->nodeVec()); + print_sparser_node(j, std::dynamic_pointer_cast(node)->children()); } else if (type == "suite") { - print_sparser_node(j, std::dynamic_pointer_cast(node)->nodeVec()); + print_sparser_node(j, std::dynamic_pointer_cast(node)->children()); } } @@ -177,7 +177,7 @@ ojson get_node_tree(const std::string& path, bool add_id = false) { for (const auto& suite : suites) { ojson js = make_node_json(suite); - print_node(js, suite->nodeVec(), add_id); + print_node(js, suite->children(), add_id); j["suites"].push_back(js); } @@ -194,10 +194,10 @@ ojson get_node_tree(const std::string& path, bool add_id = false) { const std::string type = ecf::algorithm::tolower(node->debugType()); if (type == "family") { - print_node(j, std::dynamic_pointer_cast(node)->nodeVec(), add_id); + print_node(j, std::dynamic_pointer_cast(node)->children(), add_id); } else if (type == "suite") { - print_node(j, std::dynamic_pointer_cast(node)->nodeVec(), add_id); + print_node(j, std::dynamic_pointer_cast(node)->children(), add_id); } } diff --git a/libs/test/overall/TestOrderCmd.cpp b/libs/test/overall/TestOrderCmd.cpp index c0d9f854b..4a49d89c4 100644 --- a/libs/test/overall/TestOrderCmd.cpp +++ b/libs/test/overall/TestOrderCmd.cpp @@ -116,33 +116,33 @@ void test_ordering() { TestFixture::client().order("/a/a", NOrder::toString(NOrder::ORDER)); TestFixture::client().sync_local(); BOOST_CHECK_MESSAGE(!TestFixture::client().server_reply().full_sync(), "Expected incremental sync"); - BOOST_CHECK_MESSAGE(to_string_vec(TestFixture::client().defs()->suites()[0]->nodeVec()) == str_c_b_a, + BOOST_CHECK_MESSAGE(to_string_vec(TestFixture::client().defs()->suites()[0]->children()) == str_c_b_a, "Order not as expected"); TestFixture::client().order("/a/a", NOrder::toString(NOrder::ALPHA)); TestFixture::client().sync_local(); BOOST_CHECK_MESSAGE(!TestFixture::client().server_reply().full_sync(), "Expected incremental sync"); - BOOST_CHECK_MESSAGE(to_string_vec(TestFixture::client().defs()->suites()[0]->nodeVec()) == str_a_b_c, + BOOST_CHECK_MESSAGE(to_string_vec(TestFixture::client().defs()->suites()[0]->children()) == str_a_b_c, "Order not as expected"); TestFixture::client().order("/a/a", NOrder::toString(NOrder::BOTTOM)); TestFixture::client().sync_local(); BOOST_CHECK_MESSAGE(!TestFixture::client().server_reply().full_sync(), "Expected incremental sync"); - BOOST_CHECK_MESSAGE(to_string_vec(TestFixture::client().defs()->suites()[0]->nodeVec()) == str_b_c_a, + BOOST_CHECK_MESSAGE(to_string_vec(TestFixture::client().defs()->suites()[0]->children()) == str_b_c_a, "Order not as expected"); TestFixture::client().order("/a/a", NOrder::toString(NOrder::ALPHA)); TestFixture::client().order("/a/a", NOrder::toString(NOrder::DOWN)); TestFixture::client().sync_local(); BOOST_CHECK_MESSAGE(!TestFixture::client().server_reply().full_sync(), "Expected incremental sync"); - BOOST_CHECK_MESSAGE(to_string_vec(TestFixture::client().defs()->suites()[0]->nodeVec()) == str_b_a_c, + BOOST_CHECK_MESSAGE(to_string_vec(TestFixture::client().defs()->suites()[0]->children()) == str_b_a_c, "Order not as expected"); TestFixture::client().order("/a/a", NOrder::toString(NOrder::ALPHA)); TestFixture::client().order("/a/c", NOrder::toString(NOrder::UP)); TestFixture::client().sync_local(); BOOST_CHECK_MESSAGE(!TestFixture::client().server_reply().full_sync(), "Expected incremental sync"); - BOOST_CHECK_MESSAGE(to_string_vec(TestFixture::client().defs()->suites()[0]->nodeVec()) == str_a_c_b, + BOOST_CHECK_MESSAGE(to_string_vec(TestFixture::client().defs()->suites()[0]->children()) == str_a_c_b, "Order not as expected"); } } diff --git a/libs/test/overall/harness/ServerTestHarness.cpp b/libs/test/overall/harness/ServerTestHarness.cpp index 0e8ae1ff1..d54fd769f 100644 --- a/libs/test/overall/harness/ServerTestHarness.cpp +++ b/libs/test/overall/harness/ServerTestHarness.cpp @@ -453,9 +453,9 @@ void ServerTestHarness::createDirAndEcfFiles(NodeContainer* nc, theManFile << "%end\n"; } - for (node_ptr n : nc->nodeVec()) { + for (auto node : nc->children()) { - Task* t = n->isTask(); + Task* t = node->isTask(); if (t) { std::string ecf_file = smshome + t->absNodePath() + File::ECF_EXTN(); #ifdef DEBUG_TEST_HARNESS @@ -473,7 +473,7 @@ void ServerTestHarness::createDirAndEcfFiles(NodeContainer* nc, } } else { - Family* f = n->isFamily(); + Family* f = node->isFamily(); assert(f); createDirAndEcfFiles(f, smshome, customTaskSmsMap, customSmsCnt); } diff --git a/libs/test/simulator/src/ecflow/simulator/DefsAnalyserVisitor.cpp b/libs/test/simulator/src/ecflow/simulator/DefsAnalyserVisitor.cpp index 0f140c143..66885b0c5 100644 --- a/libs/test/simulator/src/ecflow/simulator/DefsAnalyserVisitor.cpp +++ b/libs/test/simulator/src/ecflow/simulator/DefsAnalyserVisitor.cpp @@ -40,8 +40,8 @@ void DefsAnalyserVisitor::visitNodeContainer(NodeContainer* nc) { std::set dependentNodes; analyse(nc, dependentNodes); - for (node_ptr t : nc->nodeVec()) { - t->acceptVisitTraversor(*this); + for (auto node : nc->children()) { + node->acceptVisitTraversor(*this); } } @@ -84,8 +84,8 @@ void DefsAnalyserVisitor::analyse(Node* node, std::set& dependentNodes, b // follow child nodes auto* nc = dynamic_cast(node); if (nc) { - for (node_ptr t : nc->nodeVec()) { - t->acceptVisitTraversor(*this); + for (auto n : nc->children()) { + n->acceptVisitTraversor(*this); } } } @@ -97,8 +97,8 @@ void DefsAnalyserVisitor::analyse(Node* node, std::set& dependentNodes, b // follow child nodes auto* nc = dynamic_cast(node); if (nc) { - for (node_ptr t : nc->nodeVec()) { - t->acceptVisitTraversor(*this); + for (node_ptr n : nc->children()) { + n->acceptVisitTraversor(*this); } } } diff --git a/libs/test/simulator/src/ecflow/simulator/FlatAnalyserVisitor.cpp b/libs/test/simulator/src/ecflow/simulator/FlatAnalyserVisitor.cpp index 2b809aad7..71e7ad5ff 100644 --- a/libs/test/simulator/src/ecflow/simulator/FlatAnalyserVisitor.cpp +++ b/libs/test/simulator/src/ecflow/simulator/FlatAnalyserVisitor.cpp @@ -48,8 +48,8 @@ void FlatAnalyserVisitor::visitNodeContainer(NodeContainer* nc) { // Don't traverse children if the parent is holding on trigger/complete expression if (traverseChildren) { - for (node_ptr t : nc->nodeVec()) { - t->acceptVisitTraversor(*this); + for (auto node : nc->children()) { + node->acceptVisitTraversor(*this); } } } diff --git a/libs/test/simulator/src/ecflow/simulator/SimulatorVisitor.cpp b/libs/test/simulator/src/ecflow/simulator/SimulatorVisitor.cpp index ff4424247..62aa59cc3 100644 --- a/libs/test/simulator/src/ecflow/simulator/SimulatorVisitor.cpp +++ b/libs/test/simulator/src/ecflow/simulator/SimulatorVisitor.cpp @@ -128,8 +128,8 @@ void SimulatorVisitor::visitNodeContainer(NodeContainer* nc) { foundTime_ = true; } - for (node_ptr t : nc->nodeVec()) { - t->acceptVisitTraversor(*this); + for (auto node : nc->children()) { + node->acceptVisitTraversor(*this); } } diff --git a/libs/test/simulator/test/TestAutoArchive.cpp b/libs/test/simulator/test/TestAutoArchive.cpp index af3af879b..b1fe6899b 100644 --- a/libs/test/simulator/test/TestAutoArchive.cpp +++ b/libs/test/simulator/test/TestAutoArchive.cpp @@ -84,9 +84,9 @@ BOOST_AUTO_TEST_CASE(test_autoarchive_suite) { BOOST_CHECK_MESSAGE(fs::exists(s2->archive_path()), "Expected suite " << s2->absNodePath() << " to be archived"); BOOST_CHECK_MESSAGE(fs::exists(s3->archive_path()), "Expected suite " << s3->absNodePath() << " to be archived"); - BOOST_CHECK_MESSAGE(s1->nodeVec().empty(), "Expected suite " << s1->absNodePath() << " to be empty"); - BOOST_CHECK_MESSAGE(s1->nodeVec().empty(), "Expected suite " << s2->absNodePath() << " to be empty"); - BOOST_CHECK_MESSAGE(s1->nodeVec().empty(), "Expected suite " << s3->absNodePath() << " to be empty"); + BOOST_CHECK_MESSAGE(s1->children().empty(), "Expected suite " << s1->absNodePath() << " to be empty"); + BOOST_CHECK_MESSAGE(s1->children().empty(), "Expected suite " << s2->absNodePath() << " to be empty"); + BOOST_CHECK_MESSAGE(s1->children().empty(), "Expected suite " << s3->absNodePath() << " to be empty"); s1->restore(); s2->restore(); @@ -109,9 +109,9 @@ BOOST_AUTO_TEST_CASE(test_autoarchive_suite) { BOOST_CHECK_MESSAGE(!fs::exists(s2->archive_path()), "Expected file " << s2->archive_path() << " to be removed"); BOOST_CHECK_MESSAGE(!fs::exists(s3->archive_path()), "Expected file " << s3->archive_path() << " to be removed"); - BOOST_CHECK_MESSAGE(!s1->nodeVec().empty(), "Expected suite " << s1->absNodePath() << " to be restored"); - BOOST_CHECK_MESSAGE(!s1->nodeVec().empty(), "Expected suite " << s2->absNodePath() << " to be restored"); - BOOST_CHECK_MESSAGE(!s1->nodeVec().empty(), "Expected suite " << s3->absNodePath() << " to be restored"); + BOOST_CHECK_MESSAGE(!s1->children().empty(), "Expected suite " << s1->absNodePath() << " to be restored"); + BOOST_CHECK_MESSAGE(!s1->children().empty(), "Expected suite " << s2->absNodePath() << " to be restored"); + BOOST_CHECK_MESSAGE(!s1->children().empty(), "Expected suite " << s3->absNodePath() << " to be restored"); // remove generated log file. Comment out to debug std::string logFileName = findTestDataLocation("test_autoarchive_suite.def") + ".log"; @@ -249,7 +249,7 @@ BOOST_AUTO_TEST_CASE(test_autoarchive_family) { "Expected family " << family->absNodePath() << " to be archived"); BOOST_CHECK_MESSAGE(fs::exists(family->archive_path()), "Expected family " << family->absNodePath() << " to be archived"); - BOOST_CHECK_MESSAGE(family->nodeVec().empty(), "Expected family " << family->absNodePath() << " to be empty"); + BOOST_CHECK_MESSAGE(family->children().empty(), "Expected family " << family->absNodePath() << " to be empty"); family->restore(); BOOST_CHECK_MESSAGE(family->get_flag().is_set(ecf::Flag::RESTORED), @@ -258,7 +258,7 @@ BOOST_AUTO_TEST_CASE(test_autoarchive_family) { "Expected family " << family->absNodePath() << " to be restored"); BOOST_CHECK_MESSAGE(!fs::exists(family->archive_path()), "Expected file " << family->absNodePath() << " to be removed"); - BOOST_CHECK_MESSAGE(!family->nodeVec().empty(), + BOOST_CHECK_MESSAGE(!family->children().empty(), "Expected family " << family->absNodePath() << " to be restored"); } @@ -299,7 +299,7 @@ BOOST_AUTO_TEST_CASE(test_two_autoarchive_in_hierarchy) { "Expected suite " << suite->absNodePath() << " to be archived"); BOOST_CHECK_MESSAGE(fs::exists(suite->archive_path()), "Expected family " << suite->absNodePath() << " to be archived"); - BOOST_CHECK_MESSAGE(suite->nodeVec().empty(), "Expected family " << suite->absNodePath() << " to be empty"); + BOOST_CHECK_MESSAGE(suite->children().empty(), "Expected family " << suite->absNodePath() << " to be empty"); // The family should have been deleted node_ptr fam = theDefs.findAbsNode("/test_two_autoarchive_in_hierarchy/family"); diff --git a/libs/test/simulator/test/TestAutoRestore.cpp b/libs/test/simulator/test/TestAutoRestore.cpp index 6bce11f34..0875f6cbe 100644 --- a/libs/test/simulator/test/TestAutoRestore.cpp +++ b/libs/test/simulator/test/TestAutoRestore.cpp @@ -79,7 +79,7 @@ BOOST_AUTO_TEST_CASE(test_autorestore_suite) { BOOST_CHECK_MESSAGE(!s1->get_flag().is_set(ecf::Flag::ARCHIVED), "Expected suite " << s1->absNodePath() << " to be restored"); BOOST_CHECK_MESSAGE(!fs::exists(s1->archive_path()), "Expected file " << s1->archive_path() << " to be removed"); - BOOST_CHECK_MESSAGE(!s1->nodeVec().empty(), "Expected suite " << s1->absNodePath() << " to be restored"); + BOOST_CHECK_MESSAGE(!s1->children().empty(), "Expected suite " << s1->absNodePath() << " to be restored"); // remove generated log file. Comment out to debug std::string logFileName = findTestDataLocation("test_autorestore_suite.def") + ".log"; @@ -160,7 +160,7 @@ BOOST_AUTO_TEST_CASE(test_autorestore_family) { "Expected family " << family->absNodePath() << " to be restored"); BOOST_CHECK_MESSAGE(!fs::exists(family->archive_path()), "Expected file " << family->absNodePath() << " to be removed"); - BOOST_CHECK_MESSAGE(!family->nodeVec().empty(), + BOOST_CHECK_MESSAGE(!family->children().empty(), "Expected family " << family->absNodePath() << " to be restored"); } From 65b5960014ea56bef82e33b581ff51647ac1510e Mon Sep 17 00:00:00 2001 From: Marcos Bento Date: Mon, 20 Apr 2026 15:03:54 +0100 Subject: [PATCH 30/90] refactor: remove [[deprecated]] from Node::gen_variables This function is indeed useful, for performance reason, in the cases where generated variables need to be collected from multiple nodes. Re ECFLOW-2079 --- libs/node/src/ecflow/node/Node.hpp | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/libs/node/src/ecflow/node/Node.hpp b/libs/node/src/ecflow/node/Node.hpp index 511e006a1..c1022bb82 100644 --- a/libs/node/src/ecflow/node/Node.hpp +++ b/libs/node/src/ecflow/node/Node.hpp @@ -440,8 +440,36 @@ class Node : public std::enable_shared_from_this { const MiscAttrs* get_misc_attrs() const { return misc_attrs_.get(); } - [[deprecated]] virtual void gen_variables(std::vector&) const; + /// + /// @brief Retrieves the generated variables associated with this node. + /// + /// @note generated variables are automatically created from repeat attributes and provide + /// additional context about the current repeat iteration. + /// @note this virtual function allows derived classes (e.g., Submittable) to add additional + /// generated variables beyond those from the repeat attribute. + /// + /// @param vars Reference to a vector that will be populated with the generated variables. + /// The function appends to the vector rather than replacing its contents. + /// + /// @see repeat attribute documentation for details on the specific generated variables created + /// by each repeat type. + /// + virtual void gen_variables(std::vector& vars) const; + + /// + /// @brief Retrieves the generated variables associated with this node. + /// + /// This is a convenience wrapper around gen_variables(std::vector&) that + /// creates and returns a new vector containing the generated variables. + /// + /// @note For performance-critical code where a vector already exists, prefer using + /// gen_variables(std::vector&) to avoid unnecessary allocations. + /// + /// @return A vector containing all generated variables for this node's repeat attribute. + /// Returns an empty vector if the node has no repeat attribute. + /// std::vector gen_variables() const; + bool getLabelValue(const std::string& name, std::string& value) const; bool getLabelNewValue(const std::string& name, std::string& value) const; From 20b360f9b25b7e0587a437399d026aa17c43ef30 Mon Sep 17 00:00:00 2001 From: Marcos Bento Date: Mon, 20 Apr 2026 15:44:50 +0100 Subject: [PATCH 31/90] fix: correct [-Woverloaded-virtual] compilation warning Re ECFLOW-2079 --- cmake/CompilerOptions.cmake | 2 -- .../src/ecflow/base/cts/ClientToServerCmd.hpp | 36 +++++++++++++++++-- .../src/ecflow/base/cts/user/CSyncCmd.hpp | 4 +-- .../src/ecflow/base/cts/user/GroupCTSCmd.hpp | 2 +- 4 files changed, 37 insertions(+), 7 deletions(-) diff --git a/cmake/CompilerOptions.cmake b/cmake/CompilerOptions.cmake index e2570d78a..fa768e5eb 100644 --- a/cmake/CompilerOptions.cmake +++ b/cmake/CompilerOptions.cmake @@ -92,14 +92,12 @@ if (HAVE_WARNINGS) ## Clang (MacOS AppleClang) $<$,$>:-Wno-deprecated-copy-with-user-provided-copy> # silence warnings in Qt5 related headers $<$,$>:-Wno-missing-field-initializers> # silence warning in Boost.Python related headers - $<$,$>:-Wno-overloaded-virtual> $<$,$>:-Wno-unused-parameter> $<$,$,$,14.0.0>>:-Wno-c++20-attribute-extensions> # silence warning in Qt6 related headers $<$,$,$,21.0.0>>:-Wno-character-conversion> # silence warning in Qt6 related headers ## Clang (Intel Clang-based) $<$,$>:-Wno-deprecated-copy-with-user-provided-copy> # silence warnings in Qt5 related headers $<$,$>:-Wno-missing-field-initializers> # silence warning in Boost.Python related headers - $<$,$>:-Wno-overloaded-virtual> $<$,$>:-Wno-unused-parameter> ) diff --git a/libs/base/src/ecflow/base/cts/ClientToServerCmd.hpp b/libs/base/src/ecflow/base/cts/ClientToServerCmd.hpp index d4502b58b..f5bd507d0 100644 --- a/libs/base/src/ecflow/base/cts/ClientToServerCmd.hpp +++ b/libs/base/src/ecflow/base/cts/ClientToServerCmd.hpp @@ -155,8 +155,40 @@ class ClientToServerCmd { virtual bool show_cmd() const { return false; } virtual void add_edit_history(Defs*) const; - // used by group_cmd to postfix syncCmd on all user commands that modify defs - virtual void set_client_handle(int /*client_handle*/) {} // used by group_cmd + /// + /// @brief Sets the client handle for this command, used during group command execution. + /// + /// This function is part of the client handle synchronisation mechanism used when commands + /// are executed as part of a GroupCTSCmd. It allows commands that create, modify, or drop + /// client handles to propagate that information to subsequent commands in the group + /// (typically a sync command). + /// + /// Client handles are identifiers that allow clients to register interest in a specific + /// subset of suites on the server, enabling efficient synchronisation: + /// - client_handle > 0: References a specific registered set of suites + /// - client_handle == 0: References all suites (global scope) + /// + /// Call flow example when a group contains [ClientHandleCmd, CSyncCmd]: + /// 1. ClientHandleCmd creates a new handle (e.g., 42) + /// 2. ClientHandleCmd calls group_cmd_->set_client_handle(42) + /// 3. GroupCTSCmd propagates this to the next command (CSyncCmd) + /// 4. CSyncCmd stores the handle and uses it to sync only the relevant suites + /// + /// This function is const because it's called from const member functions (doHandleRequest). + /// It doesn't modify the command's observable state, only propagates information or updates + /// internal synchronisation state (via mutable members in derived classes). + /// + /// @param client_handle The client handle to set. Use 0 for global scope (all suites), + /// or a positive value for a specific registered set of suites. + /// + /// @see GroupCTSCmd::set_client_handle() for propagation logic + /// @see CSyncCmd::set_client_handle() for storage and usage + /// @see ClientHandleCmd for client handle creation/management + /// + virtual void set_client_handle([[maybe_unused]] int client_handle) const { + // Default implementation: no-op, ignored by most command types + } + virtual void set_group_cmd(const GroupCTSCmd*) {} // CLIENT side Parse and command construction, create can throw std::runtime_error for errors diff --git a/libs/base/src/ecflow/base/cts/user/CSyncCmd.hpp b/libs/base/src/ecflow/base/cts/user/CSyncCmd.hpp index 1ab1491ba..f488784d9 100644 --- a/libs/base/src/ecflow/base/cts/user/CSyncCmd.hpp +++ b/libs/base/src/ecflow/base/cts/user/CSyncCmd.hpp @@ -39,7 +39,7 @@ class CSyncCmd final : public UserCmd { int client_modify_change_no() const { return client_modify_change_no_; } int client_handle() const { return client_handle_; } - void set_client_handle(int client_handle) override { client_handle_ = client_handle; } // used by group_cmd + void set_client_handle(int client_handle) const override { client_handle_ = client_handle; } // used by group_cmd void print(std::string&) const override; std::string print_short() const override; void print_only(std::string&) const override; @@ -61,7 +61,7 @@ class CSyncCmd final : public UserCmd { STC_Cmd_ptr doHandleRequest(AbstractServer*) const override; Api api_{SYNC}; - int client_handle_{0}; + mutable int client_handle_{0}; int client_state_change_no_{0}; int client_modify_change_no_{0}; diff --git a/libs/base/src/ecflow/base/cts/user/GroupCTSCmd.hpp b/libs/base/src/ecflow/base/cts/user/GroupCTSCmd.hpp index 84220743d..d5244d677 100644 --- a/libs/base/src/ecflow/base/cts/user/GroupCTSCmd.hpp +++ b/libs/base/src/ecflow/base/cts/user/GroupCTSCmd.hpp @@ -45,7 +45,7 @@ class GroupCTSCmd final : public UserCmd { bool why_cmd(std::string&) const override; bool group_cmd() const override { return true; } - void set_client_handle(int client_handle) const; // used in group sync with client register + void set_client_handle(int client_handle) const override; // used in group sync with client register void print(std::string&) const override; std::string print_short() const override; From f3839b0232af9080312b1894dfe31cc4d4560a93 Mon Sep 17 00:00:00 2001 From: Marcos Bento Date: Mon, 20 Apr 2026 16:57:55 +0100 Subject: [PATCH 32/90] fix: correct compilation warnings These changes correct also warnings related to Qt deprecations. Re ECFLOW-2079 --- Viewer/ecflowUI/src/LogLoadView.cpp | 14 +- Viewer/ecflowUI/src/LogLoadWidget.cpp | 5 +- Viewer/ecflowUI/src/NodeExpression.cpp | 10 +- Viewer/ecflowUI/src/NodeQueryOption.cpp | 14 ++ Viewer/ecflowUI/src/ServerListDialog.cpp | 18 +++ Viewer/ecflowUI/src/ShellCommand.cpp | 2 +- .../src/TextPager/TextPagerLayout_p.cpp | 2 +- Viewer/ecflowUI/src/TimelineData.cpp | 5 + Viewer/ecflowUI/src/TimelineData.hpp | 5 +- Viewer/ecflowUI/src/TimelineHeaderView.cpp | 38 ++---- Viewer/ecflowUI/src/VFileTransfer.cpp | 2 +- Viewer/libViewer/src/LogData.cpp | 9 ++ Viewer/libViewer/src/LogData.hpp | 6 +- Viewer/libViewer/src/LogLoadData.cpp | 12 +- cmake/CompilerOptions.cmake | 2 + .../src/ecflow/base/cts/user/CtsNodeCmd.cpp | 2 +- .../ecflow/base/cts/user/EditScriptCmd.cpp | 2 +- libs/client/test/TestClientTimeout.cpp | 2 +- libs/client/test/harness/SCPort.cpp | 10 +- libs/pyext/src/ecflow/python/Edit.cpp | 4 +- libs/pyext/src/ecflow/python/ExportDefs.cpp | 24 ++-- libs/pyext/src/ecflow/python/ExportNode.cpp | 8 +- .../src/ecflow/python/ExportNodeAttr.cpp | 26 ++-- libs/pyext/src/ecflow/python/NodeUtil.cpp | 129 +++++++++--------- libs/pyext/src/ecflow/python/PythonUtil.cpp | 18 ++- libs/pyext/src/ecflow/python/Trigger.cpp | 8 +- 26 files changed, 223 insertions(+), 154 deletions(-) diff --git a/Viewer/ecflowUI/src/LogLoadView.cpp b/Viewer/ecflowUI/src/LogLoadView.cpp index 04e9817d3..1edf26ba9 100644 --- a/Viewer/ecflowUI/src/LogLoadView.cpp +++ b/Viewer/ecflowUI/src/LogLoadView.cpp @@ -650,7 +650,9 @@ void ChartView::setCallout(qreal val) { if (auto* axisY = static_cast(ViewerUtil::chartAxisY(chart()))) { qreal m = axisY->max(); callout_->setAnchor(QPointF(val, m)); -#if QT_VERSION >= QT_VERSION_CHECK(5, 2, 0) +#if QT_VERSION >= QT_VERSION_CHECK(6, 9, 0) + QString txt = QDateTime::fromMSecsSinceEpoch(val, QTimeZone::utc()).toString("hh:mm:ss dd/MM/yyyy"); +#elif QT_VERSION >= QT_VERSION_CHECK(5, 2, 0) QString txt = QDateTime::fromMSecsSinceEpoch(val, Qt::UTC).toString("hh:mm:ss dd/MM/yyyy"); #else QString txt = QDateTime::fromMSecsSinceEpoch(val).toUTC().toString("hh:mm:ss dd/MM/yyyy"); @@ -1581,7 +1583,9 @@ void LogRequestView::scanPositionChanged(qreal pos) { QColor dateCol(210, 211, 214); QString txt = ""; -#if QT_VERSION >= QT_VERSION_CHECK(5, 2, 0) +#if QT_VERSION >= QT_VERSION_CHECK(6, 9, 0) + QString dateTxt = QDateTime::fromMSecsSinceEpoch(t, QTimeZone::utc()).toString("hh:mm:ss dd/MM/yyyy"); +#elif QT_VERSION >= QT_VERSION_CHECK(5, 2, 0) QString dateTxt = QDateTime::fromMSecsSinceEpoch(t, Qt::UTC).toString("hh:mm:ss dd/MM/yyyy"); #else QString dateTxt = QDateTime::fromMSecsSinceEpoch(t).toUTC().toString("hh:mm:ss dd/MM/yyyy"); @@ -1594,7 +1598,11 @@ void LogRequestView::scanPositionChanged(qreal pos) { // QDateTime::fromMSecsSinceEpoch(t,Qt::UTC).toString("hh:mm:ss dd/MM/yyyy"), // dateCol); -#if QT_VERSION >= QT_VERSION_CHECK(5, 2, 0) +#if QT_VERSION >= QT_VERSION_CHECK(6, 9, 0) + dateTxt = (hasData) + ? QDateTime::fromMSecsSinceEpoch(seriesTime(idx), QTimeZone::utc()).toString("hh:mm:ss dd/MM/yyyy") + : " N/A"; +#elif QT_VERSION >= QT_VERSION_CHECK(5, 2, 0) dateTxt = (hasData) ? QDateTime::fromMSecsSinceEpoch(seriesTime(idx), Qt::UTC).toString("hh:mm:ss dd/MM/yyyy") : " N/A"; #else diff --git a/Viewer/ecflowUI/src/LogLoadWidget.cpp b/Viewer/ecflowUI/src/LogLoadWidget.cpp index 01e212c3d..426bb4478 100644 --- a/Viewer/ecflowUI/src/LogLoadWidget.cpp +++ b/Viewer/ecflowUI/src/LogLoadWidget.cpp @@ -834,7 +834,10 @@ void LogLoadWidget::setMaxReadSize(int maxReadSizeInMb) { } void LogLoadWidget::periodChanged(qint64 start, qint64 end) { -#if QT_VERSION >= QT_VERSION_CHECK(5, 2, 0) +#if QT_VERSION >= QT_VERSION_CHECK(6, 9, 0) + QDateTime startDt = QDateTime::fromMSecsSinceEpoch(start, QTimeZone::utc()); + QDateTime endDt = QDateTime::fromMSecsSinceEpoch(end, QTimeZone::utc()); +#elif QT_VERSION >= QT_VERSION_CHECK(5, 2, 0) QDateTime startDt = QDateTime::fromMSecsSinceEpoch(start, Qt::UTC); QDateTime endDt = QDateTime::fromMSecsSinceEpoch(end, Qt::UTC); #else diff --git a/Viewer/ecflowUI/src/NodeExpression.cpp b/Viewer/ecflowUI/src/NodeExpression.cpp index 69635a629..83c67ab0b 100644 --- a/Viewer/ecflowUI/src/NodeExpression.cpp +++ b/Viewer/ecflowUI/src/NodeExpression.cpp @@ -1007,7 +1007,11 @@ bool AttributeStateCondition::execute(VItem* item) { IsoDateCondition::IsoDateCondition(QString dateStr) { QDateTime d = QDateTime::fromString(dateStr, Qt::ISODate); +#if QT_VERSION >= QT_VERSION_CHECK(6, 9, 0) + d.setTimeZone(QTimeZone::utc()); +#else d.setTimeSpec(Qt::UTC); +#endif if (d.isValid()) { secsSinceEpoch_ = d.toMSecsSinceEpoch() / 1000; } @@ -1015,7 +1019,11 @@ IsoDateCondition::IsoDateCondition(QString dateStr) { std::string IsoDateCondition::print() { if (secsSinceEpoch_ > 0) -#if QT_VERSION >= QT_VERSION_CHECK(5, 2, 0) +#if QT_VERSION >= QT_VERSION_CHECK(6, 9, 0) + return QDateTime::fromMSecsSinceEpoch(secsSinceEpoch_ * 1000, QTimeZone::utc()) + .toString(Qt::ISODate) + .toStdString(); +#elif QT_VERSION >= QT_VERSION_CHECK(5, 2, 0) return QDateTime::fromMSecsSinceEpoch(secsSinceEpoch_ * 1000, Qt::UTC).toString(Qt::ISODate).toStdString(); #else return QDateTime::fromMSecsSinceEpoch(secsSinceEpoch_ * 1000).toUTC().toString(Qt::ISODate).toStdString(); diff --git a/Viewer/ecflowUI/src/NodeQueryOption.cpp b/Viewer/ecflowUI/src/NodeQueryOption.cpp index 09758bae2..1c24bb146 100644 --- a/Viewer/ecflowUI/src/NodeQueryOption.cpp +++ b/Viewer/ecflowUI/src/NodeQueryOption.cpp @@ -14,6 +14,7 @@ #include #include +#include #include "NodeQuery.hpp" #include "UiLog.hpp" @@ -348,8 +349,13 @@ void NodeQueryPeriodOption::clear() { periodUnits_.clear(); fromDate_ = QDateTime(); toDate_ = QDateTime(); +#if QT_VERSION >= QT_VERSION_CHECK(6, 9, 0) + fromDate_.setTimeZone(QTimeZone::utc()); + toDate_.setTimeZone(QTimeZone::utc()); +#else fromDate_.setTimeSpec(Qt::UTC); toDate_.setTimeSpec(Qt::UTC); +#endif } void NodeQueryPeriodOption::setLastPeriod(int period, QString periodUnits) { @@ -511,9 +517,17 @@ void NodeQueryPeriodOption::load(VSettings* vs) { QString to = QString::fromStdString(vs->get("to", fromDate_.toString(Qt::ISODate).toStdString())); fromDate_ = QDateTime::fromString(from, Qt::ISODate); +#if QT_VERSION >= QT_VERSION_CHECK(6, 9, 0) + fromDate_.setTimeZone(QTimeZone::utc()); +#else fromDate_.setTimeSpec(Qt::UTC); +#endif toDate_ = QDateTime::fromString(to, Qt::ISODate); +#if QT_VERSION >= QT_VERSION_CHECK(6, 9, 0) + toDate_.setTimeZone(QTimeZone::utc()); +#else toDate_.setTimeSpec(Qt::UTC); +#endif // Check if dates are valis if (!fromDate_.isValid() || !fromDate_.isValid()) { diff --git a/Viewer/ecflowUI/src/ServerListDialog.cpp b/Viewer/ecflowUI/src/ServerListDialog.cpp index 31dcbcc86..f78d422eb 100644 --- a/Viewer/ecflowUI/src/ServerListDialog.cpp +++ b/Viewer/ecflowUI/src/ServerListDialog.cpp @@ -1047,15 +1047,33 @@ ServerListFilterModel::ServerListFilterModel(QObject* parent) void ServerListFilterModel::setFilterStr(QString t) { QString newStr = t.simplified(); if (newStr != filterStr_) { +#if QT_VERSION >= QT_VERSION_CHECK(6, 9, 0) + beginFilterChange(); +#endif + filterStr_ = newStr; + +#if QT_VERSION >= QT_VERSION_CHECK(6, 9, 0) + endFilterChange(); +#else invalidateFilter(); +#endif } } void ServerListFilterModel::setFilterFavourite(bool b) { if (b != filterFavourite_) { +#if QT_VERSION >= QT_VERSION_CHECK(6, 9, 0) + beginFilterChange(); +#endif + filterFavourite_ = b; + +#if QT_VERSION >= QT_VERSION_CHECK(6, 9, 0) + endFilterChange(); +#else invalidateFilter(); +#endif } } diff --git a/Viewer/ecflowUI/src/ShellCommand.cpp b/Viewer/ecflowUI/src/ShellCommand.cpp index 2a017e681..ae811f251 100644 --- a/Viewer/ecflowUI/src/ShellCommand.cpp +++ b/Viewer/ecflowUI/src/ShellCommand.cpp @@ -97,7 +97,7 @@ ShellCommand::ShellCommand(const std::string& cmdStr, const std::string& cmdDefS startTime_ = QDateTime::currentDateTime(); -#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) +#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) proc_->start("/bin/sh", QStringList() << "-c" << command_); #else proc_->start("/bin/sh -c \"" + command_ + "\""); diff --git a/Viewer/ecflowUI/src/TextPager/TextPagerLayout_p.cpp b/Viewer/ecflowUI/src/TextPager/TextPagerLayout_p.cpp index 8d2180892..7e7ec9f12 100644 --- a/Viewer/ecflowUI/src/TextPager/TextPagerLayout_p.cpp +++ b/Viewer/ecflowUI/src/TextPager/TextPagerLayout_p.cpp @@ -81,7 +81,7 @@ int TextPagerLayout::doLayout(int index, QList* sections) // range.length = qMin(l->position() + l->size(), index) - lineStart - range.start; range.format = l->format(); -#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) +#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) formatMap.insert(l->priority(), range); #else formatMap.insertMulti(l->priority(), range); diff --git a/Viewer/ecflowUI/src/TimelineData.cpp b/Viewer/ecflowUI/src/TimelineData.cpp index 822149025..1ce0fa574 100644 --- a/Viewer/ecflowUI/src/TimelineData.cpp +++ b/Viewer/ecflowUI/src/TimelineData.cpp @@ -432,7 +432,12 @@ bool TimelineData::parseLine(const std::string& line, // Convert status time into secs QDateTime dt = QDateTime::fromString(QString::fromStdString(time_stamp), "hh:mm:ss d.M.yyyy"); + +#if QT_VERSION >= QT_VERSION_CHECK(6, 9, 0) + dt.setTimeZone(QTimeZone::utc()); +#else dt.setTimeSpec(Qt::UTC); +#endif statusTime = dt.toMSecsSinceEpoch() / 1000; return true; diff --git a/Viewer/ecflowUI/src/TimelineData.hpp b/Viewer/ecflowUI/src/TimelineData.hpp index 8b030fcc4..ae5054a70 100644 --- a/Viewer/ecflowUI/src/TimelineData.hpp +++ b/Viewer/ecflowUI/src/TimelineData.hpp @@ -17,6 +17,7 @@ #include #include #include +#include #include struct TimelineItemStats @@ -60,7 +61,9 @@ class TimelineItem { static unsigned int fromQDateTime(QDateTime dt) { return dt.toMSecsSinceEpoch() / 1000; } static QDateTime toQDateTime(unsigned int t) { -#if QT_VERSION >= QT_VERSION_CHECK(5, 2, 0) +#if QT_VERSION >= QT_VERSION_CHECK(6, 9, 0) + return QDateTime::fromMSecsSinceEpoch(static_cast(t) * 1000, QTimeZone::utc()); +#elif QT_VERSION >= QT_VERSION_CHECK(5, 2, 0) return QDateTime::fromMSecsSinceEpoch(static_cast(t) * 1000, Qt::UTC); #else return QDateTime::fromMSecsSinceEpoch(static_cast(t) * 1000).toUTC(); diff --git a/Viewer/ecflowUI/src/TimelineHeaderView.cpp b/Viewer/ecflowUI/src/TimelineHeaderView.cpp index 280821ffe..a4137a50c 100644 --- a/Viewer/ecflowUI/src/TimelineHeaderView.cpp +++ b/Viewer/ecflowUI/src/TimelineHeaderView.cpp @@ -642,21 +642,16 @@ void MainTimelineHeader::renderTimeline(const QRect& rect, QPainter* painter, in if (actSec % majorTick == 0) { painter->drawLine(xp, majorTickTop, xp, majorTickBottom); + const char* format = majorTick < 60 ? "H:mm:ss" : "H:mm"; + QString s; - if (majorTick < 60) { -#if QT_VERSION >= QT_VERSION_CHECK(5, 2, 0) - s = QDateTime::fromMSecsSinceEpoch(actSec * 1000, Qt::UTC).toString("H:mm:ss"); -#else - s = QDateTime::fromMSecsSinceEpoch(actSec * 1000).toUTC().toString("H:mm:ss"); -#endif - } - else { -#if QT_VERSION >= QT_VERSION_CHECK(5, 2, 0) - s = QDateTime::fromMSecsSinceEpoch(actSec * 1000, Qt::UTC).toString("H:mm"); +#if QT_VERSION >= QT_VERSION_CHECK(6, 9, 0) + s = QDateTime::fromMSecsSinceEpoch(actSec * 1000, QTimeZone::utc()).toString(format); +#elif QT_VERSION >= QT_VERSION_CHECK(5, 2, 0) + s = QDateTime::fromMSecsSinceEpoch(actSec * 1000, Qt::UTC).toString(format); #else - s = QDateTime::fromMSecsSinceEpoch(actSec * 1000).toUTC().toString("H:mm"); + s = QDateTime::fromMSecsSinceEpoch(actSec * 1000).toUTC().toString(format); #endif - } int textW = ViewerUtil::textWidth(fm_, s); painter->setFont(font_); @@ -933,21 +928,16 @@ void NodeTimelineHeader::renderTimeline(const QRect& rect, QPainter* painter, in if (actSec % majorTick == 0) { painter->drawLine(xp, majorTickTop, xp, majorTickBottom); + const char* format = majorTick < 60 ? "H:mm:ss" : "H:mm"; + QString s; - if (majorTick < 60) { -#if QT_VERSION >= QT_VERSION_CHECK(5, 2, 0) - s = QDateTime::fromMSecsSinceEpoch(actSec * 1000, Qt::UTC).toString("H:mm:ss"); -#else - s = QDateTime::fromMSecsSinceEpoch(actSec * 1000).toUTC().toString("H:mm:ss"); -#endif - } - else { -#if QT_VERSION >= QT_VERSION_CHECK(5, 2, 0) - s = QDateTime::fromMSecsSinceEpoch(actSec * 1000, Qt::UTC).toString("H:mm"); +#if QT_VERSION >= QT_VERSION_CHECK(6, 9, 0) + s = QDateTime::fromMSecsSinceEpoch(actSec * 1000, QTimeZone::utc()).toString(format); +#elif QT_VERSION >= QT_VERSION_CHECK(5, 2, 0) + s = QDateTime::fromMSecsSinceEpoch(actSec * 1000, Qt::UTC).toString(format); #else - s = QDateTime::fromMSecsSinceEpoch(actSec * 1000).toUTC().toString("H:mm"); + s = QDateTime::fromMSecsSinceEpoch(actSec * 1000).toUTC().toString(format); #endif - } int textW = ViewerUtil::textWidth(fm_, s); painter->setFont(font_); diff --git a/Viewer/ecflowUI/src/VFileTransfer.cpp b/Viewer/ecflowUI/src/VFileTransfer.cpp index 9857efc9d..39e2deb81 100644 --- a/Viewer/ecflowUI/src/VFileTransfer.cpp +++ b/Viewer/ecflowUI/src/VFileTransfer.cpp @@ -144,7 +144,7 @@ void VFileTransferCore::transferIt() { stopper_.start(); -#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) +#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) proc_->start("/bin/sh", QStringList() << "-c" << command); #else proc_->start("/bin/sh -c \"" + command + "\""); diff --git a/Viewer/libViewer/src/LogData.cpp b/Viewer/libViewer/src/LogData.cpp index 9213e8597..8b037f8ae 100644 --- a/Viewer/libViewer/src/LogData.cpp +++ b/Viewer/libViewer/src/LogData.cpp @@ -50,7 +50,11 @@ LogDataItem::LogDataItem(const std::string& line, qint64& refTimeInMs) } QDateTime dt = QDateTime::fromString(QString::fromStdString(d), "hh:mm:ss d.M.yyyy"); +#if QT_VERSION >= QT_VERSION_CHECK(6, 9, 0) + dt.setTimeZone(QTimeZone::utc()); +#else dt.setTimeSpec(Qt::UTC); +#endif if (refTimeInMs == 0) { time_ = 0; @@ -94,7 +98,12 @@ qint64 LogDataItem::getTimeInMs(const std::string& line) { QString d = QString::fromStdString(line.substr(pos + 1, pos1 - pos - 1)); QDateTime dt = QDateTime::fromString(d, "hh:mm:ss d.M.yyyy"); +#if QT_VERSION >= QT_VERSION_CHECK(6, 9, 0) + dt.setTimeZone(QTimeZone::utc()); +#else dt.setTimeSpec(Qt::UTC); +#endif + return dt.toMSecsSinceEpoch(); } diff --git a/Viewer/libViewer/src/LogData.hpp b/Viewer/libViewer/src/LogData.hpp index f9b0745e3..4e8857d91 100644 --- a/Viewer/libViewer/src/LogData.hpp +++ b/Viewer/libViewer/src/LogData.hpp @@ -15,6 +15,7 @@ #include #include +#include #include #include "LogConsumer.hpp" @@ -48,7 +49,10 @@ class LogData : public LogConsumer { } QDateTime date(int idx) const { -#if QT_VERSION >= QT_VERSION_CHECK(5, 2, 0) +#if QT_VERSION >= QT_VERSION_CHECK(6, 9, 0) + return QDateTime::fromMSecsSinceEpoch(refTimeInMs_ + static_cast(data_[idx].time_) * 1000, + QTimeZone::utc()); +#elif QT_VERSION >= QT_VERSION_CHECK(5, 2, 0) return QDateTime::fromMSecsSinceEpoch(refTimeInMs_ + static_cast(data_[idx].time_) * 1000, Qt::UTC); #else return QDateTime::fromMSecsSinceEpoch(refTimeInMs_ + static_cast(data_[idx].time_) * 1000).toUTC(); diff --git a/Viewer/libViewer/src/LogLoadData.cpp b/Viewer/libViewer/src/LogLoadData.cpp index 4a7c140c4..d6c84e4a3 100644 --- a/Viewer/libViewer/src/LogLoadData.cpp +++ b/Viewer/libViewer/src/LogLoadData.cpp @@ -492,7 +492,9 @@ qint64 LogLoadData::period() const { } QDateTime LogLoadData::startTime() const { -#if QT_VERSION >= QT_VERSION_CHECK(5, 2, 0) +#if QT_VERSION >= QT_VERSION_CHECK(6, 9, 0) + return (time_.empty()) ? QDateTime() : QDateTime::fromMSecsSinceEpoch(time_[0], QTimeZone::utc()); +#elif QT_VERSION >= QT_VERSION_CHECK(5, 2, 0) return (time_.empty()) ? QDateTime() : QDateTime::fromMSecsSinceEpoch(time_[0], Qt::UTC); #else return (time_.empty()) ? QDateTime() : QDateTime::fromMSecsSinceEpoch(time_[0]).toUTC(); @@ -500,7 +502,9 @@ QDateTime LogLoadData::startTime() const { } QDateTime LogLoadData::endTime() const { -#if QT_VERSION >= QT_VERSION_CHECK(5, 2, 0) +#if QT_VERSION >= QT_VERSION_CHECK(6, 9, 0) + return (time_.empty()) ? QDateTime() : QDateTime::fromMSecsSinceEpoch(time_[0], QTimeZone::utc()); +#elif QT_VERSION >= QT_VERSION_CHECK(5, 2, 0) return (time_.empty()) ? QDateTime() : QDateTime::fromMSecsSinceEpoch(time_[time_.size() - 1], Qt::UTC); #else return (time_.empty()) ? QDateTime() : QDateTime::fromMSecsSinceEpoch(time_[time_.size() - 1]).toUTC(); @@ -823,7 +827,11 @@ void LogLoadData::add(std::vector time_stamp, QString s = QString::fromStdString(time_stamp[0]) + " " + QString::fromStdString(time_stamp[1]); QDateTime dt = QDateTime::fromString(s, "HH:mm:ss d.M.yyyy"); +#if QT_VERSION >= QT_VERSION_CHECK(6, 9, 0) + dt.setTimeZone(QTimeZone::utc()); +#else dt.setTimeSpec(Qt::UTC); +#endif time_.push_back(dt.toMSecsSinceEpoch()); size_t index = time_.size() - 1; diff --git a/cmake/CompilerOptions.cmake b/cmake/CompilerOptions.cmake index fa768e5eb..62f424df4 100644 --- a/cmake/CompilerOptions.cmake +++ b/cmake/CompilerOptions.cmake @@ -89,12 +89,14 @@ if (HAVE_WARNINGS) $<$,$>:-Wno-unused-parameter> $<$,$,$,14.0.0>>:-Wno-c++20-attribute-extensions> # silence warning in Qt6 related headers $<$,$,$,21.0.0>>:-Wno-character-conversion> # silence warning in Qt6 related headers + $<$,$,$,22.0.0>>:-Wno-c2y-extensions> # silence warning in Boost related headers (__COUNTER__) ## Clang (MacOS AppleClang) $<$,$>:-Wno-deprecated-copy-with-user-provided-copy> # silence warnings in Qt5 related headers $<$,$>:-Wno-missing-field-initializers> # silence warning in Boost.Python related headers $<$,$>:-Wno-unused-parameter> $<$,$,$,14.0.0>>:-Wno-c++20-attribute-extensions> # silence warning in Qt6 related headers $<$,$,$,21.0.0>>:-Wno-character-conversion> # silence warning in Qt6 related headers + $<$,$,$,22.0.0>>:-Wno-c2y-extensions> # silence warning in Boost related headers (__COUNTER__) ## Clang (Intel Clang-based) $<$,$>:-Wno-deprecated-copy-with-user-provided-copy> # silence warnings in Qt5 related headers $<$,$>:-Wno-missing-field-initializers> # silence warning in Boost.Python related headers diff --git a/libs/base/src/ecflow/base/cts/user/CtsNodeCmd.cpp b/libs/base/src/ecflow/base/cts/user/CtsNodeCmd.cpp index 729d91e72..c4e0dd597 100644 --- a/libs/base/src/ecflow/base/cts/user/CtsNodeCmd.cpp +++ b/libs/base/src/ecflow/base/cts/user/CtsNodeCmd.cpp @@ -110,7 +110,7 @@ bool CtsNodeCmd::equals(ClientToServerCmd* rhs) const { if (api_ != the_rhs->api()) { return false; } - if (absNodePath_ != the_rhs->absNodePath()) { + if (absNodePath_ != the_rhs->pathToNode()) { return false; } return UserCmd::equals(rhs); diff --git a/libs/base/src/ecflow/base/cts/user/EditScriptCmd.cpp b/libs/base/src/ecflow/base/cts/user/EditScriptCmd.cpp index d5efa28d0..df8710607 100644 --- a/libs/base/src/ecflow/base/cts/user/EditScriptCmd.cpp +++ b/libs/base/src/ecflow/base/cts/user/EditScriptCmd.cpp @@ -32,7 +32,7 @@ bool EditScriptCmd::equals(ClientToServerCmd* rhs) const { if (!the_rhs) { return false; } - if (path_to_node_ != the_rhs->path_to_node()) { + if (path_to_node_ != the_rhs->pathToNode()) { return false; } if (edit_type_ != the_rhs->edit_type()) { diff --git a/libs/client/test/TestClientTimeout.cpp b/libs/client/test/TestClientTimeout.cpp index 5da22472d..240c9ebbc 100644 --- a/libs/client/test/TestClientTimeout.cpp +++ b/libs/client/test/TestClientTimeout.cpp @@ -65,7 +65,7 @@ BOOST_AUTO_TEST_CASE(test_client_timeout, *boost::unit_test::disabled()) { /// Now see what timeout value we succeed with bool loaded_defs = false; for (int i = 2; i < 30; ++i) { - theClient.set_connect_timeout(i); + theClient.set_connect_timeout(std::chrono::seconds{i}); try { std::cout << "Trying with timeout of " << i << " seconds\n"; theClient.loadDefs(path); diff --git a/libs/client/test/harness/SCPort.cpp b/libs/client/test/harness/SCPort.cpp index b8f7c4d2d..28795f08c 100644 --- a/libs/client/test/harness/SCPort.cpp +++ b/libs/client/test/harness/SCPort.cpp @@ -96,7 +96,7 @@ std::string SCPort::next_only(bool debug) { if (debug) { std::cout << " SCPort::next_only() seed_port(" << thePort_ << ")\n"; } - return SCPort::find_free_port(thePort_, debug); + return SCPort::find_available_port(std::to_string(thePort_)); } bool SCPort::is_free_port(int port, bool debug) { @@ -111,8 +111,8 @@ bool SCPort::is_free_port(int port, bool debug) { } ClientInvoker client; - client.set_retry_connection_period(1); // avoid long wait - client.set_connection_attempts(1); // avoid long wait + client.set_retry_connection_period(std::chrono::seconds{1}); // avoid long wait + client.set_connection_attempts(1); // avoid long wait const auto the_port = ecf::convert_to(port); try { @@ -167,8 +167,8 @@ std::string SCPort::find_free_port(int seed_port_number, bool debug) { int the_port = seed_port_number; std::string free_port; ClientInvoker client; - client.set_retry_connection_period(1); // avoid long wait - client.set_connection_attempts(1); // avoid long wait + client.set_retry_connection_period(std::chrono::seconds{1}); // avoid long wait + client.set_connection_attempts(1); // avoid long wait while (true) { free_port = ecf::convert_to(the_port); try { diff --git a/libs/pyext/src/ecflow/python/Edit.cpp b/libs/pyext/src/ecflow/python/Edit.cpp index ce95e16a6..8a90e9bb8 100644 --- a/libs/pyext/src/ecflow/python/Edit.cpp +++ b/libs/pyext/src/ecflow/python/Edit.cpp @@ -26,8 +26,8 @@ py::object Edit::init(py::tuple args, py::dict kw) { // cout << "Edit::init args: " << len(args) << " kwargs " << len(kw) << "\n"; // args[0] is Edit(i.e self) for (int i = 1; i < len(args); ++i) { - if (py::extract(args[i]).check()) { - py::dict d = py::extract(args[i]); + if (auto extracted = py::extract(args[i]); extracted.check()) { + py::dict d = extracted(); return args[0].attr("__init__")(d, kw); // calls -> .def(init() -> Edit(dict,dict) } else { diff --git a/libs/pyext/src/ecflow/python/ExportDefs.cpp b/libs/pyext/src/ecflow/python/ExportDefs.cpp index 5c5d71957..058c56785 100644 --- a/libs/pyext/src/ecflow/python/ExportDefs.cpp +++ b/libs/pyext/src/ecflow/python/ExportDefs.cpp @@ -193,28 +193,28 @@ static py::object do_add(defs_ptr self, const py::object& arg) { if (arg.ptr() == py::object().ptr()) { return py::object(self); // *IGNORE* None } - else if (py::extract(arg).check()) { - self->addSuite(py::extract(arg)); + else if (auto extracted = py::extract(arg); extracted.check()) { + self->addSuite(extracted()); } - else if (py::extract(arg).check()) { - add_variable_dict(self, py::extract(arg)); + else if (auto extracted = py::extract(arg); extracted.check()) { + add_variable_dict(self, extracted()); } - else if (py::extract(arg).check()) { - Edit edit = py::extract(arg); + else if (auto extracted = py::extract(arg); extracted.check()) { + Edit edit = extracted(); const std::vector& vec = edit.variables(); for (const auto& i : vec) { self->server_state().add_or_update_user_variables(i.name(), i.theValue()); } } - else if (py::extract(arg).check()) { - py::list the_list = py::extract(arg); + else if (auto extracted = py::extract(arg); extracted.check()) { + py::list the_list = extracted(); int the_list_size = len(the_list); for (int i = 0; i < the_list_size; ++i) { (void)do_add(self, the_list[i]); // recursive } } - else if (py::extract(arg).check()) { - Variable var = py::extract(arg); + else if (auto extracted = py::extract(arg); extracted.check()) { + Variable var = extracted(); self->server_state().add_or_update_user_variables(var.name(), var.theValue()); } else { @@ -269,8 +269,8 @@ py::object defs_raw_constructor(py::tuple args, py::dict kw) { py::list the_list; std::string name; for (int i = 1; i < len(args); ++i) { - if (py::extract(args[i]).check()) { - name = py::extract(args[i]); + if (auto extracted = py::extract(args[i]); extracted.check()) { + name = extracted(); } else { the_list.append(args[i]); diff --git a/libs/pyext/src/ecflow/python/ExportNode.cpp b/libs/pyext/src/ecflow/python/ExportNode.cpp index 2186219f7..f1fd7f010 100644 --- a/libs/pyext/src/ecflow/python/ExportNode.cpp +++ b/libs/pyext/src/ecflow/python/ExportNode.cpp @@ -403,12 +403,12 @@ static py::object do_rshift(node_ptr self, const py::object& arg) { // std::cout << "do_rshift\n"; (void)NodeUtil::do_add(self, arg); - if (py::extract(arg).check()) { + if (auto extracted = py::extract(arg); extracted.check()) { NodeContainer* nc = self->isNodeContainer(); if (!nc) { throw std::runtime_error("ExportNode::do_rshift() : Can only add a child to Suite or Family"); } - node_ptr child = py::extract(arg); + node_ptr child = extracted(); std::vector children; nc->immediateChildren(children); @@ -435,13 +435,13 @@ static py::object do_lshift(node_ptr self, const py::object& arg) { // std::cout << "do_lshift : " << self->name() << "\n"; cout << flush; (void)NodeUtil::do_add(self, arg); - if (py::extract(arg).check()) { + if (auto extracted = py::extract(arg); extracted.check()) { NodeContainer* nc = self->isNodeContainer(); if (!nc) { throw std::runtime_error("ExportNode::do_lshift() : Can only add a child to Suite or Family"); } - node_ptr child = py::extract(arg); + node_ptr child = extracted(); std::vector children; nc->immediateChildren(children); diff --git a/libs/pyext/src/ecflow/python/ExportNodeAttr.cpp b/libs/pyext/src/ecflow/python/ExportNodeAttr.cpp index 4243de339..b28ea7a92 100644 --- a/libs/pyext/src/ecflow/python/ExportNodeAttr.cpp +++ b/libs/pyext/src/ecflow/python/ExportNodeAttr.cpp @@ -58,10 +58,10 @@ static void extract_late_keyword_arguments(std::shared_ptr late, py::list keys = dict.keys(); const int no_of_keys = len(keys); for (int i = 0; i < no_of_keys; ++i) { - if (py::extract(keys[i]).check()) { - std::string first = py::extract(keys[i]); - if (py::extract(dict[keys[i]]).check()) { - std::string second = py::extract(dict[keys[i]]); + if (auto k = py::extract(keys[i]); k.check()) { + std::string first = k(); + if (auto v = py::extract(dict[keys[i]]); v.check()) { + std::string second = v(); int hour = 0; int min = 0; bool relative = ecf::TimeSeries::getTime(second, hour, min); @@ -102,15 +102,15 @@ py::object cron_raw_constructor(py::tuple args, py::dict kw) { // cout << "cron_raw_constructor len(args):" << len(args) << endl; // args[0] is Cron(i.e self) args[1] is string name for (int i = 1; i < len(args); ++i) { - if (py::extract(args[i]).check()) { - std::string time_series = py::extract(args[i]); + if (auto extracted = py::extract(args[i]); extracted.check()) { + std::string time_series = extracted(); if (time_series.empty()) { throw std::runtime_error("cron_raw_constructor: Empty string, please pass a valid time, i.e '12:30'"); } return args[0].attr("__init__")(time_series, kw); // calls -> init(const std::string& ts, dict kw) } - if (py::extract(args[i]).check()) { - ecf::TimeSeries time_series = py::extract(args[i]); + if (auto extracted = py::extract(args[i]); extracted.check()) { + ecf::TimeSeries time_series = extracted(); return args[0].attr("__init__")(time_series, kw); // calls -> init(const ecf::TimeSeries& ts, dict kw) } else { @@ -126,11 +126,11 @@ static void extract_cron_keyword_arguments(std::shared_ptr cron, const int no_of_keys = len(keys); for (int i = 0; i < no_of_keys; ++i) { - if (py::extract(keys[i]).check()) { - std::string first = py::extract(keys[i]); - if (py::extract(dict[keys[i]]).check()) { + if (auto k = py::extract(keys[i]); k.check()) { + std::string first = k(); + if (auto v = py::extract(dict[keys[i]]); v.check()) { - py::list second = py::extract(dict[keys[i]]); + py::list second = v(); std::vector int_vec; pyutil_list_to_int_vec(second, int_vec); @@ -153,7 +153,7 @@ static void extract_cron_keyword_arguments(std::shared_ptr cron, "last_week_days_of_the_month | days_of_month | months | last_day_of_the_month"); } } - else if (py::extract(dict[keys[i]]).check()) { + else if (auto b = py::extract(dict[keys[i]]); b.check()) { if (first == "last_day_of_the_month") { cron->add_last_day_of_month(); } diff --git a/libs/pyext/src/ecflow/python/NodeUtil.cpp b/libs/pyext/src/ecflow/python/NodeUtil.cpp index 123fb8cdd..8fe959091 100644 --- a/libs/pyext/src/ecflow/python/NodeUtil.cpp +++ b/libs/pyext/src/ecflow/python/NodeUtil.cpp @@ -46,8 +46,8 @@ py::object NodeUtil::node_raw_constructor(py::tuple args, py::dict kw) { py::list the_list; std::string name; for (int i = 1; i < len(args); ++i) { - if (py::extract(args[i]).check()) { - name = py::extract(args[i]); + if (auto extracted = py::extract(args[i]); extracted.check()) { + name = extracted(); } else { the_list.append(args[i]); @@ -83,119 +83,118 @@ py::object NodeUtil::do_add(node_ptr self, const py::object& arg) { return py::object(self); // *IGNORE* None } - if (py::extract(arg).check()) { - Edit edit = py::extract(arg); + if (auto extracted = py::extract(arg); extracted.check()) { + Edit edit = extracted(); const std::vector& vec = edit.variables(); for (const auto& i : vec) { self->addVariable(i); } } - else if (py::extract(arg).check()) { + else if (auto extracted = py::extract(arg); extracted.check()) { // std::cout << " do_add node_ptr\n"; NodeContainer* nc = self->isNodeContainer(); if (!nc) { throw std::runtime_error("ExportNode::add() : Can only add a child to Suite or Family"); } - node_ptr child = py::extract(arg); - nc->addChild(child); + nc->addChild(extracted()); } - else if (py::extract(arg).check()) { - self->addEvent(py::extract(arg)); + else if (auto extracted = py::extract(arg); extracted.check()) { + self->addEvent(extracted()); } - else if (py::extract(arg).check()) { - self->addMeter(py::extract(arg)); + else if (auto extracted = py::extract(arg); extracted.check()) { + self->addMeter(extracted()); } - else if (py::extract