ecflow-5.17.0/0000775000175000017500000000000015211533253013247 5ustar alastairalastairecflow-5.17.0/NOTICE0000664000175000017500000000216615211533244014160 0ustar alastairalastair The following third-party software is included in the ecFlow source tree, and is licensed as described below. cereal (C++ header-only serialisation library) https://uscilab.github.io/cereal/ Licensed under the BSD license, included at `3rdparty/cereal/`. cpp-httplib (HTTP/HTTPS C++ header-only server and client library) https://github.com/yhirose/cpp-httplib Licensed under the MIT license, included at `3rdparty/cpp-httplib/`. nlohmann-json (C++ header-only library for JSON parsing and serialisation) https://github.com/nlohmann/json Licensed under the MIT license, included at `3rdparty/json/`. Certificate.hpp (C++ header-only library for handling X.509 certificates) https://gist.github.com/nathan-osman/5041136 Licensed under the MIT license, included at `libs/rest/test/Certificate.hpp`. LazyTextEdit (Qt-based text editor that lazily loads data from disk when necessary) https://github.com/Andersbakken/LazyTextEdit Licensed under the Apache License Version 2.0, included at `Viewer/ecflowUI/src/TextPager/`. spinning_wheel.gif Generated using the website http://www.ajaxload.info/ Freely licensed, as described there. ecflow-5.17.0/bin/0000775000175000017500000000000015211533244014017 5ustar alastairalastairecflow-5.17.0/bin/ecbuild0000775000175000017500000003165315211533244015364 0ustar alastairalastair#!/bin/bash --noprofile # (C) Copyright 2011- 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. set -eua CMAKE_MIN_REQUIRED=3.11 CMAKE_BUILD_VERSION=3.18.3 usage() { echo "Usage: ecbuild [--help] [--version]" exit $1 } help() { cat < ecbuild [option...] [--] [cmake-argument...] DESCRIPTION: ecbuild is a build system based on CMake, but providing a lot of macro's to make it easier to work with. Upon execution, the equivalent cmake command is printed. ecbuild/cmake must be called from an out-of-source build directory and forbids in-source builds. SYNOPSIS: --help Display this help --version Display ecbuild version Available values for "option": --cmakebin= Set which cmake binary to use. Default is 'cmake' --prefix= Set the install path to . Equivalent to cmake argument "-DCMAKE_INSTALL_PREFIX=" --build= Set the build-type to . Equivalent to cmake argument "-DCMAKE_BUILD_TYPE=" can be any of: - debug : Lowest optimization level, useful for debugging - release : Highest optimization level, for best performance - bit : Highest optimization level while staying bit-reproducible - ...others depending on project --log= Set the ecbuild log-level Equivalent to "-DECBUILD_LOG_LEVEL=" can be any of: - DEBUG - INFO - WARN - ERROR - CRITICAL - OFF Every choice outputs also the log-levels listed below itself --static Build static libraries. Equivalent to "-DBUILD_SHARED_LIBS=OFF" --dynamic, --shared Build dynamic libraries (usually the default). Equivalent to "-DBUILD_SHARED_LIBS=ON" --config= Configuration file using CMake syntax that gets included Equivalent to cmake argument "-DECBUILD_CONFIG=" --toolchain= Use a platform specific toolchain, containing settings such as compilation flags, locations of commonly used dependencies. should be the path to a custom toolchain file. Equivalent to cmake argument "-DCMAKE_TOOLCHAIN_FILE=" --cache= (advanced) A file called "ecbuild-cache.cmake" is generated during configuration. This file can be moved to a safe location, and specified for future builds to speed up checking of compiler/platform capabilities. Note that this is only accelerating fresh builds, as cmake internally caches also. Therefore this option is *not* recommended. --get-cmake[=] Automatically download CMake binaries from version $CMAKE_BUILD_VERSION. Requires an internet connection. If no prefix is given, install into $PWD/.local/. --build-cmake[=] Automatically download and build CMake version $CMAKE_BUILD_VERSION. Requires an internet connection and may take a while. If no prefix is given, install into $PWD/.local/. --dryrun Don't actually execute the cmake call, just print what would have been executed. Available values for "cmake-argument": Any value that can be usually passed to cmake to (re)configure the build. Typically these values start with "-D". example: -DENABLE_TESTS=ON -DENABLE_MPI=OFF -DECKIT_PATH=... They can be explicitly separated from [option...] with a "--", for the case there is a conflicting option with the "cmake" executable, and the latter's option is requested. ------------------------------------------------------------------------ NOTE: When reconfiguring a build, it is only necessary to change the relevant options, as everything stays cached. For example: > ecbuild --prefix=PREFIX . > ecbuild -DENABLE_TESTS=ON . ------------------------------------------------------------------------ Compiling: To compile the project with threads: > make -j To get verbose compilation/linking output: > make VERBOSE=1 Testing: To run the project's tests > ctest Also check the ctest manual/help for more options on running tests Installing: To install the project in location PREFIX with "--prefix=PREFIX" or "-DCMAKE_INSTALL_PREFIX=PREFIX" > make install ------------------------------------------------------------------------ ECMWF" EOF exit $1 } INSTALL_DIR="$( cd $( dirname "${BASH_SOURCE[0]}" ) && pwd -P )" export ecbuild_ROOT="$( cd "$INSTALL_DIR/.." && pwd -P )" export ecbuild_DIR=$ecbuild_ROOT # for versions of CMake < 3.12 ECBUILD_MODULE_PATH="" # If there is a directory share/ecbuild/cmake relative to the parent directory # (as in an install tree), add it to CMAKE_MODULE_PATH if [ -d $INSTALL_DIR/../share/ecbuild/cmake ]; then ECBUILD_MODULE_PATH="$( cd "$INSTALL_DIR/../share/ecbuild/cmake" && pwd -P )" # If there is a cmake subdirectory relative to the script directory (as in a # tarball), add it to CMAKE_MODULE_PATH elif [ -d $INSTALL_DIR/../cmake ]; then ECBUILD_MODULE_PATH="$( cd "$INSTALL_DIR/../cmake" && pwd -P )" fi # Fail if we couldn't find ecBuild modules if [ ! -f "$ECBUILD_MODULE_PATH/ecbuild_system.cmake" ]; then echo "FATAL: ecBuild modules could not be found in either $INSTALL_DIR/../share/ecbuild/cmake or $INSTALL_DIR/../cmake" >&2 exit 1 fi ADD_ECBUILD_OPTIONS="-DCMAKE_MODULE_PATH=$ECBUILD_MODULE_PATH" version() { ecbuild_version=$(cat ${ECBUILD_MODULE_PATH}/VERSION) echo "ecbuild version ${ecbuild_version}" command -v cmake >/dev/null 2>&1 || { exit 0; } cmake --version | head -1 exit 0 } log() { log_level=$(tr "[a-z]" "[A-Z]" <<< "$1") ADD_ECBUILD_OPTIONS="$ADD_ECBUILD_OPTIONS -DECBUILD_LOG_LEVEL=${log_level}" } prefix() { ADD_ECBUILD_OPTIONS="$ADD_ECBUILD_OPTIONS -DCMAKE_INSTALL_PREFIX=${1/#\~\//$HOME/}" } config() { arg=${1/#\~\//$HOME/} if [ -f $arg ]; then config_file=$arg config_file="$( cd $( dirname "${config_file}" ) && pwd -P )/$( basename ${config_file} )" else echo "Error:" echo " Config file [$arg] is not found or is not a file." exit 1 fi ADD_ECBUILD_OPTIONS="$ADD_ECBUILD_OPTIONS -DECBUILD_CONFIG=${config_file}" } toolchain() { arg=${1/#\~\//$HOME/} if [ -f $arg ]; then toolchain_file=$arg fi if [ -z ${toolchain_file+x} ]; then echo "Error toolchain $arg is not valid" exit 1 else ADD_ECBUILD_OPTIONS="$ADD_ECBUILD_OPTIONS -DCMAKE_TOOLCHAIN_FILE=${toolchain_file}" fi } cache() { arg=$1 if [ -f $arg ]; then cache_file=$arg cache_file="$( cd $( dirname "${cache_file}" ) && pwd -P )/$( basename ${cache_file} )" else echo "Error:" echo " Cache file [$arg] is not found or is not a file." exit 1 fi ADD_ECBUILD_OPTIONS="$ADD_ECBUILD_OPTIONS -DECBUILD_CACHE=${cache_file}" } if test $# -eq 0; then usage 1 fi while test $# -gt 0; do # Split --option=value in $opt="--option" and $val="value" opt="" val="" case "$1" in --*=*) opt=`echo "$1" | sed 's/=.*//'` val=`echo "$1" | sed 's/--[_a-zA-Z0-9-]*=//'` ;; --*) opt=$1 ;; # -D*) # ADD_ECBUILD_OPTIONS="$ADD_ECBUILD_OPTIONS $1" # ;; *) break ;; esac # echo "debug opt: $opt $val" # Parse options case "$opt" in --help) help 0 ;; --version) version ;; --dryrun) dryrun="yes" ;; --cmakebin) cmakebin="$val" ;; --prefix) prefix "$val" ;; --build) ADD_ECBUILD_OPTIONS="$ADD_ECBUILD_OPTIONS -DCMAKE_BUILD_TYPE=$val" ;; --log) log $val ;; --static) ADD_ECBUILD_OPTIONS="$ADD_ECBUILD_OPTIONS -DBUILD_SHARED_LIBS=OFF" ;; --dynamic) ADD_ECBUILD_OPTIONS="$ADD_ECBUILD_OPTIONS -DBUILD_SHARED_LIBS=ON" ;; --shared) ADD_ECBUILD_OPTIONS="$ADD_ECBUILD_OPTIONS -DBUILD_SHARED_LIBS=ON" ;; --toolchain) toolchain $val ;; --config) config $val ;; --cache) cache $val ;; --get-cmake) get_cmake="bin" if [[ -n $val ]]; then cmake_prefix="$val" fi ;; --build-cmake) get_cmake="src" if [[ -n $val ]]; then cmake_prefix="$val" fi ;; --) shift break ;; *) echo "unknown option: $opt" usage 1 ;; esac shift done # If no arguments remain, set srcARG to "." if [ $# -eq 0 ]; then srcARG="." fi if [ -z ${toolchain_file+x} ]; then if [ -z ${ECBUILD_TOOLCHAIN+x} ]; then : else toolchain ${ECBUILD_TOOLCHAIN} echo "ecbuild toolchain set using environment variable ECBUILD_TOOLCHAIN" fi fi src=${srcARG:=""} cmake=${cmakebin:=cmake} dryrun=${dryrun:=no} get_cmake=${get_cmake:=""} cmake_prefix=${cmake_prefix:=$PWD/.local} cmake_found="" cmake_version_sufficient="" # Check that version $1 satisfies $2 # CMake versions have no more than 4 fields # Version sort (sort -V) is not available on all platforms version_gte() { [ "$2" = "$(echo -e "$1\n$2" | sort -t '.' -k 1,1 -k 2,2 -k 3,3 -k 4,4 -g | head -n1)" ] } # Download a CMake tarball # $1: version # $2: suffix (optional) download_cmake() { tarball=cmake-$1${2:-""}.tar.gz if [[ ! -r $tarball ]]; then shortver=$(echo $1 | cut -d. -f1-2) url=http://www.cmake.org/files/v$shortver/$tarball # -N Download only if the remote version of the file is newer # --continue Continue an interrupted download # -T 60 Time out a download attempt after 60 seconds # -t 3 Only make 3 download attempts wget -N --continue -T 60 -t 3 $url || { echo "Failed to download CMake release $1." >&2 echo "Please download from $url" >&2 echo "and place $tarball in $PWD" >&2 exit 1 } fi echo $tarball } # Use already built CMake if any if [[ -x "${cmake_prefix}/bin/cmake" ]]; then echo "Using already built CMake in ${cmake_prefix}/bin/cmake" >&2 cmake="${cmake_prefix}/bin/cmake" # Get a CMake binary if requested and no sufficient version found elif [[ $get_cmake = "bin" ]]; then plat=$(uname -s) arch=$(uname -m) if [[ "${plat}" != "Linux" ]] && [[ "${plat}" != "Darwin" ]] ; then echo "Cannot download CMake binaries for this platform." >&2 echo "Please use --build-cmake to build from source." >&2 exit 1 fi if [[ "${arch}" != "x86_64" ]] ; then echo "Cannot download CMake binaries for this architecture." >&2 echo "Please use --build-cmake to build from source." >&2 exit 1 fi echo "Downloading CMake version ${CMAKE_BUILD_VERSION} binaries and installing into ${cmake_prefix} ..." >&2 tarball=$(download_cmake "${CMAKE_BUILD_VERSION}" "-${plat}-${arch}") mkdir -p "${cmake_prefix}" tar xzf $tarball -C "${cmake_prefix}" --strip-components=1 cmake="${cmake_prefix}/bin/cmake" # Build CMake from source if requested and no sufficient version found elif [[ $get_cmake = "src" ]]; then echo "Building CMake version ${CMAKE_BUILD_VERSION} and installing into ${cmake_prefix} ..." >&2 tarball=$(download_cmake "${CMAKE_BUILD_VERSION}") tar xzf $tarball ( mkdir -p build_cmake cd build_cmake ../cmake-${CMAKE_BUILD_VERSION}/bootstrap --prefix="${cmake_prefix}" && make && make install ) cmake="${cmake_prefix}/bin/cmake" fi # Check if the cmake version is sufficient if $(command -v $cmake >/dev/null 2>&1); then cmake_found="yes" cmake_version=$($cmake --version | head -n1 | awk '{ print $3 }') echo "Found CMake version $cmake_version" >& 2 if version_gte $cmake_version $CMAKE_MIN_REQUIRED; then cmake_version_sufficient="yes" fi fi # Fail if we don't have a sufficient CMake if [[ ! $cmake_version_sufficient ]]; then if [[ ! $cmake_found ]]; then echo "CMake is required and cannot be found in the PATH." >&2 else echo "CMake version $CMAKE_MIN_REQUIRED is required but only $cmake_version was found." >&2 fi echo "" >&2 echo " Try 'module load cmake', specify a CMake binary with --cmakebin=/path/to/cmake" >&2 echo " or let ecbuild download and build CMake with the --build-cmake option." >&2 exit 1 fi echo "" echo "$cmake ${ADD_ECBUILD_OPTIONS} $@ $src" echo "" if [ ${dryrun} == "yes" ]; then echo "[DRYRUN] -- not executing" exit 0 fi $cmake ${ADD_ECBUILD_OPTIONS} "$@" $src ecflow-5.17.0/bin/CMakeLists.txt0000664000175000017500000000007315211533244016557 0ustar alastairalastairinstall( PROGRAMS ecbuild DESTINATION ${INSTALL_BIN_DIR} ) ecflow-5.17.0/libs/0000775000175000017500000000000015211533244014200 5ustar alastairalastairecflow-5.17.0/libs/attribute/0000775000175000017500000000000015211533244016203 5ustar alastairalastairecflow-5.17.0/libs/attribute/src/0000775000175000017500000000000015211533244016772 5ustar alastairalastairecflow-5.17.0/libs/attribute/src/ecflow/0000775000175000017500000000000015211533244020251 5ustar alastairalastairecflow-5.17.0/libs/attribute/src/ecflow/attribute/0000775000175000017500000000000015211533244022254 5ustar alastairalastairecflow-5.17.0/libs/attribute/src/ecflow/attribute/DayAttr.hpp0000664000175000017500000001257415211533244024346 0ustar alastairalastair/* * 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. */ #ifndef ecflow_attribute_DayAttr_HPP #define ecflow_attribute_DayAttr_HPP #include #include "ecflow/core/Chrono.hpp" namespace cereal { class access; } namespace ecf { class Calendar; } // namespace ecf // Use default copy constructor, assignment operator, destructor class DayAttr { public: enum Day_t { SUNDAY = 0, MONDAY = 1, TUESDAY = 2, WEDNESDAY = 3, THURSDAY = 4, FRIDAY = 5, SATURDAY = 6 }; DayAttr() = default; explicit DayAttr(Day_t day) : day_(day) {} explicit DayAttr(const std::string& str) : day_(DayAttr::getDay(str)) {} explicit DayAttr(const boost::gregorian::date& date) : day_(static_cast(date.day_of_week().as_number())), date_(date) {} bool operator==(const DayAttr& rhs) const; bool operator<(const DayAttr& rhs) const { return day_ < rhs.day_; } bool structureEquals(const DayAttr& rhs) const; /// called when definition is restored from a file/checkpoint. handle reset of date_ void handle_migration(const ecf::Calendar&); // called at begin time. void reset(); void reset(const ecf::Calendar& c); // called when we need a requeue based on a time attribute. Should *NOT* clear expired flag. void requeue_time(); // called when re-queueing because of: // - automatic re-queue due to repeat increment // - manual re-queue // Clears expired flag, and sets day attribute to a next matching *FUTURE* day or current day void requeue_manual(const ecf::Calendar& c); // can match today if today is match day void requeue_repeat_increment(const ecf::Calendar& c); // match *FUTURE* day // Called after a node has completed, if calendar day corresponds to *THIS* day. *Expire* it // This should be called just before: checkForRequeue. // This day attribute should be treated as being deleted. returns false from // - isFree() stops re-queue on expired day // - calendarChanged() stops clearing of free. // Expired flag is RESET only by: // - void requeue_manual(const ecf::Calendar& c); // - void requeue_repeat_increment(const ecf::Calendar& c); void check_for_expiration(const ecf::Calendar&); // We must use a real date, using enum is not sufficient as in ecflow4 // 0,1,2,3,4,5,6 // ^ ^ // sunday saturday // old/buggy: check_for_requeue() { return (day_ > calendar.day_of_week() );} // The old check for re-queue would determine if this day is in the future, with reference to calendar day. // Hence day_(Saturday) is would always re-queue, day_(Sunday) is would never re-queue // When multiple days were involved, it would get even buggier. // By using a real date, we fix the issue, the real date is updated during manual re-queue, or automatically vi // repeat increment. bool checkForRequeue(const ecf::Calendar&) const; void setFree(); // ensures that isFree() always returns true bool isSetFree() const { return free_; } void calendarChanged(const ecf::Calendar& c, bool clear_at_midnight = true); // can set attribute free bool isFree(const ecf::Calendar&) const; bool validForHybrid(const ecf::Calendar&) const; bool why(const ecf::Calendar&, std::string& theReasonWhy) const; // The state_change_no is never reset. Must be incremented if it can affect equality unsigned int state_change_no() const { return state_change_no_; } std::string name() const; // for display/gui only std::string toString() const; std::string dump() const; std::string as_simple_string() const; // return the days, if input is not valid will throw a runtime_error static DayAttr create(const std::string& dayStr); static DayAttr create(const std::vector& lineTokens, bool read_state); static DayAttr::Day_t getDay(const std::string&); static std::vector allDays(); // access DayAttr::Day_t day() const { return day_; } const boost::gregorian::date& date() const { return date_; } boost::gregorian::date next_matching_date(const ecf::Calendar& c) const; void set_expired(); // ********* TREAT this Day Attribute as deleted ********** bool expired() const { return expired_; } void read_state(const std::vector& lineTokens); private: void clearFree(); // resets the free flag void clear_expired(); bool is_free(const ecf::Calendar&) const; // ignores free_ boost::gregorian::date matching_date(const ecf::Calendar& c) const; public: void write(std::string&) const; private: DayAttr::Day_t day_{DayAttr::SUNDAY}; unsigned int state_change_no_{0}; // *not* persisted, only used on server side bool free_{false}; // persisted for use by why() on client side bool expired_{false}; // added for ecflow 5.4.0 boost::gregorian::date date_; // corresponding to day_ friend class cereal::access; template void serialize(Archive& ar); }; #endif /* ecflow_attribute_DayAttr_HPP */ ecflow-5.17.0/libs/attribute/src/ecflow/attribute/LateAttr.cpp0000664000175000017500000002117115211533244024502 0ustar alastairalastair/* * 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 "ecflow/attribute/LateAttr.hpp" #include #include "ecflow/core/Calendar.hpp" #include "ecflow/core/Ecf.hpp" #include "ecflow/core/NState.hpp" #include "ecflow/core/Serialization.hpp" #include "ecflow/core/Str.hpp" #include "ecflow/core/TimeSeries.hpp" namespace ecf { LateAttr::LateAttr() = default; std::string LateAttr::toString() const { std::string ret; write(ret); return ret; } void LateAttr::write(std::string& ret) const { ret += "late"; if (!s_.isNULL()) { ret += " -s +"; s_.write(ret); } if (!a_.isNULL()) { ret += " -a "; a_.write(ret); } if (!c_.isNULL()) { ret += " -c "; if (c_is_rel_) { ret += "+"; } c_.write(ret); } } bool LateAttr::operator==(const LateAttr& rhs) const { if (c_is_rel_ != rhs.c_is_rel_) { return false; } if (s_ != rhs.s_) { return false; } if (a_ != rhs.a_) { return false; } if (c_ != rhs.c_) { return false; } if (isLate_ != rhs.isLate_) { return false; } return true; } bool LateAttr::isNull() const { return (s_.isNULL() && a_.isNULL() && c_.isNULL()); } void LateAttr::checkForLateness(const std::pair& state, const ecf::Calendar& calendar) { if (isLate_ || isNull()) { return; } if (check_for_lateness(state, calendar)) { setLate(true); } } bool LateAttr::check_for_lateness(const std::pair& state, const ecf::Calendar& calendar) const { if (isNull()) { return false; } if (state.first == NState::SUBMITTED || state.first == NState::QUEUED) { // Submitted is always relative, ASSUME this means relative to suite start if (state.first == NState::SUBMITTED && !s_.isNULL()) { // ECFLOW-322 // The late attr check for being late in submitted state, is always *RELATIVE* // Previously we had: // // if ( calendar.duration() >= s_.duration() ) { // setLate(true); // return; // } // This is incorrect since calendar.duration() is essentially duration until // the last call to Calendar::init() i.e suite duration time. Hence, late was // set straight away. // // To check for submitted, we need the duration *after* state went into submitted state. // `state.second` is when state went SUBMITTED, relative to suite start auto time_in_submitted_state = calendar.duration() - state.second; if (time_in_submitted_state >= s_.duration()) { return true; } } // In Submitted or queued state, check for active, in REAL time if (!a_.isNULL() && calendar.suiteTime().time_of_day() >= a_.duration()) { return true; } } else if (state.first == NState::ACTIVE && !c_.isNULL()) { if (c_is_rel_) { // To check for complete, we need the duration when state went into active state. // `state.second` is when state went ACTIVE, relative to suite start auto runtime = calendar.duration() - state.second; if (runtime >= c_.duration()) { return true; } } else { // Real time if (calendar.suiteTime().time_of_day() >= c_.duration()) { return true; } } } return false; } void LateAttr::setLate(bool f) { if (f != isLate_) { // minimise changes to state_change_no_ isLate_ = f; state_change_no_ = Ecf::incr_state_change_no(); } #ifdef DEBUG_STATE_CHANGE_NO std::cout << "LateAttr::setLate changed=" << (f != isLate_) << "\n"; #endif } void LateAttr::override_with(LateAttr* in_late) { if (in_late) { if (!in_late->submitted().isNULL()) { s_ = in_late->submitted(); } if (!in_late->active().isNULL()) { a_ = in_late->active(); } if (!in_late->complete().isNULL()) { c_ = in_late->complete(); } c_is_rel_ = in_late->complete_is_relative(); // DO NOT override isLate_, because if the parent is late, we do not want *ALL* children to be set late // isLate_ = in_late->isLate(); } } void LateAttr::parse(LateAttr& lateAttr, const std::string& line, const std::vector& lineTokens, size_t index) { // late -s +00:15 -a 20:00 -c +02:00 #The option can be in any order // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 // late -s +00:15 -c +02:00 # not all options are needed // 0 1 2 3 4 5 assert(lateAttr.isNull()); size_t line_token_size = lineTokens.size(); for (size_t i = index; i < line_token_size; i += 1) { if (lineTokens[i][0] == '#') { break; } if (lineTokens[i] == "-s") { if (!lateAttr.submitted().isNULL()) { throw std::runtime_error("LateParser::doParse: Invalid late, submitted specified twice :" + line); } if (i + 1 < line_token_size) { int hour = -1; int min = -1; TimeSeries::getTime(lineTokens[i + 1], hour, min); lateAttr.addSubmitted(TimeSlot(hour, min)); i++; } else { throw std::runtime_error("LateParser::doParse: Invalid late, submitted time not specified :" + line); } } else if (lineTokens[i] == "-a") { if (!lateAttr.active().isNULL()) { throw std::runtime_error("LateParser::doParse: Invalid late, active specified twice :" + line); } if (i + 1 < line_token_size) { int hour = -1; int min = -1; TimeSeries::getTime(lineTokens[i + 1], hour, min); lateAttr.addActive(TimeSlot(hour, min)); i++; } else { throw std::runtime_error("LateParser::doParse: Invalid late, active time not specified :" + line); } } else if (lineTokens[i] == "-c") { if (!lateAttr.complete().isNULL()) { throw std::runtime_error("LateParser::doParse: Invalid late, complete specified twice :" + line); } if (i + 1 < line_token_size) { int hour = -1; int min = -1; bool relative = TimeSeries::getTime(lineTokens[i + 1], hour, min); lateAttr.addComplete(TimeSlot(hour, min), relative); i++; } else { throw std::runtime_error("LateParser::doParse: Invalid late, active time not specified :" + line); } } else { throw std::runtime_error("LateParser::doParse:5: Invalid late :" + line); } } if (lateAttr.isNull()) { throw std::runtime_error("LateParser::doParse:6: Invalid late :" + line); } } LateAttr LateAttr::create(const std::string& lateString) { std::vector lineTokens; ecf::algorithm::split_at(lineTokens, lateString); if (lineTokens.empty()) { throw std::runtime_error("LateParser::create: empty string no late specified ?" + lateString); } // adjust the index size_t index = 0; if (lineTokens[0] == "late") { index = 1; } LateAttr lateAttr; parse(lateAttr, lateString, lineTokens, index); return lateAttr; } template void LateAttr::serialize(Archive& ar) { CEREAL_OPTIONAL_NVP(ar, s_, [this]() { return !s_.isNULL(); }); CEREAL_OPTIONAL_NVP(ar, a_, [this]() { return !a_.isNULL(); }); CEREAL_OPTIONAL_NVP(ar, c_, [this]() { return !c_.isNULL(); }); CEREAL_OPTIONAL_NVP(ar, c_is_rel_, [this]() { return c_is_rel_; }); CEREAL_OPTIONAL_NVP(ar, isLate_, [this]() { return isLate_; }); } CEREAL_TEMPLATE_SPECIALIZE(LateAttr); } // namespace ecf ecflow-5.17.0/libs/attribute/src/ecflow/attribute/QueueAttr.hpp0000664000175000017500000000575315211533244024716 0ustar alastairalastair/* * 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. */ #ifndef ecflow_attribute_QueueAttr_HPP #define ecflow_attribute_QueueAttr_HPP #include #include "ecflow/core/NState.hpp" ///////////////////////////////////////////////////////////////////////// class QueueAttr { public: QueueAttr(const std::string& name, const std::vector& theQueue); QueueAttr() = default; ~QueueAttr(); bool operator==(const QueueAttr& rhs) const; bool operator<(const QueueAttr& rhs) const { return name_ < rhs.name(); } /// Accessor const std::string& name() const { return name_; } std::string value() const; int index_or_value() const; int index() const { return currentIndex_; } bool empty() const { return name_.empty(); } const std::vector& list() const { return theQueue_; } const std::vector& state_vec() const { return state_vec_; } NState::State state(const std::string& step) const; std::string toString() const; std::string dump() const; // Mutators void requeue(); std::string active(); // return current index and value, make active, increment index void complete(const std::string& step); // step is more index:value void aborted(const std::string& step); std::string no_of_aborted() const; void reset_index_to_first_queued_or_aborted(); void set_used_in_trigger(bool f) { used_in_trigger_ = f; } // used by simulator only bool used_in_trigger() const { return used_in_trigger_; } static void parse(QueueAttr&, const std::string& line, std::vector& lineTokens, bool parse_state); void set_name(const std::string& name); void set_queue(const std::vector& theQueue, int index, const std::vector& state_vec); void set_state_vec(const std::vector& state_vec); void set_index(int index) { currentIndex_ = index; }; // Added to support return by reference static const QueueAttr& EMPTY(); static QueueAttr& EMPTY1(); unsigned int state_change_no() const { return state_change_no_; } private: void incr_state_change_no(); public: void write(std::string&) const; private: std::vector theQueue_; std::vector state_vec_; std::string name_; int currentIndex_{0}; unsigned int state_change_no_{0}; // *not* persisted, only used on server side bool used_in_trigger_{false}; // *not* persisted, used by simulator only private: friend class cereal::access; template void serialize(Archive& ar, std::uint32_t const version); }; #endif /* ecflow_attribute_QueueAttr_HPP */ ecflow-5.17.0/libs/attribute/src/ecflow/attribute/ZombieAttr.cpp0000664000175000017500000002627315211533244025052 0ustar alastairalastair/* * 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 "ecflow/attribute/ZombieAttr.hpp" #include #include #include #include "ecflow/core/Converter.hpp" #include "ecflow/core/Serialization.hpp" #include "ecflow/core/Str.hpp" #include "ecflow/core/ZombieCtrlAction.hpp" using namespace ecf; const ZombieAttr& ZombieAttr::EMPTY() { static const ZombieAttr ZOMBIEATTR = ZombieAttr(); return ZOMBIEATTR; } // Constructor ============================================================================== ZombieAttr::ZombieAttr(ecf::Child::ZombieType t, const std::vector& c, ZombieCtrlAction a, int zombie_lifetime) : child_cmds_(c), zombie_type_(t), action_(a), zombie_lifetime_(zombie_lifetime) { /// Server typically checks every 60 seconds, hence this is lowest valid value for if (zombie_lifetime_ <= 0) { // default constructor. Set defaults switch (zombie_type_) { case Child::USER: zombie_lifetime_ = default_user_zombie_life_time(); break; case Child::PATH: zombie_lifetime_ = default_path_zombie_life_time(); break; case Child::ECF: zombie_lifetime_ = default_ecf_zombie_life_time(); break; case Child::ECF_PID: zombie_lifetime_ = default_ecf_zombie_life_time(); break; case Child::ECF_PID_PASSWD: zombie_lifetime_ = default_ecf_zombie_life_time(); break; case Child::ECF_PASSWD: zombie_lifetime_ = default_ecf_zombie_life_time(); break; case Child::NOT_SET: assert(false); break; } } else if (zombie_lifetime_ < minimum_zombie_life_time()) { zombie_lifetime_ = minimum_zombie_life_time(); } } bool ZombieAttr::operator==(const ZombieAttr& rhs) const { if (child_cmds_ != rhs.child_cmds_) { return false; } if (zombie_type_ != rhs.zombie_type_) { return false; } if (action_ != rhs.action_) { return false; } if (zombie_lifetime_ != rhs.zombie_lifetime_) { return false; } return true; } std::string ZombieAttr::toString() const { /// format is zombie_type : child_cmds(optional) : action : zombie_lifetime_(optional) std::string ret; write(ret); return ret; } 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 += ecf::string_constants::colon; ret += ecf::to_string(action_); ret += ecf::string_constants::colon; ret += Child::to_string(child_cmds_); ret += ecf::string_constants::colon; ret += ecf::convert_to(zombie_lifetime_); } bool ZombieAttr::fob(ecf::Child::CmdType child_cmd) const { if (action_ != ZombieCtrlAction::FOB) { return false; } if (child_cmds_.empty()) { return true; } // If we have child commands specified, then the action is only applicable for that child cmd // for all other child cmds we block for (auto i : child_cmds_) { if (i == child_cmd) { return true; } } return false; } bool ZombieAttr::fail(ecf::Child::CmdType child_cmd) const { if (action_ != ZombieCtrlAction::FAIL) { return false; } if (child_cmds_.empty()) { return true; } // If we have child commands specified, then the action is only applicable for that child cmd // for all other child cmds we block for (auto i : child_cmds_) { if (i == child_cmd) { return true; } } return false; } bool ZombieAttr::adopt(ecf::Child::CmdType child_cmd) const { if (action_ != ZombieCtrlAction::ADOPT) { return false; } if (child_cmds_.empty()) { return true; } // If we have child commands specified, then the action is only applicable for that child cmd // for all other child cmds we block for (auto i : child_cmds_) { if (i == child_cmd) { return true; } } return false; } bool ZombieAttr::remove(ecf::Child::CmdType child_cmd) const { if (action_ != ZombieCtrlAction::REMOVE) { return false; } if (child_cmds_.empty()) { return true; } // If we have child commands specified, then the action is only applicable for that child cmd // for all other child cmds we block for (auto i : child_cmds_) { if (i == child_cmd) { return true; } } return false; } bool ZombieAttr::block(ecf::Child::CmdType child_cmd) const { if (action_ != ZombieCtrlAction::BLOCK) { return false; } if (child_cmds_.empty()) { return true; } // If we have child commands specified, then the action is only applicable for that child cmd // for all other child cmds we block for (auto i : child_cmds_) { if (i == child_cmd) { return true; } } return false; } bool ZombieAttr::kill(ecf::Child::CmdType child_cmd) const { if (action_ != ZombieCtrlAction::KILL) { return false; } if (child_cmds_.empty()) { return true; } // If we have child commands specified, then the action is only applicable for that child cmd // for all other child cmds we block for (auto i : child_cmds_) { if (i == child_cmd) { return true; } } return false; } ZombieAttr ZombieAttr::create(const std::string& string_to_parse) { /// Use boost tokenizer instead of Str::split, as it allows preservation of empty tokens boost::char_separator sep(":", "", boost::keep_empty_tokens); using tokenizer = boost::tokenizer>; tokenizer tokenise(string_to_parse, sep); std::vector tokens; std::copy(tokenise.begin(), tokenise.end(), back_inserter(tokens)); if (tokens.size() < 2) { throw std::runtime_error("ZombieAttr::create failed: Invalid zombie type " + string_to_parse); } /// expects ::child_cmds:zombie_lifetime std::string str_zombie_type; std::string action_str; std::string child_cmds; std::string lifetime; size_t tokens_size = tokens.size(); for (size_t i = 0; i < tokens_size; i++) { // cout << " token " << i << ": '" << tokens[i] << "'\n"; if (i == 0) { str_zombie_type = tokens[i]; continue; } if (i == 1) { action_str = tokens[i]; continue; } if (i == 2) { child_cmds = tokens[i]; continue; } if (i == 3) { lifetime = tokens[i]; continue; } throw std::runtime_error("ZombieAttr::create failed: Invalid zombie tokens " + string_to_parse); } // std::cout << " zombie_type = " << str_zombie_type << " user_action = " << action_str << " child_cmds = " // << child_cmds<< " zombie_lifetime = " << lifetime << "\n"; if (!Child::valid_zombie_type(str_zombie_type)) { throw std::runtime_error("ZombieAttr::create failed: Invalid zombie type, expected one of [ user | ecf | " "ecf_pid | ecf_pid_passed | ecf_passwd | path ] but found " + str_zombie_type + std::string(":") + string_to_parse); } if (!action_str.empty() && !ecf::Enumerate::is_valid(action_str)) { throw std::runtime_error("ZombieAttr::create failed: Invalid user action, expected one of [ fob | fail | " "remove | block | adopt | kill ] but found " + action_str + std::string(":") + string_to_parse); } if (!child_cmds.empty() && !Child::valid_child_cmds(child_cmds)) { throw std::runtime_error("ZombieAttr::create failed: Invalid child type, expected one or more of [ " "init,event,meter,label,wait,queue,abort,complete] but found " + tokens[2] + std::string(":") + string_to_parse); } int zombie_lifetime = -1; if (!lifetime.empty()) { try { zombie_lifetime = ecf::convert_to(lifetime); } catch (const ecf::bad_conversion&) { throw std::runtime_error("ZombieAttr::create failed: Zombie life time must be convertible to an integer " + lifetime + std::string(":") + string_to_parse); } } if (action_str.empty() && zombie_lifetime == -1) { throw std::runtime_error( "ZombieAttr::create failed: User Action(fob,fail,remove,adopt,block) or lifetime must be specified: " + string_to_parse); } auto zombie_type = Child::zombie_type(str_zombie_type); auto action = Enumerate::to_enum(action_str).value_or(ZombieCtrlAction::BLOCK); auto childVec = Child::child_cmds(child_cmds); /// If zombie_lifetime is still -1 constructor will reset to standard defaults return ZombieAttr(zombie_type, childVec, action, zombie_lifetime); } ZombieAttr ZombieAttr::get_default_attr(ecf::Child::ZombieType zt) { switch (zt) { case Child::USER: return ZombieAttr( zt, std::vector(), ZombieCtrlAction::BLOCK, default_user_zombie_life_time()); case Child::PATH: return ZombieAttr( zt, std::vector(), ZombieCtrlAction::BLOCK, default_path_zombie_life_time()); case Child::ECF: return ZombieAttr( zt, std::vector(), ZombieCtrlAction::BLOCK, default_ecf_zombie_life_time()); case Child::ECF_PID: return ZombieAttr( zt, std::vector(), ZombieCtrlAction::BLOCK, default_ecf_zombie_life_time()); case Child::ECF_PID_PASSWD: return ZombieAttr( zt, std::vector(), ZombieCtrlAction::BLOCK, default_ecf_zombie_life_time()); case Child::ECF_PASSWD: return ZombieAttr( zt, std::vector(), ZombieCtrlAction::BLOCK, default_ecf_zombie_life_time()); case Child::NOT_SET: break; } return ZombieAttr( Child::ECF, std::vector(), ZombieCtrlAction::BLOCK, default_ecf_zombie_life_time()); } template void ZombieAttr::serialize(Archive& ar, std::uint32_t const version) { ar(CEREAL_NVP(child_cmds_), CEREAL_NVP(zombie_type_), CEREAL_NVP(action_), CEREAL_NVP(zombie_lifetime_)); } CEREAL_TEMPLATE_SPECIALIZE_V(ZombieAttr); ecflow-5.17.0/libs/attribute/src/ecflow/attribute/DateAttr.cpp0000664000175000017500000002604515211533244024477 0ustar alastairalastair/* * 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 "ecflow/attribute/DateAttr.hpp" #include #include #include "ecflow/core/Calendar.hpp" #include "ecflow/core/Chrono.hpp" #include "ecflow/core/Converter.hpp" #include "ecflow/core/Ecf.hpp" #include "ecflow/core/Extract.hpp" #include "ecflow/core/Message.hpp" #include "ecflow/core/Serialization.hpp" using namespace ecf; //========================================================================================== DateAttr::DateAttr(int day, int month, int year) : day_(day), month_(month), year_(year) { checkDate(day_, month_, year_, true /* allow wild cards */); } DateAttr::DateAttr(const std::string& str) { DateAttr::getDate(str, day_, month_, year_); checkDate(day_, month_, year_, true /* allow wild cards */); } bool DateAttr::operator<(const DateAttr& rhs) const { if (year_ < rhs.year_) { return true; } if (year_ == rhs.year_) { if (month_ < rhs.month_) { return true; } if (month_ == rhs.month_) { return day_ < rhs.day_; } } return false; } void DateAttr::checkDate(int day, int month, int year, bool allow_wild_cards) { if (allow_wild_cards) { if (day != 0 && (day < 1 || day > 31)) { throw std::out_of_range( "Invalid Date(day,month,year) : the day >= 0 and day < 31, where 0 means wild card "); } if (month != 0 && (month < 1 || month > 12)) { throw std::out_of_range( "Invalid Date(day,month,year): the month >=0 and month <= 12, where 0 means wild card"); } if (year < 0) { throw std::out_of_range("Invalid Date(day,month,year): the year >=0, where 0 means wild card"); } } else { if (day < 1 || day > 31) { throw std::out_of_range("Invalid date attribute : the day >= 1 and day < 31"); } if (month < 1 || month > 12) { throw std::out_of_range("Invalid date attribute: the month >=1 and month <= 12"); } if (year <= 0) { throw std::out_of_range("Invalid date attribute: the year >0"); } } if (day != 0 && month != 0 && year != 0) { // The following validates the given date // -- i.e. an bad_year/_month/_day_of_month exception is thrown in case the date is invalid boost::gregorian::date(year, month, day); } } void DateAttr::calendarChanged(const ecf::Calendar& c, bool clear_at_midnight) { // See ECFLOW-337 versus ECFLOW-1550 if (c.dayChanged()) { if (clear_at_midnight) { clearFree(); } } if (free_) { return; } if (is_free(c)) { setFree(); } } void DateAttr::reset() { free_ = false; state_change_no_ = Ecf::incr_state_change_no(); } void DateAttr::requeue() { free_ = false; state_change_no_ = Ecf::incr_state_change_no(); } void DateAttr::setFree() { free_ = true; state_change_no_ = Ecf::incr_state_change_no(); #ifdef DEBUG_STATE_CHANGE_NO std::cout << "DateAttr::setFree()\n"; #endif } void DateAttr::clearFree() { free_ = false; state_change_no_ = Ecf::incr_state_change_no(); #ifdef DEBUG_STATE_CHANGE_NO std::cout << "DateAttr::clearFree()\n"; #endif } bool DateAttr::isFree(const ecf::Calendar& calendar) const { // The FreeDepCmd can be used to free the dates, if (free_) { return true; } return is_free(calendar); } bool DateAttr::is_free(const ecf::Calendar& calendar) const { bool dayMatches = true; bool monthMatches = true; bool yearMatches = true; if (day_ != 0) { dayMatches = calendar.day_of_month() == day_; } if (month_ != 0) { monthMatches = calendar.month() == month_; } if (year_ != 0) { yearMatches = calendar.year() == year_; } return (dayMatches && monthMatches && yearMatches); } bool DateAttr::checkForRequeue(const ecf::Calendar& calendar) const { // if calendar is hybrid, we can't re-queue if (calendar.hybrid()) { return false; } // checkForRequeue is called when we are deciding whether to re-queue the node. // If this date is in the future, we should re-queue if (day_ != 0 && month_ != 0 && year_ != 0) { return boost::gregorian::date(year_, month_, day_) > calendar.date(); } bool futureDayMatches = true; bool futureMonthMatches = true; bool futureYearMatches = true; if (day_ != 0) { futureDayMatches = day_ > calendar.day_of_month(); } if (month_ != 0) { futureMonthMatches = month_ > calendar.month(); } if (year_ != 0) { futureYearMatches = year_ > calendar.year(); } return (futureDayMatches || futureMonthMatches || futureYearMatches); } bool DateAttr::validForHybrid(const ecf::Calendar& calendar) const { if (day_ == 0) { return false; // relies on day change i.e. date *.10.2009 } if (month_ == 0) { return false; // relies on day change i.e. date 12.*.2009 } if (year_ == 0) { return false; // relies on day change i.e. date 12.10.* } // if the date matches exactly for today return (day_ == calendar.day_of_month() && month_ == calendar.month() && year_ == calendar.year()); } bool DateAttr::why(const ecf::Calendar& c, std::string& theReasonWhy) const { if (isFree(c)) { return false; } theReasonWhy += MESSAGE(" is date dependent ( next run on " << boost::gregorian::to_simple_string(next_matching_date(c)) << " the current date is " << c.day_of_month() << "/" << c.month() << "/" << c.year() << " )"); return true; } std::string DateAttr::name() const { std::string os; write(os); if (free_) { os += " # free"; } return os; } std::string DateAttr::toString() const { std::string ret; write(ret); return ret; } void DateAttr::write(std::string& ret) const { ret += "date "; if (day_ == 0) { ret += "*."; } else { ret += ecf::convert_to(day_); ret += "."; } if (month_ == 0) { ret += "*."; } else { ret += ecf::convert_to(month_); ret += "."; } if (year_ == 0) { ret += "*"; } else { ret += ecf::convert_to(year_); } } std::string DateAttr::dump() const { return MESSAGE(toString() << (free_ ? " (free)" : " (holding)")); } bool DateAttr::operator==(const DateAttr& rhs) const { if (free_ != rhs.free_) { return false; } return structureEquals(rhs); } bool DateAttr::structureEquals(const DateAttr& rhs) const { if (day_ != rhs.day_) { return false; } if (month_ != rhs.month_) { return false; } if (year_ != rhs.year_) { return false; } return true; } DateAttr DateAttr::create(const std::string& dateString) { int day = -1, month = -1, year = -1; getDate(dateString, day, month, year); return {day, month, year}; } DateAttr DateAttr::create(const std::vector& lineTokens, bool read_state) { // date 15.11.2009 # free // with PersistStyle::STATE & MIGRATE // date 15.*.* # // date *.1.* // for(size_t i =0; i < lineTokens.size() ; i++) { // cout << "lineTokens[" << i << "] = '" << lineTokens[i] << "'\n"; // } DateAttr date = DateAttr::create(lineTokens[1]); if (read_state) { for (size_t i = 3; i < lineTokens.size(); i++) { if (lineTokens[i] == "free") { date.setFree(); } } } return date; } void DateAttr::getDate(const std::string& date, int& day, int& month, int& year) { size_t firstDotPos = date.find_first_of('.'); size_t lastDotPos = date.find_first_of('.', firstDotPos + 1); if (firstDotPos == std::string::npos) { throw std::runtime_error("DateAttr::getDate Invalid date missing first dot :" + date); } if (lastDotPos == std::string::npos) { throw std::runtime_error("DateAttr::getDate: Invalid date missing second dot :" + date); } if (firstDotPos == lastDotPos) { throw std::runtime_error("DateAttr::getDate: Invalid date :" + date); } std::string theDay = date.substr(0, firstDotPos); std::string theMonth = date.substr(firstDotPos + 1, (lastDotPos - firstDotPos) - 1); std::string theYear = date.substr(lastDotPos + 1); if (theDay == "*") { day = 0; } else { day = Extract::value(theDay, "DateAttr::getDate: Invalid day :" + date); if (day < 1 || day > 31) { throw std::runtime_error("DateAttr::getDate: Invalid clock date: " + date); } } if (theMonth == "*") { month = 0; } else { month = Extract::value(theMonth, "DateAttr::getDate: Invalid month :" + date); if (month < 1 || month > 12) { throw std::runtime_error("DateAttr::getDate Invalid clock date: " + date); } } if (theYear == "*") { year = 0; } else { year = Extract::value(theYear, "DateAttr::getDate: Invalid year :" + date); } if (day == -1 || month == -1 || year == -1) { throw std::runtime_error("DateAttr::getDate: Invalid clock date:" + date); } if (day != 0 && month != 0 && year != 0) { // The following validates the given date // -- i.e. an bad_year/_month/_day_of_month exception is thrown in case the date is invalid boost::gregorian::date(year, month, day); } } boost::gregorian::date DateAttr::next_matching_date(const ecf::Calendar& c) const { auto next_matching_date = c.date(); // today's date auto one_day = boost::gregorian::date_duration(1); bool day_matches = (day_ == 0) ? true : false; bool month_matches = (month_ == 0) ? true : false; bool year_matches = (year_ == 0) ? true : false; for (int i = 0; i < 365; i++) { next_matching_date += one_day; if (day_ != 0 && next_matching_date.day() == day_) { day_matches = true; } if (month_ != 0 && next_matching_date.month() == month_) { month_matches = true; } if (year_ != 0 && next_matching_date.year() == year_) { year_matches = true; } if (day_matches && month_matches && year_matches) { return next_matching_date; } } return c.date(); } template void DateAttr::serialize(Archive& ar) { ar(CEREAL_NVP(day_), CEREAL_NVP(month_), CEREAL_NVP(year_)); CEREAL_OPTIONAL_NVP(ar, free_, [this]() { return free_; }); // conditionally save } CEREAL_TEMPLATE_SPECIALIZE(DateAttr); ecflow-5.17.0/libs/attribute/src/ecflow/attribute/AutoCancelAttr.hpp0000664000175000017500000000342415211533244025641 0ustar alastairalastair/* * 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. */ #ifndef ecflow_attribute_AutoCancelAttr_HPP #define ecflow_attribute_AutoCancelAttr_HPP #include #include "ecflow/core/TimeSlot.hpp" namespace ecf { class Calendar; } // namespace ecf namespace ecf { // Use compiler , destructor, assignment, copy constructor class AutoCancelAttr { public: AutoCancelAttr() = default; AutoCancelAttr(int hour, int minute, bool relative) : time_(hour, minute), relative_(relative) {} AutoCancelAttr(const TimeSlot& ts, bool relative) : time_(ts), relative_(relative) {} explicit AutoCancelAttr(int days) : time_(TimeSlot(days * 24, 0)), days_(true) {} bool operator==(const AutoCancelAttr& rhs) const; bool operator<(const AutoCancelAttr& rhs) const { return time_ < rhs.time(); } bool isFree(const ecf::Calendar&, const boost::posix_time::time_duration& suiteDurationAtComplete) const; std::string toString() const; const TimeSlot& time() const { return time_; } bool relative() const { return relative_; } bool days() const { return days_; } public: void write(std::string& ret) const; private: TimeSlot time_; bool relative_{true}; bool days_{false}; friend class cereal::access; template void serialize(Archive& ar, std::uint32_t const /*version*/); }; } // namespace ecf #endif /* ecflow_attribute_AutoCancelAttr_HPP */ ecflow-5.17.0/libs/attribute/src/ecflow/attribute/LateAttr.hpp0000664000175000017500000001102615211533244024505 0ustar alastairalastair/* * 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. */ #ifndef ecflow_attribute_LateAttr_HPP #define ecflow_attribute_LateAttr_HPP #include #include // for pair #include #include "ecflow/core/Chrono.hpp" #include "ecflow/core/TimeSlot.hpp" class NState; namespace ecf { class Calendar; } // namespace ecf namespace ecf { /// ======================================================================== /// Use compiler, destructor, assignment, copy constructor, /// /// ************************************************************************ /// late attribute: Only applies to a task, it can be set on suite/family /// but this is treated as an inherited attribute. /// late attribute lower down the hierarchy overrides it. /// ************************************************************************ /// The late _late_ attribute will not work correctly when the suites clock /// start and stops with the server. Since the late relies on real time /// for some of its functionality. /// -s submitted: The time node can stay submitted (format [+]hh:mm). submitted is always /// relative, so + is simply ignored, if present. If the node stays submitted /// longer than the time specified, the late flag is set /// -a Active : The time of day the node must have become active (format hh:mm). If the node /// is still queued or submitted, the late flag is set /// -c Complete : The time node must become complete (format {+}hh:mm). If relative, time is /// taken from the time the node became active, otherwise node must be complete by /// the time given. /// ======================================================================== class LateAttr { public: LateAttr(); void print(std::string&) const; bool operator==(const LateAttr& rhs) const; void addSubmitted(const TimeSlot& s) { s_ = s; } void add_submitted(int hour, int minute) { s_ = TimeSlot(hour, minute); } void addActive(const TimeSlot& s) { a_ = s; } void add_active(int hour, int minute) { a_ = TimeSlot(hour, minute); } void addComplete(const TimeSlot& s, bool relative) { c_ = s; c_is_rel_ = relative; } void add_complete(int hour, int minute, bool relative) { c_ = TimeSlot(hour, minute); c_is_rel_ = relative; } const TimeSlot& submitted() const { return s_; } const TimeSlot& active() const { return a_; } const TimeSlot& complete() const { return c_; } bool complete_is_relative() const { return c_is_rel_; } /// i.e no time structs specified bool isNull() const; /// Given the state and time of state change, and calendar work out if we are late /// if we are sets the late flag void checkForLateness(const std::pair& state, const ecf::Calendar& c); bool check_for_lateness(const std::pair& state, const ecf::Calendar& c) const; /// To be used by GUI to inform used that a node is late bool isLate() const { return isLate_; } /// To be called at begin and re-queue time void reset() { setLate(false); } // Override this late attributes with the settings form the input. void override_with(LateAttr*); // The state_change_no is never reset. Must be incremented if it can affect equality unsigned int state_change_no() const { return state_change_no_; } /// set flag to be late void setLate(bool f); std::string toString() const; std::string name() const { return toString(); } static void parse(LateAttr&, const std::string& line, const std::vector& lineTokens, size_t index); static LateAttr create(const std::string& lateString); public: void write(std::string&) const; private: TimeSlot s_; // relative by default TimeSlot a_; TimeSlot c_; unsigned int state_change_no_{0}; // *not* persisted, only used on server side bool c_is_rel_{false}; bool isLate_{false}; private: friend class cereal::access; template void serialize(Archive& ar); }; } // namespace ecf #endif /* ecflow_attribute_LateAttr_HPP */ ecflow-5.17.0/libs/attribute/src/ecflow/attribute/RepeatAttr.hpp0000664000175000017500000012037015211533244025043 0ustar alastairalastair/* * 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. */ #ifndef ecflow_attribute_RepeatAttr_HPP #define ecflow_attribute_RepeatAttr_HPP /// /// \brief Repeat Attribute. Please note that for repeat string, enumeration /// the positional index is used for evaluation. /// /// Simulation: Simulation must not affect the real job submission in the server. /// o Infinite repeats cause problems with simulation, hence we have /// a mechanism to stop this, when reset is called, via server this is disabled /// #include #include #include #include #include #include #include #include #include #include "ecflow/attribute/Variable.hpp" #include "ecflow/core/Chrono.hpp" #include "ecflow/core/TypeTraits.hpp" ///////////////////////////////////////////////////////////////////////// // Node can only have one repeat. // class RepeatBase { public: explicit RepeatBase(const std::string& name) : name_(name) {} RepeatBase() = default; virtual ~RepeatBase(); /// make non-virtual so that it can be in-lined. Called millions of times const std::string& name() const { return name_; } virtual int start() const = 0; virtual int end() const = 0; virtual int step() const = 0; /// /// @brief Retrieve the zero-based index of the current repeat position. /// /// @return the zero-based index of the current repeat position /// virtual long current_index() const = 0; /// /// @brief Retrieve the current repeat value /// /// @return the current repeat value as a human-readable string. /// virtual std::string current_value() const = 0; // Handle generated variables virtual void gen_variables(std::vector& vec) const { vec.push_back(var_); } virtual const Variable& find_gen_variable(const std::string& name) const { return name == name_ ? var_ : Variable::EMPTY(); } virtual void update_repeat_genvar() const; // After Repeat expiration the last call to increment() can cause // value to be beyond the last valid value // Depending on the kind of repeat the returned can be value or the current index // RepeatDate -> value // RepeatDateTime -> value // RepeatDateList -> value // RepeatString -> index into array of strings // RepeatInteger -> value // RepeatEnumerated -> index | value return value at index if cast-able to integer, otherwise return index ****** // RepeatDay -> value virtual long value() const = 0; // Depending on the kind of repeat the returned can be value or *current* index // RepeatDate -> value // RepeatDateTime -> value // RepeatDateList -> value // RepeatString -> index ( will always return an index) // RepeatInteger -> value // RepeatEnumerated -> index ( will always return an index) // RepeatDay -> value virtual long index_or_value() const = 0; virtual void increment() = 0; // returns a value with in the range start/end // Hence at Repeat expiration will return value associated with end() virtual long last_valid_value() const = 0; virtual long last_valid_value_minus(int val) const { return last_valid_value() - val; } virtual long last_valid_value_plus(int val) const { return last_valid_value() + val; } virtual RepeatBase* clone() const = 0; virtual bool compare(RepeatBase*) const = 0; virtual bool valid() const = 0; virtual void setToLastValue() = 0; virtual std::string valueAsString() const = 0; // uses last_valid_value virtual std::string value_as_string(int index) const = 0; // used in test only virtual std::string next_value_as_string() const = 0; virtual std::string prev_value_as_string() const = 0; virtual void reset() = 0; virtual void change(const std::string& newValue) = 0; // can throw std::runtime_error virtual void changeValue(long newValue) = 0; // can throw std::runtime_error virtual void set_value(long new_value_or_index) = 0; // will NOT throw, allows any value std::string toString() const; virtual std::string dump() const = 0; unsigned int state_change_no() const { return state_change_no_; } /// Simulator functions: virtual bool isInfinite() const = 0; virtual bool is_repeat_day() const { return false; } virtual bool isDate() const { return false; } virtual bool isDateTime() const { return false; } virtual bool isDateList() const { return false; } virtual bool isDateTimeList() const { return false; } virtual bool isInteger() const { return false; } virtual bool isEnumerated() const { return false; } virtual bool isString() const { return false; } virtual bool isDay() const { return false; } protected: void incr_state_change_no(); mutable Variable var_; // *not* persisted std::string name_; unsigned int state_change_no_{0}; // *not* persisted, only used on server side private: friend class cereal::access; template void serialize(Archive& ar); }; /// /// The date has no meaning in a physical sense, only used as a for loop over dates class RepeatDate final : public RepeatBase { public: RepeatDate(const std::string& variable, int start, int end, int delta = 1 /* always in days*/); RepeatDate() = default; void gen_variables(std::vector& vec) const override; const Variable& find_gen_variable(const std::string& name) const override; void update_repeat_genvar() const override; int start() const override { return start_; } int end() const override { return end_; } int step() const override { return delta_; } long value() const override { return value_; } long index_or_value() const override { return value_; } long last_valid_value() const override; long last_valid_value_minus(int value) const override; long last_valid_value_plus(int value) const override; /// /// @brief Retrieve the current zero-based index /// /// @return the zero-based index into the repeat sequence /// long current_index() const override; /// /// @brief Retrieve the current value /// /// @return the current date formatted as a yyyymmdd string. /// std::string current_value() const override { return std::to_string(value_); } void delta(int d) { delta_ = d; } int delta() const { return delta_; } bool operator==(const RepeatDate& rhs) const; bool operator<(const RepeatDate& rhs) const { return name() < rhs.name(); } RepeatDate* clone() const override { return new RepeatDate(name_, start_, end_, delta_, value_); } bool compare(RepeatBase*) const override; bool valid() const override { return (delta_ > 0) ? (value_ <= end_) : (value_ >= end_); } std::string valueAsString() const override; std::string value_as_string(int index) const override; std::string next_value_as_string() const override; std::string prev_value_as_string() const override; void setToLastValue() override; void reset() override; void increment() override; void change(const std::string& newValue) override; // can throw std::runtime_error void changeValue(long newValue) override; // can throw std::runtime_error void set_value(long newValue) override; // will NOT throw, allows any value std::string dump() const override; bool isDate() const override { return true; } /// Simulator functions: bool isInfinite() const override { return false; } private: long valid_value(long value) const; RepeatDate(const std::string& name, int start, int end, int delta, long value) : RepeatBase(name), start_(start), end_(end), delta_(delta), value_(value) {} void update_repeat_genvar_value() const; private: int start_{0}; int end_{0}; int delta_{0}; long value_{0}; mutable Variable yyyy_; // *not* persisted mutable Variable mm_; // *not* persisted mutable Variable dom_; // *not* persisted mutable Variable dow_; // *not* persisted mutable Variable julian_; // *not* persisted friend class cereal::access; template void serialize(Archive& ar, std::uint32_t const version); }; class RepeatDateTime final : public RepeatBase { public: RepeatDateTime(const std::string& variable, const std::string& start, const std::string& end, const std::string& delta = "24:00:00"); RepeatDateTime(const std::string& variable, ecf::Instant start, ecf::Instant end, ecf::Duration delta); RepeatDateTime() = default; void gen_variables(std::vector& vec) const override; const Variable& find_gen_variable(const std::string& name) const override; void update_repeat_genvar() const override; const ecf::Instant& start_instant() const { return start_; } const ecf::Instant& end_instant() const { return end_; } const ecf::Duration& step_duration() const { return delta_; } const ecf::Instant& value_instant() const { return value_; } int start() const override { return coerce_from_instant_into_seconds(start_); } int end() const override { return coerce_from_instant_into_seconds(end_); } int step() const override { return delta_.as_seconds().count(); } long value() const override { return coerce_from_instant_into_seconds(value_); } long index_or_value() const override { return coerce_from_instant_into_seconds(value_); } long last_valid_value() const override; long last_valid_value_minus(int value) const override; long last_valid_value_plus(int value) const override; /// /// @brief Retrieve the current zero-based index /// /// @return the zero-based index into the repeat sequence /// long current_index() const override { return (value() - start()) / step(); } /// /// @brief Retrieve the current value /// /// @return the current value formatted as a yyyymmddTHHMMSS string. /// std::string current_value() const override { return ecf::Instant::format(value_); } void delta(const ecf::Duration& d) { delta_ = d; } bool operator==(const RepeatDateTime& rhs) const; bool operator<(const RepeatDateTime& rhs) const { return name() < rhs.name(); } RepeatDateTime* clone() const override { return new RepeatDateTime(name_, start_, end_, delta_, value_); } bool compare(RepeatBase*) const override; bool valid() const override { return (delta_ > ecf::Duration{std::chrono::seconds{0}}) ? (value_ <= end_) : (value_ >= end_); } std::string valueAsString() const override; std::string value_as_string(int index) const override; std::string next_value_as_string() const override; std::string prev_value_as_string() const override; void setToLastValue() override; void reset() override; void increment() override; void change(const std::string& newValue) override; // can throw std::runtime_error void changeValue(long newValue) override; // can throw std::runtime_error void set_value(long newValue) override; // will NOT throw, allows any value std::string dump() const override; bool isDateTime() const override { return true; } /// Simulator functions: bool isInfinite() const override { return false; } private: ecf::Instant valid_value(const ecf::Instant& value) const; RepeatDateTime(const std::string& name, ecf::Instant start, ecf::Instant end, ecf::Duration delta, ecf::Instant value) : RepeatBase(name), start_(start), end_(end), delta_(delta), value_(value) {} void update_repeat_genvar_value() const; private: ecf::Instant start_; ecf::Instant end_; ecf::Duration delta_; ecf::Instant value_; // *not* persisted mutable VariableMap generated_{ // clang-format off // Date Variable(name_ + "_DATE", ""), // Date Components Variable(name_ + "_YYYY", ""), Variable(name_ + "_MM", ""), Variable(name_ + "_DD", ""), Variable(name_ + "_JULIAN", ""), // Time Variable(name_ + "_TIME", ""), // Time Components4 Variable(name_ + "_HOURS", ""), Variable(name_ + "_MINUTES", ""), Variable(name_ + "_SECONDS", "") // clang-format on }; friend class cereal::access; template void serialize(Archive& ar, std::uint32_t const version); }; class RepeatDateList final : public RepeatBase { public: RepeatDateList(const std::string& variable, const std::vector&); // will throw for empty list RepeatDateList() = default; void gen_variables(std::vector& vec) const override; const Variable& find_gen_variable(const std::string& name) const override; void update_repeat_genvar() const override; bool operator==(const RepeatDateList& rhs) const; bool operator<(const RepeatDateList& rhs) const { return name() < rhs.name(); } int start() const override; int end() const override; int step() const override { return 1; } long value() const override; // return value at index otherwise return 0 long value_at(size_t i) const { assert(i < list_.size()); return list_[i]; } long index_or_value() const override { return currentIndex_; } long last_valid_value() const override; long last_valid_value_minus(int value) const override; long last_valid_value_plus(int value) const override; /// /// @brief Retrieve the current zero-based index /// /// @return the zero-based index into the repeat sequence /// long current_index() const override { return currentIndex_; } /// /// @brief Retrieve the current value /// /// @return the current value formatted as a yyyymmdd string, or "" if out of bounds. /// std::string current_value() const override { return currentIndex_ >= 0 && currentIndex_ < static_cast(list_.size()) ? std::to_string(list_[currentIndex_]) : ""; } RepeatBase* clone() const override { return new RepeatDateList(name_, list_, currentIndex_); } bool compare(RepeatBase*) const override; bool valid() const override { return (currentIndex_ >= 0 && currentIndex_ < static_cast(list_.size())); } std::string valueAsString() const override; std::string value_as_string(int index) const override; std::string next_value_as_string() const override; std::string prev_value_as_string() const override; const std::vector& values() const { return list_; } void setToLastValue() override; void reset() override; void increment() override; void change(const std::string& newValue) override; // can throw std::runtime_error void changeValue(long newValue) override; // can throw std::runtime_error void set_value(long newValue) override; // will NOT throw, allows any value std::string dump() const override; bool isDateList() const override { return true; } int indexNum() const { return static_cast(list_.size()); } /// Simulator functions: bool isInfinite() const override { return false; } private: RepeatDateList(const std::string& variable, const std::vector& l, int index) : RepeatBase(variable), currentIndex_(index), list_(l) {} void update_repeat_genvar_value() const; private: int currentIndex_{0}; std::vector list_; mutable Variable yyyy_; // *not* persisted mutable Variable mm_; // *not* persisted mutable Variable dom_; // *not* persisted mutable Variable dow_; // *not* persisted mutable Variable julian_; // *not* persisted friend class cereal::access; template void serialize(Archive& ar, std::uint32_t const version); }; class RepeatDateTimeList final : public RepeatBase { public: RepeatDateTimeList(const std::string& variable, const std::vector&); // will throw for empty list RepeatDateTimeList() = default; void gen_variables(std::vector& vec) const override; const Variable& find_gen_variable(const std::string& name) const override; void update_repeat_genvar() const override; bool operator==(const RepeatDateTimeList& rhs) const; bool operator<(const RepeatDateTimeList& rhs) const { return name() < rhs.name(); } int start() const override; int end() const override; int step() const override { return 1; } long value() const override; long index_or_value() const override { return currentIndex_; } long last_valid_value() const override; long last_valid_value_minus(int value) const override; long last_valid_value_plus(int value) const override; /// /// @brief Retrieve the current zero-based index /// /// @return the zero-based index into the repeat sequence /// long current_index() const override { return currentIndex_; } /// /// @brief Retrieve the current value /// /// @return the current value fomratted as a yyyymmddTHHMMSS string, or "" if out of bounds. /// std::string current_value() const override { return currentIndex_ >= 0 && currentIndex_ < static_cast(list_.size()) ? ecf::Instant::format(list_[currentIndex_]) : ""; } RepeatBase* clone() const override { return new RepeatDateTimeList(name_, list_, currentIndex_); } bool compare(RepeatBase*) const override; bool valid() const override { return (currentIndex_ >= 0 && currentIndex_ < static_cast(list_.size())); } std::string valueAsString() const override; std::string value_as_string(int index) const override; std::string next_value_as_string() const override; std::string prev_value_as_string() const override; const std::vector& values() const { return list_; } void setToLastValue() override; void reset() override; void increment() override; void change(const std::string& newValue) override; // can throw std::runtime_error void changeValue(long newValue) override; // can throw std::runtime_error void set_value(long newValue) override; // will NOT throw, allows any value std::string dump() const override; bool isDateTimeList() const override { return true; } int indexNum() const { return static_cast(list_.size()); } /// Simulator functions: bool isInfinite() const override { return false; } private: RepeatDateTimeList(const std::string& variable, const std::vector& l, int index) : RepeatBase(variable), currentIndex_(index), list_(l) {} void update_repeat_genvar_value() const; private: int currentIndex_{0}; std::vector list_; // *not* persisted mutable VariableMap generated_{ // clang-format off Variable(name_ + "_DATE", ""), Variable(name_ + "_YYYY", ""), Variable(name_ + "_MM", ""), Variable(name_ + "_DD", ""), Variable(name_ + "_JULIAN", ""), Variable(name_ + "_TIME", ""), Variable(name_ + "_HOURS", ""), Variable(name_ + "_MINUTES", ""), Variable(name_ + "_SECONDS", "") // clang-format on }; friend class cereal::access; template void serialize(Archive& ar, std::uint32_t const version); }; class RepeatInteger final : public RepeatBase { public: RepeatInteger(const std::string& variable, int start, int end, int delta = 1); RepeatInteger(); bool operator==(const RepeatInteger& rhs) const; bool operator<(const RepeatInteger& rhs) const { return name() < rhs.name(); } int start() const override { return start_; } int end() const override { return end_; } int step() const override { return delta_; } long value() const override { return value_; } long index_or_value() const override { return value_; } long last_valid_value() const override; int delta() const { return delta_; } /// /// @brief Retrieve the current zero-based index /// /// @return the zero-based index into the repeat sequence /// long current_index() const override { return (value_ - start_) / delta_; } /// /// @brief Retrieve the current value /// /// @return the current value as a decimal string. /// std::string current_value() const override { return std::to_string(value_); } RepeatInteger* clone() const override { return new RepeatInteger(name_, start_, end_, delta_, value_); } bool compare(RepeatBase*) const override; bool valid() const override { return (delta_ > 0) ? (value_ <= end_) : (value_ >= end_); } std::string valueAsString() const override; std::string value_as_string(int index) const override; std::string next_value_as_string() const override; std::string prev_value_as_string() const override; void setToLastValue() override; void reset() override; void increment() override; void change(const std::string& newValue) override; // can throw std::runtime_error void changeValue(long newValue) override; // can throw std::runtime_error void set_value(long newValue) override; // will NOT throw, allows any value std::string dump() const override; bool isInteger() const override { return true; } /// Simulator functions: bool isInfinite() const override { return false; } private: long valid_value(long value) const; RepeatInteger(const std::string& name, int start, int end, int delta, long value) : RepeatBase(name), start_(start), end_(end), delta_(delta), value_(value) {} private: int start_{0}; int end_{0}; int delta_{0}; long value_{0}; friend class cereal::access; template void serialize(Archive& ar, std::uint32_t const version); }; // Note:: Difference between RepeatEnumerated and RepeatString, is that // RepeatEnumerated::value() will return the value at the index if cast-able to integer, // whereas RepeatString::value() will always return the index. class RepeatEnumerated final : public RepeatBase { public: RepeatEnumerated(const std::string& variable, const std::vector& theEnums); RepeatEnumerated() = default; bool operator==(const RepeatEnumerated& rhs) const; bool operator<(const RepeatEnumerated& rhs) const { return name() < rhs.name(); } int start() const override { return 0; } int end() const override; int step() const override { return 1; } long value() const override; // return value at index if cast-able to integer, otherwise return index long index_or_value() const override { return currentIndex_; } long last_valid_value() const override; /// /// @brief Retrieve the current zero-based index /// /// @return the zero-based index into the repeat sequence /// long current_index() const override { return currentIndex_; } /// /// @brief Retrieve the current value /// /// @return the current string value, or "" if out of bounds. /// std::string current_value() const override { return currentIndex_ >= 0 && currentIndex_ < static_cast(theEnums_.size()) ? theEnums_[currentIndex_] : ""; } const std::vector& values() const { return theEnums_; } RepeatBase* clone() const override { return new RepeatEnumerated(name_, theEnums_, currentIndex_); } bool compare(RepeatBase*) const override; bool valid() const override { return (currentIndex_ >= 0 && currentIndex_ < static_cast(theEnums_.size())); } std::string valueAsString() const override; std::string value_as_string(int index) const override; std::string next_value_as_string() const override; std::string prev_value_as_string() const override; void setToLastValue() override; void reset() override; void increment() override; void change(const std::string& newValue) override; // can throw std::runtime_error void changeValue(long newValue) override; // can throw std::runtime_error void set_value(long newValue) override; // will NOT throw, allows any value std::string dump() const override; bool isEnumerated() const override { return true; } int indexNum() const { return static_cast(theEnums_.size()); } /// Simulator functions: bool isInfinite() const override { return false; } private: RepeatEnumerated(const std::string& variable, const std::vector& theEnums, int index) : RepeatBase(variable), currentIndex_(index), theEnums_(theEnums) {} private: int currentIndex_{0}; std::vector theEnums_; friend class cereal::access; template void serialize(Archive& ar, std::uint32_t const version); }; class RepeatString final : public RepeatBase { public: RepeatString(const std::string& variable, const std::vector& theEnums); RepeatString() = default; bool operator==(const RepeatString& rhs) const; bool operator<(const RepeatString& rhs) const { return name() < rhs.name(); } int start() const override { return 0; } int end() const override; int step() const override { return 1; } long value() const override { return currentIndex_; } long index_or_value() const override { return currentIndex_; } long last_valid_value() const override; // returns the index /// /// @brief Retrieve the current zero-based index /// /// @return the zero-based index into the repeat sequence /// long current_index() const override { return currentIndex_; } /// /// @brief Retrieve the current value /// /// @return the current string value, or "" if out of bounds. /// std::string current_value() const override { return currentIndex_ >= 0 && currentIndex_ < static_cast(theStrings_.size()) ? theStrings_[currentIndex_] : ""; } RepeatBase* clone() const override { return new RepeatString(name_, theStrings_, currentIndex_); } bool compare(RepeatBase*) const override; bool valid() const override { return (currentIndex_ >= 0 && currentIndex_ < static_cast(theStrings_.size())); } std::string valueAsString() const override; std::string value_as_string(int index) const override; std::string next_value_as_string() const override; std::string prev_value_as_string() const override; const std::vector& values() const { return theStrings_; } void setToLastValue() override; void reset() override; void increment() override; void change(const std::string& newValue) override; // can throw std::runtime_error void changeValue(long newValue) override; // can throw std::runtime_error void set_value(long newValue) override; // will NOT throw, allows any value std::string dump() const override; bool isString() const override { return true; } int indexNum() const { return static_cast(theStrings_.size()); } /// Simulator functions: bool isInfinite() const override { return false; } private: RepeatString(const std::string& variable, const std::vector& theEnums, int index) : RepeatBase(variable), currentIndex_(index), theStrings_(theEnums) {} private: int currentIndex_{0}; std::vector theStrings_; friend class cereal::access; template void serialize(Archive& ar, std::uint32_t const version); }; /// The current repeat day is not that well-defined or deterministic. /// **** Currently I have not come across any suites that use an end-date **** /// o If the suite has defined a real clock /// then number of repeats is deterministic /// However if the end-date is less than suite clock this should be reported as error /// o Currently under the hybrid clock the date is not updated, this raises /// a whole-lot of issues. (We don't want a separate calendar, just for this). /// o If there is _no_ suite clock, then we must use the current day /// now the number of repeats varies, and if end-date is less than the current /// day we need to report an error /// It is not clear what behaviour is required here, hence I will not implement /// the end-date functionality. Until there is clear requirement is this area. /// end-date will be treated as a parser error. /// The minimum deterministic functionality here is to implement the infinite /// repeat that has no end date /// /// RepeatDay do not really have a name: However we need maintain invariant that all NON-empty repeats /// have a name. Hence, the name will be as day /// Note: this applies to the clone as well class RepeatDay final : public RepeatBase { public: RepeatDay() : RepeatBase("day") {} // Enable implicit conversion from integer RepeatDay(int step) : RepeatBase("day"), step_(step) {} bool operator==(const RepeatDay& rhs) const; bool operator<(const RepeatDay& rhs) const { return step_ < rhs.step(); } int start() const override { return 0; } int end() const override { return 0; } int step() const override { return step_; } void increment() override { /* do nothing */ } long value() const override { return step_; } long index_or_value() const override { return step_; } long last_valid_value() const override { return step_; } /// /// @brief Retrieve the current index /// /// For RepeatDay the index is not really meaningful, but we return the step value for consistency with other Repeat /// types. /// /// @return the current index /// long current_index() const override { return step_; } /// /// @brief Retrieve the current value /// /// For RepeatDay the value is not really meaningful, but we return the step value as a string for consistency with /// other Repeat types. /// /// @return the current value as a decimal string. /// std::string current_value() const override { return std::to_string(step_); } RepeatBase* clone() const override { return new RepeatDay(step_, valid_); } bool compare(RepeatBase*) const override; bool valid() const override { return valid_; } std::string valueAsString() const override { return std::string{}; } std::string value_as_string(int) const override { return std::string{}; } std::string next_value_as_string() const override { return std::string{}; } std::string prev_value_as_string() const override { return std::string{}; } void setToLastValue() override { /* do nothing ?? */ } void reset() override { valid_ = true; } void change(const std::string& /*newValue*/) override { /* do nothing */ } void changeValue(long /*newValue*/) override { /* do nothing */ } void set_value(long /*newValue*/) override { /* do nothing */ } std::string dump() const override; bool isDay() const override { return true; } /// Simulator functions: bool isInfinite() const override { return true; } bool is_repeat_day() const override { return true; } private: RepeatDay(int step, bool valid) : RepeatBase("day"), step_(step), valid_(valid) {} private: int step_{1}; bool valid_{true}; // not persisted since only used in simulator friend class cereal::access; template void serialize(Archive& ar, std::uint32_t const version); }; class Repeat { public: Repeat(); // for serialisation // Enable implicit conversion to Repeat Repeat(const RepeatDate&); Repeat(const RepeatDateTime&); Repeat(const RepeatDateList&); Repeat(const RepeatDateTimeList&); Repeat(const RepeatInteger&); Repeat(const RepeatEnumerated&); Repeat(const RepeatString&); Repeat(const RepeatDay&); // Enable copy & move semantics Repeat(const Repeat&); Repeat(Repeat&& rhs); Repeat& operator=(Repeat rhs); ~Repeat(); bool operator==(const Repeat& rhs) const; bool operator<(const Repeat& rhs) const { return name() < rhs.name(); } bool empty() const { return (type_) ? false : true; } void clear() { type_.reset(nullptr); } const std::string& name() const; void gen_variables(std::vector& vec) const { if (type_) { type_->gen_variables(vec); } } const Variable& find_gen_variable(const std::string& name) const { return (type_) ? type_->find_gen_variable(name) : Variable::EMPTY(); } void update_repeat_genvar() const { if (type_) { type_->update_repeat_genvar(); } } int start() const { return (type_) ? type_->start() : 0; } int end() const { return (type_) ? type_->end() : 0; } int step() const { return (type_) ? type_->step() : 0; } long value() const { return (type_) ? type_->value() : 0; } long index_or_value() const { return (type_) ? type_->index_or_value() : 0; } long last_valid_value() const { return (type_) ? type_->last_valid_value() : 0; } long last_valid_value_minus(int val) const { return (type_) ? type_->last_valid_value_minus(val) : -val; } long last_valid_value_plus(int val) const { return (type_) ? type_->last_valid_value_plus(val) : val; } /// /// @brief Retrieve the current zero-based index /// /// @return the current index, or 0 if not holding a specific Repeat type. /// long current_index() const { return (type_) ? type_->current_index() : 0; } /// /// @brief Retrieve the current value /// /// @return the current string value, or "" if not holding a specific Repeat type. /// std::string current_value() const { return (type_) ? type_->current_value() : std::string{}; } bool valid() const { return (type_) ? type_->valid() : false; } void setToLastValue() { if (type_) { type_->setToLastValue(); } } std::string valueAsString() const { return (type_) ? type_->valueAsString() : std::string{}; } std::string value_as_string(int index) const { return (type_) ? type_->value_as_string(index) : std::string{}; } std::string next_value_as_string() const { return (type_) ? type_->next_value_as_string() : std::string{}; } std::string prev_value_as_string() const { return (type_) ? type_->prev_value_as_string() : std::string{}; } void reset() { if (type_) { type_->reset(); } } void increment() { if (type_) { type_->increment(); } } void change(const std::string& newValue) { if (type_) { type_->change(newValue); } } void changeValue(long newValue) { if (type_) { type_->changeValue(newValue); } } void set_value(long newValue) { if (type_) { type_->set_value(newValue); } } std::string toString() const { return (type_) ? type_->toString() : std::string{}; } std::string dump() const { return (type_) ? type_->dump() : std::string{}; } // additional state unsigned int state_change_no() const { return (type_) ? type_->state_change_no() : 0; } /// simulator functions: bool isInfinite() const { return (type_) ? type_->isInfinite() : false; } // Allows Repeat's to be returned by reference static const Repeat& EMPTY(); bool is_repeat_day() const { return (type_) ? type_->is_repeat_day() : false; } /// Expose base for the GUI only, use with caution RepeatBase* repeatBase() { return type_.get(); } const RepeatBase* repeatBase() const { return type_.get(); } template const T& as() const { return dynamic_cast(*repeatBase()); } private: std::unique_ptr type_; friend class cereal::access; template void serialize(Archive& ar, std::uint32_t const version); }; namespace ecf { namespace repeat { template inline std::optional current_value_of(const RepeatType& repeat) { // // This primary template is intentionally reached for combinations where the // Result type does not match the native type of RepeatType — for example, // current_value_of(const RepeatDate&) has no specialisation and // should return nullopt silently (RepeatDate yields int, not string). // // If RepeatType is a completely unknown type (e.g. a new RepeatBase subtype with no // specialisations at all), that is a programming error caught here at compile time. // static_assert( ecf::is_one_of_v, "current_value_of: RepeatType is an unknown Repeat subtype. " "Add explicit specialisation of current_value_of(const RepeatType&) in RepeatAttr.hpp."); return std::nullopt; } template <> inline std::optional current_value_of(const RepeatDate& repeat) { return repeat.value(); } template <> inline std::optional current_value_of(const RepeatDateList& repeat) { return repeat.value(); } template <> inline std::optional current_value_of(const RepeatDateTime& repeat) { return repeat.current_value(); } template <> inline std::optional current_value_of(const RepeatDateTimeList& repeat) { return repeat.current_value(); } template <> inline std::optional current_value_of(const RepeatInteger& repeat) { return repeat.value(); } template <> inline std::optional current_value_of(const RepeatEnumerated& repeat) { return repeat.current_value(); } template <> inline std::optional current_value_of(const RepeatString& repeat) { return repeat.current_value(); } template <> inline std::optional current_value_of(const RepeatDay& repeat) { return repeat.value(); } template inline std::optional current_value_as(const RepeatBase& base) { if (base.isDate()) { auto& repeat = static_cast(base); return current_value_of(repeat); } else if (base.isDateTime()) { auto& repeat = static_cast(base); return current_value_of(repeat); } else if (base.isDateList()) { auto& repeat = static_cast(base); return current_value_of(repeat); } else if (base.isDateTimeList()) { auto& repeat = static_cast(base); return current_value_of(repeat); } else if (base.isInteger()) { auto& repeat = static_cast(base); return current_value_of(repeat); } else if (base.isEnumerated()) { auto& repeat = static_cast(base); return current_value_of(repeat); } else if (base.isString()) { auto& repeat = static_cast(base); return current_value_of(repeat); } else if (base.isDay()) { auto& repeat = static_cast(base); return current_value_of(repeat); } else { return std::nullopt; } } template std::optional current_value_as(const Repeat& repeat) { auto base = repeat.repeatBase(); return (base) ? current_value_as(*base) : std::nullopt; } } // namespace repeat } // namespace ecf #endif /* ecflow_attribute_RepeatAttr_HPP */ ecflow-5.17.0/libs/attribute/src/ecflow/attribute/VerifyAttr.hpp0000664000175000017500000000346415211533244025073 0ustar alastairalastair/* * 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. */ #ifndef ecflow_attribute_VerifyAttr_HPP #define ecflow_attribute_VerifyAttr_HPP #include "ecflow/core/NState.hpp" // Class VerifyAttr: // This class is only used for testing/verification purposes. It allows us to // embed expected number of states, within the definition file and so // reduce the need for golden log files. // Use compiler , generated destructor, assignment, copy constructor class VerifyAttr { public: VerifyAttr(NState::State state, int expected, int actual = 0) : state_(state), expected_(expected), actual_(actual), state_change_no_(0) {} VerifyAttr() = default; bool operator==(const VerifyAttr& rhs) const; void print(std::string&) const; NState::State state() const { return state_; } int expected() const { return expected_; } int actual() const { return actual_; } void incrementActual(); void reset(); // The state_change_no is never reset. Must be incremented if it can affect equality unsigned int state_change_no() const { return state_change_no_; } std::string toString() const; std::string dump() const; private: NState::State state_{NState::UNKNOWN}; int expected_{0}; int actual_{0}; unsigned int state_change_no_{0}; // *not* persisted, only used on server side friend class cereal::access; template void serialize(Archive& ar); }; #endif /* ecflow_attribute_VerifyAttr_HPP */ ecflow-5.17.0/libs/attribute/src/ecflow/attribute/CronAttr.cpp0000664000175000017500000007466115211533244024532 0ustar alastairalastair/* * 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 "ecflow/attribute/CronAttr.hpp" #include #include #include #include "ecflow/core/Calendar.hpp" #include "ecflow/core/Converter.hpp" #include "ecflow/core/Ecf.hpp" #include "ecflow/core/Message.hpp" #include "ecflow/core/Serialization.hpp" #include "ecflow/core/Str.hpp" using namespace ecf; // #define DEBUG_CRON_ATTR 1 // #define DEBUG_CRON_PARSING 1 // #define DEBUG_CRON_SIM 1 namespace ecf { CronAttr::CronAttr() = default; CronAttr::CronAttr(const std::string& str) { if (str.empty()) { throw std::runtime_error("CronAttr::CronAttr : empty string passed"); } std::vector tokens; ecf::algorithm::split_at(tokens, str); if (tokens.empty()) { throw std::runtime_error("CronAttr::CronAttr : incorrect time string ?"); } size_t index = 0; timeSeries_ = TimeSeries::create(index, tokens, false /*parse_state*/); } void CronAttr::addWeekDays(const std::vector& w) { weekDays_ = w; for (int day : weekDays_) { if (day < 0 || day > 6) { throw std::out_of_range( MESSAGE("Invalid range for day(" << day << ") of the week expected range is 0==Sun to 6==Sat")); } auto result = std::find(std::begin(last_week_days_of_month_), std::end(last_week_days_of_month_), day); if (result != std::end(last_week_days_of_month_)) { throw std::runtime_error( MESSAGE("Duplicate day(" << day << ") of the week also found in last week day of the month")); } } } void CronAttr::add_last_week_days_of_month(const std::vector& w) { last_week_days_of_month_ = w; for (int day : last_week_days_of_month_) { if (day < 0 || day > 6) { throw std::out_of_range( MESSAGE("Invalid range for day(" << day << ") of the week expected range is 0==Sun to 6==Sat")); } auto result = std::find(std::begin(weekDays_), std::end(weekDays_), day); if (result != std::end(weekDays_)) { throw std::runtime_error( MESSAGE("Duplicate last week day (" << day << ") of the month also found in week day")); } } } void CronAttr::addDaysOfMonth(const std::vector& d) { daysOfMonth_ = d; for (int day_of_month : daysOfMonth_) { if (day_of_month < 1 || day_of_month > 31) { throw std::out_of_range( MESSAGE("Invalid range for day of month(" << day_of_month << ") expected range is 1-31")); } } } void CronAttr::addMonths(const std::vector& m) { months_ = m; for (int month : months_) { if (month < 1 || month > 12) { throw std::out_of_range( MESSAGE("Invalid range for month(" << month << ") expected range is 1==Jan to 12==Dec")); } } } std::string CronAttr::name() const { std::string ret; write(ret); timeSeries_.write_state_for_gui(ret, free_); return ret; } std::string CronAttr::toString() const { std::string ret; write(ret); return ret; } void CronAttr::write(std::string& ret) const { ret += "cron "; if (!weekDays_.empty()) { ret += "-w "; for (size_t i = 0; i < weekDays_.size(); ++i) { ret += ecf::convert_to(weekDays_[i]); if (i != weekDays_.size() - 1) { ret += ","; } } if (last_week_days_of_month_.empty()) { ret += " "; } else { ret += ","; } } if (!last_week_days_of_month_.empty()) { if (weekDays_.empty()) { ret += "-w "; } for (size_t i = 0; i < last_week_days_of_month_.size(); ++i) { ret += ecf::convert_to(last_week_days_of_month_[i]); ret += 'L'; if (i != last_week_days_of_month_.size() - 1) { ret += ","; } } ret += " "; } if (!daysOfMonth_.empty()) { ret += "-d "; for (size_t i = 0; i < daysOfMonth_.size(); ++i) { ret += ecf::convert_to(daysOfMonth_[i]); if (i != daysOfMonth_.size() - 1) { ret += ","; } } if (!last_day_of_month_) { ret += " "; } } if (last_day_of_month_) { if (daysOfMonth_.empty()) { ret += "-d L "; } else { ret += ",L "; } } if (!months_.empty()) { ret += "-m "; for (size_t i = 0; i < months_.size(); ++i) { ret += ecf::convert_to(months_[i]); if (i != months_.size() - 1) { ret += ","; } } ret += " "; } timeSeries_.write(ret); // no new line added, up to caller } std::string CronAttr::dump() const { return MESSAGE(toString() << (free_ ? " (free)" : " (holding)")); } bool CronAttr::operator==(const CronAttr& rhs) const { if (free_ != rhs.free_) { return false; } if (last_day_of_month_ != rhs.last_day_of_month_) { return false; } if (weekDays_ != rhs.weekDays_) { return false; } if (last_week_days_of_month_ != rhs.last_week_days_of_month_) { return false; } if (daysOfMonth_ != rhs.daysOfMonth_) { return false; } if (months_ != rhs.months_) { return false; } return timeSeries_.operator==(rhs.timeSeries_); } bool CronAttr::structureEquals(const CronAttr& rhs) const { if (last_day_of_month_ != rhs.last_day_of_month_) { return false; } if (weekDays_ != rhs.weekDays_) { return false; } if (daysOfMonth_ != rhs.daysOfMonth_) { return false; } if (last_week_days_of_month_ != rhs.last_week_days_of_month_) { return false; } if (months_ != rhs.months_) { return false; } return timeSeries_.structureEquals(rhs.timeSeries_); } void CronAttr::calendarChanged(const ecf::Calendar& c) { // ensure this called first , since we need always update for relative duration ECFLOW-1648 // This assumes that calendarChanged will set TimeSeries::isValid = true, at day change if (timeSeries_.calendarChanged(c)) { state_change_no_ = Ecf::incr_state_change_no(); } if (free_) { return; } // Once a cron is free, it stays free until re-queue if (isFree(c)) { setFree(); } // A cron is always re-queueable, hence we use isFree to control when it can actually run. } void CronAttr::resetRelativeDuration() { if (timeSeries_.resetRelativeDuration()) { state_change_no_ = Ecf::incr_state_change_no(); } } void CronAttr::setFree() { free_ = true; state_change_no_ = Ecf::incr_state_change_no(); #ifdef DEBUG_STATE_CHANGE_NO std::cout << "CronAttr::setFree()\n"; #endif } void CronAttr::clearFree() { free_ = false; state_change_no_ = Ecf::incr_state_change_no(); #ifdef DEBUG_STATE_CHANGE_NO std::cout << "CronAttr::clearFree()\n"; #endif } void CronAttr::miss_next_time_slot() { // A cron attribute with a single time slot is repeated indefinitely hence always re-queues timeSeries_.miss_next_time_slot(); state_change_no_ = Ecf::incr_state_change_no(); } // ************************************************************************************** // FOR DEBUG THIS IS THE MAIN FUNCTION, AS THIS DECIDES WHETHER WE CONTINUE OR STOP // ************************************************************************************** bool CronAttr::checkForRequeue(const ecf::Calendar& calendar) const { // checkForRequeue is called when a task/family has reach the complete state // This simple checks if node should be put in re-queued state // A cron is always re-queueable // Hence, in order to use this it should be used in conjunction // with a parent node that has complete expression, (& maybe a dummy task) // This will allow its use with a parent repeat somewhere in the hierarchy return true; } bool CronAttr::validForHybrid(const ecf::Calendar& calendar) const { if (timeSeries_.hasIncrement()) { if (last_day_of_month_) { return false; // relies on day change } if (!months_.empty()) { return false; // relies on day change } if (!daysOfMonth_.empty()) { return false; // relies on day change } if (!weekDays_.empty()) { if (weekDays_.size() != 1) { return false; // relies on day change } return (weekDays_[0] == calendar.day_of_week()); } // cron 10:00 20:00 01:00 // valid for hybrid ? return true; } // A time series that does not have an increment runs indefinitely and hence relies on day change // cron 23:00 return false; } bool CronAttr::why(const ecf::Calendar& c, std::string& theReasonWhy) const { // This will logically AND all the times if (isFree(c)) { return false; } // We are here because: // 1/ Not on a valid time slot in the time series // *OR* // 2/ Logical *AND* of day of week, day of month, or month returned false theReasonWhy += "is cron dependent"; // Let's start by assuming that the time series was NOT free. // First check if week day, day of month, month, matches if (is_day_of_week_day_of_month_and_month_free(c)) { if (timeSeries_.is_valid()) { // This can apply to single and series boost::posix_time::time_duration calendar_time = timeSeries_.duration(c); if (calendar_time < timeSeries_.start().duration()) { timeSeries_.why(c, theReasonWhy); return true; } // calendar_time >= timeSeries_.start().duration() if (timeSeries_.hasIncrement()) { if (calendar_time < timeSeries_.finish().duration()) { timeSeries_.why(c, theReasonWhy); return true; } } } // calendar_time >= timeSeries_.start().duration() && calendar_time >= timeSeries_.finish().duration() // past the end of time slot, find next valid date } // take into account, user can use run/force complete to miss time slots bool do_a_requeue = timeSeries_.requeueable(c); if (do_a_requeue && weekDays_.empty() && daysOfMonth_.empty() && months_.empty()) { TimeSlot the_next_time_slot = timeSeries_.compute_next_time_slot(c); if (the_next_time_slot.isNULL()) { theReasonWhy += " ( *re-queue* to run at this time "; } else { theReasonWhy += " ( *re-queue* to run at "; theReasonWhy += the_next_time_slot.toString(); } theReasonWhy += ", otherwise next run is at "; } else { theReasonWhy += " ( next run is at "; } // Find the *NEXT* date that matches, and use the first time slot auto the_next_date = next_date(c); theReasonWhy += timeSeries_.start().toString(); theReasonWhy += " "; theReasonWhy += to_simple_string(the_next_date); std::ostringstream ss; TimeSlot currentTime = TimeSlot(timeSeries_.duration(c)); ss << ", current time "; if (timeSeries_.relative()) { ss << "+"; } ss << currentTime.toString() << " " << to_simple_string(c.date()) << " )"; theReasonWhy += ss.str(); return true; } void CronAttr::reset_only() { clearFree(); timeSeries_.reset_only(); } void CronAttr::reset(const ecf::Calendar& c) { clearFree(); timeSeries_.reset(c); } void CronAttr::requeue(const ecf::Calendar& c, bool reset_next_time_slot) { clearFree(); timeSeries_.requeue(c, reset_next_time_slot); } bool CronAttr::isFree(const ecf::Calendar& c) const { // The FreeDepCmd can be used to free the crons, if (free_) { return true; } if (!timeSeries_.isFree(c)) { return false; } // Ok time series is Free // ******************************************************************** // IMPORTANT: When there are multiple week days, days of month and months, // the attribute is *ONLY* free, if *ALL* are free, i.e. we need Boolean AND behaviour // ******************************************************************** return is_day_of_week_day_of_month_and_month_free(c); } bool CronAttr::is_day_of_week_day_of_month_and_month_free(const ecf::Calendar& c) const { #ifdef DEBUG_CRON_SIM cout << toString() << " cal : " << to_simple_string(c.date()) << " c.day_of_week:" << c.day_of_week() << " c.day_of_month:" << c.day_of_month(); cout.flush(); #endif bool the_week_day_matches = weekDays_.empty() && last_week_days_of_month_.empty(); // week day matches if no week days bool the_day_of_month_matches = daysOfMonth_.empty(); // day of month if no days of month bool the_month_matches = months_.empty(); // month matches if no months if (!weekDays_.empty()) { the_week_day_matches = week_day_matches(c.day_of_week()); } if (!the_week_day_matches && !last_week_days_of_month_.empty()) { the_week_day_matches = last_week_day_of_month_matches(c); } if (!daysOfMonth_.empty() || last_day_of_month_) { the_day_of_month_matches = day_of_month_matches(c.day_of_month(), c); } if (!months_.empty()) { the_month_matches = month_matches(c.month()); } // Remember we *AND* across -w, -d, -m or *OR* for each element in -w, -d,-m bool matches = false; if (daysOfMonth_.empty() && !last_day_of_month_ && months_.empty()) { // cron -w 0L 10:00 # run on the last sunday of each month matches = the_week_day_matches; // only week day, } else { matches = the_week_day_matches && the_day_of_month_matches && the_month_matches; } #ifdef DEBUG_CRON_SIM if (matches) { cout << " *MATCHES*"; } cout << "\n"; #endif return matches; } bool CronAttr::week_day_matches(int theDayOfWeek) const { for (int theWeekDay : weekDays_) { if (theDayOfWeek == theWeekDay) { return true; } } return false; } bool CronAttr::last_week_day_of_month_matches(const ecf::Calendar& c) const { int cal_day_of_week = c.day_of_week(); auto last_day_of_month = c.date().end_of_month(); auto diff_current_date_and_last_day_of_month = last_day_of_month - c.date(); for (int cron_last_week_day_of_month : last_week_days_of_month_) { if (cal_day_of_week == cron_last_week_day_of_month) { if (diff_current_date_and_last_day_of_month.days() < 7) { return true; } } } return false; } bool CronAttr::day_of_month_matches(int theDayOfMonth, const ecf::Calendar& c) const { for (int dayOfMonth : daysOfMonth_) { if (theDayOfMonth == dayOfMonth) { return true; } } if (last_day_of_month_) { return c.date() == c.date().end_of_month(); } return false; } bool CronAttr::month_matches(int theMonth) const { for (int month : months_) { if (theMonth == month) { return true; } } return false; } //------------------------------------------------------------------ bool CronAttr::checkInvariants(std::string& errormsg) const { return timeSeries_.checkInvariants(errormsg); } //-------------------------------------------------------------- boost::gregorian::date CronAttr::next_date(const ecf::Calendar& calendar) const { // Find the next date that matches, day of week, day of year, and month // that is greater than today's date. // This *ASSUMES* day of week, day of month, and month are evaluated with _AND_ together auto one_day = boost::gregorian::date_duration(1); auto future_date = calendar.date(); // today's date #ifdef DEBUG_CRON_SIM cout << "cron : " << toString() << "\n"; cout << "future_date start : " << to_simple_string(future_date) << "\n"; #endif future_date += one_day; // add one day, so its in the future while (true) { bool week_day_matches = weekDays_.empty(); // week day matches if no week days bool the_last_week_day_of_month_matches = last_week_days_of_month_.empty(); // matches if EMPTY bool day_of_month_matches = daysOfMonth_.empty(); // day of month if no days of month bool month_matches = months_.empty(); // month matches if no months if (daysOfMonth_.empty() && last_day_of_month_) { day_of_month_matches = false; } // deal with case where we have: cron -w 0,1 for (int weekDay : weekDays_) { if (future_date.day_of_week().as_number() == weekDay) { week_day_matches = true; break; } } // *IMPORTANT* the days in weekDays_ and last_week_days_of_month_ can *NOT* overlap. for (int weekDay : last_week_days_of_month_) { if (future_date.day_of_week().as_number() == weekDay) { boost::gregorian::date_duration diff = future_date - future_date.end_of_month(); if (diff.days() < 7) { the_last_week_day_of_month_matches = true; } break; } } // deal with case where we have: cron -d 14,15,16,L # L means last day of month if (!daysOfMonth_.empty() || last_day_of_month_) { for (int d : daysOfMonth_) { if (future_date.day() == d) { day_of_month_matches = true; break; } } if (last_day_of_month_ && future_date == future_date.end_of_month()) { day_of_month_matches = true; } } // deal with case where we have: cron -w 0,1 -d 14,15,16 -m 8, 9 for (int month : months_) { if (future_date.month() == month) { month_matches = true; break; } } // if it all matches, then return the future day // Remember we *AND* across -w, -d, -m or *OR* for each element in -w, -d,-m if ((week_day_matches || the_last_week_day_of_month_matches) && day_of_month_matches && month_matches) { break; // return future_date, replaced with break to keep HPUX compiler happy // otherwise it complains that return at the end of the function is // unreachable } future_date += one_day; #ifdef DEBUG_CRON_SIM cout << "future_date " << to_simple_string(future_date) << "\n"; #endif } return future_date; // should never happen, i.e. we can find future date that matches } //========================================================================================================= // code for parsing a cron: static bool isComment(const std::string& token) { if (token.find("#") == std::string::npos) { return false; } return true; } static bool isTimeSpec(const std::string& token) { if (token.find(ecf::string_constants::colon) == std::string::npos) { return false; } return true; } static bool isOption(const std::string& token) { if (token.find("-w") != std::string::npos) { return true; } if (token.find("-d") != std::string::npos) { return true; } if (token.find("-m") != std::string::npos) { return true; } return false; } static std::string nextToken(size_t& index, const std::vector& lineTokens) { assert(index < lineTokens.size()); index++; if (index < lineTokens.size()) { #ifdef DEBUG_CRON_PARSING cerr << "nextToken lineTokens[" << index << "] = " << lineTokens[index] << "\n"; #endif return lineTokens[index]; } #ifdef DEBUG_CRON_PARSING cerr << "nextToken empty \n"; #endif return std::string(); } std::string extract_list(size_t& index, const std::vector& lineTokens) { // cron -w 0,1L,2L,3 -d 1,12,14,L -m 5,6,7,8 10:00 20:00 01:00 assert(index < lineTokens.size()); // Collate the list of integers, these may have been separated by spaces // since we stop on option or time spec, the top level code should decrement index std::string theIntList; while (index < lineTokens.size() && (!isOption(lineTokens[index]) || !isTimeSpec(lineTokens[index]))) { std::string theNextToken = nextToken(index, lineTokens); if (theNextToken.empty()) { break; } if (isOption(theNextToken)) { break; } if (isTimeSpec(theNextToken)) { break; } theIntList += theNextToken; } #ifdef DEBUG_CRON_PARSING cerr << "theIntList = " << theIntList << "\n"; #endif return theIntList; } std::vector extract_month(size_t& index, const std::vector& lineTokens, const std::string& option) { // cron -w 0,1L,2L,3 -d 1,12,14,L -m 5,6,7,8 10:00 20:00 01:00 assert(index < lineTokens.size()); // Collate the list of integers, these may have been separated by spaces // since we stop on option or time spec, the top level code should decrement index std::string theIntList = extract_list(index, lineTokens); // should have 0,1,2,3 std::vector theIntVec; boost::char_separator sep(",", nullptr, boost::drop_empty_tokens); using tokenizer = boost::tokenizer>; tokenizer theTokenizer(theIntList, sep); for (tokenizer::iterator beg = theTokenizer.begin(); beg != theTokenizer.end(); ++beg) { std::string theIntToken = *beg; ecf::algorithm::trim(theIntToken); if (theIntToken.empty()) { continue; } try { auto theInt = ecf::convert_to(theIntToken); theIntVec.push_back(theInt); } catch (const ecf::bad_conversion&) { throw std::runtime_error(MESSAGE("Invalid cron option: " << option)); } } return theIntVec; } void extract_days_of_week(size_t& index, const std::vector& lineTokens, const std::string& option, std::vector& days_of_week, std::vector& last_week_days_of_month) { // cron -w 0,1L,2L,3 10:00 20:00 01:00 assert(index < lineTokens.size()); // Collate the list of integers, these may have been separated by spaces // since we stop on option or time spec, the top level code should decrement index std::string theIntList = extract_list(index, lineTokens); // should have 0,1,2,3,4L boost::char_separator sep(",", nullptr, boost::drop_empty_tokens); using tokenizer = boost::tokenizer>; tokenizer theTokenizer(theIntList, sep); for (tokenizer::iterator beg = theTokenizer.begin(); beg != theTokenizer.end(); ++beg) { std::string theIntToken = *beg; ecf::algorithm::trim(theIntToken); if (theIntToken.empty()) { continue; } try { if (theIntToken.size() == 2) { if (theIntToken[1] != 'L') { throw std::runtime_error(MESSAGE("Invalid cron option: " << option << " " << theIntToken)); } auto theInt = ecf::convert_to(theIntToken[0]); last_week_days_of_month.push_back(theInt); } else { auto theInt = ecf::convert_to(theIntToken); days_of_week.push_back(theInt); } } catch (const ecf::bad_conversion&) { throw std::runtime_error(MESSAGE("Invalid cron option: " << option)); } } } void extract_days_of_month(size_t& index, const std::vector& lineTokens, const std::string& option, std::vector& days_of_month, bool& last_day_of_month) { // cron -d 1,12,14,L 10:00 20:00 01:00 assert(index < lineTokens.size()); // Collate the list of integers, these may have been separated by spaces // since we stop on option or time spec, the top level code should decrement index std::string theIntList = extract_list(index, lineTokens); // should have 0,1,2,3,4L boost::char_separator sep(",", nullptr, boost::drop_empty_tokens); using tokenizer = boost::tokenizer>; tokenizer theTokenizer(theIntList, sep); for (tokenizer::iterator beg = theTokenizer.begin(); beg != theTokenizer.end(); ++beg) { std::string theIntToken = *beg; ecf::algorithm::trim(theIntToken); if (theIntToken.empty()) { continue; } try { if (theIntToken == "L") { last_day_of_month = true; } else { auto theInt = ecf::convert_to(theIntToken); days_of_month.push_back(theInt); } } catch (const ecf::bad_conversion&) { throw std::runtime_error(MESSAGE("Invalid cron option: " << option)); } } } void extractOption(CronAttr& cronAttr, size_t& index, const std::vector& lineTokens) { assert(index < lineTokens.size()); if (lineTokens[index] == "-w") { std::vector days_of_week; std::vector last_week_days_of_month; extract_days_of_week(index, lineTokens, "week days", days_of_week, last_week_days_of_month); cronAttr.addWeekDays(days_of_week); cronAttr.add_last_week_days_of_month(last_week_days_of_month); } else if (lineTokens[index] == "-d") { std::vector days_of_month; bool last_day_of_month = false; extract_days_of_month(index, lineTokens, "Days of the month", days_of_month, last_day_of_month); cronAttr.addDaysOfMonth(days_of_month); if (last_day_of_month) { cronAttr.add_last_day_of_month(); } } else if (lineTokens[index] == "-m") { cronAttr.addMonths(extract_month(index, lineTokens, "Months")); } else { throw std::runtime_error("extractOption: Invalid cron option :" + lineTokens[index]); } } void CronAttr::parse(CronAttr& cronAttr, const std::vector& lineTokens, size_t index, bool parse_state) { // cron 23:00 # run every day at 23:00 // cron 10:00 20:00 01:00 # run every hour between 10am and 8pm // cron -w 0,1 10:00 # run every sunday and monday at 10am // cron -d 10,11,12 12:00 # run 10th, 11th and 12th of each month at noon // cron -m 1,2,3 12:00 # run on Jan,Feb and March every day at noon. // cron -w 0 -m 5,6,7,8 10:00 20:00 01:00 # run every sunday, between May-Aug, every hour between 10am and 8pm // cron -w 0,1,2L -d 5,6,L 23:00 # run every sunday,monday, and last tuesday of the month, 5,6 on month, and *last* // day of month @11 pm // make *sure* a time spec is specified bool time_spec_specified = false; size_t line_tokens_size = lineTokens.size(); while (index < line_tokens_size) { const std::string& token = lineTokens[index]; #ifdef DEBUG_CRON_PARSING cerr << "CronAttr::doParse " << token << "\n"; #endif if (isOption(token)) { #ifdef DEBUG_CRON_PARSING cerr << "CronAttr::doParse isOption \n"; #endif extractOption(cronAttr, index, lineTokens); index--; // since we did a look ahead } else if (!time_spec_specified && isTimeSpec(token)) { #ifdef DEBUG_CRON_PARSING cerr << "CronAttr::doParse isTimeSpec \n"; #endif // index is passed by *reference*, and used skip over time series cronAttr.addTimeSeries(TimeSeries::create(index, lineTokens, parse_state)); time_spec_specified = true; if (parse_state) { // if index is on the comment, back track, so that we can add cron state( free) if (index < line_tokens_size && lineTokens[index] == "#") { index--; } } else { break; // need to read state after comment } } else if (isComment(token)) { // cron -m 1,2,3 12:00 # free if (parse_state && index + 1 < line_tokens_size) { if (lineTokens[index + 1] == "free") { cronAttr.setFree(); } } break; } index++; } if (!time_spec_specified) { throw std::runtime_error("Invalid cron, no time specified"); } #ifdef DEBUG_CRON_PARSING cronAttr.print(cerr); cerr << "\n"; #endif } CronAttr CronAttr::create(const std::string& cronString) { std::vector lineTokens; ecf::algorithm::split_at(lineTokens, cronString); CronAttr theCronAttr; if (lineTokens.empty()) { return theCronAttr; } // adjust the index size_t index = 0; if (lineTokens[0] == "cron") { index = 1; } parse(theCronAttr, lineTokens, index); return theCronAttr; } template void CronAttr::serialize(Archive& ar, std::uint32_t const version) { ar(CEREAL_NVP(timeSeries_)); CEREAL_OPTIONAL_NVP(ar, weekDays_, [this]() { return !weekDays_.empty(); }); // conditionally save CEREAL_OPTIONAL_NVP( ar, last_week_days_of_month_, [this]() { return !last_week_days_of_month_.empty(); }); // conditionally save CEREAL_OPTIONAL_NVP(ar, daysOfMonth_, [this]() { return !daysOfMonth_.empty(); }); // conditionally save CEREAL_OPTIONAL_NVP(ar, months_, [this]() { return !months_.empty(); }); // conditionally save CEREAL_OPTIONAL_NVP(ar, free_, [this]() { return free_; }); // conditionally save CEREAL_OPTIONAL_NVP(ar, last_day_of_month_, [this]() { return last_day_of_month_; }); // conditionally save CEREAL_OPTIONAL_NVP(ar, w_, [this]() { return w_ != 0; }); // conditionally save } CEREAL_TEMPLATE_SPECIALIZE_V(CronAttr); } // namespace ecf ecflow-5.17.0/libs/attribute/src/ecflow/attribute/GenericAttr.hpp0000664000175000017500000000337315211533244025202 0ustar alastairalastair/* * 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. */ #ifndef ecflow_attribute_GenericAttr_HPP #define ecflow_attribute_GenericAttr_HPP #include #include namespace cereal { class access; } // Class GenericAttr: // Use compiler , generated destructor, assignment, copy constructor // GenericAttr does *not* have any changeable state class GenericAttr { public: GenericAttr(const std::string& name, const std::vector& values); explicit GenericAttr(const std::string& name); GenericAttr() = default; bool operator==(const GenericAttr& rhs) const; bool operator<(const GenericAttr& rhs) const { return name_ < rhs.name(); } bool empty() const { return name_.empty(); } const std::string& name() const { return name_; } const std::vector& values() const { return values_; } std::vector::const_iterator values_begin() const { return values_.begin(); } // for python std::vector::const_iterator values_end() const { return values_.end(); } // for python std::string to_string() const; // Added to support return by reference static const GenericAttr& EMPTY(); public: void write(std::string& ret) const; private: std::string name_; std::vector values_; friend class cereal::access; template void serialize(Archive& ar); }; #endif /* ecflow_attribute_GenericAttr_HPP */ ecflow-5.17.0/libs/attribute/src/ecflow/attribute/QueueAttr.cpp0000664000175000017500000002132215211533244024677 0ustar alastairalastair/* * 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 "ecflow/attribute/QueueAttr.hpp" #include #include "ecflow/core/Ecf.hpp" #include "ecflow/core/Extract.hpp" #include "ecflow/core/Message.hpp" #include "ecflow/core/Serialization.hpp" #include "ecflow/core/Str.hpp" using namespace ecf; ///////////////////////////////////////////////////////////////////////////////////////////// const QueueAttr& QueueAttr::EMPTY() { static const QueueAttr queueAttr = QueueAttr(); return queueAttr; } QueueAttr& QueueAttr::EMPTY1() { static QueueAttr queueAttr = QueueAttr(); return queueAttr; } QueueAttr::QueueAttr(const std::string& name, const std::vector& theQueue) : theQueue_(theQueue), name_(name) { std::string msg; if (!ecf::algorithm::is_valid_name(name, msg)) { throw std::runtime_error("QueueAttr::QueueAttr: Invalid queue name : " + msg); } if (theQueue.empty()) { throw std::runtime_error("QueueAttr::QueueAttr: No queue items specified"); } for (size_t i = 0; i < theQueue.size(); i++) { state_vec_.push_back(NState::QUEUED); } } QueueAttr::~QueueAttr() = default; bool QueueAttr::operator==(const QueueAttr& rhs) const { if (name_ != rhs.name_) { return false; } if (theQueue_ != rhs.theQueue_) { return false; } if (state_vec_ != rhs.state_vec_) { return false; } if (currentIndex_ != rhs.currentIndex_) { return false; } return true; } std::string QueueAttr::value() const { if (currentIndex_ >= 0 && currentIndex_ < static_cast(theQueue_.size())) { return theQueue_[currentIndex_]; } return ""; } int QueueAttr::index_or_value() const { if (currentIndex_ >= 0 && currentIndex_ < static_cast(theQueue_.size())) { try { return ecf::convert_to(theQueue_[currentIndex_]); } catch (ecf::bad_conversion&) { // Ignore and return currentIndex_ } } return currentIndex_; } NState::State QueueAttr::state(const std::string& step) const { for (size_t i = 0; i < theQueue_.size(); i++) { if (step == theQueue_[i]) { if (i >= state_vec_.size()) { throw std::runtime_error("QueueAttr::state: index out of range"); } return state_vec_[i]; } } throw std::runtime_error("QueueAttr::state: could not find step " + step); } void QueueAttr::requeue() { currentIndex_ = 0; for (auto& i : state_vec_) { i = NState::QUEUED; } incr_state_change_no(); } std::string QueueAttr::active() { if (currentIndex_ >= 0 && currentIndex_ < static_cast(theQueue_.size())) { state_vec_[currentIndex_] = NState::ACTIVE; std::string ret = theQueue_[currentIndex_]; currentIndex_++; incr_state_change_no(); return ret; } return ""; } void QueueAttr::complete(const std::string& step) { for (size_t i = 0; i < theQueue_.size(); i++) { if (step == theQueue_[i]) { state_vec_[i] = NState::COMPLETE; incr_state_change_no(); return; } } throw std::runtime_error(MESSAGE("QueueAttr::complete: Could not find " << step << " in queue " << name_)); } void QueueAttr::aborted(const std::string& step) { for (size_t i = 0; i < theQueue_.size(); i++) { if (step == theQueue_[i]) { state_vec_[i] = NState::ABORTED; incr_state_change_no(); return; } } throw std::runtime_error(MESSAGE("QueueAttr::aborted: Could not find " << step << " in queue " << name_)); } std::string QueueAttr::no_of_aborted() const { int count = 0; for (auto i : state_vec_) { if (i == NState::ABORTED) { count++; } } if (count != 0) { return ecf::convert_to(count); } return std::string{}; } void QueueAttr::reset_index_to_first_queued_or_aborted() { for (size_t i = 0; i < state_vec_.size(); i++) { if (state_vec_[i] == NState::QUEUED || state_vec_[i] == NState::ABORTED) { currentIndex_ = i; incr_state_change_no(); break; } } } std::string QueueAttr::toString() const { std::string ret; write(ret); return ret; } void QueueAttr::write(std::string& ret) const { ret += "queue "; ret += name_; for (const auto& i : theQueue_) { ret += " "; ret += i; } } std::string QueueAttr::dump() const { std::ostringstream ss; ss << toString() << " # " << currentIndex_; for (auto i : state_vec_) { ss << " " << i; } return ss.str(); } void QueueAttr::incr_state_change_no() { state_change_no_ = Ecf::incr_state_change_no(); } void QueueAttr::parse(QueueAttr& queAttr, const std::string& line, std::vector& lineTokens, bool parse_state) { size_t line_tokens_size = lineTokens.size(); if (line_tokens_size < 3) { throw std::runtime_error(MESSAGE("QueueAttr::parse: expected at least 3 tokens, found " << line_tokens_size << " on line:" << line << "\n")); } // queue name "first" "second" "last" # current_index state state state // 0 1 2 3 4 5 6 queAttr.set_name(lineTokens[1]); std::vector theEnums; theEnums.reserve(line_tokens_size); for (size_t i = 2; i < line_tokens_size; i++) { std::string theEnum = lineTokens[i]; if (theEnum[0] == '#') { break; } // 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()) { throw std::runtime_error("queue: has no values " + line); } int index = 0; std::vector state_vec; if (parse_state) { // queue VARIABLE a b c d # index active complete aborted queued for (size_t i = 3; i < line_tokens_size; i++) { if (lineTokens[i] == "#" && i + 1 < line_tokens_size) { i++; index = Extract::value(lineTokens[i], "QueueAttr::parse, could not extract index"); i++; for (; i < line_tokens_size; i++) { NState::State state = NState::toState(lineTokens[i]); state_vec.push_back(state); } break; } } } queAttr.set_queue(theEnums, index, state_vec); } void QueueAttr::set_queue(const std::vector& theQueue, int index, const std::vector& state_vec) { if (theQueue.empty()) { throw std::runtime_error("QueueAttr::set_queue: No queue items specified"); } if (!state_vec.empty()) { if (state_vec.size() != theQueue.size()) { throw std::runtime_error(MESSAGE("QueueAttr::set_state: for queue " << name_ << " size " << theQueue.size() << " does not match state size " << state_vec.size())); } state_vec_ = state_vec; } else { for (size_t i = 0; i < theQueue.size(); i++) { state_vec_.push_back(NState::QUEUED); } } currentIndex_ = index; theQueue_ = theQueue; } void QueueAttr::set_state_vec(const std::vector& state_vec) { state_vec_ = state_vec; if (theQueue_.size() != state_vec_.size()) { std::cout << "QueueAttr::set_state_vec: for queue " << name_ << " queue size " << theQueue_.size() << " not equal to state_vec size " << state_vec_.size() << "\n"; } } void QueueAttr::set_name(const std::string& name) { std::string msg; if (!ecf::algorithm::is_valid_name(name, msg)) { throw std::runtime_error("QueueAttr::set_name: Invalid queue name : " + msg); } name_ = name; } template void QueueAttr::serialize(Archive& ar, std::uint32_t const version) { ar(CEREAL_NVP(theQueue_), CEREAL_NVP(state_vec_), CEREAL_NVP(name_), CEREAL_NVP(currentIndex_)); } CEREAL_TEMPLATE_SPECIALIZE_V(QueueAttr); ecflow-5.17.0/libs/attribute/src/ecflow/attribute/DayAttr.cpp0000664000175000017500000003523315211533244024336 0ustar alastairalastair/* * 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 "ecflow/attribute/DayAttr.hpp" #include #include #include #include "ecflow/core/Calendar.hpp" #include "ecflow/core/Ecf.hpp" #include "ecflow/core/Extract.hpp" #include "ecflow/core/Message.hpp" #include "ecflow/core/Serialization.hpp" #include "ecflow/core/cereal_boost_time.hpp" using namespace ecf; // #define DEBUG_DAYS 1 //=============================================================================== static const char* theDay(DayAttr::Day_t day) { switch (day) { case DayAttr::SUNDAY: return "sunday"; case DayAttr::MONDAY: return "monday"; case DayAttr::TUESDAY: return "tuesday"; case DayAttr::WEDNESDAY: return "wednesday"; case DayAttr::THURSDAY: return "thursday"; case DayAttr::FRIDAY: return "friday"; case DayAttr::SATURDAY: return "saturday"; default: assert(false); } return nullptr; } //=============================================================================== void DayAttr::calendarChanged(const ecf::Calendar& c, bool clear_at_midnight) { // See ECFLOW-337 // repeat .... // family start // family 0 // time 10:00 // day monday # if there was no c.dayChanged(), then after re-queue, & before midnight Monday is still // free. task dummy # hence we will end up also running on Tuesday at 10:00 // complete 1==1 // trigger 0==1 // // ECFLOW-1550 # If children of a family with day/date are still active/submitted/queued, then don't // clear at midnight. repeat .... # This is only applicable for NodeContainers, for task with day/date // always CLEAR at midnight // family f1 // day monday // time 23:00 // task t1 # Task t1 took longer than 1 hour // task t2 # allow task t2 to continue to the next day, i.e. clear_at_midnight = False // trigger t1 == complete #ifdef DEBUG_DAYS cout << " DayAttr::calendarChanged " << dump() << " clear_at_midnight " << clear_at_midnight << " calendar:" << c.suite_time_str() << "\n"; #endif if (expired_) { // ********* TREAT this Day Attribute as deleted ECFLOW-128 ********** return; } if (c.dayChanged()) { if (clear_at_midnight) { clearFree(); #ifdef DEBUG_DAYS cout << " DayAttr::calendarChanged MIDNIGHT " << dump() << " calendar:" << c.suite_time_str() << "\n"; #endif } } if (free_) { return; } if (is_free(c)) { setFree(); #ifdef DEBUG_DAYS cout << " DayAttr::calendarChanged SET FREE " << dump() << " calendar:" << c.suite_time_str() << "\n"; #endif } } void DayAttr::reset() { expired_ = false; free_ = false; date_ = boost::gregorian::date(); state_change_no_ = Ecf::incr_state_change_no(); #ifdef DEBUG_DAYS cout << " DayAttr::reset :" << dump() << "\n"; #endif } void DayAttr::handle_migration(const ecf::Calendar& c) { // temp once in Bologna, and when ecflow4 no longer used. if (date_.is_special()) { if (!c.is_special()) { date_ = matching_date(c); } } } void DayAttr::reset(const ecf::Calendar& c) { reset(); date_ = matching_date(c); #ifdef DEBUG_DAYS cout << " DayAttr::reset(calendar) :" << dump() << " calendar:" << c.suite_time_str() << "\n"; #endif } void DayAttr::requeue_time() { #ifdef DEBUG_DAYS cout << " DayAttr::requeue " << dump() << "\n"; #endif if (expired_) { // ********* TREAT this Day Attribute as deleted ********** #ifdef DEBUG_DAYS cout << " DayAttr::requeue_time " << dump() << " EXPIRED(do nothing) returning\n"; #endif return; } free_ = false; state_change_no_ = Ecf::incr_state_change_no(); } void DayAttr::requeue_manual(const ecf::Calendar& c) { reset(); date_ = matching_date(c); #ifdef DEBUG_DAYS cout << " DayAttr::requeue_manual(calendar) " << dump() << " calendar:" << c.suite_time_str() << "\n"; #endif } void DayAttr::requeue_repeat_increment(const ecf::Calendar& c) { reset(); date_ = next_matching_date(c); #ifdef DEBUG_DAYS cout << " DayAttr::requeue_repeat_increment(calendar) " << dump() << " calendar:" << c.suite_time_str() << "\n"; #endif } bool DayAttr::isFree(const ecf::Calendar& c) const { if (expired_) { // ********* TREAT this Day Attribute as deleted ********** #ifdef DEBUG_DAYS cout << " DayAttr::isFree " << dump() << " calendar:" << c.suite_time_str() << " HOLDING due to EXPIRED flag\n"; #endif return false; } // The FreeDepCmd can be used to free the dates, if (free_) { #ifdef DEBUG_DAYS cout << " DayAttr::isFree " << dump() << " calendar:" << c.suite_time_str() << " FREE free_ = TRUE\n"; #endif return true; } bool res = is_free(c); #ifdef DEBUG_DAYS if (res) { cout << " DayAttr::isFree " << dump() << " calendar:" << c.suite_time_str() << " date is FREE\n"; } else { cout << " DayAttr::isFree " << dump() << " calendar:" << c.suite_time_str() << " date is HOLDING\n"; } #endif return res; } bool DayAttr::is_free(const ecf::Calendar& c) const { return (c.date() == date_); } void DayAttr::setFree() { free_ = true; state_change_no_ = Ecf::incr_state_change_no(); #ifdef DEBUG_DAYS cout << " DayAttr::setFree() " << dump() << "\n"; #endif } void DayAttr::clearFree() { free_ = false; state_change_no_ = Ecf::incr_state_change_no(); #ifdef DEBUG_DAYS cout << " DayAttr::clearFree() " << dump() << "\n"; #endif } void DayAttr::set_expired() { expired_ = true; state_change_no_ = Ecf::incr_state_change_no(); #ifdef DEBUG_DAYS cout << " DayAttr::set_expired() " << dump() << "\n"; #endif } void DayAttr::clear_expired() { expired_ = false; state_change_no_ = Ecf::incr_state_change_no(); #ifdef DEBUG_DAYS cout << " DayAttr::clear_expired() " << dump() << "\n"; #endif } void DayAttr::check_for_expiration(const ecf::Calendar& c) { #ifdef DEBUG_DAYS cout << "DayAttr::check_for_expiration " << dump() << " calendar:" << c.suite_time_str() << "\n"; #endif // This function is called when a Node has COMPLETED. Avoid running again on same day date <= calendar, expire the // date Note: time attribute, they get handled before. i.e. allowing multiple re-queues on the same date This // function *MUST* be called before checkForRequeue. if (date_.is_special()) { // migration 4->5, or 5->5 from checkpoint date_ = matching_date(c); } if (day_ == c.day_of_week()) { set_expired(); return; } if (date_ <= c.date()) { set_expired(); } } bool DayAttr::checkForRequeue(const ecf::Calendar& c) const { if (expired_) { #ifdef DEBUG_DAYS cout << " DayAttr::check_for_requeue ALREADY EXPIRED " << dump() << " calendar:" << c.suite_time_str() << " HOLDING <<<<<<<<<<<<\n"; #endif return false; } // if calendar is hybrid, we can't requeue if (c.hybrid()) { return false; } // checkForRequeue is called when we are deciding whether to re-queue the node // Hence we *MUST* have completed. Also, all cron, time, and today attributes have returned false. // *IF* this date is in the future, we should re-queue assert(!date_.is_special()); bool future_date = (date_ > c.date()); #ifdef DEBUG_DAYS cout << " DayAttr::check_for_requeue " << dump() << " calendar:" << c.suite_time_str() << " returning " << future_day << " ************\n"; #endif return future_date; } bool DayAttr::validForHybrid(const ecf::Calendar& calendar) const { return isFree(calendar); } bool DayAttr::why(const ecf::Calendar& c, std::string& theReasonWhy) const { if (isFree(c)) { return false; } theReasonWhy += " is day dependent ( next run on "; theReasonWhy += theDay(day_); theReasonWhy += " "; if (date_.is_special()) { theReasonWhy += to_simple_string(next_matching_date(c)); } else { theReasonWhy += to_simple_string(date_); } theReasonWhy += " the current day is "; theReasonWhy += theDay(static_cast(c.day_of_week())); theReasonWhy += " )"; return true; } std::string DayAttr::name() const { // for display/GUI only std::string os; write(os); bool added_hash = false; if (expired_) { os += " # expired"; added_hash = true; } else { if (free_) { os += " # free"; added_hash = true; } } if (added_hash) { os += " "; os += to_simple_string(date_); } else { os += " # "; os += to_simple_string(date_); } return os; } std::string DayAttr::toString() const { std::string ret; write(ret); return ret; } void DayAttr::write(std::string& ret) const { ret += "day "; ret += theDay(day_); } std::string DayAttr::dump() const { return MESSAGE(toString() << (free_ ? " (free)" : "") << (expired_ ? " (expired)" : "") << " " << as_simple_string()); } std::string DayAttr::as_simple_string() const { return boost::gregorian::to_simple_string(date_); } bool DayAttr::operator==(const DayAttr& rhs) const { if (free_ != rhs.free_) { #ifdef DEBUG if (Ecf::debug_equality()) { std::cout << "DayAttr::operator== free_ != rhs.free_ (free_:" << free_ << " rhs.free_:" << rhs.free_ << ") " << dump() << "\n"; } #endif return false; } if (expired_ != rhs.expired_) { #ifdef DEBUG if (Ecf::debug_equality()) { std::cout << "DayAttr::operator== expired_ != rhs.expired_ (expired_:" << expired_ << " rhs.expired_:" << rhs.expired_ << ") " << dump() << "\n"; } #endif return false; } if (date_ != rhs.date_) { #ifdef DEBUG if (Ecf::debug_equality()) { std::cout << "DayAttr::operator== date_ != rhs.date_ (date_:" << date_ << " rhs.date_:" << rhs.date_ << ") " << dump() << "\n"; } #endif return false; } return structureEquals(rhs); } bool DayAttr::structureEquals(const DayAttr& rhs) const { return (day_ == rhs.day_); } DayAttr DayAttr::create(const std::string& dayStr) { return DayAttr(getDay(dayStr)); } DayAttr DayAttr::create(const std::vector& lineTokens, bool read_state) { if (lineTokens.size() < 2) { throw std::runtime_error("DayAttr::create date tokens to short :"); } // for(size_t i =0; i < lineTokens.size() ; i++) { // cout << "lineTokens[" << i << "] = '" << lineTokens[i] << "'\n"; // } // day monday # free expired date:**** // day monday # expired DayAttr day = DayAttr::create(lineTokens[1]); // state if (read_state) { day.read_state(lineTokens); } return day; } void DayAttr::read_state(const std::vector& lineTokens) { std::string date; for (size_t i = 3; i < lineTokens.size(); i++) { if (lineTokens[i] == "free") { free_ = true; } else if (lineTokens[i] == "expired") { expired_ = true; } else if (lineTokens[i].find("date:") != std::string::npos) { if (!Extract::split_get_second(lineTokens[i], date)) { throw std::runtime_error("DayAttr::read_state failed: (date:)"); } // when a date_ is special date = not-a-date-time\n" if (date.find("not") == std::string::npos) { date_ = boost::gregorian::from_simple_string(date); } } } } DayAttr::Day_t DayAttr::getDay(const std::string& day) { if (day == "monday") { return DayAttr::MONDAY; } if (day == "tuesday") { return DayAttr::TUESDAY; } if (day == "wednesday") { return DayAttr::WEDNESDAY; } if (day == "thursday") { return DayAttr::THURSDAY; } if (day == "friday") { return DayAttr::FRIDAY; } if (day == "saturday") { return DayAttr::SATURDAY; } if (day == "sunday") { return DayAttr::SUNDAY; } throw std::runtime_error(MESSAGE( "Invalid day(" << day << ") specification expected one of [monday,tuesday,wednesday,thursday,friday,saturday,sunday]: ")); } std::vector DayAttr::allDays() { std::vector vec; vec.reserve(7); vec.emplace_back("monday"); vec.emplace_back("tuesday"); vec.emplace_back("wednesday"); vec.emplace_back("thursday"); vec.emplace_back("friday"); vec.emplace_back("saturday"); vec.emplace_back("sunday"); return vec; } boost::gregorian::date DayAttr::matching_date(const ecf::Calendar& c) const { auto one_day = boost::gregorian::date_duration(1); auto matching_date = c.date(); // today's date for (int i = 0; i < 7; i++) { if (matching_date.day_of_week().as_number() == day_) { return matching_date; } matching_date += one_day; } assert(false); // no matching day ?s return c.date(); } boost::gregorian::date DayAttr::next_matching_date(const ecf::Calendar& c) const { auto one_day = boost::gregorian::date_duration(1); auto the_next_matching_date = c.date(); // today's date for (int i = 0; i < 7; i++) { the_next_matching_date += one_day; if (the_next_matching_date.day_of_week().as_number() == day_) { return the_next_matching_date; } } assert(false); return c.date(); } template void DayAttr::serialize(Archive& ar) { ar(CEREAL_NVP(day_)); CEREAL_OPTIONAL_NVP(ar, free_, [this]() { return free_; }); // conditionally save CEREAL_OPTIONAL_NVP(ar, expired_, [this]() { return expired_; }); // conditionally save, new to ecflow 5.4.0, should be ignored by old clients. see tests CEREAL_OPTIONAL_NVP(ar, date_, [this]() { return !date_.is_special(); }); // conditionally save, new to ecflow 5.5.0, should be ignored by old clients. see tests } CEREAL_TEMPLATE_SPECIALIZE(DayAttr); ecflow-5.17.0/libs/attribute/src/ecflow/attribute/ClockAttr.hpp0000664000175000017500000000630615211533244024660 0ustar alastairalastair/* * 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. */ #ifndef ecflow_attribute_ClockAttr_HPP #define ecflow_attribute_ClockAttr_HPP #include #include "ecflow/core/Chrono.hpp" namespace cereal { class access; } namespace ecf { class Calendar; } // namespace ecf /// The clock attribute is defined on the suite ONLY /// Use default copy constructor and assignment operator, destructor /// The clock attribute is used to initialise the calendar object /// ********************************************************************** /// In the OLD sms the date is actually used as a gain factor(well at least /// according to the user/reference manual), in the ec-flow /// a date, is a date. i.e. it allows us to start a suite in the past. /// /// The Constructor will update the State change number, since it can be added /// by the AlterCmd. in the Client Context, state change number is not incremented /// ************************************************************************ /// class ClockAttr { public: /// The following constructor is used for test only. It allows us to /// create a clock attribute initialised with given date and time explicit ClockAttr(const boost::posix_time::ptime&, bool hybrid = false, bool positiveGain = true); ClockAttr(int day, int month, int year, bool hybrid = false); explicit ClockAttr(bool hybrid = false); bool operator==(const ClockAttr& rhs) const; void date(int day, int month, int year); void set_gain(int hour, int min, bool positiveGain = true); void set_gain_in_seconds(long theGain, bool positiveGain = true); void hybrid(bool f); void set_end_clock() { end_clock_ = true; } // clear local attributes so that, when the suite is requeued, the computer clock is synced void sync(); void init_calendar(ecf::Calendar&); void begin_calendar(ecf::Calendar&) const; // The state_change_no is never reset. Must be incremented if it can affect equality unsigned int state_change_no() const { return state_change_no_; } // access int day() const { return day_; } int month() const { return month_; } int year() const { return year_; } long gain() const { return gain_; } bool positive_gain() const { return positiveGain_; } bool hybrid() const { return hybrid_; } std::string toString() const; boost::posix_time::ptime ptime() const; public: void write(std::string& os) const; private: long gain_{0}; // in seconds int day_{0}; int month_{0}; int year_{0}; unsigned int state_change_no_{0}; // *not* persisted, only used on server side bool hybrid_{false}; bool positiveGain_{false}; bool end_clock_{false}; // *NOT* persisted, used for end clock, simulator only friend class cereal::access; template void serialize(Archive& ar, std::uint32_t const version); }; #endif /* ecflow_attribute_ClockAttr_HPP */ ecflow-5.17.0/libs/attribute/src/ecflow/attribute/AutoArchiveAttr.cpp0000664000175000017500000000737515211533244026041 0ustar alastairalastair/* * 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 "ecflow/attribute/AutoArchiveAttr.hpp" #include "ecflow/core/Calendar.hpp" #include "ecflow/core/Chrono.hpp" #include "ecflow/core/Converter.hpp" #include "ecflow/core/Log.hpp" #include "ecflow/core/Serialization.hpp" namespace ecf { std::string AutoArchiveAttr::toString() const { std::string ret; write(ret); return ret; } void AutoArchiveAttr::write(std::string& ret) const { ret += "autoarchive "; if (days_) { ret += ecf::convert_to(time_.hour() / 24); if (idle_) { ret += " -i"; } return; } if (relative_) { ret += "+"; } time_.print(ret); if (idle_) { ret += " -i"; } } bool AutoArchiveAttr::operator==(const AutoArchiveAttr& rhs) const { if (relative_ != rhs.relative_) { return false; } if (days_ != rhs.days_) { return false; } if (idle_ != rhs.idle_) { return false; } return time_.operator==(rhs.time_); } bool AutoArchiveAttr::isFree( const ecf::Calendar& calendar, const std::pair& last_state_and_change_duration) const { // suiteTime() // suiteDurationAtComplete autoarchive time calendar duration // | | | // V V V // ----------------------------------------------------------------------------------> time // ^ ^ // |--------elapsed time---------------------------------------| // // bool is_valid_state = false; if (last_state_and_change_duration.first == NState::COMPLETE) { is_valid_state = true; } if (idle_) { if (last_state_and_change_duration.first == NState::QUEUED) { is_valid_state = true; } if (last_state_and_change_duration.first == NState::ABORTED) { is_valid_state = true; } } if (!is_valid_state) { return false; } if (relative_) { boost::posix_time::time_duration time_elapsed = calendar.duration() - last_state_and_change_duration.second; LOG_ASSERT(!time_elapsed.is_negative(), "should always be positive or some things gone wrong"); if (time_elapsed >= time_.duration()) { return true; } } else { // real time // #ifdef DEBUG // cout << "real time time_(" << to_simple_string(time_.duration()) // << ") calendar.suiteTime().time_of_day(" << to_simple_string(calendar.suiteTime().time_of_day()) << // ")\n"; // #endif if (calendar.suiteTime().time_of_day() >= time_.duration()) { return true; } } return false; } template void AutoArchiveAttr::serialize(Archive& ar, std::uint32_t const version) { ar(CEREAL_NVP(time_)); CEREAL_OPTIONAL_NVP(ar, relative_, [this]() { return !relative_; }); // conditionally save CEREAL_OPTIONAL_NVP(ar, days_, [this]() { return days_; }); // conditionally save CEREAL_OPTIONAL_NVP(ar, idle_, [this]() { return idle_; }); // conditionally save } CEREAL_TEMPLATE_SPECIALIZE_V(AutoArchiveAttr); } // namespace ecf ecflow-5.17.0/libs/attribute/src/ecflow/attribute/AutoCancelAttr.cpp0000664000175000017500000000605715211533244025641 0ustar alastairalastair/* * 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 "ecflow/attribute/AutoCancelAttr.hpp" #include "ecflow/core/Calendar.hpp" #include "ecflow/core/Chrono.hpp" #include "ecflow/core/Converter.hpp" #include "ecflow/core/Log.hpp" #include "ecflow/core/Serialization.hpp" namespace ecf { std::string AutoCancelAttr::toString() const { std::string ret; write(ret); return ret; } void AutoCancelAttr::write(std::string& ret) const { ret += "autocancel "; if (days_) { ret += ecf::convert_to(time_.hour() / 24); return; } if (relative_) { ret += "+"; } time_.print(ret); } bool AutoCancelAttr::operator==(const AutoCancelAttr& rhs) const { if (relative_ != rhs.relative_) { return false; } if (days_ != rhs.days_) { return false; } return time_.operator==(rhs.time_); } bool AutoCancelAttr::isFree(const ecf::Calendar& calendar, const boost::posix_time::time_duration& suiteDurationAtComplete) const { // suiteTime() // suiteDurationAtComplete autocancel time calendar duration // | | | // V V V // ----------------------------------------------------------------------------------> time // ^ ^ // |--------elapsed time---------------------------------------| // // if (relative_) { boost::posix_time::time_duration timeElapsedAfterComplete = calendar.duration() - suiteDurationAtComplete; LOG_ASSERT(!timeElapsedAfterComplete.is_negative(), "should always be positive or some things gone wrong"); if (timeElapsedAfterComplete >= time_.duration()) { return true; } } else { // real time // #ifdef DEBUG // cout << "real time time_(" << to_simple_string(time_.duration()) // << ") calendar.suiteTime().time_of_day(" << // to_simple_string(calendar.suiteTime().time_of_day()) << ")\n"; #endif if (calendar.suiteTime().time_of_day() >= time_.duration()) { return true; } } return false; } template void AutoCancelAttr::serialize(Archive& ar, std::uint32_t const /*version*/) { ar(CEREAL_NVP(time_)); CEREAL_OPTIONAL_NVP(ar, relative_, [this]() { return !relative_; }); // conditionally save CEREAL_OPTIONAL_NVP(ar, days_, [this]() { return days_; }); // conditionally save } CEREAL_TEMPLATE_SPECIALIZE_V(AutoCancelAttr); } // namespace ecf ecflow-5.17.0/libs/attribute/src/ecflow/attribute/TodayAttr.hpp0000664000175000017500000002006415211533244024702 0ustar alastairalastair/* * 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. */ #ifndef ecflow_attribute_TodayAttr_HPP #define ecflow_attribute_TodayAttr_HPP /// /// \brief The Today attribute is heavily tied to the `begin` command /// /// Real Clock: /// 1/ Suite Begin time > Today time /// If the suite 'begin' time is past the time given for today /// the node is free to run. /// 2/ Suite Begin time < Today Time /// The node will 'hold' until current time > today time /// 3/ Suite time, has passed midnight(next day) /// then today command will permanently hold the node /// 4/ Under Real/hybrid clocks today will hold node after /// current is past last today time. /// /// Take the following example when we have a single time slot: /// today 10:00 /// isFree:-----free----- /// begin: /// V /// checkForRequeue:rrrrrrrrrrrrrrrrhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh /// isFree:hhhhhhhhhhhhhhhh|fffffffffffffffffffffffffffffffffffffff *once* free we stay free /// (single slot *only*) begin: /// V /// Today ======================0=====================0================= /// 10:00 Midnight /// /// Difference between time and today. If begin is started after the time slot /// then the node is free to re-run /// /// When we have a today time series: /// today 10:00 20:00 01:00 /// /// *** If the beginning time is past 10:00 as in the case above then the /// *** node should is free to run once. However, for a range its different /// *** if suite begin time is past 20:00 then the node is held. /// /// At 10am the Node is free, when node completes, it is re-queued /// At 11am the Node is free, when node completes, it is re-queued /// .... /// At 20pm the Node is free, when node completes, it is *NOT* re-queued. /// /// isFree is called when a node is queued. if it returns true, Task can be submitted /// checkForRequeue: is called when a node has completed, and need to determine if it should run again. /// These are different/orthogonal concerns. /// There is a *separate* issue of whether nodes should be queued when a node is *manually* /// a/ Set complete /// b/ Runs and then completes /// /// For a *single* time slot we can't requeue. /// ****However we could have a set of time slots ***** /// /// isFree:ffffffffffffffff /// Begin: /// V /// checkForRequeue:hhhhhhhhhhhhhhhhhrrrrrrrrhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh /// isFree:hhhhhhhhhhhhhhhh|fffffffffffffffffffffffffffffffffffffff /// begin : /// V /// Today ======================0========0===========0==================== /// 10:00 11:00 Midnight /// isFree:hhhhhhhhhhhhhhhhhh /// V /// CheckForRequeue:hhhhhhhrrrrrrrrrrrrrrrrrrrrrrrrrhhhhhhhhhhhhhhhhhhhhhhhhhhhhh /// isFree:hhhhhhhFhhhhFhhhhFhhhhFhhhhFhhhhFFFFFFFFFFFFFFFFFFFFFFFFFFFFF /// V | | | | | | | /// Today ================o====|====|====|====|====0========0==================== /// 10:00 1 2 3 4 15:00 Midnight /// /// If the job starts at 10:00 but takes more than 1 hour, then it will miss the 11:00 slot /// and will have to start at 12:00 /// #include "ecflow/core/TimeSeries.hpp" class DateAttr; // Used in Why class DayAttr; // Used in Why namespace ecf { class Calendar; } // namespace ecf namespace ecf { // Use compiler , destructor, assignment, copy constructor class TodayAttr { public: explicit TodayAttr(const std::string&); TodayAttr() = default; TodayAttr(int hour, int minute, bool relative = false) : ts_(hour, minute, relative) {} explicit TodayAttr(const TimeSlot& t, bool relative = false) : ts_(t, relative) {} explicit TodayAttr(const TimeSeries& ts) : ts_(ts) {} TodayAttr(const TimeSlot& start, const TimeSlot& finish, const TimeSlot& incr, bool relative = false) : ts_(start, finish, incr, relative) {} bool operator==(const TodayAttr& rhs) const; bool operator<(const TodayAttr& rhs) const { return ts_ < rhs.ts_; } bool structureEquals(const TodayAttr& rhs) const; /// This can set attribute as free, once free its stays free, until re-queue/reset void calendarChanged(const ecf::Calendar& c); void resetRelativeDuration(); void reset_only() { clearFree(); ts_.reset_only(); } void reset(const ecf::Calendar& c) { clearFree(); ts_.reset(c); } // updates state_change_no_ void requeue(const ecf::Calendar& c, bool reset_next_time_slot = true) { clearFree(); ts_.requeue(c, reset_next_time_slot); } // updates state_change_no_ void miss_next_time_slot(); // updates state_change_no_ void setFree(); // ensures that isFree() always returns true, updates state_change_no_ bool isSetFree() const { return free_; } // This is used when we have a *single* today attribute // single-slot is free, if calendar time >= today_time // (range) is free, if calendar time == (one of the time ranges) bool isFree(const ecf::Calendar&) const; // This is used when we have a *multiple* today attribute // (single | range) is free, if calendar time == (one of the time ranges) // if timer *expired* returns false // task t1 // today 09:00 // today 10:00 // If current times is 11:00, then we will return false. // since both 09:00 and 10:00 have expired // Multiple single today, should behave like a today with a range. bool isFreeMultipleContext(const ecf::Calendar& c) const { return ts_.isFree(c); } bool checkForRequeue(const ecf::Calendar& c, const TimeSlot& the_min, const TimeSlot& the_max, bool cmd_context = false) const { return ts_.checkForRequeue(c, the_min, the_max, cmd_context); } void min_max_time_slots(TimeSlot& the_min, TimeSlot& the_max) const { ts_.min_max_time_slots(the_min, the_max); } bool why(const ecf::Calendar&, const std::vector&, const std::vector&, std::string& theReasonWhy) const; bool checkInvariants(std::string& errormsg) const { return ts_.checkInvariants(errormsg); } // The state_change_no is never reset. Must be incremented if it can affect equality // Note: changes in state of ts_, i.e. affect the equality operator (used in test) // must be captured. i.e. things like relative duration & next_time_slot are // reported by the Why command, & hence need to be synced. unsigned int state_change_no() const { return state_change_no_; } std::string name() const; // for display/GUI std::string toString() const; void write(std::string&) const; // access const TimeSeries& time_series() const { return ts_; } private: void clearFree(); // resets the free flag, updates state_change_no_ bool is_free(const ecf::Calendar&) const; // ignores free_ private: TimeSeries ts_; unsigned int state_change_no_{0}; // *not* persisted, only used on server side bool free_{false}; // persisted for use by why() on client side && for state changes friend class cereal::access; template void serialize(Archive& ar); }; } // namespace ecf #endif /* ecflow_attribute_TodayAttr_HPP */ ecflow-5.17.0/libs/attribute/src/ecflow/attribute/DateAttr.hpp0000664000175000017500000000676715211533244024515 0ustar alastairalastair/* * 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. */ #ifndef ecflow_attribute_DateAttr_HPP #define ecflow_attribute_DateAttr_HPP /// /// \note calendarChanged() once a Date is free, it stays free. It relies on parent cron/repeat to re-queue /// #include #include #include "ecflow/core/Chrono.hpp" namespace cereal { class access; } namespace ecf { class Calendar; } // namespace ecf // Use default copy constructor, assignment operator, destructor // Value of 0 for day,month,year means *, i.e. means any value class DateAttr { public: DateAttr(int day, int month, int year); // will throw std::out_of_range for if invalid date explicit DateAttr(const std::string&); // will throw std::runtime_error for if invalid date DateAttr() = default; // for serialisation explicit DateAttr(const boost::gregorian::date& date) : day_(date.day()), month_(date.month()), year_(date.year()) {} // for test bool operator==(const DateAttr& rhs) const; bool operator<(const DateAttr& rhs) const; bool structureEquals(const DateAttr& rhs) const; void reset(); void requeue(); void setFree(); // ensures that isFree() always returns true void clearFree(); // resets the free flag bool isSetFree() const { return free_; } void calendarChanged(const ecf::Calendar& c, bool clear_at_midnight = true); // can set attribute free bool isFree(const ecf::Calendar&) const; bool checkForRequeue(const ecf::Calendar&) const; bool validForHybrid(const ecf::Calendar&) const; bool why(const ecf::Calendar&, std::string& theReasonWhy) const; // The state_change_no is never reset. Must be incremented if it can affect equality unsigned int state_change_no() const { return state_change_no_; } std::string name() const; // for display/gui only std::string toString() const; std::string dump() const; /// Check the date, will throw std::out_of_range or derivative if invalid static void checkDate(int day, int month, int year, bool allow_wild_cards); /// Extract the date, if return integer is zero, date was of the *, i.e. any day,month,year /// will throw std::runtime_error for parse errors /// expect: /// 15.11.2009 /// 15.*.* /// *.1.* static void getDate(const std::string& date, int& day, int& month, int& year); static DateAttr create(const std::string& dateString); static DateAttr create(const std::vector& lineTokens, bool read_state); boost::gregorian::date next_matching_date(const ecf::Calendar&) const; // access int day() const { return day_; } int month() const { return month_; } int year() const { return year_; } bool is_free(const ecf::Calendar&) const; // ignores free_ public: void write(std::string&) const; private: int day_{0}; int month_{0}; int year_{0}; unsigned int state_change_no_{0}; // *not* persisted, only used on server side bool free_{false}; // persisted for use by why() on client side friend class cereal::access; template void serialize(Archive& ar); }; #endif /* ecflow_attribute_DateAttr_HPP */ ecflow-5.17.0/libs/attribute/src/ecflow/attribute/RepeatAttr.cpp0000664000175000017500000020033315211533244025034 0ustar alastairalastair/* * 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 "ecflow/attribute/RepeatAttr.hpp" #include #include #include #include #include "ecflow/core/Calendar.hpp" #include "ecflow/core/Converter.hpp" #include "ecflow/core/Ecf.hpp" #include "ecflow/core/Log.hpp" #include "ecflow/core/Serialization.hpp" #include "ecflow/core/Str.hpp" #include "ecflow/node/formatter/DefsWriter.hpp" namespace { template std::string as_n_digits(T value) { static_assert(N > 0, "N must be greater than zero"); static_assert(std::is_integral::value, "T must be an integral type"); std::ostringstream ss; ss << std::setfill('0') << std::setw(N) << value; return ss.str(); } template std::string as_2_digits(T value) { return as_n_digits<2>(value); } template std::string as_4_digits(T value) { return as_n_digits<4>(value); } } // namespace using namespace ecf; const Repeat& Repeat::EMPTY() { static const Repeat REPEAT = Repeat(); return REPEAT; } //========================================================================= Repeat::Repeat() = default; Repeat::Repeat(const RepeatDate& r) : type_(std::make_unique(r)) { } Repeat::Repeat(const RepeatDateTime& r) : type_(std::make_unique(r)) { } Repeat::Repeat(const RepeatDateList& r) : type_(std::make_unique(r)) { } Repeat::Repeat(const RepeatDateTimeList& r) : type_(std::make_unique(r)) { } Repeat::Repeat(const RepeatInteger& r) : type_(std::make_unique(r)) { } Repeat::Repeat(const RepeatEnumerated& r) : type_(std::make_unique(r)) { } Repeat::Repeat(const RepeatString& r) : type_(std::make_unique(r)) { } Repeat::Repeat(const RepeatDay& r) : type_(std::make_unique(r)) { } Repeat::~Repeat() = default; Repeat::Repeat(const Repeat& rhs) { if (rhs.type_) { type_.reset(rhs.type_->clone()); } } Repeat::Repeat(Repeat&& rhs) : type_(std::move(rhs.type_)) { } Repeat& Repeat::operator=(Repeat rhs) { using std::swap; swap(this->type_, rhs.type_); return *this; } bool Repeat::operator==(const Repeat& rhs) const { if (!type_ && rhs.type_) { return false; } if (type_ && !rhs.type_) { return false; } if (!type_ && !rhs.type_) { return true; } return type_->compare(rhs.type_.get()); } const std::string& Repeat::name() const { return (type_.get()) ? type_->name() : ecf::string_constants::empty; } // ========================================================================= RepeatBase::~RepeatBase() = default; void RepeatBase::incr_state_change_no() { state_change_no_ = Ecf::incr_state_change_no(); #ifdef DEBUG_STATE_CHANGE_NO std::cout << "RepeatBase::incr_state_change_no()\n"; #endif } void RepeatBase::update_repeat_genvar() const { // **** reset name since generated variables are not persisted var_.set_name(name_); // valueAsString() use the last_valid_value() which should always be in range. // Note repeat::value() can be on e past the last valid value, at expiration of Repeat loop // However Repeat::last_valid_value() will just return the last valid value. var_.set_value(valueAsString()); } std::string RepeatBase::toString() const { return ecf::as_string(*this, PrintStyle::DEFS); } // ============================================================= RepeatDate::RepeatDate(const std::string& variable, int start, int end, int delta /* always in days*/ ) : RepeatBase(variable), start_(start), end_(end), delta_(delta), value_(start) { if (!ecf::algorithm::is_valid_name(variable)) { throw std::runtime_error("RepeatDate::RepeatDate: Invalid name: " + variable); } if (delta == 0) { throw std::runtime_error(MESSAGE("Invalid Repeat date: the delta cannot be zero" << "repeat " << variable << " " << start << " " << end << " " << delta)); } std::string theStart = ecf::convert_to(start); if (theStart.size() != 8) { throw std::runtime_error( MESSAGE("Invalid Repeat date: The start is not a valid date. Please use yyyymmdd format." << "repeat " << variable << " " << start << " " << end << " " << delta)); } std::string theEnd = ecf::convert_to(end); if (theEnd.size() != 8) { throw std::runtime_error(MESSAGE("Invalid Repeat date: The end is not a valid date. Please use yyyymmdd format." << "repeat " << variable << " " << start << " " << end << " " << delta)); } if (delta_ > 0) { // assert end => start if (!(end >= start)) { throw std::runtime_error( MESSAGE("Invalid Repeat date: The end must be greater than the start date, when delta is positive " << "repeat " << variable << " " << start << " " << end << " " << delta)); } } else { // assert start >= end if (!(start >= end)) { throw std::runtime_error( MESSAGE("Invalid Repeat date: The start must be greater than the end date, when delta is negative " << "repeat " << variable << " " << start << " " << end << " " << delta)); } } // Use date lib to check YMD try { (void)boost::gregorian::date(boost::gregorian::from_undelimited_string(theStart)); (void)boost::gregorian::date(boost::gregorian::from_undelimited_string(theEnd)); } catch (std::exception& e) { throw std::runtime_error(MESSAGE("Invalid Repeat date: The start/end is not a valid date." << "repeat " << variable << " " << start << " " << end << " " << delta)); } } void RepeatDate::gen_variables(std::vector& vec) const { vec.push_back(yyyy_); vec.push_back(mm_); vec.push_back(dom_); vec.push_back(dow_); vec.push_back(julian_); RepeatBase::gen_variables(vec); } const Variable& RepeatDate::find_gen_variable(const std::string& name) const { if (name == name_) { return var_; } if (name == julian_.name()) { return julian_; } if (name == dow_.name()) { return dow_; } if (name == dom_.name()) { return dom_; } if (name == mm_.name()) { return mm_; } if (name == yyyy_.name()) { return yyyy_; } return Variable::EMPTY(); } void RepeatDate::update_repeat_genvar() const { RepeatBase::update_repeat_genvar(); yyyy_.set_name(name_ + "_YYYY"); yyyy_.set_value(""); mm_.set_name(name_ + "_MM"); mm_.set_value(""); dom_.set_name(name_ + "_DD"); dom_.set_value(""); dow_.set_name(name_ + "_DOW"); dom_.set_value(""); julian_.set_name(name_ + "_JULIAN"); julian_.set_value(""); yyyy_.set_name(name_ + "_YYYY"); mm_.set_name(name_ + "_MM"); dom_.set_name(name_ + "_DD"); dow_.set_name(name_ + "_DOW"); julian_.set_name(name_ + "_JULIAN"); update_repeat_genvar_value(); } void RepeatDate::update_repeat_genvar_value() const { std::string date_as_string = valueAsString(); if (valid()) { try { auto the_date = boost::gregorian::from_undelimited_string(date_as_string); if (the_date.is_special()) { log(Log::ERR, MESSAGE("RepeatDate::update_repeat_genvar(): invalid current date: " << date_as_string << " is_special")); return; } auto year = static_cast(the_date.year()); yyyy_.set_value(as_4_digits(year)); auto month = the_date.month().as_number(); mm_.set_value(as_2_digits(month)); auto day_of_month = the_date.day().as_number(); dom_.set_value(as_2_digits(day_of_month)); auto day_of_week = the_date.day_of_week().as_number(); dow_.set_value(std::to_string(day_of_week)); auto last_value = last_valid_value(); julian_.set_value(std::to_string(ecf::CalendarDate(last_value).as_julian_day().value())); } catch (std::exception& e) { log(Log::ERR, MESSAGE("RepeatDate::update_repeat_genvar_value : " << toString() << "\n The current date(" << date_as_string << ") is not valid, due to: " << e.what())); return; } } } bool RepeatDate::compare(RepeatBase* rb) const { auto* rhs = dynamic_cast(rb); if (!rhs) { return false; } return operator==(*rhs); } void RepeatDate::setToLastValue() { set_value(end_); } long RepeatDate::current_index() const { // Use Julian-day arithmetic so the index is correct even when the // date sequence crosses a month or year boundary. auto start_julian = ecf::CalendarDate(start_).as_julian_day().value(); auto value_julian = ecf::CalendarDate(value_).as_julian_day().value(); return (value_julian - start_julian) / delta_; } long RepeatDate::last_valid_value() const { return valid_value(value_); } long RepeatDate::valid_value(long value) const { if (delta_ > 0) { if (value < start_) { return start_; } if (value > end_) { return end_; } return value; } if (value > start_) { return start_; } if (value < end_) { return end_; } return value; } long RepeatDate::last_valid_value_minus(int val) const { auto result = ecf::CalendarDate(last_valid_value()) - val; return result.value(); } long RepeatDate::last_valid_value_plus(int val) const { auto result = ecf::CalendarDate(last_valid_value()) + val; return result.value(); } void RepeatDate::reset() { set_value(start_); } std::string RepeatDate::dump() const { return MESSAGE(toString() << " value(" << value_ << ")"); } bool RepeatDate::operator==(const RepeatDate& rhs) const { if (name_ != rhs.name_) { return false; } if (start_ != rhs.start_) { return false; } if (end_ != rhs.end_) { return false; } if (delta_ != rhs.delta_) { return false; } if (value_ != rhs.value_) { return false; } return true; } std::string RepeatDate::valueAsString() const { /// will throw an ecf::bad_conversion if value is not convertible to a string try { return ecf::convert_to(last_valid_value()); } catch (const ecf::bad_conversion&) { LOG_ASSERT(false, "RepeatDate::valueAsString(): could not convert value " << value_ << " to a string"); } return std::string{}; } std::string RepeatDate::value_as_string(int index) const { /// will throw an ecf::bad_conversion if value is not convertible to a string try { return ecf::convert_to(index); } catch (const ecf::bad_conversion&) { } return std::string{}; } std::string RepeatDate::next_value_as_string() const { auto result = ecf::CalendarDate(last_valid_value()) + delta_; long val = result.value(); try { return ecf::convert_to(valid_value(val)); } catch (const ecf::bad_conversion&) { } return std::string{}; } std::string RepeatDate::prev_value_as_string() const { auto result = ecf::CalendarDate(last_valid_value()) - delta_; long val = result.value(); try { return ecf::convert_to(valid_value(val)); } catch (const ecf::bad_conversion&) { } return std::string{}; } void RepeatDate::increment() { auto result = ecf::CalendarDate(last_valid_value()) + delta_; set_value(result.value()); } void RepeatDate::change(const std::string& newdate) { if (newdate.size() != 8) { throw std::runtime_error(MESSAGE( "RepeatDate::change: " << toString() << " The new date is not valid, expected 8 characters in yyyymmdd format but found " << newdate)); } long the_new_date = 0; try { the_new_date = ecf::convert_to(newdate); } catch (const ecf::bad_conversion&) { throw std::runtime_error(MESSAGE("RepeatDate::change: " << toString() << " The new date(" << newdate << ") is not convertible to an long")); } // Use date lib to check YMD try { (void)boost::gregorian::date(boost::gregorian::from_undelimited_string(newdate)); } catch (std::exception& e) { throw std::runtime_error( MESSAGE("RepeatDate::change: " << toString() << " The new date(" << newdate << ") is not valid")); } changeValue(the_new_date); } void RepeatDate::changeValue(long the_new_date) { if (delta_ > 0) { if (the_new_date < start_ || the_new_date > end_) { throw std::runtime_error( MESSAGE("RepeatDate::changeValue: " << toString() << "\nThe new date should be in the range[" << start_ << " : " << end_ << "] but found " << the_new_date)); } } else { if (the_new_date > start_ || the_new_date < end_) { throw std::runtime_error( MESSAGE("RepeatDate::changeValue: " << toString() << "\nThe new date should be in the range[" << start_ << " : " << end_ << "] but found " << the_new_date)); } } // Check new value is in step. ECFLOW-325 repeat date 7 long julian_new_date = ecf::CalendarDate(the_new_date).as_julian_day().value(); long julian_start = ecf::CalendarDate(start_).as_julian_day().value(); long diff = julian_new_date - julian_start; if (diff % delta_ != 0) { throw std::runtime_error(MESSAGE("RepeatDate::changeValue: " << toString() << "\nThe new date " << the_new_date << " is not in line with the delta/step")); } set_value(the_new_date); } void RepeatDate::set_value(long the_new_date) { // Note: the node is incremented one past, the last value // In Node we increment() then check for validity // hence the_new_value may be outside the valid range. // This can occur when an incremental sync happens, // hence, allow memento to copy the value as is. value_ = the_new_date; update_repeat_genvar_value(); incr_state_change_no(); } // ============================================================= RepeatDateTime::RepeatDateTime(const std::string& variable, const std::string& start, const std::string& end, const std::string& delta) : RepeatDateTime(variable, Instant::parse(start), Instant::parse(end), Duration::parse(delta)) { } RepeatDateTime::RepeatDateTime(const std::string& variable, Instant start, Instant end, Duration delta) : RepeatBase(variable), start_(start), end_(end), delta_(delta), value_(start) { if (!ecf::algorithm::is_valid_name(variable)) { throw std::runtime_error("RepeatDateTime::RepeatDateTime: Invalid name: " + variable); } if (delta == Duration{std::chrono::seconds{0}}) { throw std::runtime_error(MESSAGE("Invalid Repeat datetime: the delta cannot be zero" << "repeat " << variable << " " << start << " " << end << " " << delta)); } auto theStart = boost::lexical_cast(start); if (theStart.size() != 15) { throw std::runtime_error( MESSAGE("Invalid Repeat datetime: The start is not a valid date+time. Please use yyyymmddTMMHHSS format." << "repeat " << variable << " " << start << " " << end << " " << delta)); } auto theEnd = boost::lexical_cast(end); if (theEnd.size() != 15) { throw std::runtime_error( MESSAGE("Invalid Repeat datetime: The end is not a valid date+time. Please use yyyymmddTHHMMSS format." << "repeat " << variable << " " << start << " " << end << " " << delta)); } if (delta_ > Duration{std::chrono::seconds{0}}) { // assert end => start if (!(end >= start)) { throw std::runtime_error(MESSAGE( "Invalid Repeat datetime: The end must be greater than the start date+time, when delta is positive " << "repeat " << variable << " " << start << " " << end << " " << delta)); } } else { // assert start >= end if (!(start >= end)) { throw std::runtime_error(MESSAGE( "Invalid Repeat datetime: The start must be greater than the end date+time, when delta is negative " << "repeat " << variable << " " << start << " " << end << " " << delta)); } } } void RepeatDateTime::gen_variables(std::vector& vec) const { for (const auto& entry : generated_) { vec.push_back(entry); } RepeatBase::gen_variables(vec); } const Variable& RepeatDateTime::find_gen_variable(const std::string& name) const { if (name == name_) { return var_; } for (const auto& entry : generated_) { if (entry.name() == name) { return entry; } } return Variable::EMPTY(); } void RepeatDateTime::update_repeat_genvar() const { RepeatBase::update_repeat_genvar(); // Reset variables values generated_.set_value(""); update_repeat_genvar_value(); } void RepeatDateTime::update_repeat_genvar_value() const { std::string date_as_string = valueAsString(); if (valid()) { try { auto dt = boost::posix_time::from_iso_string(date_as_string); // Using boost posix_time/gregorian, since C++17 still doesn't include calendar types. auto d = dt.date(); // Date generated_[name_ + "_DATE"].set_value(boost::gregorian::to_iso_string(d)); // Date Components auto year = static_cast(d.year()); generated_[name_ + "_YYYY"].set_value(as_4_digits(year)); auto month = d.month().as_number(); generated_[name_ + "_MM"].set_value(as_2_digits(month)); auto day = d.day().as_number(); generated_[name_ + "_DD"].set_value(as_2_digits(day)); auto julian = d.julian_day(); generated_[name_ + "_JULIAN"].set_value(std::to_string(julian)); auto t = dt.time_of_day(); // Time generated_[name_ + "_TIME"].set_value(boost::posix_time::to_iso_string(t)); // Time Components int hours = t.hours(); generated_[name_ + "_HOURS"].set_value(as_2_digits(hours)); int minutes = t.minutes(); generated_[name_ + "_MINUTES"].set_value(as_2_digits(minutes)); int seconds = t.seconds(); generated_[name_ + "_SECONDS"].set_value(as_2_digits(seconds)); } catch (std::exception& e) { log(Log::ERR, MESSAGE("RepeatDateTime::update_repeat_genvar_value : " << toString() << "\n The current date(" << date_as_string << ") is not valid, due to: " << e.what())); return; } } } bool RepeatDateTime::compare(RepeatBase* rb) const { auto* rhs = dynamic_cast(rb); if (!rhs) { return false; } return operator==(*rhs); } void RepeatDateTime::setToLastValue() { set_value(coerce_from_instant_into_seconds(end_)); } long RepeatDateTime::last_valid_value() const { Instant valid = valid_value(value_); return coerce_from_instant_into_seconds(valid); } Instant RepeatDateTime::valid_value(const Instant& value) const { if (delta_ > Duration{std::chrono::seconds{0}}) { if (value < start_) { return start_; } if (value > end_) { return end_; } return value; } if (value > start_) { return start_; } if (value < end_) { return end_; } return value; } long RepeatDateTime::last_valid_value_minus(int val) const { Instant last_value = coerce_from_seconds_into_instant(last_valid_value()); Instant updated_value = last_value - Duration{std::chrono::seconds{val}}; return coerce_from_instant_into_seconds(updated_value); } long RepeatDateTime::last_valid_value_plus(int val) const { Instant last_value = coerce_from_seconds_into_instant(last_valid_value()); Instant updated_value = last_value + Duration{std::chrono::seconds{val}}; return coerce_from_instant_into_seconds(updated_value); } void RepeatDateTime::reset() { set_value(coerce_from_instant_into_seconds(start_)); } std::string RepeatDateTime::dump() const { return MESSAGE(toString() << " value(" << value_ << ")"); } bool RepeatDateTime::operator==(const RepeatDateTime& rhs) const { if (name_ != rhs.name_) { return false; } if (start_ != rhs.start_) { return false; } if (end_ != rhs.end_) { return false; } if (delta_ != rhs.delta_) { return false; } if (value_ != rhs.value_) { return false; } return true; } std::string RepeatDateTime::valueAsString() const { /// will throw a boost::bad_lexical_cast& if value is not convertible to a string try { long value = last_valid_value(); Instant instant = coerce_from_seconds_into_instant(value); return boost::lexical_cast(instant); } catch (boost::bad_lexical_cast&) { LOG_ASSERT(false, "RepeatDateTime::valueAsString(): could not convert value " << value_ << " to a string"); } return {}; } std::string RepeatDateTime::value_as_string(int index) const { /// will throw a boost::bad_lexical_cast& if value is not convertible to a string try { return boost::lexical_cast(index); } catch (boost::bad_lexical_cast&) { } return {}; } std::string RepeatDateTime::next_value_as_string() const { Instant next = coerce_from_seconds_into_instant(last_valid_value()) + delta_; try { return boost::lexical_cast(valid_value(next)); } catch (boost::bad_lexical_cast&) { } return {}; } std::string RepeatDateTime::prev_value_as_string() const { Instant previous = coerce_from_seconds_into_instant(last_valid_value()) - delta_; try { return boost::lexical_cast(valid_value(previous)); } catch (boost::bad_lexical_cast&) { } return {}; } void RepeatDateTime::increment() { auto new_value = value_ + delta_; set_value(ecf::coerce_from_instant_into_seconds(new_value)); } void RepeatDateTime::change(const std::string& newdate) { long the_new_date; try { auto instant = Instant::parse(newdate); the_new_date = ecf::coerce_from_instant_into_seconds(instant); } catch (std::exception& e) { throw std::runtime_error( MESSAGE("RepeatDateTime::change: " << toString() << " The new date(" << newdate << ") is not valid")); } changeValue(the_new_date); } void RepeatDateTime::changeValue(long the_new_date) { auto new_date = ecf::coerce_from_seconds_into_instant(the_new_date); if (delta_ > Duration{std::chrono::seconds{0}}) { if (new_date < start_ || new_date > end_) { throw std::runtime_error( MESSAGE("RepeatDateTime::changeValue: " << toString() << "\nThe new date should be in the range[" << start_ << " : " << end_ << "] but found " << new_date)); } } else { if (new_date > start_ || new_date < end_) { throw std::runtime_error( MESSAGE("RepeatDateTime::changeValue: " << toString() << "\nThe new date should be in the range[" << start_ << " : " << end_ << "] but found " << the_new_date)); } } // Ensure that new value is in step auto diff = new_date - start_; if (diff.as_seconds().count() % delta_.as_seconds().count() != 0) { throw std::runtime_error(MESSAGE("RepeatDateTime::changeValue: " << toString() << "\nThe new date " << the_new_date << " is not in line with the delta/step")); } set_value(the_new_date); } void RepeatDateTime::set_value(long the_new_date) { // Note: the node is incremented one past, the last value // In Node we increment() then check for validity // hence the_new_value may be outside the valid range. // This can occur when an incremental sync happens, // hence, allow memento to copy the value as is. value_ = coerce_from_seconds_into_instant(the_new_date); update_repeat_genvar_value(); incr_state_change_no(); } //====================================================================================== RepeatDateList::RepeatDateList(const std::string& variable, const std::vector& l) : RepeatBase(variable), list_(l) { if (!ecf::algorithm::is_valid_name(variable)) { throw std::runtime_error("RepeatDateList: Invalid name: " + variable); } if (list_.empty()) { throw std::runtime_error("RepeatDateList: " + variable + " is empty"); } for (int i : list_) { std::string date_i = ecf::convert_to(i); if (date_i.size() != 8) { throw std::runtime_error( MESSAGE("Invalid Repeat datelist : " << variable << " the date " << i << " is not valid. Please use yyyymmdd format.")); } try { (void)boost::gregorian::date(boost::gregorian::from_undelimited_string(date_i)); } catch (std::exception& e) { throw std::runtime_error( MESSAGE("Invalid Repeat datelist : " << variable << " the date " << i << " is not valid. Please use yyyymmdd format.")); } } } void RepeatDateList::gen_variables(std::vector& vec) const { vec.push_back(yyyy_); vec.push_back(mm_); vec.push_back(dom_); vec.push_back(dow_); vec.push_back(julian_); RepeatBase::gen_variables(vec); } const Variable& RepeatDateList::find_gen_variable(const std::string& name) const { if (name == name_) { return var_; } if (name == yyyy_.name()) { return yyyy_; } if (name == mm_.name()) { return mm_; } if (name == dom_.name()) { return dom_; } if (name == dow_.name()) { return dow_; } if (name == julian_.name()) { return julian_; } return Variable::EMPTY(); } void RepeatDateList::update_repeat_genvar() const { RepeatBase::update_repeat_genvar(); yyyy_.set_name(name_ + "_YYYY"); yyyy_.set_value(""); mm_.set_name(name_ + "_MM"); mm_.set_value(""); dom_.set_name(name_ + "_DD"); dom_.set_value(""); dow_.set_name(name_ + "_DOW"); dom_.set_value(""); julian_.set_name(name_ + "_JULIAN"); julian_.set_value(""); update_repeat_genvar_value(); } void RepeatDateList::update_repeat_genvar_value() const { if (valid()) { std::string date_as_string = valueAsString(); try { auto the_date = boost::gregorian::from_undelimited_string(date_as_string); if (the_date.is_special()) { log(Log::ERR, MESSAGE("RepeatDateList::update_repeat_genvar_value(): " << toString() << "\n invalid current date: " << date_as_string << " is special ")); return; } auto year = static_cast(the_date.year()); yyyy_.set_value(as_4_digits(year)); auto month = the_date.month().as_number(); mm_.set_value(as_2_digits(month)); auto day_of_month = the_date.day().as_number(); dom_.set_value(as_2_digits(day_of_month)); auto day_of_week = the_date.day_of_week().as_number(); dow_.set_value(std::to_string(day_of_week)); auto julian_day = CalendarDate(last_valid_value()).as_julian_day().value(); julian_.set_value(std::to_string(julian_day)); } catch (std::exception& e) { log(Log::ERR, MESSAGE("RepeatDateList::update_repeat_genvar_value(): " << toString() << "\n invalid current date: " << date_as_string << ", due to: " << e.what())); } } } int RepeatDateList::start() const { if (list_.empty()) { return 0; } return list_[0]; } int RepeatDateList::end() const { if (list_.empty()) { return 0; } return list_[list_.size() - 1]; } bool RepeatDateList::compare(RepeatBase* rb) const { auto* rhs = dynamic_cast(rb); if (!rhs) { return false; } return operator==(*rhs); } std::string RepeatDateList::dump() const { return MESSAGE(toString() << " ordinal-value(" << value() << ") value-as-string(" << valueAsString() << ")"); } void RepeatDateList::reset() { set_value(0); } void RepeatDateList::increment() { set_value(currentIndex_ + 1); } long RepeatDateList::value() const { if (list_.empty()) { return 0; } if (currentIndex_ >= 0 && currentIndex_ < static_cast(list_.size())) { return list_[currentIndex_]; } return 0; } long RepeatDateList::last_valid_value() const { if (list_.empty()) { return 0; } if (currentIndex_ >= 0 && currentIndex_ < static_cast(list_.size())) { return list_[currentIndex_]; } if (currentIndex_ < 0) { return list_[0]; } if (currentIndex_ >= static_cast(list_.size())) { return list_[list_.size() - 1]; } return 0; } long RepeatDateList::last_valid_value_minus(int val) const { long last_value = last_valid_value(); if (last_value == 0) { return 0; } return (CalendarDate(last_value) - val).value(); } long RepeatDateList::last_valid_value_plus(int val) const { long last_value = last_valid_value(); if (last_value == 0) { return 0; } return (CalendarDate(last_value) + val).value(); } void RepeatDateList::setToLastValue() { if (list_.empty()) { return; } set_value(static_cast(list_.size() - 1)); } std::string RepeatDateList::valueAsString() const { return ecf::convert_to(last_valid_value()); } std::string RepeatDateList::value_as_string(int index) const { if (list_.empty()) { return std::string("0"); } if (index >= 0 && index < static_cast(list_.size())) { return ecf::convert_to(list_[index]); } if (index < 0) { return ecf::convert_to(list_[0]); } if (index >= static_cast(list_.size())) { return ecf::convert_to(list_[list_.size() - 1]); } return std::string{}; } std::string RepeatDateList::next_value_as_string() const { if (list_.empty()) { return std::string("0"); } int index = currentIndex_; index++; return value_as_string(index); } std::string RepeatDateList::prev_value_as_string() const { if (list_.empty()) { return std::string("0"); } int index = currentIndex_; index--; return value_as_string(index); } void RepeatDateList::change(const std::string& newValue) { // See if it matches one of the dates int new_val = 0; try { new_val = ecf::convert_to(newValue); } catch (...) { throw std::runtime_error(MESSAGE( "RepeatDateList::change: " << toString() << "\nThe new value " << newValue << " is must be convertible to integer, and correspond to an existing value\n")); } for (size_t i = 0; i < list_.size(); i++) { if (list_[i] == new_val) { set_value(i); return; } } throw std::runtime_error(MESSAGE("RepeatDateList::change: " << toString() << "\nThe new value " << newValue << " is not a valid member of the date list\n")); } void RepeatDateList::changeValue(long the_new_index) { if (list_.empty()) { return; } if (the_new_index < 0 || the_new_index >= static_cast(list_.size())) { throw std::runtime_error(MESSAGE("RepeatDateList::changeValue:" << toString() << "\nThe new value '" << the_new_index << "' is not a valid index " << "expected range[0-" << list_.size() - 1 << "] but found '" << the_new_index << "'")); } set_value(the_new_index); } void RepeatDateList::set_value(long the_new_index) { if (list_.empty()) { return; } // Note: the node is incremented one past, the last value // In Node we increment() then check for validity // hence the_new_value may be outside the valid range. // This can occur when an incremental sync happens, // hence, allow memento to copy the value as is. currentIndex_ = the_new_index; update_repeat_genvar_value(); incr_state_change_no(); } bool RepeatDateList::operator==(const RepeatDateList& rhs) const { if (name_ != rhs.name_) { #ifdef DEBUG if (Ecf::debug_equality()) { std::cout << "RepeatDateList::operator==( name_(" << name_ << ") != rhs.name_(" << rhs.name_ << "))\n"; } #endif return false; } if (list_ != rhs.list_) { #ifdef DEBUG if (Ecf::debug_equality()) { std::cout << "RepeatDateList::operator==( list_ != rhs.list_ )\n"; } #endif return false; } if (currentIndex_ != rhs.currentIndex_) { #ifdef DEBUG if (Ecf::debug_equality()) { std::cout << "RepeatDateList::operator==( currentIndex_(" << currentIndex_ << ") != rhs.currentIndex_(" << rhs.currentIndex_ << "))\n"; } #endif return false; } return true; } //====================================================================================== RepeatDateTimeList::RepeatDateTimeList(const std::string& variable, const std::vector& l) : RepeatBase(variable), list_(l) { if (!ecf::algorithm::is_valid_name(variable)) { throw std::runtime_error("RepeatDateTimeList: Invalid name: " + variable); } if (list_.empty()) { throw std::runtime_error("RepeatDateTimeList: " + variable + " is empty"); } for (const Instant& i : list_) { auto ts = boost::lexical_cast(i); if (ts.size() != 15) { throw std::runtime_error( MESSAGE("Invalid Repeat datetimelist : " << variable << " the datetime " << ts << " is not valid. Please use yyyymmddTHHMMSS format.")); } } } void RepeatDateTimeList::gen_variables(std::vector& vec) const { for (const auto& entry : generated_) { vec.push_back(entry); } RepeatBase::gen_variables(vec); } const Variable& RepeatDateTimeList::find_gen_variable(const std::string& name) const { if (name == name_) { return var_; } for (const auto& entry : generated_) { if (entry.name() == name) { return entry; } } return Variable::EMPTY(); } void RepeatDateTimeList::update_repeat_genvar() const { RepeatBase::update_repeat_genvar(); // Reset variables values generated_.set_value(""); update_repeat_genvar_value(); } void RepeatDateTimeList::update_repeat_genvar_value() const { if (valid()) { std::string date_as_string = valueAsString(); try { auto dt = boost::posix_time::from_iso_string(date_as_string); auto d = dt.date(); generated_[name_ + "_DATE"].set_value(boost::gregorian::to_iso_string(d)); auto year = static_cast(d.year()); generated_[name_ + "_YYYY"].set_value(as_4_digits(year)); auto month = d.month().as_number(); generated_[name_ + "_MM"].set_value(as_2_digits(month)); auto day = d.day().as_number(); generated_[name_ + "_DD"].set_value(as_2_digits(day)); auto julian = d.julian_day(); generated_[name_ + "_JULIAN"].set_value(std::to_string(julian)); auto t = dt.time_of_day(); generated_[name_ + "_TIME"].set_value(boost::posix_time::to_iso_string(t)); int hours = t.hours(); generated_[name_ + "_HOURS"].set_value(as_2_digits(hours)); int minutes = t.minutes(); generated_[name_ + "_MINUTES"].set_value(as_2_digits(minutes)); int seconds = t.seconds(); generated_[name_ + "_SECONDS"].set_value(as_2_digits(seconds)); } catch (std::exception& e) { log(Log::ERR, MESSAGE("RepeatDateTimeList::update_repeat_genvar_value : " << toString() << "\n The current datetime(" << date_as_string << ") is not valid, due to: " << e.what())); return; } } } int RepeatDateTimeList::start() const { if (list_.empty()) { return 0; } return coerce_from_instant_into_seconds(list_[0]); } int RepeatDateTimeList::end() const { if (list_.empty()) { return 0; } return coerce_from_instant_into_seconds(list_[list_.size() - 1]); } long RepeatDateTimeList::value() const { if (list_.empty()) { return 0; } if (currentIndex_ >= 0 && currentIndex_ < static_cast(list_.size())) { return coerce_from_instant_into_seconds(list_[currentIndex_]); } return 0; } long RepeatDateTimeList::last_valid_value() const { if (list_.empty()) { return 0; } if (currentIndex_ >= 0 && currentIndex_ < static_cast(list_.size())) { return coerce_from_instant_into_seconds(list_[currentIndex_]); } if (currentIndex_ < 0) { return coerce_from_instant_into_seconds(list_[0]); } if (currentIndex_ >= static_cast(list_.size())) { return coerce_from_instant_into_seconds(list_[list_.size() - 1]); } return 0; } long RepeatDateTimeList::last_valid_value_minus(int val) const { long lv = last_valid_value(); if (lv == 0) { return 0; } Instant last = coerce_from_seconds_into_instant(lv); Instant updated = last - Duration{std::chrono::seconds{val}}; return coerce_from_instant_into_seconds(updated); } long RepeatDateTimeList::last_valid_value_plus(int val) const { long lv = last_valid_value(); if (lv == 0) { return 0; } Instant last = coerce_from_seconds_into_instant(lv); Instant updated = last + Duration{std::chrono::seconds{val}}; return coerce_from_instant_into_seconds(updated); } bool RepeatDateTimeList::compare(RepeatBase* rb) const { auto* rhs = dynamic_cast(rb); if (!rhs) { return false; } return operator==(*rhs); } std::string RepeatDateTimeList::dump() const { return MESSAGE(toString() << " ordinal-value(" << value() << ") value-as-string(" << valueAsString() << ")"); } void RepeatDateTimeList::reset() { set_value(0); } void RepeatDateTimeList::increment() { set_value(currentIndex_ + 1); } void RepeatDateTimeList::setToLastValue() { if (list_.empty()) { return; } set_value(static_cast(list_.size() - 1)); } std::string RepeatDateTimeList::valueAsString() const { if (list_.empty()) { return {}; } int index = currentIndex_; if (index < 0) { index = 0; } if (index >= static_cast(list_.size())) { index = static_cast(list_.size() - 1); } try { return boost::lexical_cast(list_[index]); } catch (boost::bad_lexical_cast&) { } return {}; } std::string RepeatDateTimeList::value_as_string(int index) const { if (list_.empty()) { return {}; } if (index < 0) { index = 0; } if (index >= static_cast(list_.size())) { index = static_cast(list_.size() - 1); } try { return boost::lexical_cast(list_[index]); } catch (boost::bad_lexical_cast&) { } return {}; } std::string RepeatDateTimeList::next_value_as_string() const { if (list_.empty()) { return {}; } return value_as_string(currentIndex_ + 1); } std::string RepeatDateTimeList::prev_value_as_string() const { if (list_.empty()) { return {}; } return value_as_string(currentIndex_ - 1); } void RepeatDateTimeList::change(const std::string& newValue) { Instant target; try { target = Instant::parse(newValue); } catch (std::exception&) { throw std::runtime_error( MESSAGE("RepeatDateTimeList::change: " << toString() << "\nThe new value " << newValue << " is not a valid datetime (yyyymmddTHHMMSS)")); } for (size_t i = 0; i < list_.size(); i++) { if (list_[i] == target) { set_value(static_cast(i)); return; } } throw std::runtime_error(MESSAGE("RepeatDateTimeList::change: " << toString() << "\nThe new value " << newValue << " is not a valid member of the datetimelist\n")); } void RepeatDateTimeList::changeValue(long the_new_index) { if (list_.empty()) { return; } if (the_new_index < 0 || the_new_index >= static_cast(list_.size())) { throw std::runtime_error( MESSAGE("RepeatDateTimeList::changeValue:" << toString() << "\nThe new value '" << the_new_index << "' is not a valid index " << "expected range[0-" << list_.size() - 1 << "] but found '" << the_new_index << "'")); } set_value(the_new_index); } void RepeatDateTimeList::set_value(long the_new_index) { if (list_.empty()) { return; } currentIndex_ = the_new_index; update_repeat_genvar_value(); incr_state_change_no(); } bool RepeatDateTimeList::operator==(const RepeatDateTimeList& rhs) const { if (name_ != rhs.name_) { #ifdef DEBUG if (Ecf::debug_equality()) { std::cout << "RepeatDateTimeList::operator==( name_(" << name_ << ") != rhs.name_(" << rhs.name_ << "))\n"; } #endif return false; } if (list_ != rhs.list_) { #ifdef DEBUG if (Ecf::debug_equality()) { std::cout << "RepeatDateTimeList::operator==( list_ != rhs.list_ )\n"; } #endif return false; } if (currentIndex_ != rhs.currentIndex_) { #ifdef DEBUG if (Ecf::debug_equality()) { std::cout << "RepeatDateTimeList::operator==( currentIndex_(" << currentIndex_ << ") != rhs.currentIndex_(" << rhs.currentIndex_ << "))\n"; } #endif return false; } return true; } //====================================================================================== RepeatInteger::RepeatInteger(const std::string& variable, int start, int end, int delta) : RepeatBase(variable), start_(start), end_(end), delta_(delta), value_(start) { // cout << toString() << "\n"; if (!ecf::algorithm::is_valid_name(variable)) { throw std::runtime_error("RepeatInteger: Invalid name: " + variable); } } RepeatInteger::RepeatInteger() = default; bool RepeatInteger::compare(RepeatBase* rb) const { auto* rhs = dynamic_cast(rb); if (!rhs) { return false; } return operator==(*rhs); } void RepeatInteger::reset() { value_ = start_; incr_state_change_no(); } long RepeatInteger::last_valid_value() const { return valid_value(value_); } long RepeatInteger::valid_value(long value) const { if (delta_ > 0) { if (value < start_) { return start_; } if (value > end_) { return end_; } return value; } if (value > start_) { return start_; } if (value < end_) { return end_; } return value; } void RepeatInteger::increment() { value_ += delta_; incr_state_change_no(); } void RepeatInteger::change(const std::string& newValue) { long the_new_value = 0; try { the_new_value = ecf::convert_to(newValue); } catch (const ecf::bad_conversion&) { throw std::runtime_error(MESSAGE("RepeatInteger::change:" << toString() << " The new value(" << newValue << ") is not convertible to an long")); } changeValue(the_new_value); } void RepeatInteger::changeValue(long the_new_value) { if (delta_ > 0) { if (the_new_value < start_ || the_new_value > end_) { throw std::runtime_error( MESSAGE("RepeatInteger::changeValue:" << toString() << ". The new value should be in the range[" << start_ << "-" << end_ << "] but found " << the_new_value)); } } else { if (the_new_value > start_ || the_new_value < end_) { throw std::runtime_error( MESSAGE("RepeatInteger::changeValue:" << toString() << ". The new value should be in the range[" << start_ << "-" << end_ << "] but found " << the_new_value)); } } set_value(the_new_value); } void RepeatInteger::set_value(long the_new_value) { // To be used by Memento only. as it does no checking // Note: the node is incremented one past, the last value // In Node we increment() then check for validity // hence the_new_value may be outside the valid range. // This can occur when an incremental sync happens, // hence, allow memento to copy the value as is. value_ = the_new_value; incr_state_change_no(); } void RepeatInteger::setToLastValue() { value_ = end_; incr_state_change_no(); } std::string RepeatInteger::dump() const { return MESSAGE(toString() << " value(" << value_ << ")"); } bool RepeatInteger::operator==(const RepeatInteger& rhs) const { if (name_ != rhs.name_) { #ifdef DEBUG if (Ecf::debug_equality()) { std::cout << "RepeatInteger::operator==( name_(" << name_ << ") != rhs.name_(" << rhs.name_ << "))" << "\n"; } #endif return false; } if (start_ != rhs.start_) { #ifdef DEBUG if (Ecf::debug_equality()) { std::cout << "RepeatInteger::operator==( start_(" << start_ << ") != rhs.start_(" << rhs.start_ << "))" << "\n"; } #endif return false; } if (end_ != rhs.end_) { #ifdef DEBUG if (Ecf::debug_equality()) { std::cout << "RepeatInteger::operator==( end_(" << end_ << ") != rhs.end_(" << rhs.end_ << "))" << "\n"; } #endif return false; } if (delta_ != rhs.delta_) { #ifdef DEBUG if (Ecf::debug_equality()) { std::cout << "RepeatInteger::operator==( delta_(" << delta_ << ") != rhs.delta_(" << rhs.delta_ << "))" << "\n"; } #endif return false; } if (value_ != rhs.value_) { #ifdef DEBUG if (Ecf::debug_equality()) { std::cout << "RepeatInteger::operator==( value_(" << value_ << ") != rhs.value_(" << rhs.value_ << "))" << "\n"; } #endif return false; } return true; } std::string RepeatInteger::valueAsString() const { /// will throw an ecf::bad_conversion if value is not convertible to a string try { return ecf::convert_to(last_valid_value()); } catch (const ecf::bad_conversion&) { LOG_ASSERT(false, ""); } return std::string{}; } std::string RepeatInteger::value_as_string(int index) const { /// will throw an ecf::bad_conversion if value is not convertible to a string try { return ecf::convert_to(index); } catch (const ecf::bad_conversion&) { } return std::string{}; } std::string RepeatInteger::next_value_as_string() const { long val = last_valid_value(); val += delta_; try { return ecf::convert_to(valid_value(val)); } catch (const ecf::bad_conversion&) { } return std::string{}; } std::string RepeatInteger::prev_value_as_string() const { long val = last_valid_value(); val -= delta_; try { return ecf::convert_to(valid_value(val)); } catch (const ecf::bad_conversion&) { } return std::string{}; } //====================================================================================== RepeatEnumerated::RepeatEnumerated(const std::string& variable, const std::vector& theEnums) : RepeatBase(variable), theEnums_(theEnums) { if (!ecf::algorithm::is_valid_name(variable)) { throw std::runtime_error("RepeatEnumerated: Invalid name: " + variable); } if (theEnums.empty()) { throw std::runtime_error("RepeatEnumerated: " + variable + " is empty"); } } int RepeatEnumerated::end() const { if (theEnums_.empty()) { return 0; } return static_cast(theEnums_.size() - 1); } bool RepeatEnumerated::compare(RepeatBase* rb) const { auto* rhs = dynamic_cast(rb); if (!rhs) { return false; } return operator==(*rhs); } std::string RepeatEnumerated::dump() const { return MESSAGE(toString() << " ordinal-value(" << value() << ") value-as-string(" << valueAsString() << ")"); } void RepeatEnumerated::reset() { currentIndex_ = 0; incr_state_change_no(); } void RepeatEnumerated::increment() { currentIndex_++; incr_state_change_no(); } long RepeatEnumerated::value() const { if (currentIndex_ >= 0 && currentIndex_ < static_cast(theEnums_.size())) { try { return ecf::convert_to(theEnums_[currentIndex_]); } catch (const ecf::bad_conversion&) { // Ignore and return currentIndex_ } } return currentIndex_; } long RepeatEnumerated::last_valid_value() const { if (!theEnums_.empty()) { if (currentIndex_ < 0) { try { return ecf::convert_to(theEnums_[0]); } catch (const ecf::bad_conversion&) { /* Ignore and return first index */ } return 0; } if (currentIndex_ >= static_cast(theEnums_.size())) { try { return ecf::convert_to(theEnums_[theEnums_.size() - 1]); } catch (const ecf::bad_conversion&) { /* Ignore and return last index */ } return static_cast(theEnums_.size() - 1); } // return current value as integer or as index return value(); } return 0; } void RepeatEnumerated::setToLastValue() { currentIndex_ = static_cast(theEnums_.size() - 1); if (currentIndex_ < 0) { currentIndex_ = 0; } incr_state_change_no(); } std::string RepeatEnumerated::valueAsString() const { // This must always return a valid value if (!theEnums_.empty()) { // Returns the last valid value if (currentIndex_ < 0) { return theEnums_[0]; // return first } if (currentIndex_ >= static_cast(theEnums_.size())) { return theEnums_[theEnums_.size() - 1]; // return last } return theEnums_[currentIndex_]; } return std::string{}; } std::string RepeatEnumerated::value_as_string(int index) const { if (index >= 0 && index < static_cast(theEnums_.size())) { return theEnums_[index]; } return std::string{}; } std::string RepeatEnumerated::next_value_as_string() const { if (theEnums_.empty()) { return std::string{}; } int index = currentIndex_; index++; if (index < 0) { return theEnums_[0]; // return first } if (index >= static_cast(theEnums_.size())) { return theEnums_[theEnums_.size() - 1]; // return last } return theEnums_[index]; } std::string RepeatEnumerated::prev_value_as_string() const { if (theEnums_.empty()) { return std::string{}; } int index = currentIndex_; index--; if (index < 0) { return theEnums_[0]; // return first } if (index >= static_cast(theEnums_.size())) { return theEnums_[theEnums_.size() - 1]; // return last } return theEnums_[index]; } void RepeatEnumerated::change(const std::string& newValue) { // See if it matches one of the enums for (size_t i = 0; i < theEnums_.size(); i++) { if (theEnums_[i] == newValue) { currentIndex_ = i; incr_state_change_no(); return; } } // If the value is convertible to an integer, treat as an index try { auto the_new_value = ecf::convert_to(newValue); changeValue(the_new_value); // can throw if out of range return; } catch (const ecf::bad_conversion&) { } throw std::runtime_error( MESSAGE("RepeatEnumerated::change:" << toString() << "\nThe new value " << newValue << " is not a valid index or a member of the enumerated list\n")); } void RepeatEnumerated::changeValue(long the_new_value) { if (the_new_value < 0 || the_new_value >= static_cast(theEnums_.size())) { throw std::runtime_error(MESSAGE("RepeatEnumerated::changeValue:" << toString() << "\nThe new value '" << the_new_value << "' is not a valid index " << "expected range[0-" << theEnums_.size() - 1 << "] but found '" << the_new_value << "'")); } set_value(the_new_value); } void RepeatEnumerated::set_value(long the_new_value) { // Note: the node is incremented one past, the last value // In Node we increment() then check for validity // hence the_new_value may be outside the valid range. // This can occur when an incremental sync happens, // hence, allow memento to copy the value as is. currentIndex_ = the_new_value; incr_state_change_no(); } bool RepeatEnumerated::operator==(const RepeatEnumerated& rhs) const { if (name_ != rhs.name_) { #ifdef DEBUG if (Ecf::debug_equality()) { std::cout << "RepeatEnumerated::operator==( name_(" << name_ << ") != rhs.name_(" << rhs.name_ << "))\n"; } #endif return false; } if (theEnums_ != rhs.theEnums_) { #ifdef DEBUG if (Ecf::debug_equality()) { std::cout << "RepeatEnumerated::operator==( theEnums_ != rhs.theEnums_ )\n"; } #endif return false; } if (currentIndex_ != rhs.currentIndex_) { #ifdef DEBUG if (Ecf::debug_equality()) { std::cout << "RepeatEnumerated::operator==( currentIndex_(" << currentIndex_ << ") != rhs.currentIndex_(" << rhs.currentIndex_ << "))\n"; } #endif return false; } return true; } //====================================================================================== RepeatString::RepeatString(const std::string& variable, const std::vector& theEnums) : RepeatBase(variable), theStrings_(theEnums) { if (!ecf::algorithm::is_valid_name(variable)) { throw std::runtime_error("RepeatString:: Invalid name: " + variable); } if (theEnums.empty()) { throw std::runtime_error("RepeatString : " + variable + " is empty"); } } int RepeatString::end() const { if (theStrings_.empty()) { return 0; } return static_cast(theStrings_.size() - 1); } bool RepeatString::compare(RepeatBase* rb) const { auto* rhs = dynamic_cast(rb); if (!rhs) { return false; } return operator==(*rhs); } std::string RepeatString::dump() const { return MESSAGE(toString() << " ordinal-value(" << value() << ") value-as-string(" << valueAsString() << ")"); } void RepeatString::reset() { currentIndex_ = 0; incr_state_change_no(); } long RepeatString::last_valid_value() const { if (!theStrings_.empty()) { if (currentIndex_ < 0) { return 0; } if (currentIndex_ >= static_cast(theStrings_.size())) { return static_cast(theStrings_.size() - 1); } return currentIndex_; } return 0; } std::string RepeatString::next_value_as_string() const { if (!theStrings_.empty()) { int index = currentIndex_; index++; if (index < 0) { return theStrings_[0]; // return first } if (index >= static_cast(theStrings_.size())) { return theStrings_[theStrings_.size() - 1]; // return last } return theStrings_[index]; } return std::string{}; } std::string RepeatString::prev_value_as_string() const { if (!theStrings_.empty()) { int index = currentIndex_; index--; if (index < 0) { return theStrings_[0]; // return first } if (index >= static_cast(theStrings_.size())) { return theStrings_[theStrings_.size() - 1]; // return last } return theStrings_[index]; } return std::string{}; } void RepeatString::increment() { currentIndex_++; incr_state_change_no(); } void RepeatString::setToLastValue() { currentIndex_ = static_cast(theStrings_.size() - 1); if (currentIndex_ < 0) { currentIndex_ = 0; } incr_state_change_no(); } std::string RepeatString::valueAsString() const { if (!theStrings_.empty()) { return theStrings_[last_valid_value()]; } return std::string{}; } std::string RepeatString::value_as_string(int index) const { if (index >= 0 && index < static_cast(theStrings_.size())) { return theStrings_[index]; } return std::string{}; } void RepeatString::change(const std::string& newValue) { // See if it matches one of the strings for (size_t i = 0; i < theStrings_.size(); i++) { if (theStrings_[i] == newValue) { currentIndex_ = i; incr_state_change_no(); return; } } // If the value is convertible to an integer, treat as an index try { long the_new_value = ecf::convert_to(newValue); changeValue(the_new_value); return; } catch (const ecf::bad_conversion&) { } throw std::runtime_error(MESSAGE("RepeatString::change: " << toString() << "\nThe new value " << newValue << " is not a valid index or member of the string list")); } void RepeatString::changeValue(long the_new_value) { if (the_new_value < 0 || the_new_value >= static_cast(theStrings_.size())) { throw std::runtime_error(MESSAGE("RepeatString::change: " << toString() << " The new the integer " << the_new_value << " is not a valid index " << "expected range[0-" << theStrings_.size() - 1 << "]'")); } set_value(the_new_value); } void RepeatString::set_value(long the_new_value) { // Note: the node is incremented one past, the last value // In Node we increment() then check for validity // hence the_new_value may be outside the valid range. // This can occur when an incremental sync happens, // hence, allow memento to copy the value as is. currentIndex_ = the_new_value; incr_state_change_no(); } bool RepeatString::operator==(const RepeatString& rhs) const { if (name_ != rhs.name_) { return false; } if (theStrings_ != rhs.theStrings_) { return false; } if (currentIndex_ != rhs.currentIndex_) { return false; } return true; } //======================================================================================= bool RepeatDay::compare(RepeatBase* rb) const { auto* rhs = dynamic_cast(rb); if (!rhs) { return false; } return operator==(*rhs); } std::string RepeatDay::dump() const { return toString(); } bool RepeatDay::operator==(const RepeatDay& rhs) const { if (step_ != rhs.step_) { return false; } return true; } // ========================================================================================= template void RepeatBase::serialize(Archive& ar) { ar(CEREAL_NVP(name_)); } template void RepeatDate::serialize(Archive& ar, std::uint32_t const version) { ar(cereal::base_class(this), CEREAL_NVP(start_), CEREAL_NVP(end_), CEREAL_NVP(delta_), CEREAL_NVP(value_)); } template void RepeatDateTime::serialize(Archive& ar, std::uint32_t const version) { ar(cereal::base_class(this), CEREAL_NVP(start_), CEREAL_NVP(end_), CEREAL_NVP(delta_), CEREAL_NVP(value_)); } template void RepeatInteger::serialize(Archive& ar, std::uint32_t const version) { ar(cereal::base_class(this), CEREAL_NVP(start_), CEREAL_NVP(end_), CEREAL_NVP(delta_), CEREAL_NVP(value_)); } template void RepeatEnumerated::serialize(Archive& ar, std::uint32_t const version) { ar(cereal::base_class(this), CEREAL_NVP(theEnums_), CEREAL_NVP(currentIndex_)); } template void RepeatDateList::serialize(Archive& ar, std::uint32_t const version) { ar(cereal::base_class(this), CEREAL_NVP(list_), CEREAL_NVP(currentIndex_)); } template void RepeatDateTimeList::serialize(Archive& ar, std::uint32_t const version) { ar(cereal::base_class(this), CEREAL_NVP(list_), CEREAL_NVP(currentIndex_)); } template void RepeatString::serialize(Archive& ar, std::uint32_t const version) { ar(cereal::base_class(this), CEREAL_NVP(theStrings_), CEREAL_NVP(currentIndex_)); } template void RepeatDay::serialize(Archive& ar, std::uint32_t const version) { ar(cereal::base_class(this), CEREAL_NVP(step_)); } template void Repeat::serialize(Archive& ar, std::uint32_t const version) { ar(CEREAL_NVP(type_)); } CEREAL_TEMPLATE_SPECIALIZE(RepeatBase); CEREAL_TEMPLATE_SPECIALIZE_V(RepeatDate); CEREAL_TEMPLATE_SPECIALIZE_V(RepeatDateTime); CEREAL_TEMPLATE_SPECIALIZE_V(RepeatDateList); CEREAL_TEMPLATE_SPECIALIZE_V(RepeatDateTimeList); CEREAL_TEMPLATE_SPECIALIZE_V(RepeatInteger); CEREAL_TEMPLATE_SPECIALIZE_V(RepeatEnumerated); CEREAL_TEMPLATE_SPECIALIZE_V(RepeatString); CEREAL_TEMPLATE_SPECIALIZE_V(RepeatDay); CEREAL_TEMPLATE_SPECIALIZE_V(Repeat); CEREAL_REGISTER_TYPE(RepeatDate) CEREAL_REGISTER_TYPE(RepeatDateTime) CEREAL_REGISTER_TYPE(RepeatDateList) CEREAL_REGISTER_TYPE(RepeatDateTimeList) CEREAL_REGISTER_TYPE(RepeatInteger) CEREAL_REGISTER_TYPE(RepeatEnumerated) CEREAL_REGISTER_TYPE(RepeatString) CEREAL_REGISTER_TYPE(RepeatDay) ecflow-5.17.0/libs/attribute/src/ecflow/attribute/RepeatRange.hpp0000664000175000017500000002261015211533244025163 0ustar alastairalastair/* * 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. */ #ifndef ecflow_attribute_RepeatRange_HPP #define ecflow_attribute_RepeatRange_HPP #include "ecflow/attribute/RepeatAttr.hpp" #include "ecflow/core/Calendar.hpp" namespace ecf { template struct Range { explicit Range(const R& r) : r_(r) {} const R& r_; }; template <> struct Range { using size_type = std::size_t; using iterator = std::size_t; using value_type = int; explicit Range(const RepeatDay& r) : r_(r) {} iterator begin() const { return 0; } iterator end() const { return 0; } iterator current_index() const { return r_.index_or_value(); } value_type current_value() const { return r_.value(); } size_type size() const { return end() - begin(); } private: const RepeatDay& r_; }; template <> struct Range { using size_type = std::size_t; using iterator = std::size_t; using value_type = int; explicit Range(const RepeatDate& r) : r_(r) {} iterator begin() const { return 0; } iterator end() const { int i = ecf::CalendarDate(r_.start()).as_julian_day().value(); int j = ecf::CalendarDate(r_.end()).as_julian_day().value(); int s = r_.step(); int d = (j - i) + 1; return (d / s) + ((d % s == 0) ? 0 : 1); } iterator current_index() const { auto i = ecf::CalendarDate(r_.start()).as_julian_day().value(); auto s = r_.step(); auto v = ecf::CalendarDate(r_.value()).as_julian_day().value(); auto idx = (v - i) / s; return idx; } value_type current_value() const { return r_.value(); } value_type at(iterator i) const { int j = ecf::CalendarDate(r_.start()).as_julian_day().value(); return ecf::JulianDay(j + i * r_.step()).as_calendar_date().value(); } size_type size() const { return end() - begin(); } private: const RepeatDate& r_; }; template <> struct Range { using date_t = int; using size_type = std::size_t; using iterator = std::size_t; using value_type = date_t; explicit Range(const RepeatDateList& r) : r_(r) {} iterator begin() const { return 0; } iterator end() const { return r_.indexNum(); } iterator current_index() const { return r_.index_or_value(); } value_type current_value() const { return r_.value(); } value_type at(iterator i) const { return r_.value_at(i); } size_type size() const { return end() - begin(); } private: const RepeatDateList& r_; }; template <> struct Range { using size_type = std::size_t; using iterator = std::size_t; using value_type = Instant; explicit Range(const RepeatDateTimeList& r) : r_(r) {} iterator begin() const { return 0; } iterator end() const { return r_.indexNum(); } iterator current_index() const { return r_.index_or_value(); } value_type current_value() const { return r_.values().at(r_.index_or_value()); } value_type at(iterator i) const { return r_.values().at(i); } size_type size() const { return end() - begin(); } private: const RepeatDateTimeList& r_; }; template <> struct Range { using size_type = std::size_t; using iterator = std::size_t; using value_type = Instant; explicit Range(const RepeatDateTime& r) : r_(r) {} iterator begin() const { return 0; } iterator end() const { auto i = r_.start_instant(); auto j = r_.end_instant(); auto s = r_.step_duration(); auto d = (j - i); auto idx = d.as_seconds().count() / s.as_seconds().count(); auto last = i + Duration{std::chrono::seconds{s.as_seconds().count() * idx}}; if (last <= j) { idx++; } return idx; } iterator current_index() const { auto i = r_.start_instant(); auto s = r_.step_duration(); auto v = r_.value_instant(); auto idx = (v - i).as_seconds().count() / s.as_seconds().count(); return idx; } value_type current_value() const { return r_.value_instant(); } value_type at(iterator i) const { auto j = r_.start_instant(); auto s = r_.step_duration(); return j + Duration{std::chrono::seconds{s.as_seconds().count() * i}}; } size_type size() const { return end() - begin(); } private: const RepeatDateTime& r_; }; template <> struct Range { using size_type = std::size_t; using iterator = std::size_t; using value_type = int; explicit Range(const RepeatInteger& r) : r_(r) {} iterator begin() const { return 0; } iterator end() const { int i = r_.start(); int j = r_.end(); int s = r_.step(); int d = (j - i) + 1; auto idx = (d / s) + ((d % s == 0) ? 0 : 1); return idx; } iterator current_index() const { auto i = r_.start(); auto s = r_.step(); auto v = r_.value(); auto idx = (v - i) / s; return idx; } value_type current_value() const { return r_.value(); } value_type at(iterator i) const { return r_.start() + i * r_.step(); } size_type size() const { return end() - begin(); } private: const RepeatInteger& r_; }; template <> struct Range { using size_type = std::size_t; using iterator = std::size_t; using value_type = std::string; explicit Range(const RepeatEnumerated& r) : r_(r) {} iterator begin() const { return 0; } iterator end() const { return r_.end() + 1; } iterator current_index() const { return r_.index_or_value(); } value_type current_value() const { return r_.valueAsString(); } value_type at(iterator i) const { return r_.value_as_string(i); } size_type size() const { return end() - begin(); } private: const RepeatEnumerated& r_; }; template <> struct Range { using size_type = std::size_t; using iterator = std::size_t; using value_type = std::string; explicit Range(const RepeatString& r) : r_(r) {} iterator begin() const { return 0; } iterator end() const { return r_.end() + 1; } iterator current_index() const { return r_.index_or_value(); } value_type current_value() const { return r_.valueAsString(); } value_type at(iterator i) const { return r_.value_as_string(i); } size_type size() const { return end() - begin(); } private: const RepeatString& r_; }; template auto front(const Range& rng) { assert(rng.size() > 0); return rng.at(rng.begin()); } template auto back(const Range& rng) { assert(rng.size() > 0); return rng.at(rng.end() - 1); } template auto size(const Range& rng) { return rng.size(); } template bool empty(const Range& rng) { return size(rng) == 0; } struct Limits { /* * The limits of a Repeat is described by a 0-based range. * * - begin: the first index in the range (inclusive, meaning it is a valid index) * - end: the last index in the range (exclusive, meaning it is one past the last valid index) * * - current is expected to be in the interval [begin, end[ */ size_t begin; size_t end; size_t current; bool is_first() const { return current == begin; } bool is_last() const { return current + 1 >= end; } }; Limits limits_of(const RepeatBase* repeat) { if (auto r1 = dynamic_cast(repeat)) { Range rng(*r1); return {rng.begin(), rng.end(), rng.current_index()}; } else if (auto r2 = dynamic_cast(repeat)) { Range rng(*r2); return {rng.begin(), rng.end(), rng.current_index()}; } else if (auto r3 = dynamic_cast(repeat)) { Range rng(*r3); return {rng.begin(), rng.end(), rng.current_index()}; } else if (auto r3b = dynamic_cast(repeat)) { Range rng(*r3b); return {rng.begin(), rng.end(), rng.current_index()}; } else if (auto r4 = dynamic_cast(repeat)) { Range rng(*r4); return {rng.begin(), rng.end(), rng.current_index()}; } else if (auto r5 = dynamic_cast(repeat)) { Range rng(*r5); return {rng.begin(), rng.end(), rng.current_index()}; } else if (auto r6 = dynamic_cast(repeat)) { Range rng(*r6); return {rng.begin(), rng.end(), rng.current_index()}; } else if (auto r7 = dynamic_cast(repeat)) { Range rng(*r7); return {rng.begin(), rng.end(), rng.current_index()}; } assert(false); // Should never be reached! return {0, 1, 0}; } template auto make_range(const Repeat& repeat) { return Range{repeat.as()}; } } // namespace ecf #endif /* ecflow_attribute_RepeatRange_HPP */ ecflow-5.17.0/libs/attribute/src/ecflow/attribute/TimeAttr.cpp0000664000175000017500000001617615211533244024524 0ustar alastairalastair/* * 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 "ecflow/attribute/TimeAttr.hpp" #include #include #include "ecflow/attribute/DateAttr.hpp" // Used in Why #include "ecflow/attribute/DayAttr.hpp" // Used in Why #include "ecflow/core/Calendar.hpp" #include "ecflow/core/Ecf.hpp" #include "ecflow/core/Log.hpp" #include "ecflow/core/Serialization.hpp" #include "ecflow/core/Str.hpp" namespace ecf { TimeAttr::TimeAttr(const std::string& str) { if (str.empty()) { throw std::runtime_error("Time::Time: empty string passed"); } std::vector tokens; ecf::algorithm::split_at(tokens, str); if (tokens.empty()) { throw std::runtime_error("Time::Time: incorrect time string ?"); } size_t index = 0; ts_ = TimeSeries::create(index, tokens, false /*parse_state*/); } void TimeAttr::calendarChanged(const ecf::Calendar& c) { // ensure this called first , since we need always update for relative duration. ECFLOW-1648 if (ts_.calendarChanged(c)) { state_change_no_ = Ecf::incr_state_change_no(); } // log(Log::DBG,"TimeAttr::calendarChanged(1) " + dump()); // ECFLOW-1648 if (free_) { return; } // For a time series, we rely on the re queue to reset makeFree if (isFree(c)) { setFree(); } // log(Log::DBG,"TimeAttr::calendarChanged(2) " + dump()); // ECFLOW-1648 } void TimeAttr::resetRelativeDuration() { if (ts_.resetRelativeDuration()) { state_change_no_ = Ecf::incr_state_change_no(); } } std::string TimeAttr::name() const { std::string ret; write(ret); ts_.write_state_for_gui(ret, free_); return ret; } std::string TimeAttr::toString() const { std::string ret; write(ret); return ret; } void TimeAttr::write(std::string& ret) const { ret += "time "; ts_.write(ret); } std::string TimeAttr::dump() const { return MESSAGE("time " << (free_ ? "(free) " : "(holding) ") << ts_.dump()); } bool TimeAttr::operator==(const TimeAttr& rhs) const { if (free_ != rhs.free_) { return false; } return ts_.operator==(rhs.ts_); } bool TimeAttr::structureEquals(const TimeAttr& rhs) const { return ts_.structureEquals(rhs.ts_); } bool TimeAttr::isFree(const ecf::Calendar& calendar) const { // The FreeDepCmd can be used to free the time, if (free_) { return true; } return is_free(calendar); } bool TimeAttr::is_free(const ecf::Calendar& calendar) const { return ts_.isFree(calendar); } void TimeAttr::setFree() { free_ = true; state_change_no_ = Ecf::incr_state_change_no(); #ifdef DEBUG_STATE_CHANGE_NO std::cout << "TimeAttr::setFree()\n"; #endif } void TimeAttr::clearFree() { free_ = false; state_change_no_ = Ecf::incr_state_change_no(); #ifdef DEBUG_STATE_CHANGE_NO std::cout << "TimeAttr::clearFree()\n"; #endif } void TimeAttr::miss_next_time_slot() { ts_.miss_next_time_slot(); state_change_no_ = Ecf::incr_state_change_no(); } bool TimeAttr::why(const ecf::Calendar& c, const std::vector& days, const std::vector& dates, std::string& theReasonWhy) const { if (isFree(c)) { return false; } theReasonWhy += "is time "; if (!days.empty()) { theReasonWhy += "and day "; } if (!dates.empty()) { theReasonWhy += "and date "; } theReasonWhy += "dependent"; // Check to see if time has expired; if not, then report why if (ts_.is_valid()) { // This can apply to single and series boost::posix_time::time_duration calendar_time = ts_.duration(c); if (calendar_time < ts_.start().duration()) { ts_.why(c, theReasonWhy); return true; } // calendar_time >= ts_.start().duration() if (ts_.hasIncrement()) { if (calendar_time < ts_.finish().duration()) { ts_.why(c, theReasonWhy); return true; } } // calendar_time >= ts_.start().duration() && calendar_time >= ts_.finish().duration() // past the end of time slot, hence this should not hold job generation, } // the time has expired theReasonWhy += " ( '"; theReasonWhy += toString(); theReasonWhy += "' has expired,"; // take into account, user can use run/force complete to miss time slots bool do_a_requeue = ts_.requeueable(c); if (do_a_requeue) { TimeSlot the_next_time_slot = ts_.compute_next_time_slot(c); if (the_next_time_slot.isNULL() || !ts_.hasIncrement()) { theReasonWhy += " *re-queue* to run at this time"; } else { theReasonWhy += " *re-queue* to run at "; theReasonWhy += the_next_time_slot.toString(); } } else { if (ts_.relative()) { theReasonWhy += " please *re-queue*, to reset the relative duration"; } else { boost::gregorian::date the_min_next_date; if (!days.empty() || !dates.empty()) { for (const auto& day : days) { auto the_next_matching_date = day.next_matching_date(c); if (the_min_next_date.is_special()) { the_min_next_date = the_next_matching_date; } if (the_next_matching_date < the_min_next_date) { the_min_next_date = the_next_matching_date; } } for (const auto& date : dates) { auto the_next_matching_date = date.next_matching_date(c); if (the_min_next_date.is_special()) { the_min_next_date = the_next_matching_date; } if (the_next_matching_date < the_min_next_date) { the_min_next_date = the_next_matching_date; } } theReasonWhy += " next run at "; } else { auto one_day = boost::gregorian::date_duration(1); the_min_next_date = c.date(); // today's date the_min_next_date += one_day; // add one day, so its in the future theReasonWhy += " next run tomorrow at "; } theReasonWhy += ts_.start().toString(); theReasonWhy += " "; theReasonWhy += to_simple_string(the_min_next_date); } } theReasonWhy += " )"; return true; } template void TimeAttr::serialize(Archive& ar) { ar(CEREAL_NVP(ts_)); // Only persisted for testing, see usage of isSetFree() CEREAL_OPTIONAL_NVP(ar, free_, [this]() { return free_; }); // conditionally save } CEREAL_TEMPLATE_SPECIALIZE(TimeAttr); } // namespace ecf ecflow-5.17.0/libs/attribute/src/ecflow/attribute/AutoArchiveAttr.hpp0000664000175000017500000000403715211533244026036 0ustar alastairalastair/* * 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. */ #ifndef ecflow_attribute_AutoArchiveAttr_HPP #define ecflow_attribute_AutoArchiveAttr_HPP #include #include "ecflow/core/NState.hpp" #include "ecflow/core/TimeSlot.hpp" namespace ecf { class Calendar; } // namespace ecf namespace ecf { // Use compiler , destructor, assignment, copy constructor class AutoArchiveAttr { public: AutoArchiveAttr() = default; AutoArchiveAttr(int hour, int minute, bool relative, bool idle = false) : time_(hour, minute), relative_(relative), idle_(idle) {} AutoArchiveAttr(const TimeSlot& ts, bool relative, bool idle = false) : time_(ts), relative_(relative), idle_(idle) {} explicit AutoArchiveAttr(int days, bool idle = false) : time_(TimeSlot(days * 24, 0)), days_(true), idle_(idle) {} bool operator==(const AutoArchiveAttr& rhs) const; bool operator<(const AutoArchiveAttr& rhs) const { return time_ < rhs.time(); } bool isFree(const ecf::Calendar&, const std::pair& last_state_and_change_duration) const; std::string toString() const; const TimeSlot& time() const { return time_; } bool relative() const { return relative_; } bool days() const { return days_; } bool idle() const { return idle_; } public: void write(std::string&) const; private: TimeSlot time_; bool relative_{true}; bool days_{false}; bool idle_{false}; friend class cereal::access; template void serialize(Archive& ar, std::uint32_t const version); }; } // namespace ecf #endif /* ecflow_attribute_AutoArchiveAttr_HPP */ ecflow-5.17.0/libs/attribute/src/ecflow/attribute/NodeAttr.cpp0000664000175000017500000003436615211533244024514 0ustar alastairalastair/* * 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 "ecflow/attribute/NodeAttr.hpp" #include #include #include "ecflow/core/Converter.hpp" #include "ecflow/core/Ecf.hpp" #include "ecflow/core/Message.hpp" #include "ecflow/core/Serialization.hpp" #include "ecflow/core/Str.hpp" using namespace ecf; const std::string& Event::SET() { static const std::string SET = "set"; return SET; } const std::string& Event::CLEAR() { static const std::string CLEAR = "clear"; return CLEAR; } const Event& Event::EMPTY() { static const Event EVENT = Event(); return EVENT; } const Meter& Meter::EMPTY() { static const Meter METER = Meter(); return METER; } const Label& Label::EMPTY() { static const Label LABEL = Label(); return LABEL; } //////////////////////////////////////////////////////////////////////////////////////////// Event::Event(int number, const std::string& eventName, bool iv, bool check_name) : n_(eventName), number_(number), v_(iv), iv_(iv) { if (!eventName.empty() && check_name) { std::string msg; if (!ecf::algorithm::is_valid_name(eventName, msg)) { throw std::runtime_error("Event::Event: Invalid event name : " + msg); } } } Event::Event(const std::string& eventName, bool iv) : n_(eventName), v_(iv), iv_(iv) { if (eventName.empty()) { throw std::runtime_error("Event::Event: Invalid event name : name must be specified if no number supplied"); } // If the eventName is an integer, then treat it as such, by setting number_ and clearing n_ // This was added after migration failed, since *python* api allowed: // ta.add_event(1); // ta.add_event("1"); // and when we called ecflow_client --migrate/--get it generated // event 1 // event 1 // which then did *not* load. // // Test for numeric, and then casting, is ****faster***** than relying on exception alone if (eventName.find_first_of(ecf::string_constants::numeric_chars) == 0) { try { number_ = ecf::convert_to(eventName); n_.clear(); return; } catch (const ecf::bad_conversion&) { // cast failed, a real string, carry on } } std::string msg; if (!ecf::algorithm::is_valid_name(eventName, msg)) { throw std::runtime_error("Event::Event: Invalid event name : " + msg); } } Event Event::make_from_value(const std::string& name, const std::string& value) { // value is expected to be either "set" or "clear" if (!isValidState(value)) { throw std::runtime_error("Event::Event: Invalid state : " + value); } return Event(name, value == Event::SET()); } void Event::set_value(bool b) { v_ = b; state_change_no_ = Ecf::incr_state_change_no(); #ifdef DEBUG_STATE_CHANGE_NO std::cout << "Event::set_value\n"; #endif } std::string Event::name_or_number() const { if (n_.empty()) { return MESSAGE(number_); } return n_; } bool Event::operator<(const Event& rhs) const { if (!n_.empty() && !rhs.name().empty()) { return n_ < rhs.name(); } if (n_.empty() && rhs.name().empty()) { return number_ < rhs.number(); } return name_or_number() < rhs.name_or_number(); } bool Event::operator==(const Event& rhs) const { if (v_ != rhs.v_) { #ifdef DEBUG if (Ecf::debug_equality()) { std::cout << "v_ != rhs.v_ (v_:" << v_ << " rhs.v_:" << rhs.v_ << ") " << toString() << "\n"; } #endif return false; } if (number_ != rhs.number_) { #ifdef DEBUG if (Ecf::debug_equality()) { std::cout << "number_ != rhs.number_ (number_:" << number_ << " rhs.number_:" << rhs.number_ << ") " << toString() << "\n"; } #endif return false; } if (n_ != rhs.n_) { #ifdef DEBUG if (Ecf::debug_equality()) { std::cout << "n_ != rhs.n_ (n_:" << n_ << " rhs.n_:" << rhs.n_ << ") " << toString() << "\n"; } #endif return false; } if (iv_ != rhs.iv_) { #ifdef DEBUG if (Ecf::debug_equality()) { std::cout << "iv_ != rhs.iv_ (iv_:" << iv_ << " rhs.iv_:" << rhs.iv_ << ") " << toString() << "\n"; } #endif return false; } return true; } bool Event::compare(const Event& rhs) const { if (number_ != rhs.number_) { return false; } if (n_ != rhs.n_) { return false; } return true; } std::string Event::toString() const { std::string ret; write(ret); return ret; } void Event::write(std::string& ret) const { ret += "event "; if (number_ == std::numeric_limits::max()) { ret += n_; } else { ret += ecf::convert_to(number_); ret += " "; ret += n_; } if (iv_) { ret += " set"; // initial value } } std::string Event::dump() const { return MESSAGE(toString() << " value(" << v_ << ") used(" << used_ << ")"); } bool Event::isValidState(const std::string& state) { return state == Event::SET() || state == Event::CLEAR(); } //////////////////////////////////////////////////////////////////////////////////////////// Meter::Meter(const std::string& name, int min, int max, int colorChange, int value, bool check) : min_(min), max_(max), v_(value), cc_(colorChange), n_(name) { if (check) { if (!ecf::algorithm::is_valid_name(name)) { throw std::runtime_error("Meter::Meter: Invalid Meter name: " + name); } } if (min > max) { throw std::out_of_range("Meter::Meter: Invalid Meter(name,min,max,color_change) : min must be less than max"); } if (colorChange == std::numeric_limits::max()) { cc_ = max_; } if (value == std::numeric_limits::max()) { v_ = min_; } if (cc_ < min || cc_ > max) { throw std::out_of_range(MESSAGE("Meter::Meter: Invalid Meter(name,min,max,color_change) color_change(" << cc_ << ") must be between min(" << min_ << ") and max(" << max_ << ")")); } } 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_at(tokens, value, ","); if (tokens.size() != 3) { throw std::runtime_error( MESSAGE("Meter::make_from_value: Expect three comma-separated values, but found: '" << value << "'")); } try { auto min = ecf::convert_to(tokens[0]); auto max = ecf::convert_to(tokens[1]); auto value = ecf::convert_to(tokens[2]); return Meter(name, min, max, max, value, false); } catch (const ecf::bad_conversion&) { throw std::runtime_error(MESSAGE("Meter::make_from_value: Expect three comma-separated values, but found: (" << tokens[0] << ", " << tokens[1] << ", " << tokens[2] << ")")); } } void Meter::set_value(int v) { if (!isValidValue(v)) { throw std::runtime_error(MESSAGE("Meter::set_value(int): The meter(" << n_ << ") value must be in the range[" << min() << "->" << max() << "] but found '" << v << "'")); } v_ = v; state_change_no_ = Ecf::incr_state_change_no(); #ifdef DEBUG_STATE_CHANGE_NO std::cout << "Meter::set_value\n"; #endif } bool Meter::operator==(const Meter& rhs) const { if (v_ != rhs.v_) { return false; } if (min_ != rhs.min_) { return false; } if (max_ != rhs.max_) { return false; } if (cc_ != rhs.cc_) { return false; } if (n_ != rhs.n_) { return false; } return true; } std::string Meter::toString() const { std::string ret; write(ret); return ret; } void Meter::write(std::string& ret) const { ret += "meter "; ret += n_; ret += " "; ret += ecf::convert_to(min_); ret += " "; ret += ecf::convert_to(max_); ret += " "; ret += ecf::convert_to(cc_); } std::string Meter::dump() const { return MESSAGE("meter " << n_ << " min(" << min_ << ") max (" << max_ << ") colorChange(" << cc_ << ") value(" << v_ << ") used(" << used_ << ")"); } ///////////////////////////////////////////////////////////////////////////////////////////// Label::Label(const std::string& name, const std::string& value, const std::string& new_value, bool check_name) : n_(name), v_(value), new_v_(new_value) { if (check_name && !ecf::algorithm::is_valid_name(n_)) { throw std::runtime_error(MESSAGE("Label::Label: Invalid Label name :" << n_)); } } std::string Label::toString() const { // parsing always STRIPS the quotes, hence add them back std::string ret; ret.reserve(n_.size() + v_.size() + 10); write(ret); return ret; } void Label::write(std::string& ret) const { // parsing always STRIPS the quotes, hence add them back ret += "label "; ret += n_; ret += " \""; if (v_.find("\n") == std::string::npos) { ret += v_; } else { // replace \n, otherwise re-parse will fail std::string value = v_; ecf::algorithm::replace_all(value, "\n", "\\n"); ret += value; } ret += "\""; } std::string Label::dump() const { return MESSAGE(toString() << " : \"" << new_v_ << "\""); } void Label::set_new_value(const std::string& l) { new_v_ = l; state_change_no_ = Ecf::incr_state_change_no(); #ifdef DEBUG_STATE_CHANGE_NO std::cout << "Label::set_new_value\n"; #endif } void Label::reset() { new_v_.clear(); state_change_no_ = Ecf::incr_state_change_no(); #ifdef DEBUG_STATE_CHANGE_NO std::cout << "Label::reset()\n"; #endif } void Label::parse(const std::string& line, std::vector& lineTokens, bool parse_state) { parse(line, lineTokens, parse_state, n_, v_, new_v_); } void Label::parse(const std::string& line, std::vector& lineTokens, bool parse_state, std::string& the_name, std::string& the_value, std::string& the_new_value) { size_t line_token_size = lineTokens.size(); if (line_token_size < 3) { throw std::runtime_error("Label::parse: Invalid label :" + line); } the_name = lineTokens[1]; // parsing will always STRIP single or double quotes, print will add double quotes // label simple_label 'ecgems' if (line_token_size == 3) { 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) { ecf::algorithm::replace_all(the_value, "\\n", "\n"); } } else { // label complex_label "smsfetch -F %ECF_FILES% -I %ECF_INCLUDE%" # fred // label simple_label "fred" # "smsfetch -F %ECF_FILES% -I %ECF_INCLUDE%" std::string value; value.reserve(line.size()); for (size_t i = 2; i < line_token_size; ++i) { if (lineTokens[i].at(0) == '#') { break; } if (i != 2) { value += " "; } value += lineTokens[i]; } ecf::algorithm::remove_double_quotes(value); ecf::algorithm::remove_single_quotes(value); the_value = value; if (the_value.find("\\n") != std::string::npos) { ecf::algorithm::replace_all(the_value, "\\n", "\n"); } // state if (parse_state) { // label name "value" # "new value" bool comment_fnd = false; size_t first_quote_after_comment = 0; size_t last_quote_after_comment = 0; for (size_t i = line.size() - 1; i > 0; i--) { if (line[i] == '#') { comment_fnd = true; break; } if (line[i] == '"') { if (last_quote_after_comment == 0) { last_quote_after_comment = i; } first_quote_after_comment = i; } } if (comment_fnd && first_quote_after_comment != last_quote_after_comment) { std::string new_value = line.substr(first_quote_after_comment + 1, last_quote_after_comment - first_quote_after_comment - 1); // std::cout << "new label = '" << new_value << "'\n"; the_new_value = new_value; if (the_new_value.find("\\n") != std::string::npos) { ecf::algorithm::replace_all(the_new_value, "\\n", "\n"); } } } } } template void Label::serialize(Archive& ar) { ar(CEREAL_NVP(n_)); CEREAL_OPTIONAL_NVP(ar, v_, [this]() { return !v_.empty(); }); CEREAL_OPTIONAL_NVP(ar, new_v_, [this]() { return !new_v_.empty(); }); // conditionally save } template void Event::serialize(Archive& ar) { CEREAL_OPTIONAL_NVP(ar, n_, [this]() { return !n_.empty(); }); CEREAL_OPTIONAL_NVP(ar, number_, [this]() { return number_ != std::numeric_limits::max(); }); CEREAL_OPTIONAL_NVP(ar, v_, [this]() { return v_; }); CEREAL_OPTIONAL_NVP(ar, iv_, [this]() { return iv_; }); } template void Meter::serialize(Archive& ar) { ar(CEREAL_NVP(min_), CEREAL_NVP(max_), CEREAL_NVP(v_), CEREAL_NVP(n_), CEREAL_NVP(cc_)); } CEREAL_TEMPLATE_SPECIALIZE(Label); CEREAL_TEMPLATE_SPECIALIZE(Event); CEREAL_TEMPLATE_SPECIALIZE(Meter); ecflow-5.17.0/libs/attribute/src/ecflow/attribute/ZombieAttr.hpp0000664000175000017500000000614215211533244025050 0ustar alastairalastair/* * 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. */ #ifndef ecflow_attribute_ZombieAttr_HPP #define ecflow_attribute_ZombieAttr_HPP #include #include "ecflow/core/Child.hpp" #include "ecflow/core/ZombieCtrlAction.hpp" namespace cereal { class access; } // Class ZombieAttr: // Use compiler , generated destructor, assignment, copy constructor // ZombieAttr does *not* have any changeable state class ZombieAttr { public: ZombieAttr(ecf::Child::ZombieType t, const std::vector& c, ecf::ZombieCtrlAction a, int zombie_lifetime = 0); ZombieAttr() = default; bool operator==(const ZombieAttr& rhs) const; bool empty() const { return zombie_type_ == ecf::Child::NOT_SET; } ecf::Child::ZombieType zombie_type() const { return zombie_type_; } ecf::ZombieCtrlAction action() const { return action_; } int zombie_lifetime() const { return zombie_lifetime_; } const std::vector& child_cmds() const { return child_cmds_; } std::vector::const_iterator child_begin() const { return child_cmds_.begin(); } // for python std::vector::const_iterator child_end() const { return child_cmds_.end(); } // for python std::string toString() const; bool fob(ecf::Child::CmdType) const; bool fail(ecf::Child::CmdType) const; bool adopt(ecf::Child::CmdType) const; bool block(ecf::Child::CmdType) const; bool remove(ecf::Child::CmdType) const; bool kill(ecf::Child::CmdType) const; /// Create from a string. Will throw std::runtime_error of parse errors /// expects ::child_cmds:zombie_lifetime static ZombieAttr create(const std::string& str); // Added to support return by reference static const ZombieAttr& EMPTY(); // Provide the default behaviour static ZombieAttr get_default_attr(ecf::Child::ZombieType); static int default_ecf_zombie_life_time() { return 3600; } static int default_user_zombie_life_time() { return 300; } static int default_path_zombie_life_time() { return 900; } static int minimum_zombie_life_time() { return 60; } public: void write(std::string&) const; private: std::vector child_cmds_; // init, event, meter,label, complete ecf::Child::ZombieType zombie_type_{ecf::Child::NOT_SET}; // User,path or ecf ecf::ZombieCtrlAction action_{ecf::ZombieCtrlAction::BLOCK}; // fob, fail,remove, adopt, block, kill int zombie_lifetime_{0}; // How long zombie lives in server friend class cereal::access; template void serialize(Archive& ar, std::uint32_t const version); }; #endif /* ecflow_attribute_ZombieAttr_HPP */ ecflow-5.17.0/libs/attribute/src/ecflow/attribute/NodeAttr.hpp0000664000175000017500000001623315211533244024512 0ustar alastairalastair/* * 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. */ #ifndef ecflow_attribute_NodeAttr_HPP #define ecflow_attribute_NodeAttr_HPP #include // for std::numeric_limits::max() #include #include #include namespace cereal { class access; } //////////////////////////////////////////////////////////////////////////////////////// // Class Label: // Use compiler , generated destructor, assignment, copy constructor class Label : public boost::equality_comparable