diff --git a/.clang-tidy b/.clang-tidy index eca959dae..2a5f169e5 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -12,6 +12,7 @@ Checks: > -bugprone-misplaced-widening-cast, -bugprone-narrowing-conversions, -bugprone-parent-virtual-call, + -bugprone-throwing-static-initialization, cppcoreguidelines-*, -cppcoreguidelines-avoid-const-or-ref-data-members, -cppcoreguidelines-avoid-do-while, diff --git a/.github/cd-config.yml b/.github/cd-config.yml index 679cf61e0..8d4adf4ea 100644 --- a/.github/cd-config.yml +++ b/.github/cd-config.yml @@ -94,6 +94,36 @@ builds: install_test: true install_test_command: /usr/bin/ecflow_client --version + - name: "deb-debian-13-server" + type: system-package + enabled: true + config: + os: debian-13 + dependencies: + - ecmwf/ecbuild + cmake_options: + -DPython3_EXECUTABLE: /usr/bin/python3 + -DBOOST_ROOT: ${BOOST_ROOT_DIR} + -DBOOST_INCLUDEDIR: ${BOOST_INCLUDE_DIR} + -DBOOST_LIBRARYDIR: ${BOOST_LIB_DIR} + -DBoost_DEBUG: ON + -DOPENSSL_ROOT_DIR: ${OPENSSL_ROOT_DIR} + -DENABLE_STATIC_BOOST_LIBS: OFF + -DINSTALL_PYTHON3_DIR: lib/python3/dist-packages + -DENABLE_ALL_TESTS: ON + -DENABLE_SSL: ON + -DENABLE_SERVER: ON + -DENABLE_UI: OFF + -DCPACK_PACKAGE_NAME: ecflow + ctest_options: + - -L nightly + - -E s_test|s_zombies + dependency_branch: develop + parallelism_factor: 8 + install_prefix: /usr + install_test: true + install_test_command: /usr/bin/ecflow_client --version + - name: "tarball" type: "tarball" enabled: true diff --git a/.github/ci-config.yml b/.github/ci-config.yml index b54a47016..921a22ea9 100644 --- a/.github/ci-config.yml +++ b/.github/ci-config.yml @@ -8,6 +8,7 @@ cmake_options: >- -DBOOST_LIBRARYDIR=${BOOST_LIB_DIR} -DOPENSSL_ROOT_DIR=${OPENSSL_ROOT_DIR} -DENABLE_STATIC_BOOST_LIBS=OFF + -DCMAKE_PREFIX_PATH=/opt/ecmwf/stack-deps/share/cmake/pybind11 ctest_options: >- -L nightly diff --git a/.github/ci-hpc-config.yml b/.github/ci-hpc-config.yml index 9eb86ed45..be29d315c 100644 --- a/.github/ci-hpc-config.yml +++ b/.github/ci-hpc-config.yml @@ -13,6 +13,34 @@ build: ctest_options: - -L nightly +gnu-15.2.0: + build: + modules: + - boost/1.90.0 + - ninja + - python3/3.13.13-01 + - qt/6.6.1 + - cmake/new + dependencies: + - ecmwf/ecbuild@develop + parallel: 64 + cmake_options: + - >- + -DCMAKE_VERBOSE_MAKEFILE=ON + -DENABLE_ALL_TESTS=ON + -DBoost_ROOT=/usr/local/apps/boost/1.90.0/GNU/15.2 + -DBoost_INCLUDE_DIR=/usr/local/apps/boost/1.90.0/GNU/15.2/include + -DBoost_PYTHON310_LIBRARY_RELEASE=/usr/local/apps/boost/1.90.0/GNU/15.2/lib/libboost_python313.so + -DPython3_ROOT_DIR=/usr/local/apps/python3/3.13.13-01 + -DPython3_EXECUTABLE=/usr/local/apps/python3/3.13.13-01/bin/python3 + -DPython3_INCLUDE_DIRS=/usr/local/apps/python3/3.13.13-01/include/python3.13 + -DPython3_LIBRARIES=/usr/local/apps/python3/3.13.13-01/lib64/libpython3.13.so + -DPython3_LIBRARY_DIRS=/usr/local/apps/python3/3.13.13-01/lib64 + -DCMAKE_PREFIX_PATH=/usr/local/apps/python3/3.13.13-01/lib/python3.13/site-packages/pybind11/share/cmake/pybind11 + ctest_options: + # disable s_http* tests + - -L nightly -E '(s_http)' + gnu-14.2.0: build: modules: @@ -30,12 +58,12 @@ gnu-14.2.0: -DENABLE_ALL_TESTS=ON -DBoost_ROOT=/usr/local/apps/boost/1.87.0/GNU/14.2 -DBoost_INCLUDE_DIR=/usr/local/apps/boost/1.87.0/GNU/14.2/include - -DBoost_PYTHON310_LIBRARY_RELEASE=/usr/local/apps/boost/1.87.0/GNU/14.2/lib/libboost_python312.so -DPython3_ROOT_DIR=/usr/local/apps/python3/3.12.9-01 -DPython3_EXECUTABLE=/usr/local/apps/python3/3.12.9-01/bin/python3 -DPython3_INCLUDE_DIRS=/usr/local/apps/python3/3.12.9-01/include/python3.12 -DPython3_LIBRARIES=/usr/local/apps/python3/3.12.9-01/lib64/libpython3.12.so -DPython3_LIBRARY_DIRS=/usr/local/apps/python3/3.12.9-01/lib64 + -DCMAKE_PREFIX_PATH=/usr/local/apps/python3/3.12.11-01/lib/python3.12/site-packages/pybind11/share/cmake/pybind11 ctest_options: # disable s_http* tests - -L nightly -E '(s_http)' @@ -54,15 +82,15 @@ gnu-12.2.0: cmake_options: - >- -DCMAKE_VERBOSE_MAKEFILE=ON - -DENABLE_ALL_TESTS=ON - -DBoost_ROOT=/usr/local/apps/boost/1.81.0/GNU/12.2 - -DBoost_INCLUDE_DIR=/usr/local/apps/boost/1.81.0/GNU/12.2/include - -DBoost_PYTHON310_LIBRARY_RELEASE=/usr/local/apps/boost/1.81.0/GNU/12.2/lib/libboost_python310.so - -DPython3_ROOT_DIR=/usr/local/apps/python3/3.10.10-01 - -DPython3_EXECUTABLE=/usr/local/apps/python3/3.10.10-01/bin/python3 - -DPython3_INCLUDE_DIRS=/usr/local/apps/python3/3.10.10-01/include/python3.10 - -DPython3_LIBRARIES=/usr/local/apps/python3/3.10.10-01/lib64/libpython3.10.so + -DENABLE_ALL_TESTS=ON + -DBoost_ROOT=/usr/local/apps/boost/1.81.0/GNU/12.2 + -DBoost_INCLUDE_DIR=/usr/local/apps/boost/1.81.0/GNU/12.2/include + -DPython3_ROOT_DIR=/usr/local/apps/python3/3.10.10-01 + -DPython3_EXECUTABLE=/usr/local/apps/python3/3.10.10-01/bin/python3 + -DPython3_INCLUDE_DIRS=/usr/local/apps/python3/3.10.10-01/include/python3.10 + -DPython3_LIBRARIES=/usr/local/apps/python3/3.10.10-01/lib64/libpython3.10.so -DPython3_LIBRARY_DIRS=/usr/local/apps/python3/3.10.10-01/lib64 + -DCMAKE_PREFIX_PATH=/usr/local/apps/python3/3.10.10-01/lib/python3.10/site-packages/pybind11/share/cmake/pybind11 ctest_options: # disable s_http* tests - -L nightly -E '(s_http)' @@ -82,14 +110,43 @@ gnu-8.5.0: - >- -DCMAKE_VERBOSE_MAKEFILE=ON -DENABLE_ALL_TESTS=ON - -DBoost_ROOT=/usr/local/apps/boost/1.84.0/GNU/8.5 - -DBoost_INCLUDE_DIR=/usr/local/apps/boost/1.84.0/GNU/8.5/include - -DBoost_PYTHON310_LIBRARY_RELEASE=/usr/local/apps/boost/1.84.0/GNU/8.5/lib/libboost_python311.so + -DBoost_ROOT=/usr/local/apps/boost/1.84.0/GNU/8.5 + -DBoost_INCLUDE_DIR=/usr/local/apps/boost/1.84.0/GNU/8.5/include -DPython3_ROOT_DIR=/usr/local/apps/python3/3.11.8-01 - -DPython3_EXECUTABLE=/usr/local/apps/python3/3.11.8-01/bin/python3 + -DPython3_EXECUTABLE=/usr/local/apps/python3/3.11.8-01/bin/python3 -DPython3_INCLUDE_DIRS=/usr/local/apps/python3/3.11.8-01/include/python3.11 - -DPython3_LIBRARIES=/usr/local/apps/python3/3.11.8-01/lib64/libpython3.11.so + -DPython3_LIBRARIES=/usr/local/apps/python3/3.11.8-01/lib64/libpython3.11.so -DPython3_LIBRARY_DIRS=/usr/local/apps/python3/3.11.8-01/lib64 + -DCMAKE_PREFIX_PATH=/usr/local/apps/python3/3.11.8-01/lib/python3.11/site-packages/pybind11/share/cmake/pybind11 + ctest_options: + # disable s_http* tests + - -L nightly -E '(s_http)' + +intel-2025.3.1: + build: + modules: + - boost/1.90.0 + - ninja + - python3/3.13.13-01 + - qt/6.6.1 + - cmake/new + dependencies: + - ecmwf/ecbuild@develop + parallel: 64 + cmake_options: + - >- + -DCMAKE_VERBOSE_MAKEFILE=ON + -DENABLE_ALL_TESTS=ON + -DCMAKE_CXX_COMPILER=icpx + -DBoost_ROOT=/usr/local/apps/boost/1.90.0/INTEL/2025.3 + -DBoost_INCLUDE_DIR=/usr/local/apps/boost/1.90.0/INTEL/2025.3/include + -DBoost_PYTHON310_LIBRARY_RELEASE=/usr/local/apps/boost/1.90.0/INTEL/2025.3/lib/libboost_python310.so + -DPython3_ROOT_DIR=/usr/local/apps/python3/3.13.13-01 + -DPython3_EXECUTABLE=/usr/local/apps/python3/3.13.13-01/bin/python3 + -DPython3_INCLUDE_DIRS=/usr/local/apps/python3/3.13.13-01/include/python3.10 + -DPython3_LIBRARIES=/usr/local/apps/python3/3.13.13-01/lib64/libpython3.10.so + -DPython3_LIBRARY_DIRS=/usr/local/apps/python3/3.13.13-01/lib64 + -DCMAKE_PREFIX_PATH=/usr/local/apps/python3/3.13.13-01/lib/python3.13/site-packages/pybind11/share/cmake/pybind11 ctest_options: # disable s_http* tests - -L nightly -E '(s_http)' @@ -112,12 +169,12 @@ intel-2025.0.1: -DCMAKE_CXX_COMPILER=icpx -DBoost_ROOT=/usr/local/apps/boost/1.87.0/INTEL/2025.0 -DBoost_INCLUDE_DIR=/usr/local/apps/boost/1.87.0/INTEL/2025.0/include - -DBoost_PYTHON310_LIBRARY_RELEASE=/usr/local/apps/boost/1.87.0/INTEL/2025.0/lib/libboost_python312.so -DPython3_ROOT_DIR=/usr/local/apps/python3/3.12.9-01 -DPython3_EXECUTABLE=/usr/local/apps/python3/3.12.9-01/bin/python3 -DPython3_INCLUDE_DIRS=/usr/local/apps/python3/3.12.9-01/include/python3.12 -DPython3_LIBRARIES=/usr/local/apps/python3/3.12.9-01/lib64/libpython3.12.so -DPython3_LIBRARY_DIRS=/usr/local/apps/python3/3.12.9-01/lib64 + -DCMAKE_PREFIX_PATH=/usr/local/apps/python3/3.12.9-01/lib/python3.12/site-packages/pybind11/share/cmake/pybind11 ctest_options: # disable s_http* tests - -L nightly -E '(s_http)' @@ -140,14 +197,14 @@ intel-2021.4.0: -DCMAKE_CXX_COMPILER=icpx -DBoost_ROOT=/usr/local/apps/boost/1.81.0/INTEL/2021.4 -DBoost_INCLUDE_DIR=/usr/local/apps/boost/1.81.0/INTEL/2021.4/include - -DBoost_PYTHON310_LIBRARY_RELEASE=/usr/local/apps/boost/1.81.0/INTEL/2021.4/lib/libboost_python310.so -DPython3_ROOT_DIR=/usr/local/apps/python3/3.10.10-01 -DPython3_EXECUTABLE=/usr/local/apps/python3/3.10.10-01/bin/python3 -DPython3_INCLUDE_DIRS=/usr/local/apps/python3/3.10.10-01/include/python3.10 -DPython3_LIBRARIES=/usr/local/apps/python3/3.10.10-01/lib64/libpython3.10.so -DPython3_LIBRARY_DIRS=/usr/local/apps/python3/3.10.10-01/lib64 + -DCMAKE_PREFIX_PATH=/usr/local/apps/python3/3.10.10-01/lib/python3.10/site-packages/pybind11/share/cmake/pybind11 ctest_options: - - -L nightly -E py3_ + - -L nightly -E '(py3_|s_http)' nvidia-24.11: build: @@ -166,12 +223,12 @@ nvidia-24.11: -DENABLE_ALL_TESTS=ON -DBoost_ROOT=/usr/local/apps/boost/1.87.0/NVIDIA/24.11 -DBoost_INCLUDE_DIR=/usr/local/apps/boost/1.87.0/NVIDIA/24.11/include - -DBoost_PYTHON310_LIBRARY_RELEASE=/usr/local/apps/boost/1.87.0/NVIDIA/24.11/lib/libboost_python312.so -DPython3_ROOT_DIR=/usr/local/apps/python3/3.12.9-01 -DPython3_EXECUTABLE=/usr/local/apps/python3/3.12.9-01/bin/python3 -DPython3_INCLUDE_DIRS=/usr/local/apps/python3/3.12.9-01/include/python3.12 -DPython3_LIBRARIES=/usr/local/apps/python3/3.12.9-01/lib64/libpython3.12.so -DPython3_LIBRARY_DIRS=/usr/local/apps/python3/3.12.9-01/lib64 + -DCMAKE_PREFIX_PATH=/usr/local/apps/python3/3.12.9-01/lib/python3.12/site-packages/pybind11/share/cmake/pybind11 ctest_options: # disable all tests - --version @@ -193,12 +250,12 @@ nvidia-22.11: -DENABLE_ALL_TESTS=ON -DBoost_ROOT=/usr/local/apps/boost/1.81.0/NVIDIA/22.11 -DBoost_INCLUDE_DIR=/usr/local/apps/boost/1.81.0/NVIDIA/22.11/include - -DBoost_PYTHON310_LIBRARY_RELEASE=/usr/local/apps/boost/1.81.0/NVIDIA/22.11/lib/libboost_python310.so -DPython3_ROOT_DIR=/usr/local/apps/python3/3.10.10-01 -DPython3_EXECUTABLE=/usr/local/apps/python3/3.10.10-01/bin/python3 -DPython3_INCLUDE_DIRS=/usr/local/apps/python3/3.10.10-01/include/python3.10 -DPython3_LIBRARIES=/usr/local/apps/python3/3.10.10-01/lib64/libpython3.10.so -DPython3_LIBRARY_DIRS=/usr/local/apps/python3/3.10.10-01/lib64 + -DCMAKE_PREFIX_PATH=/usr/local/apps/python3/3.10.10-01/lib/python3.10/site-packages/pybind11/share/cmake/pybind11 ctest_options: # disable all tests - --version @@ -220,12 +277,12 @@ aocc-4.0.0: -DENABLE_ALL_TESTS=ON -DBoost_ROOT=/usr/local/apps/boost/1.81.0/AMD/4.0 -DBoost_INCLUDE_DIR=/usr/local/apps/boost/1.81.0/AMD/4.0/include - -DBoost_PYTHON310_LIBRARY_RELEASE=/usr/local/apps/boost/1.81.0/AMD/4.0/lib/libboost_python310.so -DPython3_ROOT_DIR=/usr/local/apps/python3/3.10.10-01 -DPython3_EXECUTABLE=/usr/local/apps/python3/3.10.10-01/bin/python3 -DPython3_INCLUDE_DIRS=/usr/local/apps/python3/3.10.10-01/include/python3.10 -DPython3_LIBRARIES=/usr/local/apps/python3/3.10.10-01/lib64/libpython3.10.so -DPython3_LIBRARY_DIRS=/usr/local/apps/python3/3.10.10-01/lib64 + -DCMAKE_PREFIX_PATH=/usr/local/apps/python3/3.10.10-01/lib/python3.10/site-packages/pybind11/share/cmake/pybind11 ctest_options: # disable all tests - --version diff --git a/.github/workflows/dockit.yml b/.github/workflows/dockit.yml new file mode 100644 index 000000000..b21c0d6cc --- /dev/null +++ b/.github/workflows/dockit.yml @@ -0,0 +1,101 @@ +name: dockit + +on: + # Trigger the workflow manually + workflow_dispatch: ~ + +jobs: + + # Create the ecflow package + build-and-package: + name: build + if: ${{ !github.event.pull_request.head.repo.fork && github.event.action != 'labeled' || github.event.label.name == 'approved-for-docker' }} + + strategy: + max-parallel: 8 + fail-fast: false + matrix: + os: + - debian-13.5 + preset: + - linux.gcc.serveronly.relwithdebinfo + jobs: [ 4 ] + + runs-on: ubuntu-latest + + container: + image: marcosbento/lumen:${{ matrix.os }} + options: --user root + + timeout-minutes: 60 + + steps: + + - name: Display build context + run: | + echo "Working directory: ${{ github.workspace }}" + echo "Environment:" + echo "$(env)" + echo "OS info:" + echo "$(cat /etc/os-release)" + echo "CPU info:" + echo "$(cat /proc/cpuinfo)" + echo "Memory info:" + echo "$(cat /proc/meminfo)" + echo "Disk info:" + echo "$(df -h)" + + - name: Checkout 'ecbuild' + uses: actions/checkout@v4 + with: + repository: ecmwf/ecbuild + path: ecbuild + + - name: Checkout 'ecflow' + uses: actions/checkout@v4 + with: + path: ecflow + + - name: Generate ecflow debian package custom version + id: generate-package-custom-version + run: | + cd ${RUNNER_WORKSPACE}/ecflow/ecflow + + - name: Define ecflow build environment + id: vars + run: | + cd ${RUNNER_WORKSPACE}/ecflow/ecflow + sha1=$(git rev-parse --short HEAD) + version=$(cat VERSION) + echo "sha1=${sha1}" >> $GITHUB_OUTPUT + echo "version=${version}" >> $GITHUB_OUTPUT + + - name: Configure ecflow + id: configure-ecflow + run: | + cd ${RUNNER_WORKSPACE}/ecflow/ecflow + cmake --preset ${{ matrix.preset }} \ + -DCUSTOM_DEBIAN_PACKAGE_VERSION=${{ steps.vars.outputs.version }}_${{ steps.vars.outputs.sha1 }} \ + -DCMAKE_BUILD_TYPE=RelWithDebInfo + + - name: Build ecflow + run: | + cd ${RUNNER_WORKSPACE}/ecflow/ecflow + cmake --build --preset ${{ matrix.preset }} --jobs ${{ matrix.jobs }} --target all + + - name: Test ecflow + run: | + cd ${RUNNER_WORKSPACE}/ecflow/ecflow + ctest --preset ${{ matrix.preset }} --output-on-failure -L nightly -j ${{ matrix.jobs }} + + - name: Package ecflow + run: | + cd ${RUNNER_WORKSPACE}/ecflow/ecflow + cmake --build --preset ${{ matrix.preset }} --target package + + - name: Create ecflow docker image + run: | + cd ${RUNNER_WORKSPACE}/ecflow/ecflow + cp -v ${RUNNER_WORKSPACE}/ecflow/ecflow/build/${{ matrix.preset }}/ecflow-*.deb \ + ${RUNNER_WORKSPACE}/ecflow/ecflow/releng/imachination/ecflow/ecflow-latest-Linux.deb || true + docker build -t ecflow:latest -f ${RUNNER_WORKSPACE}/ecflow/ecflow/releng/imachination/ecflow/Dockerfile ${RUNNER_WORKSPACE}/ecflow/ecflow/releng/imachination/ecflow diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 3e6c59b90..0fa19765d 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -16,7 +16,7 @@ jobs: build: if: ${{ (github.event_name == 'pull_request' && github.event.action != 'closed') || github.event_name == 'push' }} - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 outputs: artifact-id: ${{ steps.archivedocs.outputs.artifact-id }} @@ -28,6 +28,7 @@ jobs: sudo apt update sudo apt install -y \ libboost-all-dev \ + pybind11-dev \ libssl-dev \ doxygen \ graphviz \ diff --git a/CMakeLists.txt b/CMakeLists.txt index ea59fda6a..3f40893f4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -31,7 +31,7 @@ find_package( ecbuild 3.4 REQUIRED HINTS ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CUR # Project # ========================================================================================= -project( ecflow LANGUAGES CXX VERSION 5.16.0 ) +project( ecflow LANGUAGES CXX VERSION 5.17.0 ) # # Important: # The CMake project version is used, as generated CMake variables, to filter .../ecflow/core/ecflow_version.h.in diff --git a/LICENSE b/LICENSE index 261eeb9e9..250ddba34 100644 --- a/LICENSE +++ b/LICENSE @@ -175,18 +175,7 @@ END OF TERMS AND CONDITIONS - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] + Copyright 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/NOTICE b/NOTICE index cb1fd9101..c291a2e06 100644 --- a/NOTICE +++ b/NOTICE @@ -1,125 +1,32 @@ -Apache [ecflow] - Copyright [2009-2022] The Apache Software Foundation - This product includes software developed at - The Apache Software Foundation (http://www.apache.org/). +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/`. -Boost Software License - Version 1.0 - August 17th, 2003 -======================================================== -Permission is hereby granted, free of charge, to any person or organization -obtaining a copy of the software and accompanying documentation covered by -this license (the "Software") to use, reproduce, display, distribute, -execute, and transmit the Software, and to prepare derivative works of the -Software, and to permit third-parties to whom the Software is furnished to -do so, all subject to the following: +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/`. -The copyright notices in the Software and this entire statement, including -the above license grant, this restriction and the following disclaimer, -must be included in all copies of the Software, in whole or in part, and -all derivative works of the Software, unless such copies or derivative -works are solely in the form of machine-executable object code generated by -a source language processor. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT -SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE -FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, -ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. +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`. -The MIT License (MIT) - EOS-Portable-library -============================================ -Copyright (c) 2012 EOS GmbH -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), -to deal in the Software without restriction, including without limitation -the rights to use, copy, modify, merge, publish, distribute, sublicense, -and/or sell copies of the Software, and to permit persons to whom the -Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included -in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT -SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE -FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE -OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - -Certificate.hpp -=============== - -Much of the code there is taken from https://gist.github.com/nathan-osman/5041136, -provided with the MIT License: - -The MIT License (MIT) - -Copyright (c) 2022 Nathan Osman - -Permission is hereby granted, free of charge, to any person obtaining a copy of this -software and associated documentation files (the "Software"), to deal in the Software -without restriction, including without limitation the rights to use, copy, modify, merge, -publish, distribute, sublicense, and/or sell copies of the Software, and to permit -persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or -substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, -INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR -PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE -FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR -OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. - - - -Base64.hpp -========== - -Distributed under the BSD License - see the source file for the full text. - - - -cpp-httplib -=========== - -Distributed under the MIT License see the source directory for the full text. - - -json -==== - -Distributed under the MIT License see the source directory for the full text. - - - -LazyTextEdit -================================================ -LazyTextEdit is a Qt based text editor that lazily loads data from disk when -necessary. - -It tries to keep memory usage as low as possible and only stores chunks of data -that have been modified. Its APIs resembles that of QText(Edit|Document|Cursor). -It supports basic formatting options using TextSections and SyntaxHighlighter. - -LazyTextEdit is licensed under the Apache 2.0 license. - -Some of the source code was modified for inclusion in ecFlowUI. +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 -================================================ -This was generated from the website: - http://www.ajaxload.info/ -and is freely licensed as described there. +Generated using the website http://www.ajaxload.info/ +Freely licensed, as described there. diff --git a/Viewer/ecflowUI/src/ActionHandler.cpp b/Viewer/ecflowUI/src/ActionHandler.cpp index 844bd6e65..7599e2751 100644 --- a/Viewer/ecflowUI/src/ActionHandler.cpp +++ b/Viewer/ecflowUI/src/ActionHandler.cpp @@ -352,12 +352,12 @@ bool ActionHandler::confirmCommand(const std::vector& filteredNodes, std::string question = questionIn; std::string placeholder(""); - ecf::Str::replace_all(question, placeholder, fullNames); + ecf::algorithm::replace_all(question, placeholder, fullNames); placeholder = ""; - ecf::Str::replace_all(question, placeholder, nodeNames); + ecf::algorithm::replace_all(question, placeholder, nodeNames); if (taskNum > 0) { placeholder = ""; - ecf::Str::replace_all(question, placeholder, "" + QString::number(taskNum).toStdString() + ""); + ecf::algorithm::replace_all(question, placeholder, "" + QString::number(taskNum).toStdString() + ""); } QString msg = QString::fromStdString(question); diff --git a/Viewer/ecflowUI/src/CommandDesignerWidget.cpp b/Viewer/ecflowUI/src/CommandDesignerWidget.cpp index 10f8076ab..05a7cd350 100644 --- a/Viewer/ecflowUI/src/CommandDesignerWidget.cpp +++ b/Viewer/ecflowUI/src/CommandDesignerWidget.cpp @@ -217,7 +217,7 @@ void CommandDesignerWidget::showCommandHelp(QListWidgetItem* item, bool showFull // get the command name QString qCommand(item->text()); std::string command = qCommand.toStdString(); - ecf::Str::replace_all(command, "--", ""); // remove the "--" from the start + ecf::algorithm::replace_all(command, "--", ""); // remove the "--" from the start // try to find it in our list of commands const po::option_description* od = @@ -230,7 +230,7 @@ void CommandDesignerWidget::showCommandHelp(QListWidgetItem* item, bool showFull if (od) { // get the description, but only take the first line std::vector lines; - ecf::Str::split(od->description(), lines, "\n"); + ecf::algorithm::split_at(lines, od->description(), "\n"); if (!lines.empty()) { QString text = qCommand + QString(": "); commandHelpLabel_->setText(text + QString::fromStdString(lines[0])); diff --git a/Viewer/ecflowUI/src/CommandHandler.cpp b/Viewer/ecflowUI/src/CommandHandler.cpp index 323f53272..e2f16d28b 100644 --- a/Viewer/ecflowUI/src/CommandHandler.cpp +++ b/Viewer/ecflowUI/src/CommandHandler.cpp @@ -106,13 +106,13 @@ void CommandHandler::run(std::vector info, const std::string& cmd) { // replace placeholders with real node names std::string placeholder(""); - ecf::Str::replace_all(realCommand, placeholder, targetNodeFullNames[serverHandler]); + ecf::algorithm::replace_all(realCommand, placeholder, targetNodeFullNames[serverHandler]); placeholder = ""; - ecf::Str::replace_all(realCommand, placeholder, targetNodeNames[serverHandler]); + ecf::algorithm::replace_all(realCommand, placeholder, targetNodeNames[serverHandler]); placeholder = ""; - ecf::Str::replace_all(realCommand, placeholder, targetParentFullNames[serverHandler]); + ecf::algorithm::replace_all(realCommand, placeholder, targetParentFullNames[serverHandler]); // Shell command if (realCommand.find("sh ") == 0) { diff --git a/Viewer/ecflowUI/src/ExpandState.cpp b/Viewer/ecflowUI/src/ExpandState.cpp index 97477af16..b2a86a8b0 100644 --- a/Viewer/ecflowUI/src/ExpandState.cpp +++ b/Viewer/ecflowUI/src/ExpandState.cpp @@ -300,7 +300,7 @@ ExpandStateNode* ExpandState::find(const std::string& fullPath) { } std::vector pathVec; - ecf::algorithm::split(pathVec, fullPath, "/"); + ecf::algorithm::split_at(pathVec, fullPath, "/"); if (pathVec.size() > 0 && pathVec[0].empty()) { pathVec.erase(pathVec.begin()); @@ -347,7 +347,7 @@ void ExpandState::collectParents(const std::string& fullPath, std::vector "" std::vector pathVec; - boost::split(pathVec, fullPath, boost::is_any_of("/")); + ecf::algorithm::split_at(pathVec, fullPath, "/"); if (pathVec.size() > 0 && pathVec[0].empty()) { pathVec.erase(pathVec.begin()); } diff --git a/Viewer/ecflowUI/src/LogLoadView.cpp b/Viewer/ecflowUI/src/LogLoadView.cpp index 04e9817d3..1edf26ba9 100644 --- a/Viewer/ecflowUI/src/LogLoadView.cpp +++ b/Viewer/ecflowUI/src/LogLoadView.cpp @@ -650,7 +650,9 @@ void ChartView::setCallout(qreal val) { if (auto* axisY = static_cast(ViewerUtil::chartAxisY(chart()))) { qreal m = axisY->max(); callout_->setAnchor(QPointF(val, m)); -#if QT_VERSION >= QT_VERSION_CHECK(5, 2, 0) +#if QT_VERSION >= QT_VERSION_CHECK(6, 9, 0) + QString txt = QDateTime::fromMSecsSinceEpoch(val, QTimeZone::utc()).toString("hh:mm:ss dd/MM/yyyy"); +#elif QT_VERSION >= QT_VERSION_CHECK(5, 2, 0) QString txt = QDateTime::fromMSecsSinceEpoch(val, Qt::UTC).toString("hh:mm:ss dd/MM/yyyy"); #else QString txt = QDateTime::fromMSecsSinceEpoch(val).toUTC().toString("hh:mm:ss dd/MM/yyyy"); @@ -1581,7 +1583,9 @@ void LogRequestView::scanPositionChanged(qreal pos) { QColor dateCol(210, 211, 214); QString txt = ""; -#if QT_VERSION >= QT_VERSION_CHECK(5, 2, 0) +#if QT_VERSION >= QT_VERSION_CHECK(6, 9, 0) + QString dateTxt = QDateTime::fromMSecsSinceEpoch(t, QTimeZone::utc()).toString("hh:mm:ss dd/MM/yyyy"); +#elif QT_VERSION >= QT_VERSION_CHECK(5, 2, 0) QString dateTxt = QDateTime::fromMSecsSinceEpoch(t, Qt::UTC).toString("hh:mm:ss dd/MM/yyyy"); #else QString dateTxt = QDateTime::fromMSecsSinceEpoch(t).toUTC().toString("hh:mm:ss dd/MM/yyyy"); @@ -1594,7 +1598,11 @@ void LogRequestView::scanPositionChanged(qreal pos) { // QDateTime::fromMSecsSinceEpoch(t,Qt::UTC).toString("hh:mm:ss dd/MM/yyyy"), // dateCol); -#if QT_VERSION >= QT_VERSION_CHECK(5, 2, 0) +#if QT_VERSION >= QT_VERSION_CHECK(6, 9, 0) + dateTxt = (hasData) + ? QDateTime::fromMSecsSinceEpoch(seriesTime(idx), QTimeZone::utc()).toString("hh:mm:ss dd/MM/yyyy") + : " N/A"; +#elif QT_VERSION >= QT_VERSION_CHECK(5, 2, 0) dateTxt = (hasData) ? QDateTime::fromMSecsSinceEpoch(seriesTime(idx), Qt::UTC).toString("hh:mm:ss dd/MM/yyyy") : " N/A"; #else diff --git a/Viewer/ecflowUI/src/LogLoadWidget.cpp b/Viewer/ecflowUI/src/LogLoadWidget.cpp index 01e212c3d..426bb4478 100644 --- a/Viewer/ecflowUI/src/LogLoadWidget.cpp +++ b/Viewer/ecflowUI/src/LogLoadWidget.cpp @@ -834,7 +834,10 @@ void LogLoadWidget::setMaxReadSize(int maxReadSizeInMb) { } void LogLoadWidget::periodChanged(qint64 start, qint64 end) { -#if QT_VERSION >= QT_VERSION_CHECK(5, 2, 0) +#if QT_VERSION >= QT_VERSION_CHECK(6, 9, 0) + QDateTime startDt = QDateTime::fromMSecsSinceEpoch(start, QTimeZone::utc()); + QDateTime endDt = QDateTime::fromMSecsSinceEpoch(end, QTimeZone::utc()); +#elif QT_VERSION >= QT_VERSION_CHECK(5, 2, 0) QDateTime startDt = QDateTime::fromMSecsSinceEpoch(start, Qt::UTC); QDateTime endDt = QDateTime::fromMSecsSinceEpoch(end, Qt::UTC); #else diff --git a/Viewer/ecflowUI/src/MenuHandler.cpp b/Viewer/ecflowUI/src/MenuHandler.cpp index aa3410d27..b2d5dc5fb 100644 --- a/Viewer/ecflowUI/src/MenuHandler.cpp +++ b/Viewer/ecflowUI/src/MenuHandler.cpp @@ -420,9 +420,9 @@ void MenuHandler::interceptCommandsThatNeedConfirmation(MenuItem* item) { std::string cmdEquals = minusCmd + "="; std::string cmdEqualsYes = cmdEquals + "yes "; std::string cmdYes = minusCmd + " yes "; - if (!ecf::Str::replace(command, cmdEquals, cmdEqualsYes)) // --command=foo -> --command=yes foo + if (!ecf::algorithm::replace(command, cmdEquals, cmdEqualsYes)) // --command=foo -> --command=yes foo { - ecf::Str::replace(command, minusCmd, cmdYes); // --command foo -> --command yes foo + ecf::algorithm::replace(command, minusCmd, cmdYes); // --command foo -> --command yes foo } item->setCommand(command); } diff --git a/Viewer/ecflowUI/src/NodeExpression.cpp b/Viewer/ecflowUI/src/NodeExpression.cpp index 0f800214d..432c2364c 100644 --- a/Viewer/ecflowUI/src/NodeExpression.cpp +++ b/Viewer/ecflowUI/src/NodeExpression.cpp @@ -16,6 +16,7 @@ #endif #include #include +#include #include "MenuHandler.hpp" #include "NodeExpression.hpp" @@ -201,8 +202,8 @@ BaseNodeCondition* NodeExpressionParser::parseWholeExpression(const std::string& UiLog().dbg() << "parseWholeExpression: " << expr; - ecf::Str::replace_all(expr, std::string("("), std::string(" ( ")); - ecf::Str::replace_all(expr, std::string(")"), std::string(" ) ")); + ecf::algorithm::replace_all(expr, std::string("("), std::string(" ( ")); + ecf::algorithm::replace_all(expr, std::string(")"), std::string(" ) ")); int index = 0; int length = expr.length(); @@ -1007,7 +1008,11 @@ bool AttributeStateCondition::execute(VItem* item) { IsoDateCondition::IsoDateCondition(QString dateStr) { QDateTime d = QDateTime::fromString(dateStr, Qt::ISODate); +#if QT_VERSION >= QT_VERSION_CHECK(6, 9, 0) + d.setTimeZone(QTimeZone::utc()); +#else d.setTimeSpec(Qt::UTC); +#endif if (d.isValid()) { secsSinceEpoch_ = d.toMSecsSinceEpoch() / 1000; } @@ -1015,7 +1020,11 @@ IsoDateCondition::IsoDateCondition(QString dateStr) { std::string IsoDateCondition::print() { if (secsSinceEpoch_ > 0) -#if QT_VERSION >= QT_VERSION_CHECK(5, 2, 0) +#if QT_VERSION >= QT_VERSION_CHECK(6, 9, 0) + return QDateTime::fromMSecsSinceEpoch(secsSinceEpoch_ * 1000, QTimeZone::utc()) + .toString(Qt::ISODate) + .toStdString(); +#elif QT_VERSION >= QT_VERSION_CHECK(5, 2, 0) return QDateTime::fromMSecsSinceEpoch(secsSinceEpoch_ * 1000, Qt::UTC).toString(Qt::ISODate).toStdString(); #else return QDateTime::fromMSecsSinceEpoch(secsSinceEpoch_ * 1000).toUTC().toString(Qt::ISODate).toStdString(); diff --git a/Viewer/ecflowUI/src/NodeQueryOption.cpp b/Viewer/ecflowUI/src/NodeQueryOption.cpp index 09758bae2..1c24bb146 100644 --- a/Viewer/ecflowUI/src/NodeQueryOption.cpp +++ b/Viewer/ecflowUI/src/NodeQueryOption.cpp @@ -14,6 +14,7 @@ #include #include +#include #include "NodeQuery.hpp" #include "UiLog.hpp" @@ -348,8 +349,13 @@ void NodeQueryPeriodOption::clear() { periodUnits_.clear(); fromDate_ = QDateTime(); toDate_ = QDateTime(); +#if QT_VERSION >= QT_VERSION_CHECK(6, 9, 0) + fromDate_.setTimeZone(QTimeZone::utc()); + toDate_.setTimeZone(QTimeZone::utc()); +#else fromDate_.setTimeSpec(Qt::UTC); toDate_.setTimeSpec(Qt::UTC); +#endif } void NodeQueryPeriodOption::setLastPeriod(int period, QString periodUnits) { @@ -511,9 +517,17 @@ void NodeQueryPeriodOption::load(VSettings* vs) { QString to = QString::fromStdString(vs->get("to", fromDate_.toString(Qt::ISODate).toStdString())); fromDate_ = QDateTime::fromString(from, Qt::ISODate); +#if QT_VERSION >= QT_VERSION_CHECK(6, 9, 0) + fromDate_.setTimeZone(QTimeZone::utc()); +#else fromDate_.setTimeSpec(Qt::UTC); +#endif toDate_ = QDateTime::fromString(to, Qt::ISODate); +#if QT_VERSION >= QT_VERSION_CHECK(6, 9, 0) + toDate_.setTimeZone(QTimeZone::utc()); +#else toDate_.setTimeSpec(Qt::UTC); +#endif // Check if dates are valis if (!fromDate_.isValid() || !fromDate_.isValid()) { diff --git a/Viewer/ecflowUI/src/RepeatEditor.cpp b/Viewer/ecflowUI/src/RepeatEditor.cpp index 936def816..895c61cd7 100644 --- a/Viewer/ecflowUI/src/RepeatEditor.cpp +++ b/Viewer/ecflowUI/src/RepeatEditor.cpp @@ -438,3 +438,4 @@ static AttributeEditorMaker makerStr3("repeat_enumerated"); static AttributeEditorMaker makerStr4("repeat_date"); static AttributeEditorMaker makerStr5("repeat_datelist"); static AttributeEditorMaker makerStr6("repeat_datetime"); +static AttributeEditorMaker makerStr7("repeat_datetimelist"); diff --git a/Viewer/ecflowUI/src/ServerComThread.cpp b/Viewer/ecflowUI/src/ServerComThread.cpp index c36c84b04..c919116d9 100644 --- a/Viewer/ecflowUI/src/ServerComThread.cpp +++ b/Viewer/ecflowUI/src/ServerComThread.cpp @@ -547,8 +547,7 @@ void ServerComThread::attach(defs_ptr d) { d->attach(this); - const std::vector& suites = d->suiteVec(); - for (const auto& suite : suites) { + for (const auto& suite : d->suites()) { attach(suite.get()); } } @@ -585,8 +584,7 @@ void ServerComThread::detach(defs_ptr d) { d->detach(this); - const std::vector& suites = d->suiteVec(); - for (const auto& suite : suites) { + for (const auto& suite : d->suites()) { detach(suite.get()); } } diff --git a/Viewer/ecflowUI/src/ServerHandler.cpp b/Viewer/ecflowUI/src/ServerHandler.cpp index 9906b36fc..65cafa703 100644 --- a/Viewer/ecflowUI/src/ServerHandler.cpp +++ b/Viewer/ecflowUI/src/ServerHandler.cpp @@ -140,9 +140,13 @@ void ServerHandler::logoutAndDelete() { } delete vRoot_; + vRoot_ = nullptr; delete connectState_; + connectState_ = nullptr; delete suiteFilter_; + suiteFilter_ = nullptr; delete conf_; + conf_ = nullptr; if (queueLoggedOut) { deleteLater(); diff --git a/Viewer/ecflowUI/src/ServerList.cpp b/Viewer/ecflowUI/src/ServerList.cpp index 2ee11fbb1..32807dfce 100644 --- a/Viewer/ecflowUI/src/ServerList.cpp +++ b/Viewer/ecflowUI/src/ServerList.cpp @@ -92,7 +92,7 @@ std::vector ServerListSystemFileManager::buildFileList() { if (!s.empty()) { useProp = true; std::vector sVec; - ecf::Str::split(s, sVec, ":"); + ecf::algorithm::split_at(sVec, s, ":"); for (auto v : sVec) { if (std::find(paths.begin(), paths.end(), v) == paths.end()) { paths.push_back(v); @@ -108,7 +108,7 @@ std::vector ServerListSystemFileManager::buildFileList() { auto s = std::string(ch); if (!s.empty()) { std::vector sVec; - ecf::Str::split(s, sVec, ":"); + ecf::algorithm::split_at(sVec, s, ":"); for (auto v : sVec) { if (std::find(paths.begin(), paths.end(), v) == paths.end()) { paths.push_back(v); @@ -276,13 +276,13 @@ void ServerListSystemFileManager::loadFile(const std::string& fPath, if (in.good()) { std::string line; while (getline(in, line)) { - std::string buf = boost::trim_left_copy(line); + std::string buf = ecf::algorithm::trim_leading_copy(line); if (buf.size() > 0 && buf[0] == '#') { continue; } if (buf.size() > 0 && buf[0] == '/') { - std::string p = boost::trim_right_copy(buf); + std::string p = ecf::algorithm::trim_trailing_copy(buf); if (std::find(includedPaths.begin(), includedPaths.end(), p) == includedPaths.end()) { includedPaths.emplace_back(p); } @@ -542,14 +542,14 @@ bool ServerList::load() { int lineCnt = 1; while (getline(in, line)) { // We ignore comment lines - std::string buf = boost::trim_left_copy(line); + std::string buf = ecf::algorithm::trim_leading_copy(line); if (buf.size() > 0 && buf.at(0) == '#') { lineCnt++; continue; } std::vector sv; - ecf::algorithm::split(sv, line, ","); + ecf::algorithm::split_at(sv, line, ","); bool favourite = false; if (sv.size() >= 4) { diff --git a/Viewer/ecflowUI/src/ServerListDialog.cpp b/Viewer/ecflowUI/src/ServerListDialog.cpp index 31dcbcc86..f78d422eb 100644 --- a/Viewer/ecflowUI/src/ServerListDialog.cpp +++ b/Viewer/ecflowUI/src/ServerListDialog.cpp @@ -1047,15 +1047,33 @@ ServerListFilterModel::ServerListFilterModel(QObject* parent) void ServerListFilterModel::setFilterStr(QString t) { QString newStr = t.simplified(); if (newStr != filterStr_) { +#if QT_VERSION >= QT_VERSION_CHECK(6, 9, 0) + beginFilterChange(); +#endif + filterStr_ = newStr; + +#if QT_VERSION >= QT_VERSION_CHECK(6, 9, 0) + endFilterChange(); +#else invalidateFilter(); +#endif } } void ServerListFilterModel::setFilterFavourite(bool b) { if (b != filterFavourite_) { +#if QT_VERSION >= QT_VERSION_CHECK(6, 9, 0) + beginFilterChange(); +#endif + filterFavourite_ = b; + +#if QT_VERSION >= QT_VERSION_CHECK(6, 9, 0) + endFilterChange(); +#else invalidateFilter(); +#endif } } diff --git a/Viewer/ecflowUI/src/SessionHandler.cpp b/Viewer/ecflowUI/src/SessionHandler.cpp index b26a4dfa6..2089f7d95 100644 --- a/Viewer/ecflowUI/src/SessionHandler.cpp +++ b/Viewer/ecflowUI/src/SessionHandler.cpp @@ -172,7 +172,7 @@ void SessionHandler::readSessionListFromDisk() { std::string dirName = (*it); std::string toRemove = ".session"; std::string toReplaceWith = ""; - ecf::Str::replace(dirName, toRemove, toReplaceWith); + ecf::algorithm::replace(dirName, toRemove, toReplaceWith); add(dirName); } } diff --git a/Viewer/ecflowUI/src/ShellCommand.cpp b/Viewer/ecflowUI/src/ShellCommand.cpp index 2a017e681..ae811f251 100644 --- a/Viewer/ecflowUI/src/ShellCommand.cpp +++ b/Viewer/ecflowUI/src/ShellCommand.cpp @@ -97,7 +97,7 @@ ShellCommand::ShellCommand(const std::string& cmdStr, const std::string& cmdDefS startTime_ = QDateTime::currentDateTime(); -#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) +#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) proc_->start("/bin/sh", QStringList() << "-c" << command_); #else proc_->start("/bin/sh -c \"" + command_ + "\""); diff --git a/Viewer/ecflowUI/src/TextPager/TextPagerLayout_p.cpp b/Viewer/ecflowUI/src/TextPager/TextPagerLayout_p.cpp index 8d2180892..7e7ec9f12 100644 --- a/Viewer/ecflowUI/src/TextPager/TextPagerLayout_p.cpp +++ b/Viewer/ecflowUI/src/TextPager/TextPagerLayout_p.cpp @@ -81,7 +81,7 @@ int TextPagerLayout::doLayout(int index, QList* sections) // range.length = qMin(l->position() + l->size(), index) - lineStart - range.start; range.format = l->format(); -#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) +#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) formatMap.insert(l->priority(), range); #else formatMap.insertMulti(l->priority(), range); diff --git a/Viewer/ecflowUI/src/TimelineData.cpp b/Viewer/ecflowUI/src/TimelineData.cpp index 822149025..1ce0fa574 100644 --- a/Viewer/ecflowUI/src/TimelineData.cpp +++ b/Viewer/ecflowUI/src/TimelineData.cpp @@ -432,7 +432,12 @@ bool TimelineData::parseLine(const std::string& line, // Convert status time into secs QDateTime dt = QDateTime::fromString(QString::fromStdString(time_stamp), "hh:mm:ss d.M.yyyy"); + +#if QT_VERSION >= QT_VERSION_CHECK(6, 9, 0) + dt.setTimeZone(QTimeZone::utc()); +#else dt.setTimeSpec(Qt::UTC); +#endif statusTime = dt.toMSecsSinceEpoch() / 1000; return true; diff --git a/Viewer/ecflowUI/src/TimelineData.hpp b/Viewer/ecflowUI/src/TimelineData.hpp index 8b030fcc4..ae5054a70 100644 --- a/Viewer/ecflowUI/src/TimelineData.hpp +++ b/Viewer/ecflowUI/src/TimelineData.hpp @@ -17,6 +17,7 @@ #include #include #include +#include #include struct TimelineItemStats @@ -60,7 +61,9 @@ class TimelineItem { static unsigned int fromQDateTime(QDateTime dt) { return dt.toMSecsSinceEpoch() / 1000; } static QDateTime toQDateTime(unsigned int t) { -#if QT_VERSION >= QT_VERSION_CHECK(5, 2, 0) +#if QT_VERSION >= QT_VERSION_CHECK(6, 9, 0) + return QDateTime::fromMSecsSinceEpoch(static_cast(t) * 1000, QTimeZone::utc()); +#elif QT_VERSION >= QT_VERSION_CHECK(5, 2, 0) return QDateTime::fromMSecsSinceEpoch(static_cast(t) * 1000, Qt::UTC); #else return QDateTime::fromMSecsSinceEpoch(static_cast(t) * 1000).toUTC(); diff --git a/Viewer/ecflowUI/src/TimelineHeaderView.cpp b/Viewer/ecflowUI/src/TimelineHeaderView.cpp index 280821ffe..52c73ec28 100644 --- a/Viewer/ecflowUI/src/TimelineHeaderView.cpp +++ b/Viewer/ecflowUI/src/TimelineHeaderView.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include @@ -642,21 +643,16 @@ void MainTimelineHeader::renderTimeline(const QRect& rect, QPainter* painter, in if (actSec % majorTick == 0) { painter->drawLine(xp, majorTickTop, xp, majorTickBottom); + const char* format = majorTick < 60 ? "H:mm:ss" : "H:mm"; + QString s; - if (majorTick < 60) { -#if QT_VERSION >= QT_VERSION_CHECK(5, 2, 0) - s = QDateTime::fromMSecsSinceEpoch(actSec * 1000, Qt::UTC).toString("H:mm:ss"); -#else - s = QDateTime::fromMSecsSinceEpoch(actSec * 1000).toUTC().toString("H:mm:ss"); -#endif - } - else { -#if QT_VERSION >= QT_VERSION_CHECK(5, 2, 0) - s = QDateTime::fromMSecsSinceEpoch(actSec * 1000, Qt::UTC).toString("H:mm"); +#if QT_VERSION >= QT_VERSION_CHECK(6, 9, 0) + s = QDateTime::fromMSecsSinceEpoch(actSec * 1000, QTimeZone::utc()).toString(format); +#elif QT_VERSION >= QT_VERSION_CHECK(5, 2, 0) + s = QDateTime::fromMSecsSinceEpoch(actSec * 1000, Qt::UTC).toString(format); #else - s = QDateTime::fromMSecsSinceEpoch(actSec * 1000).toUTC().toString("H:mm"); + s = QDateTime::fromMSecsSinceEpoch(actSec * 1000).toUTC().toString(format); #endif - } int textW = ViewerUtil::textWidth(fm_, s); painter->setFont(font_); @@ -933,21 +929,16 @@ void NodeTimelineHeader::renderTimeline(const QRect& rect, QPainter* painter, in if (actSec % majorTick == 0) { painter->drawLine(xp, majorTickTop, xp, majorTickBottom); + const char* format = majorTick < 60 ? "H:mm:ss" : "H:mm"; + QString s; - if (majorTick < 60) { -#if QT_VERSION >= QT_VERSION_CHECK(5, 2, 0) - s = QDateTime::fromMSecsSinceEpoch(actSec * 1000, Qt::UTC).toString("H:mm:ss"); -#else - s = QDateTime::fromMSecsSinceEpoch(actSec * 1000).toUTC().toString("H:mm:ss"); -#endif - } - else { -#if QT_VERSION >= QT_VERSION_CHECK(5, 2, 0) - s = QDateTime::fromMSecsSinceEpoch(actSec * 1000, Qt::UTC).toString("H:mm"); +#if QT_VERSION >= QT_VERSION_CHECK(6, 9, 0) + s = QDateTime::fromMSecsSinceEpoch(actSec * 1000, QTimeZone::utc()).toString(format); +#elif QT_VERSION >= QT_VERSION_CHECK(5, 2, 0) + s = QDateTime::fromMSecsSinceEpoch(actSec * 1000, Qt::UTC).toString(format); #else - s = QDateTime::fromMSecsSinceEpoch(actSec * 1000).toUTC().toString("H:mm"); + s = QDateTime::fromMSecsSinceEpoch(actSec * 1000).toUTC().toString(format); #endif - } int textW = ViewerUtil::textWidth(fm_, s); painter->setFont(font_); diff --git a/Viewer/ecflowUI/src/TreeNodeModel.cpp b/Viewer/ecflowUI/src/TreeNodeModel.cpp index fb61e6dee..0d040113e 100644 --- a/Viewer/ecflowUI/src/TreeNodeModel.cpp +++ b/Viewer/ecflowUI/src/TreeNodeModel.cpp @@ -374,7 +374,7 @@ QVariant TreeNodeModel::attributesData(const QModelIndex& index, int role, VTree // - and, assigned to a Node that is synchronised to (i.e. a mirror of) a Node in an external ecFlow server; // we customize the foreground colour. if (VAttribute* a = vnode->attribute(index.row(), atts_); - a && a->type()->name() == "var" && vnode->node()->isMirror()) { + a && a->type()->name() == "var" && vnode->node() && vnode->node()->isMirror()) { return QColor(119, 153, 170); } else { diff --git a/Viewer/ecflowUI/src/VConfig.cpp b/Viewer/ecflowUI/src/VConfig.cpp index 02399ed75..24f2af910 100644 --- a/Viewer/ecflowUI/src/VConfig.cpp +++ b/Viewer/ecflowUI/src/VConfig.cpp @@ -403,7 +403,7 @@ bool VConfig::readRcFile(const std::string& rcFile, boost::property_tree::ptree& if (vec.size() >= 1) { std::vector par; - ecf::algorithm::split(par, vec[0], ":"); + ecf::algorithm::split_at(par, vec[0], ":"); if (par.size() == 2) { // Update diff --git a/Viewer/ecflowUI/src/VFileTransfer.cpp b/Viewer/ecflowUI/src/VFileTransfer.cpp index 9857efc9d..39e2deb81 100644 --- a/Viewer/ecflowUI/src/VFileTransfer.cpp +++ b/Viewer/ecflowUI/src/VFileTransfer.cpp @@ -144,7 +144,7 @@ void VFileTransferCore::transferIt() { stopper_.start(); -#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) +#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) proc_->start("/bin/sh", QStringList() << "-c" << command); #else proc_->start("/bin/sh -c \"" + command + "\""); diff --git a/Viewer/ecflowUI/src/VNode.cpp b/Viewer/ecflowUI/src/VNode.cpp index 4a892ef1a..9a939f082 100644 --- a/Viewer/ecflowUI/src/VNode.cpp +++ b/Viewer/ecflowUI/src/VNode.cpp @@ -721,11 +721,13 @@ QColor VNode::typeFontColour() const { } bool VNode::userLogServer(std::string& host, std::string& port) { - if (ServerHandler* sh = server()) { - host = sh->conf()->stringValue(VServerSettings::UserLogServerHost).toStdString(); - if (!host.empty()) { - port = sh->conf()->stringValue(VServerSettings::UserLogServerPort).toStdString(); - return !port.empty(); + if (auto* sh = server(); sh) { + if (auto* conf = sh->conf(); conf) { + host = conf->stringValue(VServerSettings::UserLogServerHost).toStdString(); + if (!host.empty()) { + port = conf->stringValue(VServerSettings::UserLogServerPort).toStdString(); + return !port.empty(); + } } } return false; @@ -1199,7 +1201,10 @@ void VServer::clear() { attrForSearch_.clear(); - bool hasNotifications = server_->conf()->notificationsEnabled(); + bool hasNotifications = false; + if (auto* conf = server_->conf(); conf) { + hasNotifications = conf->notificationsEnabled(); + } // Delete the children nodes. It will recursively delete all the nodes. It also saves the prevNodeState!! for (auto it = children_.begin(); it != children_.end(); ++it) { @@ -1291,7 +1296,7 @@ VNode* VServer::find(const std::string& fullPath) { } std::vector pathVec; - ecf::algorithm::split(pathVec, fullPath, "/"); + ecf::algorithm::split_at(pathVec, fullPath, "/"); if (pathVec.size() > 0 && pathVec.at(0).empty()) { pathVec.erase(pathVec.begin()); @@ -1383,12 +1388,13 @@ void VServer::endScan() { return; } - bool hasNotifications = server_->conf()->notificationsEnabled(); + bool hasNotifications = false; + if (auto* conf = server_->conf(); conf) { + hasNotifications = conf->notificationsEnabled(); + } // Scan the suits.This will recursively scan all nodes in the tree. - const std::vector& suites = defs->suiteVec(); - - for (const auto& suite : suites) { + for (const auto& suite : defs->suites()) { VNode* vn = new VSuiteNode(this, suite); totalNum_++; scan(vn, hasNotifications); @@ -1473,15 +1479,15 @@ void VServer::beginUpdate(VNode* node, const std::vector& asp // update. // Update the generated variables. There is no notification about their change so we have to do it!!! - if (node->node()) { + if (auto n = node->node(); n) { Suite* s = nullptr; - s = node->node()->isSuite(); + s = n->isSuite(); if (!s) { - s = node->node()->suite(); + s = n->suite(); } if (s && s->begun()) { - node->node()->update_generated_variables(); + n->update_generated_variables(); s->update_generated_variables(); } @@ -1728,8 +1734,12 @@ QString VServer::toolTip() { } } - auto userLogHost = server_->conf()->stringValue(VServerSettings::UserLogServerHost); - auto userLogPort = server_->conf()->stringValue(VServerSettings::UserLogServerPort); + QString userLogHost; + QString userLogPort; + if (auto* conf = server_->conf(); conf) { + userLogHost = conf->stringValue(VServerSettings::UserLogServerHost); + userLogPort = conf->stringValue(VServerSettings::UserLogServerPort); + } if (!userLogHost.isEmpty() && !userLogPort.isEmpty()) { txt += "
Custom logserver: " + userLogHost + "@" + userLogPort; } diff --git a/Viewer/ecflowUI/src/VProperty.cpp b/Viewer/ecflowUI/src/VProperty.cpp index 9b5e1e76c..dedfed7a6 100644 --- a/Viewer/ecflowUI/src/VProperty.cpp +++ b/Viewer/ecflowUI/src/VProperty.cpp @@ -343,7 +343,7 @@ VProperty* VProperty::find(const std::string& fullPath) { } std::vector pathVec; - ecf::algorithm::split(pathVec, fullPath, "."); + ecf::algorithm::split_at(pathVec, fullPath, "."); if (pathVec.size() > 0) { if (pathVec.at(0) != strName_) { diff --git a/Viewer/ecflowUI/src/VRepeatAttr.cpp b/Viewer/ecflowUI/src/VRepeatAttr.cpp index ee670e626..13794ff53 100644 --- a/Viewer/ecflowUI/src/VRepeatAttr.cpp +++ b/Viewer/ecflowUI/src/VRepeatAttr.cpp @@ -20,6 +20,7 @@ std::string VRepeatDateAttr::subType_("date"); std::string VRepeatDateTimeAttr::subType_("datetime"); std::string VRepeatDateListAttr::subType_("datelist"); +std::string VRepeatDateTimeListAttr::subType_("datetimelist"); std::string VRepeatIntAttr::subType_("integer"); std::string VRepeatStringAttr::subType_("string"); std::string VRepeatEnumAttr::subType_("enumerated"); @@ -75,7 +76,7 @@ QString VRepeatAttrType::definition(QStringList d) const { t += " " + d[EndIndex]; t += " " + d[StepIndex]; } - else if (subType == "string" || subType == "enumerated" || subType == "datelist") { + else if (subType == "string" || subType == "enumerated" || subType == "datelist" || subType == "datetimelist") { t += " " + d[NameIndex]; t += " " + d[AllValuesIndex]; } @@ -172,6 +173,9 @@ void VRepeatAttr::scan(VNode* vnode, std::vector& vec) { else if (r.repeatBase()->isDateList()) { a = new VRepeatDateListAttr(vnode); } + else if (r.repeatBase()->isDateTimeList()) { + a = new VRepeatDateTimeListAttr(vnode); + } else if (r.repeatBase()->isInteger()) { a = new VRepeatIntAttr(vnode); } @@ -466,6 +470,80 @@ int VRepeatDateListAttr::currentPosition() const { return -1; } +//===================================================== +// +// VRepeatDateTimeListAttr +// +//===================================================== + +std::string VRepeatDateTimeListAttr::value(int index) const { + if (node_ptr node = parent_->node()) { + const Repeat& r = node->repeat(); + return r.value_as_string(index); + } + return {}; +} + +int VRepeatDateTimeListAttr::endIndex() const { + if (node_ptr node = parent_->node()) { + const Repeat& r = node->repeat(); + if (auto* rdtl = static_cast(r.repeatBase())) { + return rdtl->indexNum() - 1; + } + } + return 0; +} + +int VRepeatDateTimeListAttr::currentIndex() const { + if (node_ptr node = parent_->node()) { + const Repeat& r = node->repeat(); + return r.index_or_value(); + } + return 0; +} + +QString VRepeatDateTimeListAttr::startValue() const { + return QString::fromStdString(value(0)); +} + +QString VRepeatDateTimeListAttr::endValue() const { + return QString::fromStdString(value(endIndex())); +} + +QString VRepeatDateTimeListAttr::allValues() const { + QString vals; + + if (node_ptr node = parent_->node()) { + const Repeat& r = node->repeat(); + + int start = 0; + int end = endIndex(); + + if (end <= start) { + return {}; + } + + for (int i = start; i <= end; i++) { + if (!vals.isEmpty()) { + vals += " "; + } + vals += "\"" + QString::fromStdString(r.value_as_string(i)) + "\""; + } + return vals; + } + return vals; +} + +int VRepeatDateTimeListAttr::currentPosition() const { + if (node_ptr node = parent_->node()) { + const Repeat& r = node->repeat(); + if (auto* rdtl = static_cast(r.repeatBase())) { + return get_repeat_position(*rdtl); + } + } + return -1; +} + //===================================================== // // VRepeatIntAttr diff --git a/Viewer/ecflowUI/src/VRepeatAttr.hpp b/Viewer/ecflowUI/src/VRepeatAttr.hpp index ccb97c070..22047ab59 100644 --- a/Viewer/ecflowUI/src/VRepeatAttr.hpp +++ b/Viewer/ecflowUI/src/VRepeatAttr.hpp @@ -116,6 +116,23 @@ class VRepeatDateListAttr : public VRepeatAttr { static std::string subType_; }; +class VRepeatDateTimeListAttr : public VRepeatAttr { +public: + explicit VRepeatDateTimeListAttr(VNode* n) + : VRepeatAttr(n) {} + int endIndex() const override; + int currentIndex() const override; + QString startValue() const override; + QString endValue() const override; + std::string value(int index) const override; + const std::string& subType() const override { return subType_; } + QString allValues() const override; + int currentPosition() const override; + +protected: + static std::string subType_; +}; + class VRepeatDayAttr : public VRepeatAttr { public: explicit VRepeatDayAttr(VNode* n) diff --git a/Viewer/ecflowUI/src/VServerSettings.cpp b/Viewer/ecflowUI/src/VServerSettings.cpp index 56bc35675..3da2b0eb6 100644 --- a/Viewer/ecflowUI/src/VServerSettings.cpp +++ b/Viewer/ecflowUI/src/VServerSettings.cpp @@ -106,32 +106,40 @@ VServerSettings::~VServerSettings() { } int VServerSettings::intValue(Param par) const { - return property(par)->value().toInt(); + if (auto* p = property(par); p) { + return p->value().toInt(); + } + + return 0; } bool VServerSettings::boolValue(Param par) const { - return property(par)->value().toBool(); + if (auto* p = property(par); p) { + return p->value().toBool(); + } + + return false; } QString VServerSettings::stringValue(Param par) const { - return property(par)->value().toString(); + if (auto* p = property(par); p) { + return p->value().toString(); + } + + return {}; } VProperty* VServerSettings::property(Param par) const { - auto it = parToProp_.find(par); - if (it != parToProp_.end()) { + if (auto it = parToProp_.find(par); it != parToProp_.end()) { return it->second; } - else { - assert(0); - } + UiLog().err() << "VServerSettings::property - unknown parameter: " << par; return nullptr; } void VServerSettings::notifyChange(VProperty* p) { - auto it = propToPar_.find(p); - if (it != propToPar_.end()) { + if (auto it = propToPar_.find(p); it != propToPar_.end()) { server_->confChanged(it->second, it->first); } else { @@ -140,10 +148,10 @@ void VServerSettings::notifyChange(VProperty* p) { } std::string VServerSettings::notificationId(Param par) { - auto it = notifyIds_.find(par); - if (it != notifyIds_.end()) { + if (auto it = notifyIds_.find(par); it != notifyIds_.end()) { return it->second; } + return {}; } @@ -163,6 +171,7 @@ bool VServerSettings::notificationsEnabled() const { return true; } } + return false; } diff --git a/Viewer/ecflowUI/src/VServerSettings.hpp b/Viewer/ecflowUI/src/VServerSettings.hpp index 93947ac40..361e7e319 100644 --- a/Viewer/ecflowUI/src/VServerSettings.hpp +++ b/Viewer/ecflowUI/src/VServerSettings.hpp @@ -54,9 +54,30 @@ class VServerSettings : public VPropertyObserver { UserLogServerPort }; + /// + /// @brief Access the associated integer value of the given parameter. + /// + /// @param par The parameter for which to retrieve the integer value. + /// @return The integer value for the given parameter, or 0 if the parameter is not found/not an integer. + /// int intValue(Param par) const; + + /// + /// @brief Access the associated boolean value of the given parameter. + /// + /// @param par The parameter for which to retrieve the boolean value. + /// @return The boolean value for the given parameter, or false if the parameter is not found/not a boolean. + /// bool boolValue(Param par) const; + + /// + /// @brief Access the associated string value of the given parameter. + /// + /// @param par The parameter for which to retrieve the string value. + /// @return The string value for the given parameter, or an empty string if the parameter is not found/not a string. + /// QString stringValue(Param par) const; + VProperty* guiProp() const { return guiProp_; } bool notificationsEnabled() const; static std::string notificationId(Param); @@ -76,6 +97,12 @@ class VServerSettings : public VPropertyObserver { explicit VServerSettings(ServerHandler* server); ~VServerSettings() override; + /// + /// @brief Access the property associated with the given parameter. + /// + /// @param par The parameter for which to retrieve the property. + /// @return The property associated with the parameter; or nullptr, if parameter is not found + /// VProperty* property(Param par) const; void loadSettings(); diff --git a/Viewer/ecflowUI/src/VariableModelData.cpp b/Viewer/ecflowUI/src/VariableModelData.cpp index 1ef9a00dd..bc0b71540 100644 --- a/Viewer/ecflowUI/src/VariableModelData.cpp +++ b/Viewer/ecflowUI/src/VariableModelData.cpp @@ -293,8 +293,19 @@ bool VariableModelData::isGenVar(int index) const { return (index >= static_cast(vars_.size())); } -bool VariableModelData::isMirrorVar(int index) const { - return info_->isNode() && info_->node()->node()->isMirror(); +bool VariableModelData::isMirrorVar([[maybe_unused]] int index) const { + if (!info_ || !info_->isNode()) { + return false; + } + auto vnode = info_->node(); + if (!vnode) { + return false; + } + auto node = vnode->node(); + if (!node) { + return false; + } + return node->isMirror(); } bool VariableModelData::isGenVar(const std::string& n) const { diff --git a/Viewer/libViewer/src/DirectoryHandler.cpp b/Viewer/libViewer/src/DirectoryHandler.cpp index 186164d74..6cb0f0c60 100644 --- a/Viewer/libViewer/src/DirectoryHandler.cpp +++ b/Viewer/libViewer/src/DirectoryHandler.cpp @@ -116,7 +116,19 @@ void DirectoryHandler::init(const std::string& exeStr) { // will use the value of the ECFLOW_SHARED_DIR macro to get // the location of the "share/ecflow" dir. if (!fs::exists(exePath)) { - fs::path shareDir(ECFLOW_SHARED_DIR); + fs::path shareDir; + if (auto* var = getenv("ECFLOW_SHARED_DIR"); var) { + shareDir = var; + UiLog().warn() << "Executable path does not exist. Used ECFLOW_SHARED_DIR env variable to find share dir: " + << shareDir.c_str(); + } + else { + shareDir = ECFLOW_SHARED_DIR; + UiLog().warn() + << "Executable path does not exist. Used default ECFLOW_SHARED_DIR location to find share dir: " + << shareDir.c_str(); + } + if (!fs::exists(shareDir)) { UserMessage::message( UserMessage::ERROR, diff --git a/Viewer/libViewer/src/LogData.cpp b/Viewer/libViewer/src/LogData.cpp index 9213e8597..8b037f8ae 100644 --- a/Viewer/libViewer/src/LogData.cpp +++ b/Viewer/libViewer/src/LogData.cpp @@ -50,7 +50,11 @@ LogDataItem::LogDataItem(const std::string& line, qint64& refTimeInMs) } QDateTime dt = QDateTime::fromString(QString::fromStdString(d), "hh:mm:ss d.M.yyyy"); +#if QT_VERSION >= QT_VERSION_CHECK(6, 9, 0) + dt.setTimeZone(QTimeZone::utc()); +#else dt.setTimeSpec(Qt::UTC); +#endif if (refTimeInMs == 0) { time_ = 0; @@ -94,7 +98,12 @@ qint64 LogDataItem::getTimeInMs(const std::string& line) { QString d = QString::fromStdString(line.substr(pos + 1, pos1 - pos - 1)); QDateTime dt = QDateTime::fromString(d, "hh:mm:ss d.M.yyyy"); +#if QT_VERSION >= QT_VERSION_CHECK(6, 9, 0) + dt.setTimeZone(QTimeZone::utc()); +#else dt.setTimeSpec(Qt::UTC); +#endif + return dt.toMSecsSinceEpoch(); } diff --git a/Viewer/libViewer/src/LogData.hpp b/Viewer/libViewer/src/LogData.hpp index f9b0745e3..4e8857d91 100644 --- a/Viewer/libViewer/src/LogData.hpp +++ b/Viewer/libViewer/src/LogData.hpp @@ -15,6 +15,7 @@ #include #include +#include #include #include "LogConsumer.hpp" @@ -48,7 +49,10 @@ class LogData : public LogConsumer { } QDateTime date(int idx) const { -#if QT_VERSION >= QT_VERSION_CHECK(5, 2, 0) +#if QT_VERSION >= QT_VERSION_CHECK(6, 9, 0) + return QDateTime::fromMSecsSinceEpoch(refTimeInMs_ + static_cast(data_[idx].time_) * 1000, + QTimeZone::utc()); +#elif QT_VERSION >= QT_VERSION_CHECK(5, 2, 0) return QDateTime::fromMSecsSinceEpoch(refTimeInMs_ + static_cast(data_[idx].time_) * 1000, Qt::UTC); #else return QDateTime::fromMSecsSinceEpoch(refTimeInMs_ + static_cast(data_[idx].time_) * 1000).toUTC(); diff --git a/Viewer/libViewer/src/LogLoadData.cpp b/Viewer/libViewer/src/LogLoadData.cpp index 83f6d2818..d6c84e4a3 100644 --- a/Viewer/libViewer/src/LogLoadData.cpp +++ b/Viewer/libViewer/src/LogLoadData.cpp @@ -492,7 +492,9 @@ qint64 LogLoadData::period() const { } QDateTime LogLoadData::startTime() const { -#if QT_VERSION >= QT_VERSION_CHECK(5, 2, 0) +#if QT_VERSION >= QT_VERSION_CHECK(6, 9, 0) + return (time_.empty()) ? QDateTime() : QDateTime::fromMSecsSinceEpoch(time_[0], QTimeZone::utc()); +#elif QT_VERSION >= QT_VERSION_CHECK(5, 2, 0) return (time_.empty()) ? QDateTime() : QDateTime::fromMSecsSinceEpoch(time_[0], Qt::UTC); #else return (time_.empty()) ? QDateTime() : QDateTime::fromMSecsSinceEpoch(time_[0]).toUTC(); @@ -500,7 +502,9 @@ QDateTime LogLoadData::startTime() const { } QDateTime LogLoadData::endTime() const { -#if QT_VERSION >= QT_VERSION_CHECK(5, 2, 0) +#if QT_VERSION >= QT_VERSION_CHECK(6, 9, 0) + return (time_.empty()) ? QDateTime() : QDateTime::fromMSecsSinceEpoch(time_[0], QTimeZone::utc()); +#elif QT_VERSION >= QT_VERSION_CHECK(5, 2, 0) return (time_.empty()) ? QDateTime() : QDateTime::fromMSecsSinceEpoch(time_[time_.size() - 1], Qt::UTC); #else return (time_.empty()) ? QDateTime() : QDateTime::fromMSecsSinceEpoch(time_[time_.size() - 1]).toUTC(); @@ -823,7 +827,11 @@ void LogLoadData::add(std::vector time_stamp, QString s = QString::fromStdString(time_stamp[0]) + " " + QString::fromStdString(time_stamp[1]); QDateTime dt = QDateTime::fromString(s, "HH:mm:ss d.M.yyyy"); +#if QT_VERSION >= QT_VERSION_CHECK(6, 9, 0) + dt.setTimeZone(QTimeZone::utc()); +#else dt.setTimeSpec(Qt::UTC); +#endif time_.push_back(dt.toMSecsSinceEpoch()); size_t index = time_.size() - 1; @@ -1083,10 +1091,10 @@ void LogLoadData::loadLogFileCore(const std::string& logFile, bool child_cmd = false; bool user_cmd = false; - if (line.find(ecf::Str::CHILD_CMD()) != std::string::npos) { + if (line.find(ecf::string_constants::child_cmd) != std::string::npos) { child_cmd = true; } - else if (line.find(ecf::Str::USER_CMD()) != std::string::npos) { + else if (line.find(ecf::string_constants::user_cmd) != std::string::npos) { user_cmd = true; } @@ -1121,7 +1129,7 @@ void LogLoadData::loadLogFileCore(const std::string& logFile, } std::string time_stamp = line.substr(0, first_closed_bracket); - ecf::Str::split(time_stamp, parseHelper_.new_time_stamp); + ecf::algorithm::split_at(parseHelper_.new_time_stamp, time_stamp); if (parseHelper_.new_time_stamp.size() != 2) { continue; } @@ -1141,7 +1149,7 @@ void LogLoadData::loadLogFileCore(const std::string& logFile, numOfRows_++; std::vector items; - ecf::Str::split(line, items, delimiter); + ecf::algorithm::split_at(items, line, delimiter); for (size_t i = 0; i < items.size(); ++i) { // Should be just left with " chd: " or " --, since we have removed the time // stamp @@ -1151,10 +1159,10 @@ void LogLoadData::loadLogFileCore(const std::string& logFile, line = items[i]; child_cmd = false; user_cmd = false; - if (line.find(ecf::Str::CHILD_CMD()) != std::string::npos) { + if (line.find(ecf::string_constants::child_cmd) != std::string::npos) { child_cmd = true; } - else if (line.find(ecf::Str::USER_CMD()) != std::string::npos) { + else if (line.find(ecf::string_constants::user_cmd) != std::string::npos) { user_cmd = true; if (i > 0 && line.find("--sync") != std::string::npos) { continue; @@ -1243,10 +1251,10 @@ std::streamoff LogLoadData::getStartPos(const std::string& logFile, int numOfRow bool child_cmd = false; bool user_cmd = false; - if (line.find(ecf::Str::CHILD_CMD()) != std::string::npos) { + if (line.find(ecf::string_constants::child_cmd) != std::string::npos) { child_cmd = true; } - else if (line.find(ecf::Str::USER_CMD()) != std::string::npos) { + else if (line.find(ecf::string_constants::user_cmd) != std::string::npos) { user_cmd = true; } @@ -1386,7 +1394,7 @@ bool LogLoadData::extract_suite_path(const std::string& line, if (!path.empty()) { if (path.find(":") != std::string::npos) { std::vector pathParts; - ecf::Str::split(path, pathParts, ":"); + ecf::algorithm::split_at(pathParts, path, ":"); if (pathParts.size() > 1) { path = pathParts[0]; } @@ -1394,7 +1402,7 @@ bool LogLoadData::extract_suite_path(const std::string& line, std::vector theNodeNames; theNodeNames.reserve(4); - NodePath::split(path, theNodeNames); + ecf::node::split_path(path, theNodeNames); if (!theNodeNames.empty()) { suite_name = theNodeNames[0]; } diff --git a/cmake/CompilerOptions.cmake b/cmake/CompilerOptions.cmake index 89e3066e2..62f424df4 100644 --- a/cmake/CompilerOptions.cmake +++ b/cmake/CompilerOptions.cmake @@ -81,30 +81,25 @@ if (HAVE_WARNINGS) ## GCC $<$,$>:-Wno-array-bounds> $<$,$,$,9.0.0>>:-Wno-deprecated-copy> # silence warnings in Qt5 related headers - $<$,$>:-Wno-deprecated-declarations> $<$,$>:-Wno-unused-result> $<$,$>:-Wno-unused-parameter> ## Clang (MacOS Homebrew, AMD Clang-base) $<$,$>:-Wno-deprecated-copy-with-user-provided-copy> # silence warnings in Qt5 related headers - $<$,$>:-Wno-deprecated-declarations> $<$,$>:-Wno-missing-field-initializers> # silence warning in Boost.Python related headers - $<$,$>:-Wno-overloaded-virtual> $<$,$>:-Wno-unused-parameter> $<$,$,$,14.0.0>>:-Wno-c++20-attribute-extensions> # silence warning in Qt6 related headers $<$,$,$,21.0.0>>:-Wno-character-conversion> # silence warning in Qt6 related headers + $<$,$,$,22.0.0>>:-Wno-c2y-extensions> # silence warning in Boost related headers (__COUNTER__) ## Clang (MacOS AppleClang) $<$,$>:-Wno-deprecated-copy-with-user-provided-copy> # silence warnings in Qt5 related headers - $<$,$>:-Wno-deprecated-declarations> $<$,$>:-Wno-missing-field-initializers> # silence warning in Boost.Python related headers - $<$,$>:-Wno-overloaded-virtual> $<$,$>:-Wno-unused-parameter> $<$,$,$,14.0.0>>:-Wno-c++20-attribute-extensions> # silence warning in Qt6 related headers $<$,$,$,21.0.0>>:-Wno-character-conversion> # silence warning in Qt6 related headers + $<$,$,$,22.0.0>>:-Wno-c2y-extensions> # silence warning in Boost related headers (__COUNTER__) ## Clang (Intel Clang-based) $<$,$>:-Wno-deprecated-copy-with-user-provided-copy> # silence warnings in Qt5 related headers - $<$,$>:-Wno-deprecated-declarations> $<$,$>:-Wno-missing-field-initializers> # silence warning in Boost.Python related headers - $<$,$>:-Wno-overloaded-virtual> $<$,$>:-Wno-unused-parameter> ) diff --git a/cmake/Dependencies.cmake b/cmake/Dependencies.cmake index 1374efcfc..e09cc8a8f 100644 --- a/cmake/Dependencies.cmake +++ b/cmake/Dependencies.cmake @@ -123,6 +123,25 @@ if (ENABLE_PYTHON) endif() +# ========================================================================================= +# pybind11 +# ========================================================================================= +if (ENABLE_PYTHON) + + ecbuild_info( "Locating pybind11" ) + + find_package(pybind11 2.10.3 REQUIRED) + + ecbuild_info( "pybind11 details:" ) + ecbuild_info( " * pybind11_FOUND : ${pybind11_FOUND}" ) + ecbuild_info( " * pybind11_INCLUDE_DIRS : ${pybind11_INCLUDE_DIRS}" ) + ecbuild_info( " * pybind11_VERSION : ${pybind11_VERSION}" ) + ecbuild_info( " * pybind11_VERSION_MAJOR: ${pybind11_VERSION_MAJOR}" ) + ecbuild_info( " * pybind11_VERSION_MINOR: ${pybind11_VERSION_MINOR}" ) + ecbuild_info( " * pybind11_VERSION_PATCH: ${pybind11_VERSION_PATCH}" ) + +endif() + # ========================================================================================= # Boost # ========================================================================================= @@ -174,21 +193,6 @@ if ( Boost_MINOR_VERSION GREATER_EQUAL 86 ) list(APPEND _boost_needed_libs process) endif() -if (ENABLE_PYTHON) - # The following is used to find Boost.python library, as the library name changes with python version - if ( Boost_MINOR_VERSION GREATER 66 ) - # cmake 3.15 - # see: https://gitlab.kitware.com/cmake/cmake/issues/19656 - # INTERFACE_LIBRARY targets may only have whitelisted properties. - set(_python_base_version "${Python3_VERSION_MAJOR}${Python3_VERSION_MINOR}") - else() - set(_python_base_version "${Python3_VERSION_MAJOR}") - endif() - set(ECFLOW_BOOST_PYTHON_COMPONENT "python${_python_base_version}") - - list(APPEND _boost_needed_libs ${ECFLOW_BOOST_PYTHON_COMPONENT}) -endif() - if(HAVE_TESTS) # HAVE_TESTS is defined if ecbuild ENABLE_TESTS is set, (by default this is set) list(APPEND _boost_needed_libs unit_test_framework test_exec_monitor ) endif() diff --git a/docs/build_python_api/categories.yaml b/docs/build_python_api/categories.yaml index 02cd381c1..fde1691e6 100644 --- a/docs/build_python_api/categories.yaml +++ b/docs/build_python_api/categories.yaml @@ -34,6 +34,7 @@ - RepeatDate - RepeatDateList - RepeatDateTime + - RepeatDateTimeList - RepeatDay - RepeatEnumerated - RepeatInteger diff --git a/docs/build_python_api/conf.py b/docs/build_python_api/conf.py index 18c82876e..118cc21f8 100644 --- a/docs/build_python_api/conf.py +++ b/docs/build_python_api/conf.py @@ -66,24 +66,31 @@ class TargetFile: - LAST_NAME = "ZombieVec" - def __init__(self): self.name = "" self.fp = None + def close(self): + if self.fp is not None: + self.fp.close() + self.fp = None + def write(self, source, line): _, _, s = source.rpartition("docstring of ecflow.") s = s.split(".") if s: name = s[0] if name and name[0].isupper(): + # Normal case: "docstring of ecflow.ClassName" or "docstring of ecflow.ClassName.method" self._open(name) if self.fp is not None: self.fp.write(line + "\n") - if self.name == self.LAST_NAME: - self.fp.close() - self.fp = None + elif name.startswith("pybind11_") and self.fp is not None: + # Because pybind11 leaks the type used to wrap the ABI type (i.e. pybind11_detail_function_record_v1_...) + # as the descriptor type for class methods, we detect this and continue to write to the currently open + # class file — this assumes that autodoc always processes a class's methods immediately after the class + # own docstring. + self.fp.write(line + "\n") def _open(self, name): if self.name == name and self.name != "": @@ -91,9 +98,7 @@ def _open(self, name): self.fp = open(f"rst/{name}.rst", "a") else: self.name = name - if self.fp is not None: - self.fp.close() - self.fp = None + self.close() if self.name != "" and self.name[0].isupper(): self.fp = open(f"rst/{self.name}.rst", "w") title = f"ecflow.{name}" @@ -106,12 +111,12 @@ def _open(self, name): def add_line(self, line, source, *lineno): """Append one line of generated reST to the output.""" - # _RST_FP.write(line + "\n") - # print(f"source={source}") targetFile.write(source, line) self.directive.result.append(self.indent + line, source, *lineno) sphinx.ext.autodoc.Documenter.add_line = add_line -# + +def setup(app): + app.connect("build-finished", lambda app, exception: targetFile.close()) diff --git a/docs/glossary.rst b/docs/glossary.rst index 78f1fd270..61cf1c1c9 100644 --- a/docs/glossary.rst +++ b/docs/glossary.rst @@ -1835,11 +1835,12 @@ Glossary repeat day [] # only for suites repeat integer VARIABLE [] - repeat enumerated VARIABLE [ [ [... ]]] - repeat string VARIABLE [string-2 [string-3 [... string-N]]] + repeat enumerated VARIABLE [ [... ]] + repeat string VARIABLE [string-2 [... string-N]] repeat date VARIABLE [] - repeat datelist VARIABLE [ [ [... ]]] + repeat datelist VARIABLE [ [... ]] repeat datetime VARIABLE [] + repeat datetimelist VARIABLE [ [... ]] There can only be a single repeat per :term:`node`, but several repeats can be used in combination by being placed at distinct levels of the suite tree. @@ -2106,6 +2107,7 @@ Glossary - :term:`repeat` *integer*: use the implicit integer values - :term:`repeat` *date*: use the date values as integers. Use of plus/minus on repeat date variable uses date arithmetic - :term:`repeat` *datetime*: use the date+time instant values as integers. Use of plus/minus on repeat datetime variable uses second arithmetic + - :term:`repeat` *datetimelist*: use the date+time instant values as integers. Use of plus/minus on repeat datetimelist variable uses second arithmetic - :term:`limit`: the limit value is used as an integer. This allows a degree of prioritisation amongst tasks under a limit - :term:`late`: the value is stored in a flag, and is a simple boolean. Used to signify when a task is late. diff --git a/docs/python_api/AttrType.rst b/docs/python_api/AttrType.rst index 08e790699..e232869a9 100644 --- a/docs/python_api/AttrType.rst +++ b/docs/python_api/AttrType.rst @@ -5,10 +5,24 @@ ecflow.AttrType .. py:class:: AttrType :module: ecflow - Bases: :py:class:`~Boost.Python.enum` + Bases: :py:class:`~pybind11_builtins.pybind11_object` Sortable attribute type, currently [event | meter | label | limit | variable | all ] +Members: + + event + + meter + + label + + limit + + variable + + all + .. py:attribute:: AttrType.all :module: ecflow @@ -35,11 +49,19 @@ Sortable attribute type, currently [event | meter | label | limit | variable | a :value: ecflow.AttrType.meter +.. py:property:: AttrType.name + :module: ecflow + + .. py:attribute:: AttrType.names :module: ecflow :value: {'all': ecflow.AttrType.all, 'event': ecflow.AttrType.event, 'label': ecflow.AttrType.label, 'limit': ecflow.AttrType.limit, 'meter': ecflow.AttrType.meter, 'variable': ecflow.AttrType.variable} +.. py:property:: AttrType.value + :module: ecflow + + .. py:attribute:: AttrType.values :module: ecflow :value: {1: ecflow.AttrType.event, 2: ecflow.AttrType.meter, 3: ecflow.AttrType.label, 4: ecflow.AttrType.limit, 5: ecflow.AttrType.variable, 6: ecflow.AttrType.all} diff --git a/docs/python_api/Autoarchive.rst b/docs/python_api/Autoarchive.rst index 54df57cca..c573e3c32 100644 --- a/docs/python_api/Autoarchive.rst +++ b/docs/python_api/Autoarchive.rst @@ -5,7 +5,7 @@ ecflow.Autoarchive .. py:class:: Autoarchive :module: ecflow - Bases: :py:class:`~Boost.Python.instance` + Bases: :py:class:`~pybind11_builtins.pybind11_object` Provides a way to automatically archive a suite/family which has completed or is *idle* @@ -48,25 +48,25 @@ Usage: attr = Autoarchive( 3,true ) # archive node 3 days after complete,queued or aborted, i.e node is idle -.. py:method:: Autoarchive.days( (Autoarchive)arg1) -> bool : +.. py:method:: Autoarchive.days(self: ecflow.Autoarchive) -> bool :module: ecflow Returns a boolean true if time was specified in days -.. py:method:: Autoarchive.idle( (Autoarchive)arg1) -> bool : +.. py:method:: Autoarchive.idle(self: ecflow.Autoarchive) -> bool :module: ecflow Returns a boolean true if archiving when idle, i.e queued,aborted,complete and time elapsed -.. py:method:: Autoarchive.relative( (Autoarchive)arg1) -> bool : +.. py:method:: Autoarchive.relative(self: ecflow.Autoarchive) -> bool :module: ecflow Returns a boolean where true means the time is relative -.. py:method:: Autoarchive.time( (Autoarchive)arg1) -> TimeSlot : +.. py:method:: Autoarchive.time(self: ecflow.Autoarchive) -> ecflow.TimeSlot :module: ecflow returns archive time as a TimeSlot diff --git a/docs/python_api/Autocancel.rst b/docs/python_api/Autocancel.rst index e2a8f2796..b6794070a 100644 --- a/docs/python_api/Autocancel.rst +++ b/docs/python_api/Autocancel.rst @@ -5,7 +5,7 @@ ecflow.Autocancel .. py:class:: Autocancel :module: ecflow - Bases: :py:class:`~Boost.Python.instance` + Bases: :py:class:`~pybind11_builtins.pybind11_object` Provides a way to automatically delete/remove a node which has completed @@ -38,19 +38,19 @@ Usage: Autocancel(2,0,true)) # delete task 2 hours after completion -.. py:method:: Autocancel.days( (Autocancel)arg1) -> bool : +.. py:method:: Autocancel.days(self: ecflow.Autocancel) -> bool :module: ecflow Returns a boolean true if time was specified in days -.. py:method:: Autocancel.relative( (Autocancel)arg1) -> bool : +.. py:method:: Autocancel.relative(self: ecflow.Autocancel) -> bool :module: ecflow Returns a boolean where true means the time is relative -.. py:method:: Autocancel.time( (Autocancel)arg1) -> TimeSlot : +.. py:method:: Autocancel.time(self: ecflow.Autocancel) -> ecflow.TimeSlot :module: ecflow returns cancel time as a TimeSlot diff --git a/docs/python_api/Autorestore.rst b/docs/python_api/Autorestore.rst index b49f535e8..27fe090b0 100644 --- a/docs/python_api/Autorestore.rst +++ b/docs/python_api/Autorestore.rst @@ -5,7 +5,7 @@ ecflow.Autorestore .. py:class:: Autorestore :module: ecflow - Bases: :py:class:`~Boost.Python.instance` + Bases: :py:class:`~pybind11_builtins.pybind11_object` Provides a way to automatically restore a previously archived node @@ -29,7 +29,7 @@ Usage: attr = Autorestore( ['/s1/f1','/s1/f2'] ) # restore archived node at /s1/f1 and /s1/f2 -.. py:method:: Autorestore.nodes_to_restore( (Autorestore)arg1) -> object : +.. py:method:: Autorestore.nodes_to_restore(self: ecflow.Autorestore) -> object :module: ecflow returns a list of nodes to be restored diff --git a/docs/python_api/AvisoAttr.rst b/docs/python_api/AvisoAttr.rst index 142ee3b28..02e2c7ed9 100644 --- a/docs/python_api/AvisoAttr.rst +++ b/docs/python_api/AvisoAttr.rst @@ -5,7 +5,7 @@ ecflow.AvisoAttr .. py:class:: AvisoAttr :module: ecflow - Bases: :py:class:`~Boost.Python.instance` + Bases: :py:class:`~pybind11_builtins.pybind11_object` An :term:`aviso` attribute, assigned to a :term:`node`, represents an external trigger holding the node queued untilan Aviso notification matching the attribute configuration is detected. @@ -51,37 +51,37 @@ Usage: The parameters `url`, `schema`, `polling`, and `auth` are optional -.. py:method:: AvisoAttr.auth( (AvisoAttr)arg1) -> str : +.. py:method:: AvisoAttr.auth(self: ecflow.AvisoAttr) -> str :module: ecflow Returns the path to Authentication credentials used to contact the Aviso server -.. py:method:: AvisoAttr.listener( (AvisoAttr)arg1) -> str : +.. py:method:: AvisoAttr.listener(self: ecflow.AvisoAttr) -> str :module: ecflow Returns the Aviso listener configuration -.. py:method:: AvisoAttr.name( (AvisoAttr)arg1) -> str : +.. py:method:: AvisoAttr.name(self: ecflow.AvisoAttr) -> str :module: ecflow Returns the name of the Aviso attribute -.. py:method:: AvisoAttr.polling( (AvisoAttr)arg1) -> str : +.. py:method:: AvisoAttr.polling(self: ecflow.AvisoAttr) -> str :module: ecflow Returns polling interval used to contact the Aviso server -.. py:method:: AvisoAttr.schema( (AvisoAttr)arg1) -> str : +.. py:method:: AvisoAttr.schema(self: ecflow.AvisoAttr) -> str :module: ecflow Returns the path to the schema used to contact the Aviso server -.. py:method:: AvisoAttr.url( (AvisoAttr)arg1) -> str : +.. py:method:: AvisoAttr.url(self: ecflow.AvisoAttr) -> str :module: ecflow Returns the URL used to contact the Aviso server diff --git a/docs/python_api/CheckPt.rst b/docs/python_api/CheckPt.rst index 03414cc1e..35b51793f 100644 --- a/docs/python_api/CheckPt.rst +++ b/docs/python_api/CheckPt.rst @@ -5,7 +5,7 @@ ecflow.CheckPt .. py:class:: CheckPt :module: ecflow - Bases: :py:class:`~Boost.Python.enum` + Bases: :py:class:`~pybind11_builtins.pybind11_object` CheckPt is enum that is used to control check pointing in the :term:`ecflow_server` @@ -15,32 +15,41 @@ CheckPt is enum that is used to control check pointing in the :term:`ecflow_serv - UNDEFINED : None of the the above, used to provide default argument +Members: + + NEVER + + ON_TIME + + ALWAYS + + UNDEFINED + + .. py:attribute:: CheckPt.ALWAYS :module: ecflow - :value: ecflow.CheckPt.ALWAYS + :value: .. py:attribute:: CheckPt.NEVER :module: ecflow - :value: ecflow.CheckPt.NEVER + :value: .. py:attribute:: CheckPt.ON_TIME :module: ecflow - :value: ecflow.CheckPt.ON_TIME + :value: .. py:attribute:: CheckPt.UNDEFINED :module: ecflow - :value: ecflow.CheckPt.UNDEFINED + :value: -.. py:attribute:: CheckPt.names +.. py:property:: CheckPt.name :module: ecflow - :value: {'ALWAYS': ecflow.CheckPt.ALWAYS, 'NEVER': ecflow.CheckPt.NEVER, 'ON_TIME': ecflow.CheckPt.ON_TIME, 'UNDEFINED': ecflow.CheckPt.UNDEFINED} -.. py:attribute:: CheckPt.values +.. py:property:: CheckPt.value :module: ecflow - :value: {0: ecflow.CheckPt.NEVER, 1: ecflow.CheckPt.ON_TIME, 2: ecflow.CheckPt.ALWAYS, 3: ecflow.CheckPt.UNDEFINED} diff --git a/docs/python_api/ChildCmdType.rst b/docs/python_api/ChildCmdType.rst index 4ac6990f3..2a8b64bde 100644 --- a/docs/python_api/ChildCmdType.rst +++ b/docs/python_api/ChildCmdType.rst @@ -5,7 +5,7 @@ ecflow.ChildCmdType .. py:class:: ChildCmdType :module: ecflow - Bases: :py:class:`~Boost.Python.enum` + Bases: :py:class:`~pybind11_builtins.pybind11_object` ChildCmdType represents the different :term:`child command`\ s. This type is used as a parameter to the class :py:class:`ecflow.ZombieAttr` @@ -21,6 +21,25 @@ Child commands are called within a :term:`job file`:: ChildCmdType::complete corresponds to : ecflow_client --complete +Members: + + init + + event + + meter + + label + + wait + + queue + + abort + + complete + + .. py:attribute:: ChildCmdType.abort :module: ecflow :value: ecflow.ChildCmdType.abort @@ -51,6 +70,10 @@ Child commands are called within a :term:`job file`:: :value: ecflow.ChildCmdType.meter +.. py:property:: ChildCmdType.name + :module: ecflow + + .. py:attribute:: ChildCmdType.names :module: ecflow :value: {'abort': ecflow.ChildCmdType.abort, 'complete': ecflow.ChildCmdType.complete, 'event': ecflow.ChildCmdType.event, 'init': ecflow.ChildCmdType.init, 'label': ecflow.ChildCmdType.label, 'meter': ecflow.ChildCmdType.meter, 'queue': ecflow.ChildCmdType.queue, 'wait': ecflow.ChildCmdType.wait} @@ -61,6 +84,10 @@ Child commands are called within a :term:`job file`:: :value: ecflow.ChildCmdType.queue +.. py:property:: ChildCmdType.value + :module: ecflow + + .. py:attribute:: ChildCmdType.values :module: ecflow :value: {0: ecflow.ChildCmdType.init, 1: ecflow.ChildCmdType.event, 2: ecflow.ChildCmdType.meter, 3: ecflow.ChildCmdType.label, 4: ecflow.ChildCmdType.wait, 5: ecflow.ChildCmdType.queue, 6: ecflow.ChildCmdType.abort, 7: ecflow.ChildCmdType.complete} diff --git a/docs/python_api/Client.rst b/docs/python_api/Client.rst index 624a38b55..aac9a058c 100644 --- a/docs/python_api/Client.rst +++ b/docs/python_api/Client.rst @@ -5,7 +5,7 @@ ecflow.Client .. py:class:: Client :module: ecflow - Bases: :py:class:`~Boost.Python.instance` + Bases: :py:class:`~pybind11_builtins.pybind11_object` Class client provides an interface to communicate with the :term:`ecflow_server`.: @@ -66,250 +66,258 @@ Secure communication between client and server can be enabled when creating of a A secure connection can also be enabled/disabled using the `Client.enable_ssl()/.disable_ssl()` methods. The behaviour of `Client.enable_ssl()` follows the Client initialisation described above (assuming `ECF_SSL=1` when `ECF_SSL` is not defined). -.. py:method:: Client.alter( (Client)arg1, (list)paths, (str)alter_type, (str)attribute_type [, (str)name='' [, (str)value='']]) -> None : +.. py:method:: Client.alter(*args, **kwargs) :module: ecflow +Overloaded function. + +1. alter(self: ecflow.Client, paths: list, alter_type: str, attribute_type: str, name: str = '', value: str = '') -> None + Alter command is used to change the attributes of a node - :: - - void alter( - (list | string ) paths(s) : - One or more paths to the node with the attribute(s) to be altered. - string alter_type : - The type of alteration to perform. - Must be one of [ add, change, delete, set_flag, clear_flag, sort ] - string attr_type : - The type of attribute affected by the alteration. - Depending on the type of alteration, provide one of the following. - for add : [ - variable, time, today, date, day, zombie, event, meter, - late, limit, inlimit, label, aviso, mirror - ] - for delete : [ - variable, time, today, date, day, cron, event, meter, late, generic, - queue, label, trigger, complete, repeat, limit, inlimit, limit_path, - zombie, aviso, mirror - ] - for change : [ - variable, clock_type, clock_gain, clock_date, clock_sync , event, meter - label, trigger , complete, repeat, limit_max, limit_value, defstatus - late, time, today, aviso, mirror - ] - for set_flag or clear_flag: [ - force_aborted, user_edit, task_aborted, edit_failed, ecfcmd_failed, - statuscmd_failed, killcmd_failed, no_script, killed, status, late, - message complete, queue_limit, task_waiting, locked, zombie, archived, - restored, threshold, log_error, checkpt_error - ] - for sort: [ - event, meter, label, variable, limit, all ] - string name : - The name of the attribute(s) to be altered. - Used when multiple attributes of the same type exist e.g. a variable, - a meter, an event or a labet; but optional when changing unnamed attributes, - such as trigger or complete. - string value : - The new/updated value for the attribute. - ) - - This function will raise exceptions because: - - - A provided path does not exist. - - A provided value cannot be parsed - - A provided value is invalid or out of range - - The following describes the parameters in more detail: - - .. code-block:: shell - - add variable variable_name variable_value - - add time format - # where format is +hh:mm | hh:mm | hh:mm(start) hh:mm(finish) hh:mm(increment) - - add today format - # where format is +hh:mm | hh:mm | hh:mm(start) hh:mm(finish) hh:mm(increment) - - add date format - # where format is dd.mm.yyyy, and '*' can be used to indicate any day, month or year - - add day format - # where format is one of [ sunday,monday,tuesday,wednesday,friday,saturday ] - - add zombie format - # where format is based on the following grammar - # :: | : - # := [ user | ecf | path ] - # := [ init, event, meter, label, wait, abort, complete ] - # := [ adopt | delete ] - # := [ fob | fail | block(default) ] - # := lifetime of zombie in the server - # For example, to fob all child label requests, and remove zombie as soon as possible: - # 'add zombie :label:fob:0' - - add inlimit '/path/to/node:limit' '12' - # To add an inlimit, depending on '/path/to/node:limit' and value '12' - # Important: - # * an empty inlimit value will add an inlimit with default value of 1 - # * if a 0 or negative value is provided, an exception will be raised - # * if the limit node/name is not found, an exception will be raised - # * if both '-s' and '-n' are provided, an exception will be raised - - add inlimit '/path/to/node:limit' '-s 13' - # To add an inlimit, depending on '/path/to/node:limit' and value '13'. - # By including '-s' the inlimit will affect submissions only. - - add inlimit '/path/to/node:limit' '-n 14' - # To add an inlimit, depending on '/path/to/node:limit' and value '14'. - # By including '-n' the inlimit will affect node only. - - delete variable name - # Important: if name is empty, *all* variables on the node are deleted - - delete time name - # To delete a specific time, enter the time in same format as show above, - # or as specified in the defs file - # an empty name will delete all time attributes on the node - - delete today name - # To delete a specific today attribute, enter in same format as show above, - # or as specified in the defs file. - # an empty name will delete all today attributes on the node - - delete date name - # To delete a specific date attribute, enter in same format as show above, - # or as specified in the defs file - # an empty name will delete all date attributes on the node - - delete day name - # To delete a specific day attribute, enter in same format as show above, - # or as specified in the defs file - # an empty name will delete all day attributes on the node - - delete cron name - # To delete a specific cron attribute, enter in same as specified in the defs file - # an empty name will delete all cron attributes on the node - - delete event name - # To delete a specific event, enter name or number - # an empty name will delete all events on the node - - delete meter name - # To delete a specific meter , enter the meter name - # an empty name will delete all meter on the node - - delete label name - # To delete a specific label , enter the label name - # an empty name will delete all labels on the node - - delete limit name - # To delete a specific limit , enter the limit name - # an empty name will delete all limits on the node - - delete inlimit name - # To delete a specific inlimit , enter the inlimit name - # an empty name will delete all inlimits on the node - - delete limit_path limit_name limit_path - # To delete a specific limit path - - delete trigger - # A node can only have one trigger expression, hence the name is not required - - delete complete - # A node can only have one complete expression, hence the name is not required - - delete repeat - # A node can only have one repeat, hence the name is not required - - change variable name value - # Find the specified variable, and set the new value. - - change clock_type name - # The name must be one of 'hybrid' or 'real'. - - change clock_gain name - # The gain must be convertible to an integer. - - change clock_sync name - # Sync suite calendar with the computer. - - change event name(optional ) - # if no name specified the event is set, otherwise name must be 'set' or 'clear' - - change meter name value - # The meter value must be convertible to an integer, and between meter min-max range. - - change label name value - # sets the label - - change trigger name - # The name must be expression. returns an error if the expression does not parse - - change complete name - # The name must be expression. returns an error if the expression does not parse - - change limit_max name value - # Sets the max value of the limit. The value must be convertible to an integer - - change limit_value name value - # Sets the consumed tokens to value. The value must be convertible to an integer - - change repeat value - # For date repeats, the value must be an yyyymmdd formtted integer, defined in - # the [begin, end] dates range. - # For integer repeats, the value must be an integer, defined in the [begin, end] - # values range. - # For string or enum repeats, the value must either be a valid integer (to be used - # as index) or a string matching a valid enum or strings list value. - - - Usage: - - .. code-block:: python - - try: - ci = Client() # uses default host(ECF_HOST) & port(ECF_PORT) - ci.alter('/suite/task','change','trigger','b2 == complete') - except RuntimeError, e: - print(str(e)) - - -alter( (Client)arg1, (str)abs_node_path, (str)alter_type, (str)attribute_type [, (str)name='' [, (str)value='']]) -> None - - -.. py:method:: Client.archive( (Client)arg1, (str)arg2) -> None : +:: + + void alter( + (list | string ) paths(s) : + One or more paths to the node with the attribute(s) to be altered. + string alter_type : + The type of alteration to perform. + Must be one of [ add, change, delete, set_flag, clear_flag, sort ] + string attr_type : + The type of attribute affected by the alteration. + Depending on the type of alteration, provide one of the following. + for add : [ + variable, time, today, date, day, zombie, event, meter, + late, limit, inlimit, label, aviso, mirror + ] + for delete : [ + variable, time, today, date, day, cron, event, meter, late, generic, + queue, label, trigger, complete, repeat, limit, inlimit, limit_path, + zombie, aviso, mirror + ] + for change : [ + variable, clock_type, clock_gain, clock_date, clock_sync , event, meter + label, trigger , complete, repeat, limit_max, limit_value, defstatus + late, time, today, aviso, mirror + ] + for set_flag or clear_flag: [ + force_aborted, user_edit, task_aborted, edit_failed, ecfcmd_failed, + statuscmd_failed, killcmd_failed, no_script, killed, status, late, + message complete, queue_limit, task_waiting, locked, zombie, archived, + restored, threshold, log_error, checkpt_error + ] + for sort: [ + event, meter, label, variable, limit, all ] + string name : + The name of the attribute(s) to be altered. + Used when multiple attributes of the same type exist e.g. a variable, + a meter, an event or a labet; but optional when changing unnamed attributes, + such as trigger or complete. + string value : + The new/updated value for the attribute. + ) + +This function will raise exceptions because: + +- A provided path does not exist. +- A provided value cannot be parsed +- A provided value is invalid or out of range + +The following describes the parameters in more detail: + +.. code-block:: shell + + add variable variable_name variable_value + + add time format + # where format is +hh:mm | hh:mm | hh:mm(start) hh:mm(finish) hh:mm(increment) + + add today format + # where format is +hh:mm | hh:mm | hh:mm(start) hh:mm(finish) hh:mm(increment) + + add date format + # where format is dd.mm.yyyy, and '*' can be used to indicate any day, month or year + + add day format + # where format is one of [ sunday,monday,tuesday,wednesday,friday,saturday ] + + add zombie format + # where format is based on the following grammar + # :: | : + # := [ user | ecf | path ] + # := [ init, event, meter, label, wait, abort, complete ] + # := [ adopt | delete ] + # := [ fob | fail | block(default) ] + # := lifetime of zombie in the server + # For example, to fob all child label requests, and remove zombie as soon as possible: + # 'add zombie :label:fob:0' + + add inlimit '/path/to/node:limit' '12' + # To add an inlimit, depending on '/path/to/node:limit' and value '12' + # Important: + # * an empty inlimit value will add an inlimit with default value of 1 + # * if a 0 or negative value is provided, an exception will be raised + # * if the limit node/name is not found, an exception will be raised + # * if both '-s' and '-n' are provided, an exception will be raised + + add inlimit '/path/to/node:limit' '-s 13' + # To add an inlimit, depending on '/path/to/node:limit' and value '13'. + # By including '-s' the inlimit will affect submissions only. + + add inlimit '/path/to/node:limit' '-n 14' + # To add an inlimit, depending on '/path/to/node:limit' and value '14'. + # By including '-n' the inlimit will affect node only. + + delete variable name + # Important: if name is empty, *all* variables on the node are deleted + + delete time name + # To delete a specific time, enter the time in same format as show above, + # or as specified in the defs file + # an empty name will delete all time attributes on the node + + delete today name + # To delete a specific today attribute, enter in same format as show above, + # or as specified in the defs file. + # an empty name will delete all today attributes on the node + + delete date name + # To delete a specific date attribute, enter in same format as show above, + # or as specified in the defs file + # an empty name will delete all date attributes on the node + + delete day name + # To delete a specific day attribute, enter in same format as show above, + # or as specified in the defs file + # an empty name will delete all day attributes on the node + + delete cron name + # To delete a specific cron attribute, enter in same as specified in the defs file + # an empty name will delete all cron attributes on the node + + delete event name + # To delete a specific event, enter name or number + # an empty name will delete all events on the node + + delete meter name + # To delete a specific meter , enter the meter name + # an empty name will delete all meter on the node + + delete label name + # To delete a specific label , enter the label name + # an empty name will delete all labels on the node + + delete limit name + # To delete a specific limit , enter the limit name + # an empty name will delete all limits on the node + + delete inlimit name + # To delete a specific inlimit , enter the inlimit name + # an empty name will delete all inlimits on the node + + delete limit_path limit_name limit_path + # To delete a specific limit path + + delete trigger + # A node can only have one trigger expression, hence the name is not required + + delete complete + # A node can only have one complete expression, hence the name is not required + + delete repeat + # A node can only have one repeat, hence the name is not required + + change variable name value + # Find the specified variable, and set the new value. + + change clock_type name + # The name must be one of 'hybrid' or 'real'. + + change clock_gain name + # The gain must be convertible to an integer. + + change clock_sync name + # Sync suite calendar with the computer. + + change event name(optional ) + # if no name specified the event is set, otherwise name must be 'set' or 'clear' + + change meter name value + # The meter value must be convertible to an integer, and between meter min-max range. + + change label name value + # sets the label + + change trigger name + # The name must be expression. returns an error if the expression does not parse + + change complete name + # The name must be expression. returns an error if the expression does not parse + + change limit_max name value + # Sets the max value of the limit. The value must be convertible to an integer + + change limit_value name value + # Sets the consumed tokens to value. The value must be convertible to an integer + + change repeat value + # For date repeats, the value must be an yyyymmdd formtted integer, defined in + # the [begin, end] dates range. + # For integer repeats, the value must be an integer, defined in the [begin, end] + # values range. + # For string or enum repeats, the value must either be a valid integer (to be used + # as index) or a string matching a valid enum or strings list value. + + +Usage: + +.. code-block:: python + + try: + ci = Client() # uses default host(ECF_HOST) & port(ECF_PORT) + ci.alter('/suite/task','change','trigger','b2 == complete') + except RuntimeError, e: + print(str(e)) + + +2. alter(self: ecflow.Client, abs_node_path: str, alter_type: str, attribute_type: str, name: str = '', value: str = '') -> None + + +.. py:method:: Client.archive(*args, **kwargs) :module: ecflow +Overloaded function. + +1. archive(self: ecflow.Client, arg0: str) -> None + Archives suite or family nodes. Saves the suite/family nodes to disk, and then removes then from the definition - This saves memory in the server, when dealing with huge definitions that are not needed. - If the node is re-queued or begun, it is automatically restored - Use --restore to reload the archived nodes manually - The nodes are saved to ECF_HOME/ECF_NAME.check - Usage:: - - string archive( - list paths # List of paths. - ) - string archive( - string absolute_node_path - ) - - Usage: - - .. code-block:: python - - try: - ci = Client() # use default host(ECF_HOST) & port(ECF_PORT) - print ci.archive('/suite1') - except RuntimeError, e: - print str(e) - - -archive( (Client)arg1, (list)arg2) -> None - - -.. py:method:: Client.begin_all_suites( (Client)arg1 [, (bool)force=False]) -> int : +This saves memory in the server, when dealing with huge definitions that are not needed. +If the node is re-queued or begun, it is automatically restored +Use --restore to reload the archived nodes manually +The nodes are saved to ECF_HOME/ECF_NAME.check +Usage:: + + string archive( + list paths # List of paths. + ) + string archive( + string absolute_node_path + ) + +Usage: + +.. code-block:: python + + try: + ci = Client() # use default host(ECF_HOST) & port(ECF_PORT) + print ci.archive('/suite1') + except RuntimeError, e: + print str(e) + + +2. archive(self: ecflow.Client, arg0: list) -> None + + +.. py:method:: Client.begin_all_suites(self: ecflow.Client, force: bool = False) -> int :module: ecflow Begin playing all the :term:`suite`\ s in the :term:`ecflow_server` @@ -334,7 +342,7 @@ Usage: print(str(e)) -.. py:method:: Client.begin_suite( (Client)arg1, (str)suite_name [, (bool)force=False]) -> int : +.. py:method:: Client.begin_suite(self: ecflow.Client, suite_name: str, force: bool = False) -> int :module: ecflow Begin playing the chosen :term:`suite`\ s in the :term:`ecflow_server` @@ -359,129 +367,141 @@ Usage: print(str(e)) -.. py:method:: Client.ch_add( (Client)arg1, (int)arg2, (list)arg3) -> None : +.. py:method:: Client.ch_add(*args, **kwargs) :module: ecflow +Overloaded function. + +1. ch_add(self: ecflow.Client, arg0: typing.SupportsInt | typing.SupportsIndex, arg1: list) -> None + Add a set of suites, to an existing registered handle - - When dealing with large definitions, where a user is only interested in a small subset - of suites, registering them, improves download performance from the server. - Registered suites have an associated handle. - :: - - integer ch_add( - integer handle : the handle obtained after ch_register - list suite_names : list of strings representing suite names - ) - integer ch_add( - list suite_names : list of strings representing suite names - ) - - Usage: - - .. code-block:: python - - try: - with Client() as ci: # use default host(ECF_HOST) & port(ECF_PORT) - ci.ch_register(True,[]) # register interest in any new suites - ci.ch_add(['s1','s2']) # add suites s1,s2 to the last added handle - except RuntimeError, e: - print(str(e)) - - - -ch_add( (Client)arg1, (list)arg2) -> None - - -.. py:method:: Client.ch_auto_add( (Client)arg1, (int)arg2, (bool)arg3) -> int : + +When dealing with large definitions, where a user is only interested in a small subset +of suites, registering them, improves download performance from the server. +Registered suites have an associated handle. +:: + + integer ch_add( + integer handle : the handle obtained after ch_register + list suite_names : list of strings representing suite names + ) + integer ch_add( + list suite_names : list of strings representing suite names + ) + +Usage: + +.. code-block:: python + + try: + with Client() as ci: # use default host(ECF_HOST) & port(ECF_PORT) + ci.ch_register(True,[]) # register interest in any new suites + ci.ch_add(['s1','s2']) # add suites s1,s2 to the last added handle + except RuntimeError, e: + print(str(e)) + + + +2. ch_add(self: ecflow.Client, arg0: list) -> None + + +.. py:method:: Client.ch_auto_add(*args, **kwargs) :module: ecflow +Overloaded function. + +1. ch_auto_add(self: ecflow.Client, arg0: typing.SupportsInt | typing.SupportsIndex, arg1: bool) -> int + Change an existing handle so that new suites can be added automatically - - When dealing with large definitions, where a user is only interested in a small subset - of suites, registering them, improves download performance from the server. - Registered suites have an associated handle. - :: - - void ch_auto_add( - integer handle, : the handle obtained after ch_register - bool auto_add_new_suite : automatically add new suites, this handle when they are created - ) - void ch_auto_add( - bool auto_add_new_suite : automatically add new suites using handle on the client - ) - - Usage: - - .. code-block:: python - - try: - with Client() as ci: # use default host(ECF_HOST) & port(ECF_PORT) - ci.ch_register(True,['s1','s2','s3']) # register interest in suites s1,s2,s3 and any new suites - ci.ch_auto_add( False ) # disable adding newly created suites to my handle - except RuntimeError, e: - print(str(e)) - - - -ch_auto_add( (Client)arg1, (bool)arg2) -> int - - -.. py:method:: Client.ch_drop( (Client)arg1, (int)arg2) -> int : + +When dealing with large definitions, where a user is only interested in a small subset +of suites, registering them, improves download performance from the server. +Registered suites have an associated handle. +:: + + void ch_auto_add( + integer handle, : the handle obtained after ch_register + bool auto_add_new_suite : automatically add new suites, this handle when they are created + ) + void ch_auto_add( + bool auto_add_new_suite : automatically add new suites using handle on the client + ) + +Usage: + +.. code-block:: python + + try: + with Client() as ci: # use default host(ECF_HOST) & port(ECF_PORT) + ci.ch_register(True,['s1','s2','s3']) # register interest in suites s1,s2,s3 and any new suites + ci.ch_auto_add( False ) # disable adding newly created suites to my handle + except RuntimeError, e: + print(str(e)) + + + +2. ch_auto_add(self: ecflow.Client, arg0: bool) -> int + + +.. py:method:: Client.ch_drop(*args, **kwargs) :module: ecflow +Overloaded function. + +1. ch_drop(self: ecflow.Client, arg0: typing.SupportsInt | typing.SupportsIndex) -> int + Drop/de-register the client handle. - - When dealing with large definitions, where a user is only interested in a small subset - of suites, registering them, improves download performance from the server. - Registered suites have an associated handle. - Client must ensure un-used handle are dropped otherwise they will stay, in the :term:`ecflow_server` - :: - - void ch_drop( - int client_handle : The handle must be an integer that is > 0 - ) - void ch_drop() : Uses the local handle stored on the client, from last call to ch_register() - - Exception: - - - RunTimeError thrown if handle has not been previously registered - - Usage: - - .. code-block:: python - + +When dealing with large definitions, where a user is only interested in a small subset +of suites, registering them, improves download performance from the server. +Registered suites have an associated handle. +Client must ensure un-used handle are dropped otherwise they will stay, in the :term:`ecflow_server` +:: + + void ch_drop( + int client_handle : The handle must be an integer that is > 0 + ) + void ch_drop() : Uses the local handle stored on the client, from last call to ch_register() + +Exception: + +- RunTimeError thrown if handle has not been previously registered + +Usage: + +.. code-block:: python + try: - ci = Client() # use default host(ECF_HOST) & port(ECF_PORT) + ci = Client() # use default host(ECF_HOST) & port(ECF_PORT) + ci.ch_register(False,['s1','s2']) + while( 1 ): + # get incremental changes to suites s1 & s2, uses data stored on ci/defs + ci.sync_local() # will only retrieve data for suites s1 & s2 + update(ci.get_defs()) + finally: + ci.ch_drop() + +To automatically drop the handle(Preferred) use with +: + +.. code-block:: python + + try: + with Client() as ci: ci.ch_register(False,['s1','s2']) while( 1 ): - # get incremental changes to suites s1 & s2, uses data stored on ci/defs - ci.sync_local() # will only retrieve data for suites s1 & s2 - update(ci.get_defs()) - finally: - ci.ch_drop() - - To automatically drop the handle(Preferred) use with - : - - .. code-block:: python - - try: - with Client() as ci: - ci.ch_register(False,['s1','s2']) - while( 1 ): - # get incremental changes to suites s1 & s2, uses data stored on ci/defs - ci.sync_local() # will only retrieve data for suites s1 & s2 - update(ci.get_defs()) - .... # will automatically drop last handle - except RuntimeError, e: - print(str(e)) - - -ch_drop( (Client)arg1) -> int - - -.. py:method:: Client.ch_drop_user( (Client)arg1, (str)arg2) -> int : + # get incremental changes to suites s1 & s2, uses data stored on ci/defs + ci.sync_local() # will only retrieve data for suites s1 & s2 + update(ci.get_defs()) + .... # will automatically drop last handle + except RuntimeError, e: + print(str(e)) + + +2. ch_drop(self: ecflow.Client) -> int + + +.. py:method:: Client.ch_drop_user(self: ecflow.Client, arg0: str) -> int :module: ecflow Drop/de-register all handles associated with user. @@ -514,8 +534,7 @@ Usage: ci.ch_drop_user('') # drop all handles associated with current user - -.. py:method:: Client.ch_handle( (Client)arg1) -> int : +.. py:method:: Client.ch_handle(self: ecflow.Client) -> int :module: ecflow Register interest in a set of :term:`suite`\ s. @@ -572,7 +591,7 @@ To automatically drop the handle(preferred) use with: print(str(e)) -.. py:method:: Client.ch_register( (Client)arg1, (bool)arg2, (list)arg3) -> None : +.. py:method:: Client.ch_register(self: ecflow.Client, arg0: bool, arg1: list) -> None :module: ecflow Register interest in a set of :term:`suite`\ s. @@ -629,41 +648,45 @@ To automatically drop the handle(preferred) use with: print(str(e)) -.. py:method:: Client.ch_remove( (Client)arg1, (int)arg2, (list)arg3) -> None : +.. py:method:: Client.ch_remove(*args, **kwargs) :module: ecflow +Overloaded function. + +1. ch_remove(self: ecflow.Client, arg0: typing.SupportsInt | typing.SupportsIndex, arg1: list) -> None + Remove a set of suites, from an existing handle - - When dealing with large definitions, where a user is only interested in a small subset - of suites, registering them, improves download performance from the server. - Registered suites have an associated handle. - :: - - integer ch_remove( - integer handle : the handle obtained after ch_register - list suite_names : list of strings representing suite names - ) - integer ch_remove( - list suite_names : list of strings representing suite names - ) - - Usage: - - .. code-block:: python - - try: - with Client() as ci: # use default host(ECF_HOST) & port(ECF_PORT) - ci.ch_register(True,['s1','s2','s3']) # register interest in suites s1,s2,s3 and any new suites - ci.ch_remove( ['s1'] ) # remove suites s1 from the last added handle - except RuntimeError, e: - print(str(e)) - - - -ch_remove( (Client)arg1, (list)arg2) -> None - - -.. py:method:: Client.ch_suites( (Client)arg1) -> None : + +When dealing with large definitions, where a user is only interested in a small subset +of suites, registering them, improves download performance from the server. +Registered suites have an associated handle. +:: + + integer ch_remove( + integer handle : the handle obtained after ch_register + list suite_names : list of strings representing suite names + ) + integer ch_remove( + list suite_names : list of strings representing suite names + ) + +Usage: + +.. code-block:: python + + try: + with Client() as ci: # use default host(ECF_HOST) & port(ECF_PORT) + ci.ch_register(True,['s1','s2','s3']) # register interest in suites s1,s2,s3 and any new suites + ci.ch_remove( ['s1'] ) # remove suites s1 from the last added handle + except RuntimeError, e: + print(str(e)) + + + +2. ch_remove(self: ecflow.Client, arg0: list) -> None + + +.. py:method:: Client.ch_suites(self: ecflow.Client) -> None :module: ecflow Writes to standard out the list of registered handles and the suites they reference. @@ -709,38 +732,42 @@ Usage: print(str(e)) -.. py:method:: Client.check( (Client)arg1, (str)arg2) -> str : +.. py:method:: Client.check(*args, **kwargs) :module: ecflow +Overloaded function. + +1. check(self: ecflow.Client, arg0: str) -> str + Check :term:`trigger` and :term:`complete expression`\ s and :term:`limit`\ s - - The :term:`ecflow_server` does not store :term:`extern`\ s. Hence all unresolved references - are reported as errors. - Returns a non empty string for any errors or warning - :: - - string check( - list paths # List of paths. - ) - string check( - string absolute_node_path - ) - - Usage: - - .. code-block:: python - - try: - ci = Client() # use default host(ECF_HOST) & port(ECF_PORT) - print(ci.check('/suite1')) - except RuntimeError, e: - print(str(e)) - - -check( (Client)arg1, (list)arg2) -> str - - -.. py:method:: Client.checkpt( (Client)arg1 [, (CheckPt)mode=ecflow.CheckPt.UNDEFINED [, (int)check_pt_interval=0 [, (int)check_pt_save_alarm_time=0]]]) -> int : + +The :term:`ecflow_server` does not store :term:`extern`\ s. Hence all unresolved references +are reported as errors. +Returns a non empty string for any errors or warning +:: + + string check( + list paths # List of paths. + ) + string check( + string absolute_node_path + ) + +Usage: + +.. code-block:: python + + try: + ci = Client() # use default host(ECF_HOST) & port(ECF_PORT) + print(ci.check('/suite1')) + except RuntimeError, e: + print(str(e)) + + +2. check(self: ecflow.Client, arg0: list) -> str + + +.. py:method:: Client.checkpt(self: ecflow.Client, mode: ecflow.CheckPt = , check_pt_interval: typing.SupportsInt | typing.SupportsIndex = 0, check_pt_save_alarm_time: typing.SupportsInt | typing.SupportsIndex = 0) -> int :module: ecflow Request the :term:`ecflow_server` :term:`check point`\ s the definition held in the server immediately @@ -793,55 +820,55 @@ Usage: print(str(e)) -.. py:method:: Client.child_abort( (Client)arg1 [, (str)reason='']) -> None : +.. py:method:: Client.child_abort(self: ecflow.Client, reason: str = '') -> None :module: ecflow Child command,notify server job has aborted, can provide an optional reason -.. py:method:: Client.child_complete( (Client)arg1) -> None : +.. py:method:: Client.child_complete(self: ecflow.Client) -> None :module: ecflow Child command,notify server job has complete -.. py:method:: Client.child_event( (Client)arg1, (str)event_name [, (bool)value=True]) -> None : +.. py:method:: Client.child_event(self: ecflow.Client, event_name: str, value: bool = True) -> None :module: ecflow Child command,notify server event occurred, requires the event name -.. py:method:: Client.child_init( (Client)arg1) -> None : +.. py:method:: Client.child_init(self: ecflow.Client) -> None :module: ecflow Child command,notify server job has started -.. py:method:: Client.child_label( (Client)arg1, (str)arg2, (str)arg3) -> None : +.. py:method:: Client.child_label(self: ecflow.Client, arg0: str, arg1: str) -> None :module: ecflow Child command,notify server label changed, requires label name, and new value -.. py:method:: Client.child_meter( (Client)arg1, (str)arg2, (int)arg3) -> None : +.. py:method:: Client.child_meter(self: ecflow.Client, arg0: str, arg1: typing.SupportsInt | typing.SupportsIndex) -> None :module: ecflow Child command,notify server meter changed, requires meter name and value -.. py:method:: Client.child_queue( (Client)arg1, (str)queue_name, (str)action [, (str)step='' [, (str)path_to_node_with_queue='']]) -> str : +.. py:method:: Client.child_queue(self: ecflow.Client, queue_name: str, action: str, step: str = '', path_to_node_with_queue: str = '') -> str :module: ecflow -Child command,active:return current step as string, then increment index, requires queue name, and optionally path to node with the queue +Child command,active:return current step as string, then increment index,requires queue name, and optionally path to node with the queue -.. py:method:: Client.child_wait( (Client)arg1, (str)arg2) -> None : +.. py:method:: Client.child_wait(self: ecflow.Client, arg0: str) -> None :module: ecflow Child command,wait for expression to come true -.. py:method:: Client.clear_log( (Client)arg1) -> int : +.. py:method:: Client.clear_log(self: ecflow.Client) -> int :module: ecflow Request the :term:`ecflow_server` to clear log file. @@ -860,62 +887,66 @@ Usage: print(str(e)) -.. py:method:: Client.debug( (Client)arg1, (bool)arg2) -> None : +.. py:method:: Client.debug(self: ecflow.Client, arg0: bool) -> None :module: ecflow enable/disable client api debug -.. py:method:: Client.debug_server_off( (Client)arg1) -> int : +.. py:method:: Client.debug_server_off(self: ecflow.Client) -> int :module: ecflow Disable server debug -.. py:method:: Client.debug_server_on( (Client)arg1) -> int : +.. py:method:: Client.debug_server_on(self: ecflow.Client) -> int :module: ecflow Enable server debug, Will dump to standard out on server host. -.. py:method:: Client.delete( (Client)arg1, (str)abs_node_path [, (bool)force=False]) -> int : +.. py:method:: Client.delete(*args, **kwargs) :module: ecflow +Overloaded function. + +1. delete(self: ecflow.Client, abs_node_path: str, force: bool = False) -> int + Delete the :term:`node` (s) specified. - - If a node is :term:`submitted` or :term:`active`, then a Exception will be raised. - To force the deletion at the expense of :term:`zombie` creation, then set - the force parameter to true - :: - - void delete( - list paths : List of paths. - [(bool)force=False] : If true delete even if in 'active' or 'submitted' states - Which risks creating zombies. - ) - void delete( - string absolute_node_path: Path name of node to delete. - [(bool)force=False] : If true delete even if in 'active' or 'submitted' states - ) - - Usage: - - .. code-block:: python - - try: - ci = Client() # use default host(ECF_HOST) & port(ECF_PORT) - ci.delete('/s1/f1/task1') - - paths = ['/s1/f1/t1','/s2/f1/t2'] - ci.delete(paths) # delete all tasks specified in the paths - except RuntimeError, e: - print(str(e)) - - -delete( (Client)arg1, (list)paths [, (bool)force=False]) -> None - - -.. py:method:: Client.delete_all( (Client)arg1 [, (bool)force=False]) -> int : + +If a node is :term:`submitted` or :term:`active`, then a Exception will be raised. +To force the deletion at the expense of :term:`zombie` creation, then set +the force parameter to true +:: + + void delete( + list paths : List of paths. + [(bool)force=False] : If true delete even if in 'active' or 'submitted' states + Which risks creating zombies. + ) + void delete( + string absolute_node_path: Path name of node to delete. + [(bool)force=False] : If true delete even if in 'active' or 'submitted' states + ) + +Usage: + +.. code-block:: python + + try: + ci = Client() # use default host(ECF_HOST) & port(ECF_PORT) + ci.delete('/s1/f1/task1') + + paths = ['/s1/f1/t1','/s2/f1/t2'] + ci.delete(paths) # delete all tasks specified in the paths + except RuntimeError, e: + print(str(e)) + + +2. delete(self: ecflow.Client, paths: list, force: bool = False) -> None + + +.. py:method:: Client.delete_all(self: ecflow.Client, force: bool = False) -> int :module: ecflow Delete all the :term:`node`\ s held in the :term:`ecflow_server`. @@ -943,7 +974,7 @@ Usage: print(str(e)); # expect failure since all nodes deleted -.. py:method:: Client.disable_ssl( (Client)arg1) -> None : +.. py:method:: Client.disable_ssl(self: ecflow.Client) -> None :module: ecflow Disable secure communication between client and server. @@ -961,21 +992,19 @@ Usage: print(str(e)) -.. py:method:: Client.edit_script_edit( (Client)arg1, (str)arg2) -> str : +.. py:method:: Client.edit_script_edit(self: ecflow.Client, arg0: str) -> str :module: ecflow get script for Edit - -.. py:method:: Client.edit_script_preprocess( (Client)arg1, (str)arg2) -> str : +.. py:method:: Client.edit_script_preprocess(self: ecflow.Client, arg0: str) -> str :module: ecflow get script for Edit Preprocess - -.. py:method:: Client.edit_script_submit( (Client)arg1, (str)arg2, (list)arg3, (list)arg4, (bool)arg5, (bool)arg6) -> int : +.. py:method:: Client.edit_script_submit(self: ecflow.Client, arg0: str, arg1: list, arg2: list, arg3: bool, arg4: bool) -> int :module: ecflow submit script from Edit/Preprocess @@ -985,26 +1014,26 @@ to run as alias or not: ci = Client() ci.edit_script_submit(path_to_task, - used_variables, # array name=value - file_contents, # strings array - alias, # bool False, - run # bool true + used_variables, # array name=value + file_contents, # strings array + alias, # bool False, + run # bool true ) -.. py:method:: Client.enable_http( (Client)arg1) -> None : +.. py:method:: Client.enable_http(self: ecflow.Client) -> None :module: ecflow Enable HTTP communication -.. py:method:: Client.enable_https( (Client)arg1) -> None : +.. py:method:: Client.enable_https(self: ecflow.Client) -> None :module: ecflow Enable HTTPS communication -.. py:method:: Client.enable_ssl( (Client)arg1) -> None : +.. py:method:: Client.enable_ssl(self: ecflow.Client) -> None :module: ecflow Enable secure communication between client and server. @@ -1022,7 +1051,7 @@ Usage: print(str(e)) -.. py:method:: Client.flush_log( (Client)arg1) -> int : +.. py:method:: Client.flush_log(self: ecflow.Client) -> int :module: ecflow Request the :term:`ecflow_server` to flush and then close log file @@ -1041,232 +1070,260 @@ Usage: print(str(e)) -.. py:method:: Client.force_event( (Client)arg1, (str)arg2, (str)arg3) -> None : +.. py:method:: Client.force_event(*args, **kwargs) :module: ecflow +Overloaded function. + +1. force_event(self: ecflow.Client, arg0: str, arg1: str) -> None + Set or clear a :term:`event` - :: - - void force_event( - string absolute_node_path:event: Path name to node: < event name | number> - The paths must begin with a leading '/' - string signal : [ set | clear ] - ) - void force_event( - list paths : A list of absolute node paths. Each path must include a event name - The paths must begin with a leading '/' - string signal : [ set | clear ] - ) - - Usage: - - .. code-block:: python - - try: - ci = Client() # use default host(ECF_HOST) & port(ECF_PORT) - ci.force_event('/s1/f1:event_name','set') - - # Set or clear a event for a list of events - paths = [ '/s1/t1:ev1', '/s2/t2:ev2' ] - ci.force_event(paths,'clear') - except RuntimeError, e: - print(str(e)) - - -force_event( (Client)arg1, (list)arg2, (str)arg3) -> None - - -.. py:method:: Client.force_state( (Client)arg1, (str)arg2, (State)arg3) -> None : +:: + + void force_event( + string absolute_node_path:event: Path name to node: < event name | number> + The paths must begin with a leading '/' + string signal : [ set | clear ] + ) + void force_event( + list paths : A list of absolute node paths. Each path must include a event name + The paths must begin with a leading '/' + string signal : [ set | clear ] + ) + +Usage: + +.. code-block:: python + + try: + ci = Client() # use default host(ECF_HOST) & port(ECF_PORT) + ci.force_event('/s1/f1:event_name','set') + + # Set or clear a event for a list of events + paths = [ '/s1/t1:ev1', '/s2/t2:ev2' ] + ci.force_event(paths,'clear') + except RuntimeError, e: + print(str(e)) + + +2. force_event(self: ecflow.Client, arg0: list, arg1: str) -> None + + +.. py:method:: Client.force_state(*args, **kwargs) :module: ecflow +Overloaded function. + +1. force_state(self: ecflow.Client, arg0: str, arg1: ecflow.State) -> None + Force a node(s) to a given state - - When a :term:`task` is set to :term:`complete`, it may be automatically re-queued if it has - multiple time :term:`dependencies`. In the specific case where a task has a single - time dependency and we want to interactively set it to :term:`complete` - a flag is set so that it is not automatically re-queued when set to complete. - The flag is applied up the node hierarchy until reach a node with a :term:`repeat` - or :term:`cron` attribute. This behaviour allow :term:`repeat` values to be incremented interactively. - A :term:`repeat` attribute is incremented when all the child nodes are :term:`complete` - in this case the child nodes are automatically re-queued - :: - - void force_state( - string absolute_node_path: Path name to node. The path must begin with a leading '/' - State::State state : [ unknown | complete | queued | submitted | active | aborted ] - ) - void force_state( - list paths : A list of absolute node paths. The paths must begin with a leading '/' - State::State state : [ unknown | complete | queued | submitted | active | aborted ] - ) - - Usage: - - .. code-block:: python - - try: - ci = Client() # use default host(ECF_HOST) & port(ECF_PORT) - # force a single node to complete - ci.force_state('/s1/f1',State.complete) - - # force a list of nodes to complete - paths = [ '/s1/t1', '/s1/t2', '/s1/f1/t1' ] - ci.force_state(paths,State.complete) - except RuntimeError, e: - print(str(e)) - - Effect: - - Lets see the effect of forcing complete on the following defs - - .. code-block:: shell - - suite s1 - task t1; time 10:00 # will complete straight away - task t2; time 10:00 13:00 01:00 # will re-queue 3 times and complete on fourth - - In the last case (task t2) after each force complete, the next time slot is incremented. - This can be seen by calling the Why command. - -force_state( (Client)arg1, (list)arg2, (State)arg3) -> None - - -.. py:method:: Client.force_state_recursive( (Client)arg1, (str)arg2, (State)arg3) -> None : + +When a :term:`task` is set to :term:`complete`, it may be automatically re-queued if it has +multiple time :term:`dependencies`. In the specific case where a task has a single +time dependency and we want to interactively set it to :term:`complete` +a flag is set so that it is not automatically re-queued when set to complete. +The flag is applied up the node hierarchy until reach a node with a :term:`repeat` +or :term:`cron` attribute. This behaviour allow :term:`repeat` values to be incremented interactively. +A :term:`repeat` attribute is incremented when all the child nodes are :term:`complete` +in this case the child nodes are automatically re-queued +:: + + void force_state( + string absolute_node_path: Path name to node. The path must begin with a leading '/' + State::State state : [ unknown | complete | queued | submitted | active | aborted ] + ) + void force_state( + list paths : A list of absolute node paths. The paths must begin with a leading '/' + State::State state : [ unknown | complete | queued | submitted | active | aborted ] + ) + +Usage: + +.. code-block:: python + + try: + ci = Client() # use default host(ECF_HOST) & port(ECF_PORT) + # force a single node to complete + ci.force_state('/s1/f1',State.complete) + + # force a list of nodes to complete + paths = [ '/s1/t1', '/s1/t2', '/s1/f1/t1' ] + ci.force_state(paths,State.complete) + except RuntimeError, e: + print(str(e)) + +Effect: + +Lets see the effect of forcing complete on the following defs + +.. code-block:: shell + + suite s1 + task t1; time 10:00 # will complete straight away + task t2; time 10:00 13:00 01:00 # will re-queue 3 times and complete on fourth + +In the last case (task t2) after each force complete, the next time slot is incremented. +This can be seen by calling the Why command. + +2. force_state(self: ecflow.Client, arg0: list, arg1: ecflow.State) -> None + + +.. py:method:: Client.force_state_recursive(*args, **kwargs) :module: ecflow +Overloaded function. + +1. force_state_recursive(self: ecflow.Client, arg0: str, arg1: ecflow.State) -> None + Force node(s) to a given state recursively - :: - - void force_state_recursive( - string absolute_node_path: Path name to node.The paths must begin with a leading '/' - State::State state : [ unknown | complete | queued | submitted | active | aborted ] - ) - void force_state_recursive( - list paths : A list of absolute node paths.The paths must begin with a leading '/' - State::State state : [ unknown | complete | queued | submitted | active | aborted ] - ) - - Usage: - - .. code-block:: python - - try: - ci = Client() # use default host(ECF_HOST) & port(ECF_PORT) - ci.force_state_recursive('/s1/f1',State.complete) - - # recursively force a list of nodes to complete - paths = [ '/s1', '/s2', '/s1/f1/t1' ] - ci.force_state_recursive(paths,State.complete) - except RuntimeError, e: - print(str(e)) - - -force_state_recursive( (Client)arg1, (list)arg2, (State)arg3) -> None - - -.. py:method:: Client.free_all_dep( (Client)arg1, (str)arg2) -> None : +:: + + void force_state_recursive( + string absolute_node_path: Path name to node.The paths must begin with a leading '/' + State::State state : [ unknown | complete | queued | submitted | active | aborted ] + ) + void force_state_recursive( + list paths : A list of absolute node paths.The paths must begin with a leading '/' + State::State state : [ unknown | complete | queued | submitted | active | aborted ] + ) + +Usage: + +.. code-block:: python + + try: + ci = Client() # use default host(ECF_HOST) & port(ECF_PORT) + ci.force_state_recursive('/s1/f1',State.complete) + + # recursively force a list of nodes to complete + paths = [ '/s1', '/s2', '/s1/f1/t1' ] + ci.force_state_recursive(paths,State.complete) + except RuntimeError, e: + print(str(e)) + + +2. force_state_recursive(self: ecflow.Client, arg0: list, arg1: ecflow.State) -> None + + +.. py:method:: Client.free_all_dep(*args, **kwargs) :module: ecflow +Overloaded function. + +1. free_all_dep(self: ecflow.Client, arg0: str) -> None + Free all :term:`trigger`, :term:`date` and all time(:term:`day`, :term:`today`, :term:`cron`,etc) :term:`dependencies` - :: - - void free_all_dep( - string absolute_node_path : Path name to node - ) - - After freeing the time related dependencies (i.e time,today,cron) - the next time slot will be missed. - - Usage: - - .. code-block:: python - - try: - ci = Client() # use default host(ECF_HOST) & port(ECF_PORT) - ci.free_all_dep('/s1/task') - except RuntimeError, e: - print(str(e)) - - -free_all_dep( (Client)arg1, (list)arg2) -> None - - -.. py:method:: Client.free_date_dep( (Client)arg1, (str)arg2) -> None : +:: + + void free_all_dep( + string absolute_node_path : Path name to node + ) + +After freeing the time related dependencies (i.e time,today,cron) +the next time slot will be missed. + +Usage: + +.. code-block:: python + + try: + ci = Client() # use default host(ECF_HOST) & port(ECF_PORT) + ci.free_all_dep('/s1/task') + except RuntimeError, e: + print(str(e)) + + +2. free_all_dep(self: ecflow.Client, arg0: list) -> None + + +.. py:method:: Client.free_date_dep(*args, **kwargs) :module: ecflow +Overloaded function. + +1. free_date_dep(self: ecflow.Client, arg0: str) -> None + Free :term:`date` :term:`dependencies` for a :term:`node` - :: - - void free_date_dep( - string absolute_node_path : Path name to node - ) - - Usage: - - .. code-block:: python - - try: - ci = Client() # use default host(ECF_HOST) & port(ECF_PORT) - ci.free_date_dep('/s1/task') - except RuntimeError, e: - print(str(e)) - +:: -free_date_dep( (Client)arg1, (list)arg2) -> None + void free_date_dep( + string absolute_node_path : Path name to node + ) +Usage: + +.. code-block:: python -.. py:method:: Client.free_time_dep( (Client)arg1, (str)arg2) -> None : + try: + ci = Client() # use default host(ECF_HOST) & port(ECF_PORT) + ci.free_date_dep('/s1/task') + except RuntimeError, e: + print(str(e)) + + +2. free_date_dep(self: ecflow.Client, arg0: list) -> None + + +.. py:method:: Client.free_time_dep(*args, **kwargs) :module: ecflow +Overloaded function. + +1. free_time_dep(self: ecflow.Client, arg0: str) -> None + Free all time :term:`dependencies`. i.e :term:`time`, :term:`day`, :term:`today`, :term:`cron` - :: - - void free_time_dep( - string absolute_node_path : Path name to node - ) - - After freeing the time related dependencies (i.e time,today,cron) - the next time slot will be missed. - - Usage: - - .. code-block:: python - - try: - ci = Client() # use default host(ECF_HOST) & port(ECF_PORT) - ci.free_time_dep('/s1/task') - except RuntimeError, e: - print(str(e)) - - -free_time_dep( (Client)arg1, (list)arg2) -> None - - -.. py:method:: Client.free_trigger_dep( (Client)arg1, (str)arg2) -> None : +:: + + void free_time_dep( + string absolute_node_path : Path name to node + ) + +After freeing the time related dependencies (i.e time,today,cron) +the next time slot will be missed. + +Usage: + +.. code-block:: python + + try: + ci = Client() # use default host(ECF_HOST) & port(ECF_PORT) + ci.free_time_dep('/s1/task') + except RuntimeError, e: + print(str(e)) + + +2. free_time_dep(self: ecflow.Client, arg0: list) -> None + + +.. py:method:: Client.free_trigger_dep(*args, **kwargs) :module: ecflow +Overloaded function. + +1. free_trigger_dep(self: ecflow.Client, arg0: str) -> None + Free :term:`trigger` :term:`dependencies` for a :term:`node` - :: - - void free_trigger_dep( - string absolute_node_path : Path name to node - ) - - Usage: - - .. code-block:: python - - try: - ci = Client() # use default host(ECF_HOST) & port(ECF_PORT) - ci.free_trigger_dep('/s1/f1/task') - except RuntimeError, e: - print(str(e)) - +:: + + void free_trigger_dep( + string absolute_node_path : Path name to node + ) + +Usage: + +.. code-block:: python + + try: + ci = Client() # use default host(ECF_HOST) & port(ECF_PORT) + ci.free_trigger_dep('/s1/f1/task') + except RuntimeError, e: + print(str(e)) -free_trigger_dep( (Client)arg1, (list)arg2) -> None +2. free_trigger_dep(self: ecflow.Client, arg0: list) -> None -.. py:method:: Client.get_certificate( (Client)arg1) -> str : + +.. py:method:: Client.get_certificate(self: ecflow.Client) -> str :module: ecflow Retrieves the full path to the secure communication certificate currently used by the client. @@ -1284,7 +1341,7 @@ Usage: print(str(e)) -.. py:method:: Client.get_defs( (Client)arg1) -> Defs : +.. py:method:: Client.get_defs(self: ecflow.Client) -> ecflow.Defs :module: ecflow Returns the :term:`suite definition` stored on the Client. @@ -1305,7 +1362,7 @@ Usage: print(str(e)) -.. py:method:: Client.get_file( (Client)arg1, (str)task [, (str)type='script' [, (str)max_lines='10000' [, (bool)as_bytes=False]]]) -> object : +.. py:method:: Client.get_file(self: ecflow.Client, task: str, type: str = 'script', max_lines: str = '10000', as_bytes: bool = False) -> object :module: ecflow The File command is used to request the various file types associated with a :term:`node`. @@ -1334,13 +1391,13 @@ Usage: print(str(e)) -.. py:method:: Client.get_host( (Client)arg1) -> str : +.. py:method:: Client.get_host(self: ecflow.Client) -> str :module: ecflow -Return the host, assume set_host_port() has been set, otherwise return localhost +Returns the host, assume set_host_port() has been set, otherwise return localhost -.. py:method:: Client.get_log( (Client)arg1, (int)arg2) -> str : +.. py:method:: Client.get_log(self: ecflow.Client, arg0: typing.SupportsInt | typing.SupportsIndex) -> str :module: ecflow Request the :term:`ecflow_server` to return the log file contents as a string @@ -1359,13 +1416,13 @@ Usage: print(str(e)) -.. py:method:: Client.get_port( (Client)arg1) -> str : +.. py:method:: Client.get_port(self: ecflow.Client) -> str :module: ecflow -Return the port, assume set_host_port() has been set. otherwise returns 3141 +Returns the port, assume set_host_port() has been set, otherwise returns 3141 -.. py:method:: Client.get_server_defs( (Client)arg1) -> int : +.. py:method:: Client.get_server_defs(self: ecflow.Client) -> int :module: ecflow Get all suite Node tree's from the :term:`ecflow_server`. @@ -1390,7 +1447,7 @@ Usage: print(str(e)) -.. py:method:: Client.group( (Client)arg1, (str)arg2) -> int : +.. py:method:: Client.group(self: ecflow.Client, arg0: str) -> int :module: ecflow Allows a series of commands to be executed in the :term:`ecflow_server` @@ -1412,7 +1469,7 @@ Usage: print(str(e)) -.. py:method:: Client.halt_server( (Client)arg1) -> int : +.. py:method:: Client.halt_server(self: ecflow.Client) -> int :module: ecflow Halt the :term:`ecflow_server` @@ -1431,176 +1488,184 @@ Usage: print(str(e)) -.. py:method:: Client.in_sync( (Client)arg1) -> bool : +.. py:method:: Client.in_sync(self: ecflow.Client) -> bool + :module: ecflow + +Returns true if the definition on the client is in sync with the :term:`ecflow_server` + +.. Warning:: Calling in_sync() is **only** valid after a call to sync_local(). + +Usage: + +.. code-block:: python + + try: + ci = Client() # use default host(ECF_HOST) & port(ECF_PORT) + ci.sync_local() # very first call gets the full Defs + client_defs = ci.get_defs() # End user access to the returned Defs + ... after a period of time + ci.sync_local() # Subsequent calls to sync_local() users the local Defs to sync incrementally + if ci.in_sync(): # returns true changed and changes applied to client + print('Client is now in sync with server') + client_defs = ci.get_defs() # End user access to the returned Defs + except RuntimeError, e: + print(str(e)) + + +.. py:method:: Client.is_auto_sync_enabled(self: ecflow.Client) -> bool + :module: ecflow + +Returns true if automatic syncing enabled + + +.. py:method:: Client.job_generation(self: ecflow.Client, arg0: str) -> int + :module: ecflow + +Job submission for chosen Node *based* on :term:`dependencies` + +The :term:`ecflow_server` traverses the :term:`node` tree every 60 seconds, and if the dependencies are free +does `job creation` and submission. Sometimes the user may free time/date dependencies +to avoid waiting for the server poll, this commands allows early job generation +:: + + void job_generation( + string absolute_node_path: Path name for job generation to start from + ) + If empty string specified generates for full definition. + +Usage: + +.. code-block:: python + + try: + ci = Client() # use default host(ECF_HOST) & port(ECF_PORT) + ci.job_generation('/s1') # generate jobs for suite '/s1 + except RuntimeError, e: + print(str(e)) + + +.. py:method:: Client.kill(*args, **kwargs) + :module: ecflow + +Overloaded function. + +1. kill(self: ecflow.Client, arg0: str) -> None + +Kills the job associated with the :term:`node` +:: + + void kill( + list paths: List of paths. Paths must begin with a leading '/' character + ) + void kill( + string absolute_node_path: Path name to node to kill. + ) + +If a :term:`family` or :term:`suite` is selected, will kill hierarchically. +Kill uses the ECF_KILL_CMD variable. After :term:`variable substitution` it is invoked as a command. +The ECF_KILL_CMD variable should be written in such a way that the output is written to %ECF_JOB%.kill, i.e: + +.. code-block:: shell + + kill -15 %ECF_RID% > %ECF_JOB%.kill 2>&1 + /home/ma/emos/bin/ecfkill %USER% %HOST% %ECF_RID% %ECF_JOB% > %ECF_JOB%.kill 2>&1 + + +Exceptions can be raised because: + +- The absolute_node_path does not exist in the server +- ECF_KILL_CMD variable is not defined +- :term:`variable substitution` fails + +Usage: + +.. code-block:: python + + try: + ci = Client() # use default host(ECF_HOST) & port(ECF_PORT) + ci.kill('/s1/f1') + time.sleep(2) + print(ci.file('/s1/t1','kill')) # request kill output + except RuntimeError, e: + print(str(e)) + + +2. kill(self: ecflow.Client, arg0: list) -> None + + +.. py:method:: Client.load(*args, **kwargs) :module: ecflow -Returns true if the definition on the client is in sync with the :term:`ecflow_server` +Overloaded function. -.. Warning:: Calling in_sync() is **only** valid after a call to sync_local(). +1. load(self: ecflow.Client, path_to_defs: str, force: bool = False, check_only: bool = False, print: bool = False, stats: bool = False) -> int + +Load a :term:`suite definition` or checkpoint file given by the file_path argument into the :term:`ecflow_server` +:: + + void load( + string file_path : path name to the definition file + [(bool)force=False] : If true overwrite suite of same name + [(bool)print=False] : print parsed defs to standard out + ) + +By default throws a RuntimeError exception for errors. +If force is not used and :term:`suite` of the same name already exists in the server, +then a error is thrown Usage: .. code-block:: python + defs_file = 'Hello.def' + defs = Defs() + suite = def.add_suite('s1') + family = suite.add_family('f1') + for i in [ '_1', '_2', '_3' ]: + family.add_task( 't' + i ) + defs.save_as_defs(defs_file) # write out in memory defs into the file 'Hello.def' + ... try: - ci = Client() # use default host(ECF_HOST) & port(ECF_PORT) - ci.sync_local() # very first call gets the full Defs - client_defs = ci.get_defs() # End user access to the returned Defs - ... after a period of time - ci.sync_local() # Subsequent calls to sync_local() users the local Defs to sync incrementally - if ci.in_sync(): # returns true changed and changes applied to client - print('Client is now in sync with server') - client_defs = ci.get_defs() # End user access to the returned Defs + ci = Client() # use default host(ECF_HOST) & port(ECF_PORT) + ci.load(defs_file) # open and parse defs or checkpoint file, and load into server. except RuntimeError, e: print(str(e)) -.. py:method:: Client.is_auto_sync_enabled( (Client)arg1) -> bool : - :module: ecflow - -Returns true if automatic syncing enabled - - -.. py:method:: Client.job_generation( (Client)arg1, (str)arg2) -> int : - :module: ecflow - -Job submission for chosen Node *based* on :term:`dependencies` +2. load(self: ecflow.Client, defs: ecflow.Defs, force: bool = False) -> int -The :term:`ecflow_server` traverses the :term:`node` tree every 60 seconds, and if the dependencies are free -does `job creation` and submission. Sometimes the user may free time/date dependencies -to avoid waiting for the server poll, this commands allows early job generation +Load a in memory :term:`suite definition` into the :term:`ecflow_server` :: - void job_generation( - string absolute_node_path: Path name for job generation to start from + void load( + Defs defs : A in memory definition + [(bool)force=False] : for true overwrite suite of same name ) - If empty string specified generates for full definition. + +If force is not used and :term:`suite` already exists in the server, then a error is thrown. Usage: .. code-block:: python + defs = Defs() + suite = defs.add_suite('s1') + family = suite.add_family('f1') + for i in [ '_1', '_2', '_3' ]: + family.add_task( Task( 't' + i) ) + ... try: ci = Client() # use default host(ECF_HOST) & port(ECF_PORT) - ci.job_generation('/s1') # generate jobs for suite '/s1 + ci.load(defs) # Load in memory defs, into the server except RuntimeError, e: print(str(e)) -.. py:method:: Client.kill( (Client)arg1, (str)arg2) -> None : +.. py:method:: Client.log_msg(self: ecflow.Client, arg0: str) -> int :module: ecflow -Kills the job associated with the :term:`node` - :: - - void kill( - list paths: List of paths. Paths must begin with a leading '/' character - ) - void kill( - string absolute_node_path: Path name to node to kill. - ) - - If a :term:`family` or :term:`suite` is selected, will kill hierarchically. - Kill uses the ECF_KILL_CMD variable. After :term:`variable substitution` it is invoked as a command. - The ECF_KILL_CMD variable should be written in such a way that the output is written to %ECF_JOB%.kill, i.e: - - .. code-block:: shell - - kill -15 %ECF_RID% > %ECF_JOB%.kill 2>&1 - /home/ma/emos/bin/ecfkill %USER% %HOST% %ECF_RID% %ECF_JOB% > %ECF_JOB%.kill 2>&1 - - - Exceptions can be raised because: - - - The absolute_node_path does not exist in the server - - ECF_KILL_CMD variable is not defined - - :term:`variable substitution` fails - - Usage: - - .. code-block:: python - - try: - ci = Client() # use default host(ECF_HOST) & port(ECF_PORT) - ci.kill('/s1/f1') - time.sleep(2) - print(ci.file('/s1/t1','kill')) # request kill output - except RuntimeError, e: - print(str(e)) - - -kill( (Client)arg1, (list)arg2) -> None - - -.. py:method:: Client.load( (Client)arg1, (str)path_to_defs [, (bool)force=False [, (bool)check_only=False [, (bool)print=False [, (bool)stats=False]]]]) -> int : - :module: ecflow -Load a :term:`suite definition` or checkpoint file given by the file_path argument into the :term:`ecflow_server` - :: - - void load( - string file_path : path name to the definition file - [(bool)force=False] : If true overwrite suite of same name - [(bool)print=False] : print parsed defs to standard out - ) - - By default throws a RuntimeError exception for errors. - If force is not used and :term:`suite` of the same name already exists in the server, - then a error is thrown - - Usage: - - .. code-block:: python - - defs_file = 'Hello.def' - defs = Defs() - suite = def.add_suite('s1') - family = suite.add_family('f1') - for i in [ '_1', '_2', '_3' ]: - family.add_task( 't' + i ) - defs.save_as_defs(defs_file) # write out in memory defs into the file 'Hello.def' - ... - try: - ci = Client() # use default host(ECF_HOST) & port(ECF_PORT) - ci.load(defs_file) # open and parse defs or checkpoint file, and load into server. - except RuntimeError, e: - print(str(e)) - - -load( (Client)arg1, (Defs)defs [, (bool)force=False]) -> int : - Load a in memory :term:`suite definition` into the :term:`ecflow_server` - :: - - void load( - Defs defs : A in memory definition - [(bool)force=False] : for true overwrite suite of same name - ) - - If force is not used and :term:`suite` already exists in the server, then a error is thrown. - - Usage: - - .. code-block:: python - - defs = Defs() - suite = defs.add_suite('s1') - family = suite.add_family('f1') - for i in [ '_1', '_2', '_3' ]: - family.add_task( Task( 't' + i) ) - ... - try: - ci = Client() # use default host(ECF_HOST) & port(ECF_PORT) - ci.load(defs) # Load in memory defs, into the server - except RuntimeError, e: - print(str(e)) - - - -.. py:method:: Client.log_msg( (Client)arg1, (str)arg2) -> int - :module: ecflow - - -.. py:method:: Client.new_log( (Client)arg1 [, (str)path='']) -> int : +.. py:method:: Client.new_log(self: ecflow.Client, path: str = '') -> int :module: ecflow Request the :term:`ecflow_server` to use the path provided, as the new log file @@ -1621,7 +1686,7 @@ Usage: print(str(e)) -.. py:method:: Client.news_local( (Client)arg1) -> bool : +.. py:method:: Client.news_local(self: ecflow.Client) -> bool :module: ecflow Query the :term:`ecflow_server` to detect any changes. @@ -1650,7 +1715,7 @@ Usage: print(str(e)) -.. py:method:: Client.order( (Client)arg1, (str)arg2, (str)arg3) -> None : +.. py:method:: Client.order(self: ecflow.Client, arg0: str, arg1: str) -> None :module: ecflow Re-orders the :term:`node`\ s in the :term:`suite definition` held by the :term:`ecflow_server` @@ -1687,7 +1752,7 @@ Usage: print(str(e)) -.. py:method:: Client.ping( (Client)arg1) -> int : +.. py:method:: Client.ping(self: ecflow.Client) -> int :module: ecflow Checks if the :term:`ecflow_server` is running @@ -1713,7 +1778,7 @@ Usage: print('------- Server *NOT* running------' + str(e)) -.. py:method:: Client.plug( (Client)arg1, (str)arg2, (str)arg3) -> int : +.. py:method:: Client.plug(self: ecflow.Client, arg0: str, arg1: str) -> int :module: ecflow Plug command is used to move :term:`node`\ s @@ -1750,7 +1815,7 @@ Usage: print(str(e)) -.. py:method:: Client.query( (Client)arg1, (str)arg2, (str)arg3 [, (str)arg4]) -> str : +.. py:method:: Client.query(self: ecflow.Client, query_type: str, path_to_attribute: str, attribute: str = '') -> str :module: ecflow Query the status of event, meter, state, variable, limit, limit_max or trigger expression without blocking @@ -1803,19 +1868,19 @@ Usage: print str(e) -.. py:method:: Client.reload_custom_passwd_file( (Client)arg1) -> int : +.. py:method:: Client.reload_custom_passwd_file(self: ecflow.Client) -> int :module: ecflow reload the custom passwd file. ..ecf.custom_passwd. For users using ECF_USER or --user or set_user_name() -.. py:method:: Client.reload_passwd_file( (Client)arg1) -> int : +.. py:method:: Client.reload_passwd_file(self: ecflow.Client) -> int :module: ecflow reload the passwd file. ..ecf.passwd -.. py:method:: Client.reload_wl_file( (Client)arg1) -> int : +.. py:method:: Client.reload_wl_file(self: ecflow.Client) -> int :module: ecflow Request that the :term:`ecflow_server` reload the white list file. @@ -1836,107 +1901,115 @@ Usage: print(str(e)) -.. py:method:: Client.replace( (Client)arg1, (str)arg2, (str)arg3, (bool)arg4, (bool)arg5) -> int : +.. py:method:: Client.replace(*args, **kwargs) :module: ecflow +Overloaded function. + +1. replace(self: ecflow.Client, arg0: str, arg1: str, arg2: bool, arg3: bool) -> int + Replaces a :term:`node` in a :term:`suite definition` with the given path. The definition is in the :term:`ecflow_server` - :: - - void replace( - string absolute_node_path: Path name to node in the client defs. - This is also the node we want to replace in the server. - string client_defs_file : File path to defs files, that provides the definition of the new node - [(bool)parent=False] : create parent families or suite as needed, - when absolute_node_path does not exist in the server - [(bool)force=False] : check for zombies, if force = true, bypass checks - ) - - void replace( - string absolute_node_path: Path name to node in the client defs. - This is also the node we want to replace in the server. - Defs client_defs : In memory client definition that provides the definition of the new node - [(bool)parent=False] : create parent families or suite as needed, - when absolute_node_path does not exist in the server - [(bool)force=False] : check for zombies, force = true, bypass checks - ) - - Exceptions can be raised because: - - - The absolute_node_path does not exist in the provided definition - - The provided client definition must be free of errors - - If the third argument is not provided, then the absolute_node_path must exist in the server defs - - replace will fail, if child task nodes are in :term:`active` / :term:`submitted` state - - After replace is done, we check trigger expressions. These are reported to standard output. - It is up to the user to correct invalid trigger expressions, otherwise the tasks will *not* run. - Please note, you can use check() to check trigger expression and limits in the server. - - - Usage: - - .. code-block:: python - - try: - ci = Client() # use default host(ECF_HOST) & port(ECF_PORT) - ci.replace('/s1/f1','/tmp/defs.def') - except RuntimeError, e: - print(str(e)) - - try: - ci.replace('/s1',client_defs) # replace suite 's1' in the server, with 's1' in the client_defs - except RuntimeError, e: - print(str(e)) - - -replace( (Client)arg1, (str)arg2, (Defs)arg3, (bool)arg4, (bool)arg5) -> int - -replace( (Client)arg1, (str)arg2, (Defs)arg3) -> None - -replace( (Client)arg1, (str)arg2, (str)arg3) -> None - - -.. py:method:: Client.requeue( (Client)arg1, (str)abs_node_path [, (str)option='']) -> None : +:: + + void replace( + string absolute_node_path: Path name to node in the client defs. + This is also the node we want to replace in the server. + string client_defs_file : File path to defs files, that provides the definition of the new node + [(bool)parent=False] : create parent families or suite as needed, + when absolute_node_path does not exist in the server + [(bool)force=False] : check for zombies, if force = true, bypass checks + ) + + void replace( + string absolute_node_path: Path name to node in the client defs. + This is also the node we want to replace in the server. + Defs client_defs : In memory client definition that provides the definition of the new node + [(bool)parent=False] : create parent families or suite as needed, + when absolute_node_path does not exist in the server + [(bool)force=False] : check for zombies, force = true, bypass checks + ) + +Exceptions can be raised because: + +- The absolute_node_path does not exist in the provided definition +- The provided client definition must be free of errors +- If the third argument is not provided, then the absolute_node_path must exist in the server defs +- replace will fail, if child task nodes are in :term:`active` / :term:`submitted` state + +After replace is done, we check trigger expressions. These are reported to standard output. +It is up to the user to correct invalid trigger expressions, otherwise the tasks will *not* run. +Please note, you can use check() to check trigger expression and limits in the server. + + +Usage: + +.. code-block:: python + + try: + ci = Client() # use default host(ECF_HOST) & port(ECF_PORT) + ci.replace('/s1/f1','/tmp/defs.def') + except RuntimeError, e: + print(str(e)) + + try: + ci.replace('/s1',client_defs) # replace suite 's1' in the server, with 's1' in the client_defs + except RuntimeError, e: + print(str(e)) + + +2. replace(self: ecflow.Client, arg0: str, arg1: ecflow.Defs, arg2: bool, arg3: bool) -> int + +3. replace(self: ecflow.Client, arg0: str, arg1: ecflow.Defs) -> None + +4. replace(self: ecflow.Client, arg0: str, arg1: str) -> None + + +.. py:method:: Client.requeue(*args, **kwargs) :module: ecflow +Overloaded function. + +1. requeue(self: ecflow.Client, abs_node_path: str, option: str = '') -> None + Re queues the specified :term:`node` (s) - :: - - void requeue( - list paths : A list of paths. Node paths must begin with a leading '/' character - [(str)option=''] : option = ('' | 'abort' | 'force') - '' : empty string, the default, re-queue the node - abort: means re-queue only aborted tasks below node - force: means re-queueing even if there are nodes that are active or submitted - ) - void requeue( - string absolute_node_path : Path name to node - [(string)option=''] : option = ('' | 'abort' | 'force') - ) - - Usage: - - .. code-block:: python - - try: - ci = Client() # use default host(ECF_HOST) & port(ECF_PORT) - ci.requeue('/s1','abort') # re-queue aborted tasks below suite /s1 - - path_list = ['/s1/f1/t1','/s2/f1/t2'] - ci.requeue(path_list) - except RuntimeError, e: - print(str(e)) - - -requeue( (Client)arg1, (list)paths [, (str)option='']) -> None - - -.. py:method:: Client.reset( (Client)arg1) -> None : +:: + + void requeue( + list paths : A list of paths. Node paths must begin with a leading '/' character + [(str)option=''] : option = ('' | 'abort' | 'force') + '' : empty string, the default, re-queue the node + abort: means re-queue only aborted tasks below node + force: means re-queueing even if there are nodes that are active or submitted + ) + void requeue( + string absolute_node_path : Path name to node + [(string)option=''] : option = ('' | 'abort' | 'force') + ) + +Usage: + +.. code-block:: python + + try: + ci = Client() # use default host(ECF_HOST) & port(ECF_PORT) + ci.requeue('/s1','abort') # re-queue aborted tasks below suite /s1 + + path_list = ['/s1/f1/t1','/s2/f1/t2'] + ci.requeue(path_list) + except RuntimeError, e: + print(str(e)) + + +2. requeue(self: ecflow.Client, paths: list, option: str = '') -> None + + +.. py:method:: Client.reset(self: ecflow.Client) -> None :module: ecflow reset client definition, and handle number -.. py:method:: Client.restart_server( (Client)arg1) -> int : +.. py:method:: Client.restart_server(self: ecflow.Client) -> int :module: ecflow Restart the :term:`ecflow_server` @@ -1955,34 +2028,38 @@ Usage: print(str(e)) -.. py:method:: Client.restore( (Client)arg1, (str)arg2) -> None : +.. py:method:: Client.restore(*args, **kwargs) :module: ecflow +Overloaded function. + +1. restore(self: ecflow.Client, arg0: str) -> None + Restore archived nodes. - Usage:: - - string restore( - list paths # List of paths. - ) - string restore( - string absolute_node_path - ) - - Usage: - - .. code-block:: python - - try: - ci = Client() # use default host(ECF_HOST) & port(ECF_PORT) - print ci.restore('/suite1') - except RuntimeError, e: - print str(e) - - -restore( (Client)arg1, (list)arg2) -> None - - -.. py:method:: Client.restore_from_checkpt( (Client)arg1) -> int : +Usage:: + + string restore( + list paths # List of paths. + ) + string restore( + string absolute_node_path + ) + +Usage: + +.. code-block:: python + + try: + ci = Client() # use default host(ECF_HOST) & port(ECF_PORT) + print ci.restore('/suite1') + except RuntimeError, e: + print str(e) + + +2. restore(self: ecflow.Client, arg0: list) -> None + + +.. py:method:: Client.restore_from_checkpt(self: ecflow.Client) -> int :module: ecflow Request the :term:`ecflow_server` loads the :term:`check point` file from disk @@ -2003,121 +2080,134 @@ Usage: print(str(e)) -.. py:method:: Client.resume( (Client)arg1, (str)arg2) -> None : +.. py:method:: Client.resume(*args, **kwargs) :module: ecflow +Overloaded function. + +1. resume(self: ecflow.Client, arg0: str) -> None + Resume `job creation` / generation for the given :term:`node` - :: - - void resume( - list paths: List of paths. Paths must begin with a leading '/' character - ) - void resume( - string absolute_node_path: Path name to node to resume. - ) - - Usage: - - .. code-block:: python - - try: - ci = Client() # use default host(ECF_HOST) & port(ECF_PORT) - ci.resume('/s1/f1/task1') - paths = ['/s1/f1/t1','/s2/f1/t2'] - ci.resume(paths) - except RuntimeError, e: - print(str(e)) - - -resume( (Client)arg1, (list)arg2) -> None - - -.. py:method:: Client.run( (Client)arg1, (str)arg2, (bool)arg3) -> None : +:: + + void resume( + list paths: List of paths. Paths must begin with a leading '/' character + ) + void resume( + string absolute_node_path: Path name to node to resume. + ) + +Usage: + +.. code-block:: python + + try: + ci = Client() # use default host(ECF_HOST) & port(ECF_PORT) + ci.resume('/s1/f1/task1') + paths = ['/s1/f1/t1','/s2/f1/t2'] + ci.resume(paths) + except RuntimeError, e: + print(str(e)) + + +2. resume(self: ecflow.Client, arg0: list) -> None + + +.. py:method:: Client.run(*args, **kwargs) :module: ecflow +Overloaded function. + +1. run(self: ecflow.Client, arg0: str, arg1: bool) -> None + Immediately run the jobs associated with the input :term:`node`. - - Ignore :term:`trigger`\ s, :term:`limit`\ s, :term:`suspended`, :term:`time` or :term:`date` dependencies, - just run the :term:`task`. - When a job completes, it may be automatically re-queued if it has - multiple time :term:`dependencies`. In the specific case where a :term:`task` has a SINGLE - time dependency and we want to avoid re running the :term:`task` then - a flag is set so that it is not automatically re-queued when set to :term:`complete`. - The flag is applied up the :term:`node` hierarchy until we reach a node with a :term:`repeat` - or :term:`cron` attribute. This behaviour allow :term:`repeat` values to be incremented interactively. - A :term:`repeat` attribute is incremented when all the child nodes are :term:`complete` - in this case the child nodes are automatically re-queued - :: - - void run( - string absolute_node_path : Path name to node. If the path is suite/family will recursively - run all child tasks - [(bool)force=False] : If true, run even if there are nodes that are active or submitted. - ) - void run( - list paths : List of paths. If the path is suite/family will recursively run all child tasks - [(bool)force=False] : If true, run even if there are nodes that are active or submitted. - ) - - Usage: - - .. code-block:: python - - try: - ci = Client() # use default host(ECF_HOST) & port(ECF_PORT) - ci.run('/s1') # run all tasks under suite /s1 - - ci.run(['/s1/f1/t1','/s2/f1/t2']) # run all tasks specified - except RuntimeError, e: - print(str(e)) - - Effect: - - Lets see the effect of run command on the following defs: - - .. code-block:: shell - - suite s1 - task t1; time 10:00 # will complete straight away - task t2; time 10:00 13:00 01:00 # will re-queue 3 times and complete on fourth run - - In the last case (task t2) after each run the next time slot is incremented. - This can be seen by calling the Why command. - -run( (Client)arg1, (list)arg2, (bool)arg3) -> None - - -.. py:method:: Client.server_version( (Client)arg1) -> str : + +Ignore :term:`trigger`\ s, :term:`limit`\ s, :term:`suspended`, :term:`time` or :term:`date` dependencies, +just run the :term:`task`. +When a job completes, it may be automatically re-queued if it has +multiple time :term:`dependencies`. In the specific case where a :term:`task` has a SINGLE +time dependency and we want to avoid re running the :term:`task` then +a flag is set so that it is not automatically re-queued when set to :term:`complete`. +The flag is applied up the :term:`node` hierarchy until we reach a node with a :term:`repeat` +or :term:`cron` attribute. This behaviour allow :term:`repeat` values to be incremented interactively. +A :term:`repeat` attribute is incremented when all the child nodes are :term:`complete` +in this case the child nodes are automatically re-queued +:: + + void run( + string absolute_node_path : Path name to node. If the path is suite/family will recursively + run all child tasks + [(bool)force=False] : If true, run even if there are nodes that are active or submitted. + ) + void run( + list paths : List of paths. If the path is suite/family will recursively run all child tasks + [(bool)force=False] : If true, run even if there are nodes that are active or submitted. + ) + +Usage: + +.. code-block:: python + + try: + ci = Client() # use default host(ECF_HOST) & port(ECF_PORT) + ci.run('/s1') # run all tasks under suite /s1 + + ci.run(['/s1/f1/t1','/s2/f1/t2']) # run all tasks specified + except RuntimeError, e: + print(str(e)) + +Effect: + + Lets see the effect of run command on the following defs: + +.. code-block:: shell + + suite s1 + task t1; time 10:00 # will complete straight away + task t2; time 10:00 13:00 01:00 # will re-queue 3 times and complete on fourth run + +In the last case (task t2) after each run the next time slot is incremented. +This can be seen by calling the Why command. + +2. run(self: ecflow.Client, arg0: list, arg1: bool) -> None + + +.. py:method:: Client.server_version(self: ecflow.Client) -> str :module: ecflow Returns the server version, can throw for old servers, that did not implement this request. -.. py:method:: Client.set_auto_sync( (Client)arg1, (bool)arg2) -> None : +.. py:method:: Client.set_auto_sync(self: ecflow.Client, arg0: bool) -> None :module: ecflow If true automatically sync with local definition after each call. -.. py:method:: Client.set_child_complete_del_vars( (Client)arg1, (list)arg2) -> None : +.. py:method:: Client.set_child_complete_del_vars(self: ecflow.Client, arg0: list) -> None :module: ecflow Set the list of variables to be deleted when a task becomes complete Needs a list of strings, representing the variable names. -.. py:method:: Client.set_child_init_add_vars( (Client)arg1, (dict)arg2) -> None : +.. py:method:: Client.set_child_init_add_vars(*args, **kwargs) :module: ecflow +Overloaded function. + +1. set_child_init_add_vars(self: ecflow.Client, arg0: dict) -> None + Set the list of variables to be added when a task becomes active - Needs a dictionary of name/value pairs, or a list of ecflow Variables +Needs a dictionary of name/value pairs, or a list of ecflow Variables -set_child_init_add_vars( (Client)arg1, (list)arg2) -> None : - Set the list of variables to be added when a task becomes active - Needs a dictionary of name/value pairs, or a list of ecflow Variables +2. set_child_init_add_vars(self: ecflow.Client, arg0: list) -> None + +Set the list of variables to be added when a task becomes active +Needs a dictionary of name/value pairs, or a list of ecflow Variables -.. py:method:: Client.set_child_password( (Client)arg1, (str)arg2) -> None : +.. py:method:: Client.set_child_password(self: ecflow.Client, arg0: str) -> None :module: ecflow Set the password, needed for authentication, provided by the server using %ECF_PASS% @@ -2126,7 +2216,7 @@ By default the environment variable ECF_PASS is read for the jobs password This can be overridden for the python child api -.. py:method:: Client.set_child_path( (Client)arg1, (str)arg2) -> None : +.. py:method:: Client.set_child_path(self: ecflow.Client, arg0: str) -> None :module: ecflow Set the path to the task, obtained from server using %ECF_NAME% @@ -2135,22 +2225,27 @@ By default the environment variable ECF_NAME is read for the task path This can be overridden for the python child api -.. py:method:: Client.set_child_pid( (Client)arg1, (str)arg2) -> None : +.. py:method:: Client.set_child_pid(*args, **kwargs) :module: ecflow +Overloaded function. + +1. set_child_pid(self: ecflow.Client, arg0: str) -> None + +Set the process id of this job + +By default the environment variable ECF_RID is read for the jobs process or remote id +This can be overridden for the python child api + +2. set_child_pid(self: ecflow.Client, arg0: typing.SupportsInt | typing.SupportsIndex) -> None + Set the process id of this job - - By default the environment variable ECF_RID is read for the jobs process or remote id - This can be overridden for the python child api -set_child_pid( (Client)arg1, (int)arg2) -> None : - Set the process id of this job - - By default the environment variable ECF_RID is read for the jobs process or remote id - This can be overridden for the python child api +By default the environment variable ECF_RID is read for the jobs process or remote id +This can be overridden for the python child api -.. py:method:: Client.set_child_timeout( (Client)arg1, (int)arg2) -> None : +.. py:method:: Client.set_child_timeout(self: ecflow.Client, arg0: typing.SupportsInt | typing.SupportsIndex) -> None :module: ecflow Set timeout if child cannot connect to server, default is 24 hours. The input is required to be in seconds @@ -2159,7 +2254,7 @@ By default the environment variable ECF_TIMEOUT is read to control how long chi This can be overridden for the python child api -.. py:method:: Client.set_child_try_no( (Client)arg1, (int)arg2) -> None : +.. py:method:: Client.set_child_try_no(self: ecflow.Client, arg0: typing.SupportsInt | typing.SupportsIndex) -> None :module: ecflow Set the try no, i.e the number of times this job has run, obtained from the server, using %ECF_TRYNO% @@ -2168,7 +2263,7 @@ By default the environment variable ECF_TRYNO is read to record number of times This can be overridden for the python child api -.. py:method:: Client.set_connection_attempts( (Client)arg1, (int)arg2) -> None : +.. py:method:: Client.set_connection_attempts(self: ecflow.Client, arg0: typing.SupportsInt | typing.SupportsIndex) -> None :module: ecflow Set the number of times to connect to :term:`ecflow_server`, in case of connection failures @@ -2197,51 +2292,55 @@ Usage: ci.set_retry_connection_period(1) # wait 1 second between each attempt -.. py:method:: Client.set_host_port( (Client)arg1, (str)arg2, (str)arg3) -> None : +.. py:method:: Client.set_host_port(*args, **kwargs) :module: ecflow +Overloaded function. + +1. set_host_port(self: ecflow.Client, arg0: str, arg1: str) -> None + Explicitly set the host and port to be used by the client, overriding the default host name (localhost) and port (3141) and the environment variables: ECF_HOST and ECF_PORT. - - .. code-block:: shell - - set_host_port( - string host, # The server name. Cannot be empty. - string port # The port on the server, must be unique to the server - ) - - set_host_port( - string host, # The server name. Cannot be empty. - int port # The port on the server, must be unique to the server - ) - - set_host_port( - string host_port, # Expects : or @ - ) - - Exceptions: - - - Raise a RuntimeError if the host or port is empty - - Usage: - - .. code-block:: python - - try: - ci = Client() - ci.set_host_port('localhost','3150') - ci.set_host_port('avi',3150) - ci.set_host_port('avi:3150') - except RuntimeError, e: - print(str(e)) - - - -set_host_port( (Client)arg1, (str)arg2) -> None - -set_host_port( (Client)arg1, (str)arg2, (int)arg3) -> None - - -.. py:method:: Client.set_retry_connection_period( (Client)arg1, (int)arg2) -> None : + +.. code-block:: shell + + set_host_port( + string host, # The server name. Cannot be empty. + string port # The port on the server, must be unique to the server + ) + + set_host_port( + string host, # The server name. Cannot be empty. + int port # The port on the server, must be unique to the server + ) + + set_host_port( + string host_port, # Expects : or @ + ) + +Exceptions: + +- Raise a RuntimeError if the host or port is empty + +Usage: + +.. code-block:: python + + try: + ci = Client() + ci.set_host_port('localhost','3150') + ci.set_host_port('avi',3150) + ci.set_host_port('avi:3150') + except RuntimeError, e: + print(str(e)) + + + +2. set_host_port(self: ecflow.Client, arg0: str) -> None + +3. set_host_port(self: ecflow.Client, arg0: str, arg1: typing.SupportsInt | typing.SupportsIndex) -> None + + +.. py:method:: Client.set_retry_connection_period(self: ecflow.Client, arg0: typing.SupportsInt | typing.SupportsIndex) -> None :module: ecflow Set the sleep period between connection attempts @@ -2272,19 +2371,19 @@ Usage: ci.set_retry_connection_period(1) # wait 1 second between each attempt -.. py:method:: Client.set_user_name( (Client)arg1, (str)arg2) -> None : +.. py:method:: Client.set_user_name(self: ecflow.Client, arg0: str) -> None :module: ecflow -set user name. A password must be provided in the file ..ecf.custom_passwd +Set user name. A password must be provided in the file ..ecf.custom_passwd -.. py:method:: Client.set_zombie_child_timeout( (Client)arg1, (int)arg2) -> None : +.. py:method:: Client.set_zombie_child_timeout(self: ecflow.Client, arg0: typing.SupportsInt | typing.SupportsIndex) -> None :module: ecflow Set timeout for zombie child commands,that cannot connect to server, default is 24 hours. The input is required to be in seconds -.. py:method:: Client.shutdown_server( (Client)arg1) -> int : +.. py:method:: Client.shutdown_server(self: ecflow.Client) -> int :module: ecflow Shut down the :term:`ecflow_server` @@ -2303,13 +2402,17 @@ Usage: print(str(e)) -.. py:method:: Client.sort_attributes( (Client)arg1, (str)abs_node_path, (str)attribute_name [, (bool)recursive=True]) -> None +.. py:method:: Client.sort_attributes(*args, **kwargs) :module: ecflow -sort_attributes( (Client)arg1, (list)paths, (str)attribute_name [, (bool)recursive=True]) -> None +Overloaded function. +1. sort_attributes(self: ecflow.Client, abs_node_path: str, attribute_name: str, recursive: bool = True) -> None -.. py:method:: Client.stats( (Client)arg1 [, (bool)to_stdout]) -> str : +2. sort_attributes(self: ecflow.Client, paths: list, attribute_name: str, recursive: bool = True) -> None + + +.. py:method:: Client.stats(self: ecflow.Client, to_stdout: bool = True) -> str :module: ecflow Returns the :term:`ecflow_server` statistics as a string @@ -2333,7 +2436,7 @@ Usage: print(str(e)) -.. py:method:: Client.stats_reset( (Client)arg1) -> None : +.. py:method:: Client.stats_reset(self: ecflow.Client) -> None :module: ecflow Resets the statistical data in the server @@ -2352,50 +2455,54 @@ Usage: print(str(e)) -.. py:method:: Client.status( (Client)arg1, (str)arg2) -> None : +.. py:method:: Client.status(*args, **kwargs) :module: ecflow +Overloaded function. + +1. status(self: ecflow.Client, arg0: str) -> None + Shows the status of a job associated with a :term:`task` - :: - - void status( - list paths: List of paths. Paths must begin with a leading '/' character - ) - void status( - string absolute_node_path - ) - - If a :term:`family` or :term:`suite` is selected, will invoke status command hierarchically. - Status uses the ECF_STATUS_CMD variable. After :term:`variable substitution` it is invoked as a command. - The command should be written in such a way that the output is written to %ECF_JOB%.stat, i.e: - - .. code-block:: shell - - /home/ma/emos/bin/ecfstatus %USER% %HOST% %ECF_RID% %ECF_JOB% > %ECF_JOB%.stat 2>&1 - - Exceptions can be raised because: - - - The absolute_node_path does not exist in the server - - ECF_STATUS_CMD variable is not defined - - :term:`variable substitution` fails - - Usage: - - .. code-block:: python - - try: - ci = Client() # use default host(ECF_HOST) & port(ECF_PORT) - ci.status('/s1/t1') - time.sleep(2) - print(ci.file('/s1/t1','stats')) # request status output - except RuntimeError, e: - print(str(e)) - - -status( (Client)arg1, (list)arg2) -> None - - -.. py:method:: Client.suites( (Client)arg1) -> list : +:: + + void status( + list paths: List of paths. Paths must begin with a leading '/' character + ) + void status( + string absolute_node_path + ) + +If a :term:`family` or :term:`suite` is selected, will invoke status command hierarchically. +Status uses the ECF_STATUS_CMD variable. After :term:`variable substitution` it is invoked as a command. +The command should be written in such a way that the output is written to %ECF_JOB%.stat, i.e: + +.. code-block:: shell + + /home/ma/emos/bin/ecfstatus %USER% %HOST% %ECF_RID% %ECF_JOB% > %ECF_JOB%.stat 2>&1 + +Exceptions can be raised because: + +- The absolute_node_path does not exist in the server +- ECF_STATUS_CMD variable is not defined +- :term:`variable substitution` fails + +Usage: + +.. code-block:: python + + try: + ci = Client() # use default host(ECF_HOST) & port(ECF_PORT) + ci.status('/s1/t1') + time.sleep(2) + print(ci.file('/s1/t1','stats')) # request status output + except RuntimeError, e: + print(str(e)) + + +2. status(self: ecflow.Client, arg0: list) -> None + + +.. py:method:: Client.suites(self: ecflow.Client) -> list :module: ecflow Returns a list strings representing the :term:`suite` names @@ -2415,36 +2522,40 @@ Usage: print(str(e)) -.. py:method:: Client.suspend( (Client)arg1, (str)arg2) -> None : +.. py:method:: Client.suspend(*args, **kwargs) :module: ecflow +Overloaded function. + +1. suspend(self: ecflow.Client, arg0: str) -> None + Suspend `job creation` / generation for the given :term:`node` - :: - - void suspend( - list paths: List of paths. Paths must begin with a leading '/' character - ) - void suspend( - string absolute_node_path: Path name to node to suspend. - ) - - Usage: - - .. code-block:: python - - try: - ci = Client() # use default host(ECF_HOST) & port(ECF_PORT) - ci.suspend('/s1/f1/task1') - paths = ['/s1/f1/t1','/s2/f1/t2'] - ci.suspend(paths) - except RuntimeError, e: - print(str(e)) - - -suspend( (Client)arg1, (list)arg2) -> None - - -.. py:method:: Client.sync_local( (Client)arg1 [, (bool)sync_suite_clock=False]) -> int : +:: + + void suspend( + list paths: List of paths. Paths must begin with a leading '/' character + ) + void suspend( + string absolute_node_path: Path name to node to suspend. + ) + +Usage: + +.. code-block:: python + + try: + ci = Client() # use default host(ECF_HOST) & port(ECF_PORT) + ci.suspend('/s1/f1/task1') + paths = ['/s1/f1/t1','/s2/f1/t2'] + ci.suspend(paths) + except RuntimeError, e: + print(str(e)) + + +2. suspend(self: ecflow.Client, arg0: list) -> None + + +.. py:method:: Client.sync_local(self: ecflow.Client, sync_suite_clock: bool = False) -> int :module: ecflow Requests that :term:`ecflow_server` returns the full definition or incremental change made and applies them to the client Defs @@ -2484,7 +2595,7 @@ Usage: Calling sync_local() is considerably faster than calling get_server_defs() for large Definitions -.. py:method:: Client.terminate_server( (Client)arg1) -> int : +.. py:method:: Client.terminate_server(self: ecflow.Client) -> int :module: ecflow Terminate the :term:`ecflow_server` @@ -2501,13 +2612,13 @@ Usage: print(str(e)) -.. py:method:: Client.version( (Client)arg1) -> str : +.. py:method:: Client.version(self: ecflow.Client) -> str :module: ecflow Returns the current client version -.. py:method:: Client.wait_for_server_reply( (Client)arg1 [, (int)time_out=60]) -> bool : +.. py:method:: Client.wait_for_server_reply(self: ecflow.Client, time_out: typing.SupportsInt | typing.SupportsIndex = 60) -> bool :module: ecflow Wait for a response from the :term:`ecflow_server`:: @@ -2530,42 +2641,66 @@ Usage: print('Timed out after 30 second wait for server response.?') -.. py:method:: Client.zombie_adopt( (Client)arg1, (str)arg2) -> int +.. py:method:: Client.zombie_adopt(*args, **kwargs) :module: ecflow -zombie_adopt( (Client)arg1, (list)arg2) -> None +Overloaded function. +1. zombie_adopt(self: ecflow.Client, arg0: str) -> int -.. py:method:: Client.zombie_block( (Client)arg1, (str)arg2) -> int +2. zombie_adopt(self: ecflow.Client, arg0: list) -> None + + +.. py:method:: Client.zombie_block(*args, **kwargs) :module: ecflow -zombie_block( (Client)arg1, (list)arg2) -> None +Overloaded function. + +1. zombie_block(self: ecflow.Client, arg0: str) -> int + +2. zombie_block(self: ecflow.Client, arg0: list) -> None -.. py:method:: Client.zombie_fail( (Client)arg1, (str)arg2) -> int +.. py:method:: Client.zombie_fail(*args, **kwargs) :module: ecflow -zombie_fail( (Client)arg1, (list)arg2) -> None +Overloaded function. +1. zombie_fail(self: ecflow.Client, arg0: str) -> int -.. py:method:: Client.zombie_fob( (Client)arg1, (str)arg2) -> int +2. zombie_fail(self: ecflow.Client, arg0: list) -> None + + +.. py:method:: Client.zombie_fob(*args, **kwargs) :module: ecflow -zombie_fob( (Client)arg1, (list)arg2) -> None +Overloaded function. + +1. zombie_fob(self: ecflow.Client, arg0: str) -> int + +2. zombie_fob(self: ecflow.Client, arg0: list) -> None -.. py:method:: Client.zombie_get( (Client)arg1, (int)arg2) -> ZombieVec +.. py:method:: Client.zombie_get(self: ecflow.Client, arg0: typing.SupportsInt | typing.SupportsIndex) -> list[ecflow.Zombie] :module: ecflow -.. py:method:: Client.zombie_kill( (Client)arg1, (str)arg2) -> int +.. py:method:: Client.zombie_kill(*args, **kwargs) :module: ecflow -zombie_kill( (Client)arg1, (list)arg2) -> None +Overloaded function. +1. zombie_kill(self: ecflow.Client, arg0: str) -> int -.. py:method:: Client.zombie_remove( (Client)arg1, (str)arg2) -> int +2. zombie_kill(self: ecflow.Client, arg0: list) -> None + + +.. py:method:: Client.zombie_remove(*args, **kwargs) :module: ecflow -zombie_remove( (Client)arg1, (list)arg2) -> None +Overloaded function. + +1. zombie_remove(self: ecflow.Client, arg0: str) -> int + +2. zombie_remove(self: ecflow.Client, arg0: list) -> None diff --git a/docs/python_api/Clock.rst b/docs/python_api/Clock.rst index c2b459232..86c7fee9b 100644 --- a/docs/python_api/Clock.rst +++ b/docs/python_api/Clock.rst @@ -5,7 +5,7 @@ ecflow.Clock .. py:class:: Clock :module: ecflow - Bases: :py:class:`~Boost.Python.instance` + Bases: :py:class:`~pybind11_builtins.pybind11_object` Specifies the :term:`clock` type used by the :term:`suite`. @@ -43,43 +43,43 @@ Usage: suite.add_clock(clock) -.. py:method:: Clock.day( (Clock)arg1) -> int : +.. py:method:: Clock.day(self: ecflow.Clock) -> int :module: ecflow Returns the day as an integer, range 1-31 -.. py:method:: Clock.gain( (Clock)arg1) -> int : +.. py:method:: Clock.gain(self: ecflow.Clock) -> int :module: ecflow Returns the gain as an long. This represents seconds -.. py:method:: Clock.month( (Clock)arg1) -> int : +.. py:method:: Clock.month(self: ecflow.Clock) -> int :module: ecflow Returns the month as an integer, range 1-12 -.. py:method:: Clock.positive_gain( (Clock)arg1) -> bool : +.. py:method:: Clock.positive_gain(self: ecflow.Clock) -> bool :module: ecflow Returns a boolean, where true means that the gain is positive -.. py:method:: Clock.set_gain( (Clock)arg1, (int)arg2, (int)arg3, (bool)arg4) -> None : +.. py:method:: Clock.set_gain(self: ecflow.Clock, arg0: typing.SupportsInt | typing.SupportsIndex, arg1: typing.SupportsInt | typing.SupportsIndex, arg2: bool) -> None :module: ecflow Set the gain in hours and minutes -.. py:method:: Clock.set_gain_in_seconds( (Clock)arg1, (int)arg2, (bool)arg3) -> None : +.. py:method:: Clock.set_gain_in_seconds(self: ecflow.Clock, arg0: typing.SupportsInt | typing.SupportsIndex, arg1: bool) -> None :module: ecflow Set the gain in seconds -.. py:method:: Clock.year( (Clock)arg1) -> int : +.. py:method:: Clock.year(self: ecflow.Clock) -> int :module: ecflow Returns the year as an integer, > 1400 diff --git a/docs/python_api/Complete.rst b/docs/python_api/Complete.rst index 10f17a7ed..3854b33c0 100644 --- a/docs/python_api/Complete.rst +++ b/docs/python_api/Complete.rst @@ -5,7 +5,7 @@ ecflow.Complete .. py:class:: Complete :module: ecflow - Bases: :py:class:`~Boost.Python.instance` + Bases: :py:class:`~pybind11_builtins.pybind11_object` Add a :term:`trigger` or :term:`complete expression`. @@ -64,7 +64,7 @@ If however node taskx has not yet been added to its parent, we use a relative na t2 ==complete and taskx == complete -.. py:method:: Complete.get_expression( (Complete)arg1) -> str : +.. py:method:: Complete.get_expression(self: ecflow.Complete) -> str :module: ecflow returns the complete expression as a string diff --git a/docs/python_api/Cron.rst b/docs/python_api/Cron.rst index fb3425095..ed26915c2 100644 --- a/docs/python_api/Cron.rst +++ b/docs/python_api/Cron.rst @@ -5,7 +5,7 @@ ecflow.Cron .. py:class:: Cron :module: ecflow - Bases: :py:class:`~Boost.Python.instance` + Bases: :py:class:`~pybind11_builtins.pybind11_object` A :term:`cron` defines a repeating time dependency for a node. @@ -77,7 +77,7 @@ Usage (see the :ref:`cron definition` for more examples): returns a integer list of days of the month -.. py:method:: Cron.last_day_of_the_month( (Cron)arg1) -> bool : +.. py:method:: Cron.last_day_of_the_month(self: ecflow.Cron) -> bool :module: ecflow Return true if last day of month is enabled @@ -95,52 +95,59 @@ returns a integer list of last week days of the month returns a integer list of months of the year -.. py:method:: Cron.set_days_of_month( (Cron)arg1, (list)arg2) -> None : +.. py:method:: Cron.set_days_of_month(self: ecflow.Cron, arg0: list) -> None :module: ecflow Specifies days of the month. Expects a list of integers with integer range 1-31 -.. py:method:: Cron.set_last_day_of_the_month( (Cron)arg1) -> None : +.. py:method:: Cron.set_last_day_of_the_month(self: ecflow.Cron) -> None :module: ecflow Set cron for the last day of the month -.. py:method:: Cron.set_last_week_days_of_the_month( (Cron)arg1, (list)arg2) -> None : +.. py:method:: Cron.set_last_week_days_of_the_month(self: ecflow.Cron, arg0: list) -> None :module: ecflow Specifies last week days of the month. Expects a list of integers, with integer range 0==Sun to 6==Sat -.. py:method:: Cron.set_months( (Cron)arg1, (list)arg2) -> None : +.. py:method:: Cron.set_months(self: ecflow.Cron, arg0: list) -> None :module: ecflow Specifies months. Expects a list of integers, with integer range 1-12 -.. py:method:: Cron.set_time_series( (Cron)arg1, (int)hour, (int)minute [, (bool)relative=False]) -> None : +.. py:method:: Cron.set_time_series(*args, **kwargs) :module: ecflow +Overloaded function. + +1. set_time_series(self: ecflow.Cron, hour: typing.SupportsInt | typing.SupportsIndex, minute: typing.SupportsInt | typing.SupportsIndex, relative: bool = False) -> None + time_series(hour(int),minute(int),relative to suite start(bool=false)), Add a time slot -set_time_series( (Cron)arg1, (TimeSeries)arg2) -> None : - Add a time series. This will never complete +2. set_time_series(self: ecflow.Cron, arg0: ecflow.TimeSeries) -> None + +Add a time series. This will never complete + +3. set_time_series(self: ecflow.Cron, arg0: ecflow.TimeSlot, arg1: ecflow.TimeSlot, arg2: ecflow.TimeSlot) -> None + +Add a time series. This will never complete -set_time_series( (Cron)arg1, (TimeSlot)arg2, (TimeSlot)arg3, (TimeSlot)arg4) -> None : - Add a time series. This will never complete +4. set_time_series(self: ecflow.Cron, arg0: str) -> None -set_time_series( (Cron)arg1, (str)arg2) -> None : - Add a time series. This will never complete +Add a time series. This will never complete -.. py:method:: Cron.set_week_days( (Cron)arg1, (list)arg2) -> None : +.. py:method:: Cron.set_week_days(self: ecflow.Cron, arg0: list) -> None :module: ecflow Specifies days of week. Expects a list of integers, with integer range 0==Sun to 6==Sat -.. py:method:: Cron.time( (Cron)arg1) -> TimeSeries : +.. py:method:: Cron.time(self: ecflow.Cron) -> ecflow.TimeSeries :module: ecflow return cron time as a TimeSeries diff --git a/docs/python_api/DState.rst b/docs/python_api/DState.rst index daf585948..e115a074c 100644 --- a/docs/python_api/DState.rst +++ b/docs/python_api/DState.rst @@ -5,7 +5,7 @@ ecflow.DState .. py:class:: DState :module: ecflow - Bases: :py:class:`~Boost.Python.enum` + Bases: :py:class:`~pybind11_builtins.pybind11_object` A DState is like a ecflow.State, except for the addition of SUSPENDED @@ -24,47 +24,62 @@ Usage:: Defstatus('complete')) # create in place +Members: + + unknown + + complete + + queued + + aborted + + submitted + + suspended + + active + + .. py:attribute:: DState.aborted :module: ecflow - :value: ecflow.DState.aborted + :value: .. py:attribute:: DState.active :module: ecflow - :value: ecflow.DState.active + :value: .. py:attribute:: DState.complete :module: ecflow - :value: ecflow.DState.complete + :value: -.. py:attribute:: DState.names +.. py:property:: DState.name :module: ecflow - :value: {'aborted': ecflow.DState.aborted, 'active': ecflow.DState.active, 'complete': ecflow.DState.complete, 'queued': ecflow.DState.queued, 'submitted': ecflow.DState.submitted, 'suspended': ecflow.DState.suspended, 'unknown': ecflow.DState.unknown} .. py:attribute:: DState.queued :module: ecflow - :value: ecflow.DState.queued + :value: .. py:attribute:: DState.submitted :module: ecflow - :value: ecflow.DState.submitted + :value: .. py:attribute:: DState.suspended :module: ecflow - :value: ecflow.DState.suspended + :value: .. py:attribute:: DState.unknown :module: ecflow - :value: ecflow.DState.unknown + :value: -.. py:attribute:: DState.values +.. py:property:: DState.value :module: ecflow - :value: {0: ecflow.DState.unknown, 1: ecflow.DState.complete, 2: ecflow.DState.queued, 3: ecflow.DState.aborted, 4: ecflow.DState.submitted, 5: ecflow.DState.active, 6: ecflow.DState.suspended} diff --git a/docs/python_api/Date.rst b/docs/python_api/Date.rst index 79fb9040c..5a057aa2b 100644 --- a/docs/python_api/Date.rst +++ b/docs/python_api/Date.rst @@ -5,7 +5,7 @@ ecflow.Date .. py:class:: Date :module: ecflow - Bases: :py:class:`~Boost.Python.instance` + Bases: :py:class:`~pybind11_builtins.pybind11_object` Used to define a :term:`date` dependency. @@ -38,19 +38,19 @@ Usage: Date('1.*.*')); # Create Date in place. -.. py:method:: Date.day( (Date)arg1) -> int : +.. py:method:: Date.day(self: ecflow.Date) -> int :module: ecflow Return the day. The range is 0-31, 0 means its wild-carded -.. py:method:: Date.month( (Date)arg1) -> int : +.. py:method:: Date.month(self: ecflow.Date) -> int :module: ecflow Return the month. The range is 0-12, 0 means its wild-carded -.. py:method:: Date.year( (Date)arg1) -> int : +.. py:method:: Date.year(self: ecflow.Date) -> int :module: ecflow Return the year, 0 means its wild-carded diff --git a/docs/python_api/Day.rst b/docs/python_api/Day.rst index a3961706e..b65baf26a 100644 --- a/docs/python_api/Day.rst +++ b/docs/python_api/Day.rst @@ -5,7 +5,7 @@ ecflow.Day .. py:class:: Day :module: ecflow - Bases: :py:class:`~Boost.Python.instance` + Bases: :py:class:`~pybind11_builtins.pybind11_object` Defines a :term:`day` dependency. @@ -30,7 +30,7 @@ Usage: Day('tuesday')) -.. py:method:: Day.day( (Day)arg1) -> Days : +.. py:method:: Day.day(self: ecflow.Day) -> ecflow.Days :module: ecflow Return the day as enumerator diff --git a/docs/python_api/Days.rst b/docs/python_api/Days.rst index 1b99db587..f9df84e2f 100644 --- a/docs/python_api/Days.rst +++ b/docs/python_api/Days.rst @@ -5,7 +5,7 @@ ecflow.Days .. py:class:: Days :module: ecflow - Bases: :py:class:`~Boost.Python.enum` + Bases: :py:class:`~pybind11_builtins.pybind11_object` This enum is used as argument to a :py:class:`ecflow.Day` class. @@ -23,6 +23,23 @@ Usage: Day(Days.tuesday)) +Members: + + sunday + + monday + + tuesday + + wednesday + + thursday + + friday + + saturday + + .. py:attribute:: Days.friday :module: ecflow :value: ecflow.Days.friday @@ -33,6 +50,10 @@ Usage: :value: ecflow.Days.monday +.. py:property:: Days.name + :module: ecflow + + .. py:attribute:: Days.names :module: ecflow :value: {'friday': ecflow.Days.friday, 'monday': ecflow.Days.monday, 'saturday': ecflow.Days.saturday, 'sunday': ecflow.Days.sunday, 'thursday': ecflow.Days.thursday, 'tuesday': ecflow.Days.tuesday, 'wednesday': ecflow.Days.wednesday} @@ -58,6 +79,10 @@ Usage: :value: ecflow.Days.tuesday +.. py:property:: Days.value + :module: ecflow + + .. py:attribute:: Days.values :module: ecflow :value: {0: ecflow.Days.sunday, 1: ecflow.Days.monday, 2: ecflow.Days.tuesday, 3: ecflow.Days.wednesday, 4: ecflow.Days.thursday, 5: ecflow.Days.friday, 6: ecflow.Days.saturday} diff --git a/docs/python_api/Defs.rst b/docs/python_api/Defs.rst index 750349722..3c55eb03b 100644 --- a/docs/python_api/Defs.rst +++ b/docs/python_api/Defs.rst @@ -5,7 +5,7 @@ ecflow.Defs .. py:class:: Defs :module: ecflow - Bases: :py:class:`~Boost.Python.instance` + Bases: :py:class:`~pybind11_builtins.pybind11_object` The Defs class holds the :term:`suite definition` structure. @@ -70,81 +70,80 @@ Create a Defs from an existing file on disk: print(defs) -.. py:method:: Defs.add +.. py:method:: Defs.add(self: ecflow.Defs, *args, **kwargs) -> object :module: ecflow -object add(tuple args, dict kwds) : - add(..) provides a way to append Nodes and attributes - - This is best illustrated with an example: - - .. code-block:: python - - defs = Defs().add( - Suite('s1').add( - Clock(1, 1, 2010, False), - Autocancel(1, 10, True), - Task('t1').add( - Edit({'a':'12', 'b':'bb'}, c='v',d='b'), - Edit(g='d'), - Edit(h=1), - Event(1), - Event(11,'event'), - Meter('meter',0,10,10), - Label('label','c'), - Trigger('1==1'), - Complete('1==1'), - Limit('limit',10),Limit('limit2',10), - InLimit('limitName','/limit',2), - Defstatus(DState.complete), - Today(0,30),Today('00:59'),Today('00:00 11:30 00:01'), - Time(0,30),Time('00:59'),Time('00:00 11:30 00:01'), - Day('sunday'),Day(Days.monday), - Date(1,1,0),Date(28,2,1960), - Autocancel(3) - ), - [ Family('f{}'.format(i)) for i in range(1,6)])) - - We can also use '+=' with a list here are a few examples: - - .. code-block:: python - - defs = Defs(); - defs += [ Suite('s2'),Edit({ 'x1':'y', 'aa1':'bb'}, a='v',b='b') ] - - .. code-block:: python - - defs += [ Suite('s{}'.format(i)) for i in range(1,6) ] - - .. code-block:: python - - defs = Defs() - defs += [ Suite('suite').add( - Task('x'), - Family('f').add( [ Task('t{}'.format(i)) for i in range(1,6)] ), - Task('y'), - [ Family('f{}'.format(i)) for i in range(1,6) ], - Edit(a='b'), - [ Task('t{}'.format(i)) for i in range(1,6) ], - )] - - It is also possible to use '+' - - .. code-block:: python - - defs = Defs() + Suite('s1') - defs.s1 += Autocancel(1, 10, True) - defs.s1 += Task('t1') + Edit({ 'e':1, 'f':'bb'}) +\ - Event(1) + Event(11,'event') + Meter('meter',0,10,10) + Label('label','c') + Trigger('1==1') +\ - Complete('1==1') + Limit('limit',10) + Limit('limit2',10) + InLimit('limitName','/limit',2) +\ - Defstatus(DState.complete) + Today(0,30) + Today('00:59') + Today('00:00 11:30 00:01') +\ - Time(0,30) + Time('00:59') + Time('00:00 11:30 00:01') + Day('sunday') + Day(Days.monday) +\ - Date(1,1,0) + Date(28,2,1960) + Autocancel(3) - - .. warning:: We can only use '+' when the left most object is a node, i.e Task('t1') in this case - - -.. py:method:: Defs.add_extern( (Defs)arg1, (str)arg2) -> None : +add(..) provides a way to append Nodes and attributes + +This is best illustrated with an example: + +.. code-block:: python + + defs = Defs().add( + Suite('s1').add( + Clock(1, 1, 2010, False), + Autocancel(1, 10, True), + Task('t1').add( + Edit({'a':'12', 'b':'bb'}, c='v',d='b'), + Edit(g='d'), + Edit(h=1), + Event(1), + Event(11,'event'), + Meter('meter',0,10,10), + Label('label','c'), + Trigger('1==1'), + Complete('1==1'), + Limit('limit',10),Limit('limit2',10), + InLimit('limitName','/limit',2), + Defstatus(DState.complete), + Today(0,30),Today('00:59'),Today('00:00 11:30 00:01'), + Time(0,30),Time('00:59'),Time('00:00 11:30 00:01'), + Day('sunday'),Day(Days.monday), + Date(1,1,0),Date(28,2,1960), + Autocancel(3) + ), + [ Family('f{}'.format(i)) for i in range(1,6)])) + +We can also use '+=' with a list here are a few examples: + +.. code-block:: python + + defs = Defs(); + defs += [ Suite('s2'),Edit({ 'x1':'y', 'aa1':'bb'}, a='v',b='b') ] + +.. code-block:: python + + defs += [ Suite('s{}'.format(i)) for i in range(1,6) ] + +.. code-block:: python + + defs = Defs() + defs += [ Suite('suite').add( + Task('x'), + Family('f').add( [ Task('t{}'.format(i)) for i in range(1,6)] ), + Task('y'), + [ Family('f{}'.format(i)) for i in range(1,6) ], + Edit(a='b'), + [ Task('t{}'.format(i)) for i in range(1,6) ], + )] + +It is also possible to use '+' + +.. code-block:: python + + defs = Defs() + Suite('s1') + defs.s1 += Autocancel(1, 10, True) + defs.s1 += Task('t1') + Edit({ 'e':1, 'f':'bb'}) +\ + Event(1) + Event(11,'event') + Meter('meter',0,10,10) + Label('label','c') + Trigger('1==1') +\ + Complete('1==1') + Limit('limit',10) + Limit('limit2',10) + InLimit('limitName','/limit',2) +\ + Defstatus(DState.complete) + Today(0,30) + Today('00:59') + Today('00:00 11:30 00:01') +\ + Time(0,30) + Time('00:59') + Time('00:00 11:30 00:01') + Day('sunday') + Day(Days.monday) +\ + Date(1,1,0) + Date(28,2,1960) + Autocancel(3) + +.. warning:: We can only use '+' when the left most object is a node, i.e Task('t1') in this case + + +.. py:method:: Defs.add_extern(self: ecflow.Defs, arg0: str) -> None :module: ecflow :term:`extern` refer to nodes that have not yet been defined typically due to cross suite :term:`dependencies` @@ -190,80 +189,87 @@ Usage: defs.auto_add_externs(True) # remove existing extern first. -.. py:method:: Defs.add_suite( (Defs)arg1, (Suite)arg2) -> Suite : +.. py:method:: Defs.add_suite(*args, **kwargs) :module: ecflow +Overloaded function. + +1. add_suite(self: ecflow.Defs, arg0: ecflow.Suite) -> ecflow.Suite + Add a :term:`suite` :term:`node`. See :py:class:`ecflow.Suite` - - If a new suite is added which matches the name of an existing suite, then an exception is thrown. - - Exception: - - - Throws RuntimeError is the suite name is not valid - - Throws RuntimeError if duplicate suite is added - - Usage: - - .. code-block:: python - - defs = Defs() # create a empty defs - suite = Suite('suite') # create a stand alone Suite - defs.add_suite(suite) # add suite to defs - s2 = defs.add_suite('s2') # create a suite and add to defs - - # Alternatively we can create Suite in place - defs = Defs( - Suite('s1', - Family('f1', - Task('t1'))), - Suite('s2', - Family('f1', - Task('t1')))) - - -add_suite( (Defs)arg1, (str)arg2) -> Suite : - Create a empty Defs - - - - -.. py:method:: Defs.add_variable( (Defs)arg1, (str)arg2, (str)arg3) -> Defs : + +If a new suite is added which matches the name of an existing suite, then an exception is thrown. + +Exception: + +- Throws RuntimeError is the suite name is not valid +- Throws RuntimeError if duplicate suite is added + +Usage: + +.. code-block:: python + + defs = Defs() # create a empty defs + suite = Suite('suite') # create a stand alone Suite + defs.add_suite(suite) # add suite to defs + s2 = defs.add_suite('s2') # create a suite and add to defs + + # Alternatively we can create Suite in place + defs = Defs( + Suite('s1', + Family('f1', + Task('t1'))), + Suite('s2', + Family('f1', + Task('t1')))) + + +2. add_suite(self: ecflow.Defs, arg0: str) -> ecflow.Suite + +Create a empty Defs + + +.. py:method:: Defs.add_variable(*args, **kwargs) :module: ecflow +Overloaded function. + +1. add_variable(self: ecflow.Defs, arg0: str, arg1: str) -> ecflow.Defs + Adds a name value :term:`variable`. Also see :py:class:`ecflow.Edit` - - This defines a variable for use in :term:`variable substitution` in a :term:`ecf script` file. - There can be any number of variables. The variables are names inside a pair of - '%' characters in an :term:`ecf script`. The name are case sensitive. - Special character in the value, must be placed inside single quotes if misinterpretation - is to be avoided. - The value of the variable replaces the variable name in the :term:`ecf script` at `job creation` time. - The variable names for any given node must be unique. If duplicates are added then the - the last value added is kept. - - Exception: - - - Writes warning to standard output, if a duplicate variable name is added - - Usage: - - .. code-block:: python - - task.add_variable( Variable('ECF_HOME','/tmp/')) - task.add_variable( 'TMPDIR','/tmp/') - task.add_variable( 'COUNT',2) - a_dict = { 'name':'value', 'name2':'value2', 'name3':'value3' } - task.add_variable(a_dict) - - -add_variable( (Defs)arg1, (str)arg2, (int)arg3) -> Defs - -add_variable( (Defs)arg1, (Variable)arg2) -> Defs - -add_variable( (Defs)arg1, (dict)arg2) -> Defs - - -.. py:method:: Defs.auto_add_externs( (Defs)arg1, (bool)arg2) -> None : + +This defines a variable for use in :term:`variable substitution` in a :term:`ecf script` file. +There can be any number of variables. The variables are names inside a pair of +'%' characters in an :term:`ecf script`. The name are case sensitive. +Special character in the value, must be placed inside single quotes if misinterpretation +is to be avoided. +The value of the variable replaces the variable name in the :term:`ecf script` at `job creation` time. +The variable names for any given node must be unique. If duplicates are added then the +the last value added is kept. + +Exception: + +- Writes warning to standard output, if a duplicate variable name is added + +Usage: + +.. code-block:: python + + task.add_variable( Variable('ECF_HOME','/tmp/')) + task.add_variable( 'TMPDIR','/tmp/') + task.add_variable( 'COUNT',2) + a_dict = { 'name':'value', 'name2':'value2', 'name3':'value3' } + task.add_variable(a_dict) + + +2. add_variable(self: ecflow.Defs, arg0: str, arg1: typing.SupportsInt | typing.SupportsIndex) -> ecflow.Defs + +3. add_variable(self: ecflow.Defs, arg0: ecflow.Variable) -> ecflow.Defs + +4. add_variable(self: ecflow.Defs, arg0: dict) -> ecflow.Defs + + +.. py:method:: Defs.auto_add_externs(self: ecflow.Defs, arg0: bool) -> None :module: ecflow :term:`extern` refer to nodes that have not yet been defined typically due to cross suite :term:`dependencies` @@ -309,7 +315,7 @@ Usage: defs.auto_add_externs(True) # remove existing extern first. -.. py:method:: Defs.check( (Defs)arg1) -> str : +.. py:method:: Defs.check(self: ecflow.Defs) -> str :module: ecflow Check :term:`trigger` and :term:`complete expression`\ s and :term:`limit`\ s @@ -339,61 +345,65 @@ Usage: print(str(e)) -.. py:method:: Defs.check_job_creation( (Defs)arg1 [, (bool)throw_on_error=False [, (bool)verbose=False]]) -> str : +.. py:method:: Defs.check_job_creation(*args, **kwargs) :module: ecflow +Overloaded function. + +1. check_job_creation(self: ecflow.Defs, throw_on_error: bool = False, verbose: bool = False) -> str + Check `job creation` . - - Will check the following: - - - :term:`ecf script` files and includes files can be located - - recursive includes - - manual and comments :term:`pre-processing` - - :term:`variable substitution` - - Some :term:`task`\ s are dummy tasks have no associated :term:`ecf script` file. - To disable error message for these tasks please add a variable called ECF_DUMMY_TASK to them. - Checking is done in conjunction with the class :py:class:`ecflow.JobCreationCtrl`. - If no node path is set on class JobCreationCtrl then all tasks are checked. - In the case where we want to check all tasks, use the convenience function that take no arguments. - - Usage: - - .. code-block:: python - - defs = Defs('my.def') # specify the defs we want to check, load into memory - ... - print(defs.check_job_creation()) # Check job generation for all tasks - ... - - # throw on error and Output the tasks as they are being checked - defs.check_job_creation(throw_on_error=TrueTrue,verbose=True) - - job_ctrl = JobCreationCtrl() - job_ctrl.set_verbose(True) # Output the tasks as they are being checked - defs.check_job_creation(job_ctrl) # Check job generation for all tasks, same as above - print(job_ctrl.get_error_msg()) - ... - job_ctrl = JobCreationCtrl() - job_ctrl.set_node_path('/suite/to_check') # will hierarchically check job creation under this node - defs.check_job_creation(job_ctrl) # job files generated to ECF_JOB - print(job_ctrl.get_error_msg()) - ... - job_ctrl = JobCreationCtrl() # no set_node_path() hence check job creation for all tasks - job_ctrl.set_dir_for_job_creation(tmp) # generate jobs file under this directory - defs.check_job_creation(job_ctrl) - print(job_ctrl.get_error_msg()) - ... - job_ctrl = JobCreationCtrl() # no set_node_path() hence check job creation for all tasks - job_ctrl.generate_temp_dir() # automatically generate directory for job file - defs.check_job_creation(job_ctrl) - print(job_ctrl.get_error_msg()) - - -check_job_creation( (Defs)arg1, (JobCreationCtrl)arg2) -> None - - -.. py:method:: Defs.delete_variable( (Defs)arg1, (str)arg2) -> None : + +Will check the following: + +- :term:`ecf script` files and includes files can be located +- recursive includes +- manual and comments :term:`pre-processing` +- :term:`variable substitution` + +Some :term:`task`\ s are dummy tasks have no associated :term:`ecf script` file. +To disable error message for these tasks please add a variable called ECF_DUMMY_TASK to them. +Checking is done in conjunction with the class :py:class:`ecflow.JobCreationCtrl`. +If no node path is set on class JobCreationCtrl then all tasks are checked. +In the case where we want to check all tasks, use the convenience function that take no arguments. + +Usage: + +.. code-block:: python + + defs = Defs('my.def') # specify the defs we want to check, load into memory + ... + print(defs.check_job_creation()) # Check job generation for all tasks + ... + + # throw on error and Output the tasks as they are being checked + defs.check_job_creation(throw_on_error=TrueTrue,verbose=True) + + job_ctrl = JobCreationCtrl() + job_ctrl.set_verbose(True) # Output the tasks as they are being checked + defs.check_job_creation(job_ctrl) # Check job generation for all tasks, same as above + print(job_ctrl.get_error_msg()) + ... + job_ctrl = JobCreationCtrl() + job_ctrl.set_node_path('/suite/to_check') # will hierarchically check job creation under this node + defs.check_job_creation(job_ctrl) # job files generated to ECF_JOB + print(job_ctrl.get_error_msg()) + ... + job_ctrl = JobCreationCtrl() # no set_node_path() hence check job creation for all tasks + job_ctrl.set_dir_for_job_creation(tmp) # generate jobs file under this directory + defs.check_job_creation(job_ctrl) + print(job_ctrl.get_error_msg()) + ... + job_ctrl = JobCreationCtrl() # no set_node_path() hence check job creation for all tasks + job_ctrl.generate_temp_dir() # automatically generate directory for job file + defs.check_job_creation(job_ctrl) + print(job_ctrl.get_error_msg()) + + +2. check_job_creation(self: ecflow.Defs, arg0: ecflow.JobCreationCtrl) -> None + + +.. py:method:: Defs.delete_variable(self: ecflow.Defs, arg0: str) -> None :module: ecflow An empty string will delete all user variables @@ -405,31 +415,31 @@ An empty string will delete all user variables Returns a list of :term:`extern`\ s -.. py:method:: Defs.find_abs_node( (Defs)arg1, (str)arg2) -> Node : +.. py:method:: Defs.find_abs_node(self: ecflow.Defs, arg0: str) -> ecflow.Node :module: ecflow Given a path, find the the :term:`node` -.. py:method:: Defs.find_node( (Defs)arg1, (str)arg2, (str)arg3) -> Node : +.. py:method:: Defs.find_node(self: ecflow.Defs, arg0: str, arg1: str) -> ecflow.Node :module: ecflow Given a type(suite,family,task) and a path to a node, return the node. -.. py:method:: Defs.find_node_path( (Defs)arg1, (str)arg2, (str)arg3) -> str : +.. py:method:: Defs.find_node_path(self: ecflow.Defs, arg0: str, arg1: str) -> str :module: ecflow Given a type(suite,family,task) and a name, return path of the first match, otherwise return an empty string -.. py:method:: Defs.find_suite( (Defs)arg1, (str)arg2) -> Suite : +.. py:method:: Defs.find_suite(self: ecflow.Defs, arg0: str) -> ecflow.Suite :module: ecflow Given a name, find the corresponding :term:`suite` -.. py:method:: Defs.generate_scripts( (Defs)arg1) -> None : +.. py:method:: Defs.generate_scripts(self: ecflow.Defs) -> None :module: ecflow Automatically generate template :term:`ecf script`\ s for this definition @@ -473,19 +483,19 @@ Usage: defs.generate_scripts() # generate '.ecf' and head.h/tail.h if required -.. py:method:: Defs.get_all_nodes( (Defs)arg1) -> NodeVec : +.. py:method:: Defs.get_all_nodes(self: ecflow.Defs) -> ecflow.NodeVec :module: ecflow Returns all the :term:`node`\ s in the definition -.. py:method:: Defs.get_all_tasks( (Defs)arg1) -> TaskVec : +.. py:method:: Defs.get_all_tasks(self: ecflow.Defs) -> ecflow.TaskVec :module: ecflow Returns all the :term:`task` nodes -.. py:method:: Defs.get_server_state( (Defs)arg1) -> SState : +.. py:method:: Defs.get_server_state(self: ecflow.Defs) -> ecflow.SState :module: ecflow Returns the :term:`ecflow_server` state: See :term:`server states` @@ -504,34 +514,42 @@ Usage: print(str(e)) -.. py:method:: Defs.get_state( (Defs)arg1) -> State +.. py:method:: Defs.get_state(self: ecflow.Defs) -> ecflow.State :module: ecflow -.. py:method:: Defs.has_time_dependencies( (Defs)arg1) -> bool : +.. py:method:: Defs.has_time_dependencies(self: ecflow.Defs) -> bool :module: ecflow returns True if the :term:`suite definition` has any time :term:`dependencies` -.. py:method:: Defs.restore_from_checkpt( (Defs)arg1, (str)arg2) -> None : +.. py:method:: Defs.restore_from_checkpt(self: ecflow.Defs, arg0: str) -> None :module: ecflow Restore the :term:`suite definition` from a :term:`check point` file stored on disk -.. py:method:: Defs.save_as_checkpt( (Defs)arg1, (str)arg2) -> None : +.. py:method:: Defs.save_as_checkpt(self: ecflow.Defs, arg0: str) -> None :module: ecflow Save the in memory :term:`suite definition` as a :term:`check point` file. This includes all node state. -.. py:method:: Defs.save_as_defs( (Defs)arg1, (str)arg2 [, (Style)arg3]) -> None : +.. py:method:: Defs.save_as_defs(*args, **kwargs) :module: ecflow +Overloaded function. + +1. save_as_defs(self: ecflow.Defs, arg0: str, arg1: ecflow.Style) -> None + +Save the in memory :term:`suite definition` into a file. The file name must be passed as an argument + + + +2. save_as_defs(self: ecflow.Defs, arg0: str) -> None + Save the in memory :term:`suite definition` into a file. The file name must be passed as an argument - - .. py:property:: Defs.server_variables @@ -540,7 +558,7 @@ Save the in memory :term:`suite definition` into a file. The file name must be p Returns a list of server :term:`variable`\ s -.. py:method:: Defs.simulate( (Defs)arg1) -> str : +.. py:method:: Defs.simulate(self: ecflow.Defs) -> str :module: ecflow Simulates a suite definition, allowing you predict/verify the behaviour of your suite in few seconds @@ -587,16 +605,14 @@ Usage: print(theResults) -.. py:method:: Defs.sort_attributes( (Defs)arg1, (AttrType)arg2) -> None +.. py:method:: Defs.sort_attributes(*args, **kwargs) :module: ecflow -sort_attributes( (Defs)arg1, (AttrType)arg2, (bool)arg3) -> None - -sort_attributes( (Defs)arg1, (AttrType)arg2, (bool)arg3, (list)arg4) -> None +Overloaded function. -sort_attributes( (Defs)arg1, (str)attribute_type [, (bool)recursive=True [, (list)no_sort=[]]]) -> None +1. sort_attributes(self: ecflow.Defs, attribute_type: str, recursive: bool = True, no_sort: list = []) -> None -sort_attributes( (Defs)arg1, (AttrType)arg2, (bool)attribute_type [, (object)recursive=True]) -> None +2. sort_attributes(self: ecflow.Defs, attribute_type: ecflow.AttrType, recursive: bool = True, no_sort: collections.abc.Sequence[str] = []) -> None .. py:property:: Defs.suites diff --git a/docs/python_api/Defstatus.rst b/docs/python_api/Defstatus.rst index f62e0dd1d..278fe26ea 100644 --- a/docs/python_api/Defstatus.rst +++ b/docs/python_api/Defstatus.rst @@ -5,7 +5,7 @@ ecflow.Defstatus .. py:class:: Defstatus :module: ecflow - Bases: :py:class:`~Boost.Python.instance` + Bases: :py:class:`~pybind11_builtins.pybind11_object` A :term:`node` can be set with a default status other the :term:`queued` @@ -14,6 +14,6 @@ This defines the state to take at 'begin' or 're-queue' time See :py:class:`ecflow.Node.add_defstatus` and :py:class:`ecflow.DState` -.. py:method:: Defstatus.state( (Defstatus)arg1) -> DState +.. py:method:: Defstatus.state(self: ecflow.Defstatus) -> ecflow.DState :module: ecflow diff --git a/docs/python_api/Ecf.rst b/docs/python_api/Ecf.rst index 29f927a32..9f5115b41 100644 --- a/docs/python_api/Ecf.rst +++ b/docs/python_api/Ecf.rst @@ -5,33 +5,33 @@ ecflow.Ecf .. py:class:: Ecf :module: ecflow - Bases: :py:class:`~Boost.Python.instance` + Bases: :py:class:`~pybind11_builtins.pybind11_object` Singleton used to control ecf debugging -.. py:method:: Ecf.debug_equality() -> bool : +.. py:method:: Ecf.debug_equality() -> bool :module: ecflow :staticmethod: Returns true if debugging of equality is enabled -.. py:method:: Ecf.debug_level() -> int : +.. py:method:: Ecf.debug_level() -> int :module: ecflow :staticmethod: Returns integer showing debug level. debug_level > 0 will disable some warning messages -.. py:method:: Ecf.set_debug_equality( (bool)arg1) -> None : +.. py:method:: Ecf.set_debug_equality(arg0: bool) -> None :module: ecflow :staticmethod: Set debugging for equality -.. py:method:: Ecf.set_debug_level( (int)arg1) -> None : +.. py:method:: Ecf.set_debug_level(arg0: typing.SupportsInt | typing.SupportsIndex) -> None :module: ecflow :staticmethod: diff --git a/docs/python_api/Edit.rst b/docs/python_api/Edit.rst index 41abe6d9b..5d9ac57b9 100644 --- a/docs/python_api/Edit.rst +++ b/docs/python_api/Edit.rst @@ -5,7 +5,7 @@ ecflow.Edit .. py:class:: Edit :module: ecflow - Bases: :py:class:`~Boost.Python.instance` + Bases: :py:class:`~pybind11_builtins.pybind11_object` Defines a :term:`variable` on a :term:`node` for use in :term:`ecf script`. diff --git a/docs/python_api/Event.rst b/docs/python_api/Event.rst index 62291d63d..432553324 100644 --- a/docs/python_api/Event.rst +++ b/docs/python_api/Event.rst @@ -5,7 +5,7 @@ ecflow.Event .. py:class:: Event :module: ecflow - Bases: :py:class:`~Boost.Python.instance` + Bases: :py:class:`~pybind11_builtins.pybind11_object` :term:`event`\ s are used as signal mechanism. @@ -48,37 +48,37 @@ Usage: Event(2,'event_name')) -.. py:method:: Event.empty( (Event)arg1) -> bool : +.. py:method:: Event.empty(self: ecflow.Event) -> bool :module: ecflow Return true if the Event is empty. Used when returning a NULL Event, from a find -.. py:method:: Event.initial_value( (Event)arg1) -> bool : +.. py:method:: Event.initial_value(self: ecflow.Event) -> bool :module: ecflow Return events initial value, This is value taken for begin/re-queue -.. py:method:: Event.name( (Event)arg1) -> str : +.. py:method:: Event.name(self: ecflow.Event) -> str :module: ecflow Return the Events name as string. If number supplied name may be empty. -.. py:method:: Event.name_or_number( (Event)arg1) -> str : +.. py:method:: Event.name_or_number(self: ecflow.Event) -> str :module: ecflow Returns name or number as string -.. py:method:: Event.number( (Event)arg1) -> int : +.. py:method:: Event.number(self: ecflow.Event) -> int :module: ecflow Return events number as a integer. If not specified return max integer value -.. py:method:: Event.value( (Event)arg1) -> bool : +.. py:method:: Event.value(self: ecflow.Event) -> bool :module: ecflow Return events current value diff --git a/docs/python_api/Expression.rst b/docs/python_api/Expression.rst index cafb1fd94..98876f754 100644 --- a/docs/python_api/Expression.rst +++ b/docs/python_api/Expression.rst @@ -5,7 +5,7 @@ ecflow.Expression .. py:class:: Expression :module: ecflow - Bases: :py:class:`~Boost.Python.instance` + Bases: :py:class:`~pybind11_builtins.pybind11_object` Expression holds :term:`trigger` or :term:`complete expression`. Also see :py:class:`ecflow.Trigger` @@ -57,13 +57,13 @@ Here the trigger for task2 is equivalent to 't0 == complete and t1 == complete or t4 == complete or t5 == active' -.. py:method:: Expression.add( (Expression)arg1, (PartExpression)arg2) -> None : +.. py:method:: Expression.add(self: ecflow.Expression, arg0: ecflow.PartExpression) -> None :module: ecflow Add a part expression, the second and subsequent part expressions must have 'and/or' set -.. py:method:: Expression.get_expression( (Expression)arg1) -> str : +.. py:method:: Expression.get_expression(self: ecflow.Expression) -> str :module: ecflow returns the complete expression as a string diff --git a/docs/python_api/FamilyVec.rst b/docs/python_api/FamilyVec.rst index 2646765a7..59e2ea23e 100644 --- a/docs/python_api/FamilyVec.rst +++ b/docs/python_api/FamilyVec.rst @@ -5,15 +5,65 @@ ecflow.FamilyVec .. py:class:: FamilyVec :module: ecflow - Bases: :py:class:`~Boost.Python.instance` + Bases: :py:class:`~pybind11_builtins.pybind11_object` Hold a list of :term:`family` nodes -.. py:method:: FamilyVec.append( (FamilyVec)arg1, (object)arg2) -> None +.. py:method:: FamilyVec.append(self: ecflow.FamilyVec, x: Family) -> None :module: ecflow +Add an item to the end of the list -.. py:method:: FamilyVec.extend( (FamilyVec)arg1, (object)arg2) -> None + +.. py:method:: FamilyVec.clear(self: ecflow.FamilyVec) -> None + :module: ecflow + +Clear the contents + + +.. py:method:: FamilyVec.count(self: ecflow.FamilyVec, x: Family) -> int + :module: ecflow + +Return the number of times ``x`` appears in the list + + +.. py:method:: FamilyVec.extend(*args, **kwargs) + :module: ecflow + +Overloaded function. + +1. extend(self: ecflow.FamilyVec, L: ecflow.FamilyVec) -> None + +Extend the list by appending all the items in the given list + +2. extend(self: ecflow.FamilyVec, L: collections.abc.Iterable) -> None + +Extend the list by appending all the items in the given list + + +.. py:method:: FamilyVec.insert(self: ecflow.FamilyVec, i: typing.SupportsInt | typing.SupportsIndex, x: Family) -> None + :module: ecflow + +Insert an item at a given position. + + +.. py:method:: FamilyVec.pop(*args, **kwargs) :module: ecflow +Overloaded function. + +1. pop(self: ecflow.FamilyVec) -> Family + +Remove and return the last item + +2. pop(self: ecflow.FamilyVec, i: typing.SupportsInt | typing.SupportsIndex) -> Family + +Remove and return the item at index ``i`` + + +.. py:method:: FamilyVec.remove(self: ecflow.FamilyVec, x: Family) -> None + :module: ecflow + +Remove the first item from the list whose value is x. It is an error if there is no such item. + diff --git a/docs/python_api/File.rst b/docs/python_api/File.rst index 487fe6fd8..7ea57c61b 100644 --- a/docs/python_api/File.rst +++ b/docs/python_api/File.rst @@ -5,33 +5,33 @@ ecflow.File .. py:class:: File :module: ecflow - Bases: :py:class:`~Boost.Python.instance` + Bases: :py:class:`~pybind11_builtins.pybind11_object` Utility class, Used in test only. -.. py:method:: File.build_dir() -> str : +.. py:method:: File.build_dir() -> str :module: ecflow :staticmethod: Path name to ecflow build directory -.. py:method:: File.find_client() -> str : +.. py:method:: File.find_client() -> str :module: ecflow :staticmethod: Provides pathname to the client -.. py:method:: File.find_server() -> str : +.. py:method:: File.find_server() -> str :module: ecflow :staticmethod: Provides pathname to the server -.. py:method:: File.source_dir() -> str : +.. py:method:: File.source_dir() -> str :module: ecflow :staticmethod: diff --git a/docs/python_api/Flag.rst b/docs/python_api/Flag.rst index a32a9960b..6097b7290 100644 --- a/docs/python_api/Flag.rst +++ b/docs/python_api/Flag.rst @@ -5,45 +5,45 @@ ecflow.Flag .. py:class:: Flag :module: ecflow - Bases: :py:class:`~Boost.Python.instance` + Bases: :py:class:`~pybind11_builtins.pybind11_object` Represents additional state associated with a Node. -.. py:method:: Flag.clear( (Flag)arg1, (FlagType)arg2) -> None : +.. py:method:: Flag.clear(self: ecflow.Flag, arg0: ecflow.FlagType) -> None :module: ecflow Clear the given flag. Used in test only -.. py:method:: Flag.is_set( (Flag)arg1, (FlagType)arg2) -> bool : +.. py:method:: Flag.is_set(self: ecflow.Flag, arg0: ecflow.FlagType) -> bool :module: ecflow Queries if a given flag is set -.. py:method:: Flag.list() -> FlagTypeVec : +.. py:method:: Flag.list() -> std::__1::vector> :module: ecflow :staticmethod: -Returns the list of all flag types. returns FlagTypeVec. Used in test only +Returns the list of all flag types. returns FlagTypeVec. Tests only -.. py:method:: Flag.reset( (Flag)arg1) -> None : +.. py:method:: Flag.reset(self: ecflow.Flag) -> None :module: ecflow Clears all flags. Used in test only -.. py:method:: Flag.set( (Flag)arg1, (FlagType)arg2) -> None : +.. py:method:: Flag.set(self: ecflow.Flag, arg0: ecflow.FlagType) -> None :module: ecflow Sets the given flag. Used in test only -.. py:method:: Flag.type_to_string( (FlagType)arg1) -> str : +.. py:method:: Flag.type_to_string(arg0: ecflow.FlagType) -> str :module: ecflow :staticmethod: -Convert type to a string. Used in test only +Convert type to a string. Tests only diff --git a/docs/python_api/FlagType.rst b/docs/python_api/FlagType.rst index 0ce429689..f3dde79a3 100644 --- a/docs/python_api/FlagType.rst +++ b/docs/python_api/FlagType.rst @@ -5,7 +5,7 @@ ecflow.FlagType .. py:class:: FlagType :module: ecflow - Bases: :py:class:`~Boost.Python.enum` + Bases: :py:class:`~pybind11_builtins.pybind11_object` Flags store state associated with a node @@ -36,6 +36,61 @@ Flags store state associated with a node - NOT_SET +Members: + + force_abort + + user_edit + + task_aborted + + edit_failed + + jobcmd_failed + + killcmd_failed + + statuscmd_failed + + no_script + + killed + + status + + late + + message + + byrule + + queuelimit + + wait + + locked + + zombie + + no_reque + + archived + + restored + + threshold + + sigterm + + not_set + + log_error + + checkpt_error + + remote_error + + .. py:attribute:: FlagType.archived :module: ecflow :value: ecflow.FlagType.archived @@ -96,6 +151,10 @@ Flags store state associated with a node :value: ecflow.FlagType.message +.. py:property:: FlagType.name + :module: ecflow + + .. py:attribute:: FlagType.names :module: ecflow :value: {'archived': ecflow.FlagType.archived, 'byrule': ecflow.FlagType.byrule, 'checkpt_error': ecflow.FlagType.checkpt_error, 'edit_failed': ecflow.FlagType.edit_failed, 'force_abort': ecflow.FlagType.force_abort, 'jobcmd_failed': ecflow.FlagType.jobcmd_failed, 'killcmd_failed': ecflow.FlagType.killcmd_failed, 'killed': ecflow.FlagType.killed, 'late': ecflow.FlagType.late, 'locked': ecflow.FlagType.locked, 'log_error': ecflow.FlagType.log_error, 'message': ecflow.FlagType.message, 'no_reque': ecflow.FlagType.no_reque, 'no_script': ecflow.FlagType.no_script, 'not_set': ecflow.FlagType.not_set, 'queuelimit': ecflow.FlagType.queuelimit, 'remote_error': ecflow.FlagType.remote_error, 'restored': ecflow.FlagType.restored, 'sigterm': ecflow.FlagType.sigterm, 'status': ecflow.FlagType.status, 'statuscmd_failed': ecflow.FlagType.statuscmd_failed, 'task_aborted': ecflow.FlagType.task_aborted, 'threshold': ecflow.FlagType.threshold, 'user_edit': ecflow.FlagType.user_edit, 'wait': ecflow.FlagType.wait, 'zombie': ecflow.FlagType.zombie} @@ -161,6 +220,10 @@ Flags store state associated with a node :value: ecflow.FlagType.user_edit +.. py:property:: FlagType.value + :module: ecflow + + .. py:attribute:: FlagType.values :module: ecflow :value: {0: ecflow.FlagType.force_abort, 1: ecflow.FlagType.user_edit, 2: ecflow.FlagType.task_aborted, 3: ecflow.FlagType.edit_failed, 4: ecflow.FlagType.jobcmd_failed, 5: ecflow.FlagType.no_script, 6: ecflow.FlagType.killed, 7: ecflow.FlagType.late, 8: ecflow.FlagType.message, 9: ecflow.FlagType.byrule, 10: ecflow.FlagType.queuelimit, 11: ecflow.FlagType.wait, 12: ecflow.FlagType.locked, 13: ecflow.FlagType.zombie, 14: ecflow.FlagType.no_reque, 15: ecflow.FlagType.archived, 16: ecflow.FlagType.restored, 17: ecflow.FlagType.threshold, 18: ecflow.FlagType.sigterm, 19: ecflow.FlagType.not_set, 20: ecflow.FlagType.log_error, 21: ecflow.FlagType.checkpt_error, 22: ecflow.FlagType.killcmd_failed, 23: ecflow.FlagType.statuscmd_failed, 24: ecflow.FlagType.status, 25: ecflow.FlagType.remote_error} diff --git a/docs/python_api/FlagTypeVec.rst b/docs/python_api/FlagTypeVec.rst index 79c0b2d94..5c1392744 100644 --- a/docs/python_api/FlagTypeVec.rst +++ b/docs/python_api/FlagTypeVec.rst @@ -5,15 +5,65 @@ ecflow.FlagTypeVec .. py:class:: FlagTypeVec :module: ecflow - Bases: :py:class:`~Boost.Python.instance` + Bases: :py:class:`~pybind11_builtins.pybind11_object` Hold a list of flag types -.. py:method:: FlagTypeVec.append( (FlagTypeVec)arg1, (object)arg2) -> None +.. py:method:: FlagTypeVec.append(self: ecflow.FlagTypeVec, x: ecflow.FlagType) -> None :module: ecflow +Add an item to the end of the list -.. py:method:: FlagTypeVec.extend( (FlagTypeVec)arg1, (object)arg2) -> None + +.. py:method:: FlagTypeVec.clear(self: ecflow.FlagTypeVec) -> None + :module: ecflow + +Clear the contents + + +.. py:method:: FlagTypeVec.count(self: ecflow.FlagTypeVec, x: ecflow.FlagType) -> int + :module: ecflow + +Return the number of times ``x`` appears in the list + + +.. py:method:: FlagTypeVec.extend(*args, **kwargs) + :module: ecflow + +Overloaded function. + +1. extend(self: ecflow.FlagTypeVec, L: ecflow.FlagTypeVec) -> None + +Extend the list by appending all the items in the given list + +2. extend(self: ecflow.FlagTypeVec, L: collections.abc.Iterable) -> None + +Extend the list by appending all the items in the given list + + +.. py:method:: FlagTypeVec.insert(self: ecflow.FlagTypeVec, i: typing.SupportsInt | typing.SupportsIndex, x: ecflow.FlagType) -> None + :module: ecflow + +Insert an item at a given position. + + +.. py:method:: FlagTypeVec.pop(*args, **kwargs) :module: ecflow +Overloaded function. + +1. pop(self: ecflow.FlagTypeVec) -> ecflow.FlagType + +Remove and return the last item + +2. pop(self: ecflow.FlagTypeVec, i: typing.SupportsInt | typing.SupportsIndex) -> ecflow.FlagType + +Remove and return the item at index ``i`` + + +.. py:method:: FlagTypeVec.remove(self: ecflow.FlagTypeVec, x: ecflow.FlagType) -> None + :module: ecflow + +Remove the first item from the list whose value is x. It is an error if there is no such item. + diff --git a/docs/python_api/Generic.rst b/docs/python_api/Generic.rst index 262efde08..7dc3a8d5a 100644 --- a/docs/python_api/Generic.rst +++ b/docs/python_api/Generic.rst @@ -5,18 +5,18 @@ ecflow.Generic .. py:class:: Generic :module: ecflow - Bases: :py:class:`~Boost.Python.instance` + Bases: :py:class:`~pybind11_builtins.pybind11_object` A generic attribute, used to add new attributes for the future, without requiring a API change -.. py:method:: Generic.empty( (Generic)arg1) -> bool : +.. py:method:: Generic.empty(self: ecflow.Generic) -> bool :module: ecflow Return true if the Generic is empty. Used when returning a NULL Generic, from a find -.. py:method:: Generic.name( (Generic)arg1) -> str : +.. py:method:: Generic.name(self: ecflow.Generic) -> str :module: ecflow Return the generic name as string diff --git a/docs/python_api/InLimit.rst b/docs/python_api/InLimit.rst index 2395da974..bc34bf897 100644 --- a/docs/python_api/InLimit.rst +++ b/docs/python_api/InLimit.rst @@ -5,7 +5,7 @@ ecflow.InLimit .. py:class:: InLimit :module: ecflow - Bases: :py:class:`~Boost.Python.instance` + Bases: :py:class:`~pybind11_builtins.pybind11_object` :term:`inlimit` is used in conjunction with :term:`limit` to provide simple load management: @@ -25,14 +25,31 @@ created in the order they are defined Constructor:: - InLimit(name, optional, optional, optional) - string name : The name of the referenced Limit - string path : The path to the Limit, if this is left out, then Limit of 'name' must be specified - some where up the parent hierarchy - int value : The usage of the Limit. Each job submission will consume 'value' tokens - from the Limit. defaults to 1 if no value specified. - bool limit_this_node_only : Only limits this node and *NOT* its children - Can be used load balance families + InLimit( + name, + optional, + optional, + optional, + optional + : The path to the node with associated Limit. + If not provided, the Limit 'name' will be be found + by traversing the node hierarchy towards the root. + int value + : The value used from the Limit. + Each job submission will consume 'value' tokens from the Limit. + Default value is to 1. + bool limit_this_node_only + : Only limits this node and *NOT* its children. + Can be used to load balance families. + Default value is false. + bool limit_submission_only + : The limit applies to number of submitted tasks + Default value is false. Usage: @@ -45,31 +62,31 @@ Usage: family.add_inlimit(inlimit) # add existing inlimit using function -.. py:method:: InLimit.limit_submission( (InLimit)arg1) -> bool : +.. py:method:: InLimit.limit_submission(self: ecflow.InLimit) -> bool :module: ecflow Limit submission only -.. py:method:: InLimit.limit_this_node_only( (InLimit)arg1) -> bool : +.. py:method:: InLimit.limit_this_node_only(self: ecflow.InLimit) -> bool :module: ecflow Only this node is limited. i.e. typically Family or Suite -.. py:method:: InLimit.name( (InLimit)arg1) -> str : +.. py:method:: InLimit.name(self: ecflow.InLimit) -> str :module: ecflow Return the :term:`inlimit` name as string -.. py:method:: InLimit.path_to_node( (InLimit)arg1) -> str : +.. py:method:: InLimit.path_to_node(self: ecflow.InLimit) -> str :module: ecflow Path to the node that holds the limit, can be empty -.. py:method:: InLimit.tokens( (InLimit)arg1) -> int : +.. py:method:: InLimit.tokens(self: ecflow.InLimit) -> int :module: ecflow The number of token to consume from the Limit diff --git a/docs/python_api/JobCreationCtrl.rst b/docs/python_api/JobCreationCtrl.rst index ff38020de..753c1664d 100644 --- a/docs/python_api/JobCreationCtrl.rst +++ b/docs/python_api/JobCreationCtrl.rst @@ -5,7 +5,7 @@ ecflow.JobCreationCtrl .. py:class:: JobCreationCtrl :module: ecflow - Bases: :py:class:`~Boost.Python.instance` + Bases: :py:class:`~pybind11_builtins.pybind11_object` The class JobCreationCtrl is used in `job creation` checking @@ -35,37 +35,43 @@ Usage: print(job_ctrl.get_error_msg()) -.. py:method:: JobCreationCtrl.generate_temp_dir( (JobCreationCtrl)arg1) -> None : +.. py:method:: JobCreationCtrl.generate_temp_dir(self: ecflow.JobCreationCtrl) -> None :module: ecflow Automatically generated temporary directory for job creation. Directory written to stdout for information -.. py:method:: JobCreationCtrl.get_dir_for_job_creation( (JobCreationCtrl)arg1) -> str : +.. py:method:: JobCreationCtrl.get_dir_for_job_creation(self: ecflow.JobCreationCtrl) -> str :module: ecflow Returns the directory set for job creation -.. py:method:: JobCreationCtrl.get_error_msg( (JobCreationCtrl)arg1) -> str : +.. py:method:: JobCreationCtrl.get_error_msg(self: ecflow.JobCreationCtrl) -> str :module: ecflow Returns an error message generated during checking of job creation -.. py:method:: JobCreationCtrl.set_dir_for_job_creation( (JobCreationCtrl)arg1, (str)arg2) -> None : +.. py:method:: JobCreationCtrl.is_verbose(self: ecflow.JobCreationCtrl) -> bool + :module: ecflow + +Returns true if verbose mode is on, false otherwise + + +.. py:method:: JobCreationCtrl.set_dir_for_job_creation(self: ecflow.JobCreationCtrl, arg0: str) -> None :module: ecflow Specify directory, for job creation -.. py:method:: JobCreationCtrl.set_node_path( (JobCreationCtrl)arg1, (str)arg2) -> None : +.. py:method:: JobCreationCtrl.set_node_path(self: ecflow.JobCreationCtrl, arg0: str) -> None :module: ecflow The node we want to check job creation for. If no node specified check all tasks -.. py:method:: JobCreationCtrl.set_verbose( (JobCreationCtrl)arg1, (bool)arg2) -> None : +.. py:method:: JobCreationCtrl.set_verbose(self: ecflow.JobCreationCtrl, arg0: bool) -> None :module: ecflow Output each task as its being checked. diff --git a/docs/python_api/Label.rst b/docs/python_api/Label.rst index bbb28ec53..c705d21e6 100644 --- a/docs/python_api/Label.rst +++ b/docs/python_api/Label.rst @@ -5,7 +5,7 @@ ecflow.Label .. py:class:: Label :module: ecflow - Bases: :py:class:`~Boost.Python.instance` + Bases: :py:class:`~pybind11_builtins.pybind11_object` A :term:`label` has a name and value and provides a way of displaying information in a GUI. @@ -39,25 +39,25 @@ Usage: print(label) -.. py:method:: Label.empty( (Label)arg1) -> bool : +.. py:method:: Label.empty(self: ecflow.Label) -> bool :module: ecflow Return true if the Label is empty. Used when returning a NULL Label, from a find -.. py:method:: Label.name( (Label)arg1) -> str : +.. py:method:: Label.name(self: ecflow.Label) -> str :module: ecflow Return the :term:`label` name as string -.. py:method:: Label.new_value( (Label)arg1) -> str : +.. py:method:: Label.new_value(self: ecflow.Label) -> str :module: ecflow Return the new label value as string -.. py:method:: Label.value( (Label)arg1) -> str : +.. py:method:: Label.value(self: ecflow.Label) -> str :module: ecflow Return the original :term:`label` value as string diff --git a/docs/python_api/Late.rst b/docs/python_api/Late.rst index 2ab9067b8..30f4544a2 100644 --- a/docs/python_api/Late.rst +++ b/docs/python_api/Late.rst @@ -5,7 +5,7 @@ ecflow.Late .. py:class:: Late :module: ecflow - Bases: :py:class:`~Boost.Python.instance` + Bases: :py:class:`~pybind11_builtins.pybind11_object` Sets the :term:`late` flag. @@ -34,58 +34,76 @@ Usage: Late(submitted='00:15',active='20:00')) -.. py:method:: Late.active( (Late)arg1, (int)arg2, (int)arg3) -> None : +.. py:method:: Late.active(*args, **kwargs) :module: ecflow +Overloaded function. + +1. active(self: ecflow.Late, arg0: typing.SupportsInt | typing.SupportsIndex, arg1: typing.SupportsInt | typing.SupportsIndex) -> None + active(hour,minute): The time the node must become :term:`active`. If the node is still :term:`queued` or :term:`submitted` - by the time specified, the late flag is set +by the time specified, the late flag is set + +2. active(self: ecflow.Late, arg0: ecflow.TimeSlot) -> None + +active(TimeSlot):The time the node must become :term:`active`. If the node is still :term:`queued` or :term:`submitted` +by the time specified, the late flag is set -active( (Late)arg1, (TimeSlot)arg2) -> None : - active(TimeSlot):The time the node must become :term:`active`. If the node is still :term:`queued` or :term:`submitted` - by the time specified, the late flag is set +3. active(self: ecflow.Late) -> ecflow.TimeSlot -active( (Late)arg1) -> TimeSlot : - Return the active time as a TimeSlot +Return the active time as a TimeSlot -.. py:method:: Late.complete( (Late)arg1, (int)arg2, (int)arg3, (bool)arg4) -> None : +.. py:method:: Late.complete(*args, **kwargs) :module: ecflow +Overloaded function. + +1. complete(self: ecflow.Late, arg0: typing.SupportsInt | typing.SupportsIndex, arg1: typing.SupportsInt | typing.SupportsIndex, arg2: bool) -> None + complete(hour,minute):The time the node must become :term:`complete`. If relative, time is taken from the time - the node became :term:`active`, otherwise node must be :term:`complete` by the time given +the node became :term:`active`, otherwise node must be :term:`complete` by the time given + +2. complete(self: ecflow.Late, arg0: ecflow.TimeSlot, arg1: bool) -> None -complete( (Late)arg1, (TimeSlot)arg2, (bool)arg3) -> None : - complete(TimeSlot): The time the node must become :term:`complete`. If relative, time is taken from the time - the node became :term:`active`, otherwise node must be :term:`complete` by the time given +complete(TimeSlot): The time the node must become :term:`complete`. If relative, time is taken from the time +the node became :term:`active`, otherwise node must be :term:`complete` by the time given -complete( (Late)arg1) -> TimeSlot : - Return the complete time as a TimeSlot +3. complete(self: ecflow.Late) -> ecflow.TimeSlot +Return the complete time as a TimeSlot -.. py:method:: Late.complete_is_relative( (Late)arg1) -> bool : + +.. py:method:: Late.complete_is_relative(self: ecflow.Late) -> bool :module: ecflow Returns a boolean where true means that complete is relative -.. py:method:: Late.is_late( (Late)arg1) -> bool : +.. py:method:: Late.is_late(self: ecflow.Late) -> bool :module: ecflow Return True if late -.. py:method:: Late.submitted( (Late)arg1, (TimeSlot)arg2) -> None : +.. py:method:: Late.submitted(*args, **kwargs) :module: ecflow +Overloaded function. + +1. submitted(self: ecflow.Late, arg0: ecflow.TimeSlot) -> None + submitted(TimeSlot):The time node can stay :term:`submitted`. Submitted is always relative. If the node stays - submitted longer than the time specified, the :term:`late` flag is set - +submitted longer than the time specified, the :term:`late` flag is set + + +2. submitted(self: ecflow.Late, arg0: typing.SupportsInt | typing.SupportsIndex, arg1: typing.SupportsInt | typing.SupportsIndex) -> None + +submitted(hour,minute) The time node can stay submitted. Submitted is always relative. If the node stays +submitted longer than the time specified, the late flag is set + -submitted( (Late)arg1, (int)arg2, (int)arg3) -> None : - submitted(hour,minute) The time node can stay submitted. Submitted is always relative. If the node stays - submitted longer than the time specified, the late flag is set - +3. submitted(self: ecflow.Late) -> ecflow.TimeSlot -submitted( (Late)arg1) -> TimeSlot : - Return the submitted time as a TimeSlot +Return the submitted time as a TimeSlot diff --git a/docs/python_api/Limit.rst b/docs/python_api/Limit.rst index 102789fe3..2a74bafe6 100644 --- a/docs/python_api/Limit.rst +++ b/docs/python_api/Limit.rst @@ -5,7 +5,7 @@ ecflow.Limit .. py:class:: Limit :module: ecflow - Bases: :py:class:`~Boost.Python.instance` + Bases: :py:class:`~pybind11_builtins.pybind11_object` :term:`limit` provides a simple load management @@ -32,37 +32,37 @@ Usage: suite.add_limit(limit) # add existing limit using function -.. py:method:: Limit.decrement( (Limit)arg1, (int)arg2, (str)arg3) -> None : +.. py:method:: Limit.decrement(self: ecflow.Limit, arg0: typing.SupportsInt | typing.SupportsIndex, arg1: str) -> None :module: ecflow used for test only -.. py:method:: Limit.increment( (Limit)arg1, (int)arg2, (str)arg3) -> None : +.. py:method:: Limit.increment(self: ecflow.Limit, arg0: typing.SupportsInt | typing.SupportsIndex, arg1: str) -> None :module: ecflow used for test only -.. py:method:: Limit.limit( (Limit)arg1) -> int : +.. py:method:: Limit.limit(self: ecflow.Limit) -> int :module: ecflow The max value of the :term:`limit` as an integer -.. py:method:: Limit.name( (Limit)arg1) -> str : +.. py:method:: Limit.name(self: ecflow.Limit) -> str :module: ecflow Return the :term:`limit` name as string -.. py:method:: Limit.node_paths( (Limit)arg1) -> list : +.. py:method:: Limit.node_paths(self: ecflow.Limit) -> list :module: ecflow List of nodes(paths) that have consumed a limit -.. py:method:: Limit.value( (Limit)arg1) -> int : +.. py:method:: Limit.value(self: ecflow.Limit) -> int :module: ecflow The :term:`limit` token value as an integer diff --git a/docs/python_api/Meter.rst b/docs/python_api/Meter.rst index 01a065e68..4d6b7f297 100644 --- a/docs/python_api/Meter.rst +++ b/docs/python_api/Meter.rst @@ -5,7 +5,7 @@ ecflow.Meter .. py:class:: Meter :module: ecflow - Bases: :py:class:`~Boost.Python.instance` + Bases: :py:class:`~pybind11_builtins.pybind11_object` :term:`meter`\ s can be used to indicate proportional completion of :term:`task` @@ -64,37 +64,37 @@ Using a meter requires: as a result triggers's which use meter equality may never evaluate -.. py:method:: Meter.color_change( (Meter)arg1) -> int : +.. py:method:: Meter.color_change(self: ecflow.Meter) -> int :module: ecflow returns the color change -.. py:method:: Meter.empty( (Meter)arg1) -> bool : +.. py:method:: Meter.empty(self: ecflow.Meter) -> bool :module: ecflow Return true if the Meter is empty. Used when returning a NULL Meter, from a find -.. py:method:: Meter.max( (Meter)arg1) -> int : +.. py:method:: Meter.max(self: ecflow.Meter) -> int :module: ecflow Return the Meters maximum value -.. py:method:: Meter.min( (Meter)arg1) -> int : +.. py:method:: Meter.min(self: ecflow.Meter) -> int :module: ecflow Return the Meters minimum value -.. py:method:: Meter.name( (Meter)arg1) -> str : +.. py:method:: Meter.name(self: ecflow.Meter) -> str :module: ecflow Return the Meters name as string -.. py:method:: Meter.value( (Meter)arg1) -> int : +.. py:method:: Meter.value(self: ecflow.Meter) -> int :module: ecflow Return meters current value diff --git a/docs/python_api/MirrorAttr.rst b/docs/python_api/MirrorAttr.rst index a69c0d434..3a511cd30 100644 --- a/docs/python_api/MirrorAttr.rst +++ b/docs/python_api/MirrorAttr.rst @@ -5,7 +5,7 @@ ecflow.MirrorAttr .. py:class:: MirrorAttr :module: ecflow - Bases: :py:class:`~Boost.Python.instance` + Bases: :py:class:`~pybind11_builtins.pybind11_object` A :term:`mirror` attribute, assigned to a :term:`node`, enables establishing an external link and locally replicate the state of a node executing on a remote ecFlow server. @@ -37,43 +37,43 @@ Usage: The parameters `remote_host`, `remote_port`, `polling`, `ssl`, and `auth` are optional -.. py:method:: MirrorAttr.auth( (MirrorAttr)arg1) -> str : +.. py:method:: MirrorAttr.auth(self: ecflow.MirrorAttr) -> str :module: ecflow Returns the path to Authentication credentials used to contact the remote ecFlow server -.. py:method:: MirrorAttr.name( (MirrorAttr)arg1) -> str : +.. py:method:: MirrorAttr.name(self: ecflow.MirrorAttr) -> str :module: ecflow Returns the name of the Mirror attribute -.. py:method:: MirrorAttr.polling( (MirrorAttr)arg1) -> str : +.. py:method:: MirrorAttr.polling(self: ecflow.MirrorAttr) -> str :module: ecflow Returns the polling interval used to contact the remove ecFlow server -.. py:method:: MirrorAttr.remote_host( (MirrorAttr)arg1) -> str : +.. py:method:: MirrorAttr.remote_host(self: ecflow.MirrorAttr) -> str :module: ecflow Returns the host of the remote ecFlow server -.. py:method:: MirrorAttr.remote_path( (MirrorAttr)arg1) -> str : +.. py:method:: MirrorAttr.remote_path(self: ecflow.MirrorAttr) -> str :module: ecflow Returns the path on the remote ecFlow server -.. py:method:: MirrorAttr.remote_port( (MirrorAttr)arg1) -> str : +.. py:method:: MirrorAttr.remote_port(self: ecflow.MirrorAttr) -> str :module: ecflow Returns the port of the remote ecFlow server -.. py:method:: MirrorAttr.ssl( (MirrorAttr)arg1) -> bool : +.. py:method:: MirrorAttr.ssl(self: ecflow.MirrorAttr) -> bool :module: ecflow Returns a boolean, where true means that SSL is enabled diff --git a/docs/python_api/Node.rst b/docs/python_api/Node.rst index 753b691ad..df0db80a4 100644 --- a/docs/python_api/Node.rst +++ b/docs/python_api/Node.rst @@ -5,250 +5,264 @@ ecflow.Node .. py:class:: Node :module: ecflow - Bases: :py:class:`~Boost.Python.instance` + Bases: :py:class:`~pybind11_builtins.pybind11_object` A Node class is the abstract base class for Suite, Family and Task Every Node instance has a name, and a path relative to a suite -.. py:method:: Node.add - :module: ecflow - -object add(tuple args, dict kwds) : - add(..) provides a way to append Nodes and attributes - - This is best illustrated with an example: - - .. code-block:: python - - defs = Defs().add( - Suite('s1').add( - Clock(1, 1, 2010, False), - Autocancel(1, 10, True), - Task('t1').add( - Edit({'a':'12', 'b':'bb'}, c='v',d='b'), - Edit(g='d'), - Edit(h=1), - Event(1), - Event(11,'event'), - Meter('meter',0,10,10), - Label('label','c'), - Trigger('1==1'), - Complete('1==1'), - Limit('limit',10),Limit('limit2',10), - InLimit('limitName','/limit',2), - Defstatus(DState.complete), - Today(0,30),Today('00:59'),Today('00:00 11:30 00:01'), - Time(0,30),Time('00:59'),Time('00:00 11:30 00:01'), - Day('sunday'),Day(Days.monday), - Date(1,1,0),Date(28,2,1960), - Autocancel(3) - ), - [ Family('f{}'.format(i)) for i in range(1,6)])) - - We can also use '+=' with a list here are a few examples: - - .. code-block:: python - - defs = Defs(); - defs += [ Suite('s2'),Edit({ 'x1':'y', 'aa1':'bb'}, a='v',b='b') ] - - .. code-block:: python - - defs += [ Suite('s{}'.format(i)) for i in range(1,6) ] - - .. code-block:: python - - defs = Defs() - defs += [ Suite('suite').add( - Task('x'), - Family('f').add( [ Task('t{}'.format(i)) for i in range(1,6)] ), - Task('y'), - [ Family('f{}'.format(i)) for i in range(1,6) ], - Edit(a='b'), - [ Task('t{}'.format(i)) for i in range(1,6) ], - )] - - It is also possible to use '+' - - .. code-block:: python - - defs = Defs() + Suite('s1') - defs.s1 += Autocancel(1, 10, True) - defs.s1 += Task('t1') + Edit({ 'e':1, 'f':'bb'}) +\ - Event(1) + Event(11,'event') + Meter('meter',0,10,10) + Label('label','c') + Trigger('1==1') +\ - Complete('1==1') + Limit('limit',10) + Limit('limit2',10) + InLimit('limitName','/limit',2) +\ - Defstatus(DState.complete) + Today(0,30) + Today('00:59') + Today('00:00 11:30 00:01') +\ - Time(0,30) + Time('00:59') + Time('00:00 11:30 00:01') + Day('sunday') + Day(Days.monday) +\ - Date(1,1,0) + Date(28,2,1960) + Autocancel(3) - - .. warning:: We can only use '+' when the left most object is a node, i.e Task('t1') in this case - - -.. py:method:: Node.add_autoarchive( (Node)arg1, (int)days [, (bool)idle=False]) -> Node : +.. py:method:: Node.add(self: ecflow.Node, *args, **kwargs) -> object :module: ecflow +add(..) provides a way to append Nodes and attributes + +This is best illustrated with an example: + +.. code-block:: python + + defs = Defs().add( + Suite('s1').add( + Clock(1, 1, 2010, False), + Autocancel(1, 10, True), + Task('t1').add( + Edit({'a':'12', 'b':'bb'}, c='v',d='b'), + Edit(g='d'), + Edit(h=1), + Event(1), + Event(11,'event'), + Meter('meter',0,10,10), + Label('label','c'), + Trigger('1==1'), + Complete('1==1'), + Limit('limit',10),Limit('limit2',10), + InLimit('limitName','/limit',2), + Defstatus(DState.complete), + Today(0,30),Today('00:59'),Today('00:00 11:30 00:01'), + Time(0,30),Time('00:59'),Time('00:00 11:30 00:01'), + Day('sunday'),Day(Days.monday), + Date(1,1,0),Date(28,2,1960), + Autocancel(3) + ), + [ Family('f{}'.format(i)) for i in range(1,6)])) + +We can also use '+=' with a list here are a few examples: + +.. code-block:: python + + defs = Defs(); + defs += [ Suite('s2'),Edit({ 'x1':'y', 'aa1':'bb'}, a='v',b='b') ] + +.. code-block:: python + + defs += [ Suite('s{}'.format(i)) for i in range(1,6) ] + +.. code-block:: python + + defs = Defs() + defs += [ Suite('suite').add( + Task('x'), + Family('f').add( [ Task('t{}'.format(i)) for i in range(1,6)] ), + Task('y'), + [ Family('f{}'.format(i)) for i in range(1,6) ], + Edit(a='b'), + [ Task('t{}'.format(i)) for i in range(1,6) ], + )] + +It is also possible to use '+' + +.. code-block:: python + + defs = Defs() + Suite('s1') + defs.s1 += Autocancel(1, 10, True) + defs.s1 += Task('t1') + Edit({ 'e':1, 'f':'bb'}) +\ + Event(1) + Event(11,'event') + Meter('meter',0,10,10) + Label('label','c') + Trigger('1==1') +\ + Complete('1==1') + Limit('limit',10) + Limit('limit2',10) + InLimit('limitName','/limit',2) +\ + Defstatus(DState.complete) + Today(0,30) + Today('00:59') + Today('00:00 11:30 00:01') +\ + Time(0,30) + Time('00:59') + Time('00:00 11:30 00:01') + Day('sunday') + Day(Days.monday) +\ + Date(1,1,0) + Date(28,2,1960) + Autocancel(3) + +.. warning:: We can only use '+' when the left most object is a node, i.e Task('t1') in this case + + +.. py:method:: Node.add_autoarchive(*args, **kwargs) + :module: ecflow + +Overloaded function. + +1. add_autoarchive(self: ecflow.Node, days: typing.SupportsInt | typing.SupportsIndex, idle: bool = False) -> ecflow.Node + Add a `autoarchive` attribute. See :py:class:`ecflow.Autoarchive` - - Provides a way to automatically archive a suite/family which has completed.(i.e remove children) - This is required when dealing with super large suite/families, they can be archived off, and then restored later. - The node can be recovered using 'autorestore',begin,re-queue and manually via ecflow_client --restore. - The archived node is written to disk, as ECF_HOME/..ECF_NAME.check, - where '/' is replaced with ':' in ECF_NAME. - The removal may be delayed by an amount of time in hours and minutes or expressed as days - Node removal is not immediate. The nodes are checked once a minute - A Node may only have one autoarchive attribute - - Exception: - - - Throws a RuntimeError if more than one auto archive is added - - Usage: - - .. code-block:: python - - t1 = Task('t1') - t1.add_autoarchive( Autoarchive(20,10,False) ) # hour,min, relative - t2 = Task('t2') - t2.add_autoarchive( 3 ) # 3 days - t3 = Task('t3') - t3.add_autoarchive( 20,10,True ) # hour,minutes,relative - t4 = Task('t4') - t4.add_autoarchive( TimeSlot(20,10),True ) # hour,minutes,relative - - # we can also create a Autoarchive in the Task constructor like any other attribute - t2 = Task('t2', - Autoarchive(20,10,False)) - - -add_autoarchive( (Node)arg1, (int)hour, (int)min, (bool)relative [, (bool)idle=False]) -> Node - -add_autoarchive( (Node)arg1, (TimeSlot)TimeSlot, (bool)relative [, (bool)idle=False]) -> Node - -add_autoarchive( (Node)arg1, (Autoarchive)arg2) -> Node - - -.. py:method:: Node.add_autocancel( (Node)arg1, (int)arg2) -> Node : + +Provides a way to automatically archive a suite/family which has completed.(i.e remove children) +This is required when dealing with super large suite/families, they can be archived off, and then restored later. +The node can be recovered using 'autorestore',begin,re-queue and manually via ecflow_client --restore. +The archived node is written to disk, as ECF_HOME/..ECF_NAME.check, +where '/' is replaced with ':' in ECF_NAME. +The removal may be delayed by an amount of time in hours and minutes or expressed as days +Node removal is not immediate. The nodes are checked once a minute +A Node may only have one autoarchive attribute + +Exception: + +- Throws a RuntimeError if more than one auto archive is added + +Usage: + +.. code-block:: python + + t1 = Task('t1') + t1.add_autoarchive( Autoarchive(20,10,False) ) # hour,min, relative + t2 = Task('t2') + t2.add_autoarchive( 3 ) # 3 days + t3 = Task('t3') + t3.add_autoarchive( 20,10,True ) # hour,minutes,relative + t4 = Task('t4') + t4.add_autoarchive( TimeSlot(20,10),True ) # hour,minutes,relative + + # we can also create a Autoarchive in the Task constructor like any other attribute + t2 = Task('t2', + Autoarchive(20,10,False)) + + +2. add_autoarchive(self: ecflow.Node, hour: typing.SupportsInt | typing.SupportsIndex, min: typing.SupportsInt | typing.SupportsIndex, relative: bool, idle: bool = False) -> ecflow.Node + +3. add_autoarchive(self: ecflow.Node, TimeSlot: ecflow.TimeSlot, relative: bool, idle: bool = False) -> ecflow.Node + +4. add_autoarchive(self: ecflow.Node, arg0: ecflow.Autoarchive) -> ecflow.Node + + +.. py:method:: Node.add_autocancel(*args, **kwargs) :module: ecflow +Overloaded function. + +1. add_autocancel(self: ecflow.Node, arg0: typing.SupportsInt | typing.SupportsIndex) -> ecflow.Node + Add a `autocancel` attribute. See :py:class:`ecflow.Autocancel` - - This will delete the node on completion. The deletion may be delayed by - an amount of time in hours and minutes or expressed as days - Node deletion is not immediate. The nodes are checked once a minute - and expired auto cancel nodes are deleted - A node may only have one auto cancel attribute - - Exception: - - - Throws a RuntimeError if more than one auto cancel is added - - Usage: - - .. code-block:: python - - t1 = Task('t1') - t1.add_autocancel( Autocancel(20,10,False) ) # hour,min, relative - t2 = Task('t2') - t2.add_autocancel( 3 ) # 3 days - t3 = Task('t3') - t3.add_autocancel( 20,10,True ) # hour,minutes,relative - t4 = Task('t4') - t4.add_autocancel( TimeSlot(20,10),True ) # hour,minutes,relative - - # we can also create a Autocancel in the Task constructor like any other attribute - t2 = Task('t2', - Autocancel(20,10,False)) - - -add_autocancel( (Node)arg1, (int)arg2, (int)arg3, (bool)arg4) -> Node - -add_autocancel( (Node)arg1, (TimeSlot)arg2, (bool)arg3) -> Node - -add_autocancel( (Node)arg1, (Autocancel)arg2) -> Node - - -.. py:method:: Node.add_autorestore( (Node)arg1, (Autorestore)arg2) -> Node : + +This will delete the node on completion. The deletion may be delayed by +an amount of time in hours and minutes or expressed as days +Node deletion is not immediate. The nodes are checked once a minute +and expired auto cancel nodes are deleted +A node may only have one auto cancel attribute + +Exception: + +- Throws a RuntimeError if more than one auto cancel is added + +Usage: + +.. code-block:: python + + t1 = Task('t1') + t1.add_autocancel( Autocancel(20,10,False) ) # hour,min, relative + t2 = Task('t2') + t2.add_autocancel( 3 ) # 3 days + t3 = Task('t3') + t3.add_autocancel( 20,10,True ) # hour,minutes,relative + t4 = Task('t4') + t4.add_autocancel( TimeSlot(20,10),True ) # hour,minutes,relative + + # we can also create a Autocancel in the Task constructor like any other attribute + t2 = Task('t2', + Autocancel(20,10,False)) + + +2. add_autocancel(self: ecflow.Node, arg0: typing.SupportsInt | typing.SupportsIndex, arg1: typing.SupportsInt | typing.SupportsIndex, arg2: bool) -> ecflow.Node + +3. add_autocancel(self: ecflow.Node, arg0: ecflow.TimeSlot, arg1: bool) -> ecflow.Node + +4. add_autocancel(self: ecflow.Node, arg0: ecflow.Autocancel) -> ecflow.Node + + +.. py:method:: Node.add_autorestore(*args, **kwargs) :module: ecflow +Overloaded function. + +1. add_autorestore(self: ecflow.Node, arg0: ecflow.Autorestore) -> ecflow.Node + Add a `autorestore` attribute. See :py:class:`ecflow.Autorestore` - - Auto-restore is used to automatically restore a previously auto-archived node. - The restore will fail if: - - - The node has not been archived - - The node has children. - - The file ECF_HOME/..ECF_NAME.check does not exist - - Exception: - - - Throws a RuntimeError if more than one autorestore is added - - Usage: - - .. code-block:: python - - t1 = Task('t1') - t1.add_autorestore( ['/s1/f1'] ) - t2 = Task('t2') - t2.add_autorestore( Autorestore(['/s2/f1','/s1/f2']) ) - # we can also create a Autorestore in the Task constructor like any other attribute - t2 = Task('t2', Autorestore(['/s2/f1','/s1/f2'] )) - - -add_autorestore( (Node)arg1, (list)arg2) -> Node - - -.. py:method:: Node.add_aviso( (Node)arg1, (AvisoAttr)arg2) -> Node : + +Auto-restore is used to automatically restore a previously auto-archived node. +The restore will fail if: + + - The node has not been archived + - The node has children. + - The file ECF_HOME/..ECF_NAME.check does not exist + +Exception: + +- Throws a RuntimeError if more than one autorestore is added + +Usage: + +.. code-block:: python + + t1 = Task('t1') + t1.add_autorestore( ['/s1/f1'] ) + t2 = Task('t2') + t2.add_autorestore( Autorestore(['/s2/f1','/s1/f2']) ) + # we can also create a Autorestore in the Task constructor like any other attribute + t2 = Task('t2', Autorestore(['/s2/f1','/s1/f2'] )) + + +2. add_autorestore(self: ecflow.Node, arg0: list) -> ecflow.Node + + +.. py:method:: Node.add_aviso(self: ecflow.Node, arg0: ecflow.AvisoAttr) -> ecflow.Node :module: ecflow Adds an :term:`aviso` to a :term:`node`. See :py:class:`ecflow.Aviso` - -.. py:method:: Node.add_complete( (Node)arg1, (str)arg2) -> Node : +.. py:method:: Node.add_complete(*args, **kwargs) :module: ecflow +Overloaded function. + +1. add_complete(self: ecflow.Node, arg0: str) -> ecflow.Node + Add a :term:`trigger` or :term:`complete expression`.Also see :py:class:`ecflow.Trigger` - - This defines a dependency for a :term:`node`. - There can only be one :term:`trigger` or :term:`complete expression` dependency per node. - A :term:`node` with a trigger can only be activated when the trigger has expired. - A trigger holds a node as long as the expression returns false. - - Exception: - - - Will throw RuntimeError if multiple trigger or complete expression are added - - Will throw RuntimeError if first expression is added as 'AND' or 'OR' expression - Like wise second and subsequent expression must have 'AND' or 'OR' booleans set - - Usage: - - Note we cannot make multiple add_trigger(..) calls on the same :term:`task`! - to add a simple trigger: - - .. code-block:: python - - task1.add_trigger( 't2 == active' ) - task2.add_trigger( 't1 == complete or t4 == complete' ) - task3.add_trigger( 't5 == active' ) - - Long expression can be broken up using add_part_trigger: - - .. code-block:: python - - task2.add_part_trigger( 't1 == complete or t4 == complete') - task2.add_part_trigger( 't5 == active',True) # True means AND - task2.add_part_trigger( 't7 == active',False) # False means OR - - The trigger for task2 is equivalent to: - 't1 == complete or t4 == complete and t5 == active or t7 == active' - -add_complete( (Node)arg1, (Expression)arg2) -> Node - - -.. py:method:: Node.add_cron( (Node)arg1, (Cron)arg2) -> Node : + +This defines a dependency for a :term:`node`. +There can only be one :term:`trigger` or :term:`complete expression` dependency per node. +A :term:`node` with a trigger can only be activated when the trigger has expired. +A trigger holds a node as long as the expression returns false. + +Exception: + +- Will throw RuntimeError if multiple trigger or complete expression are added +- Will throw RuntimeError if first expression is added as 'AND' or 'OR' expression + Like wise second and subsequent expression must have 'AND' or 'OR' booleans set + +Usage: + +Note we cannot make multiple add_trigger(..) calls on the same :term:`task`! +to add a simple trigger: + +.. code-block:: python + + task1.add_trigger( 't2 == active' ) + task2.add_trigger( 't1 == complete or t4 == complete' ) + task3.add_trigger( 't5 == active' ) + +Long expression can be broken up using add_part_trigger: + +.. code-block:: python + + task2.add_part_trigger( 't1 == complete or t4 == complete') + task2.add_part_trigger( 't5 == active',True) # True means AND + task2.add_part_trigger( 't7 == active',False) # False means OR + +The trigger for task2 is equivalent to: +'t1 == complete or t4 == complete and t5 == active or t7 == active' + +2. add_complete(self: ecflow.Node, arg0: ecflow.Expression) -> ecflow.Node + + +.. py:method:: Node.add_cron(self: ecflow.Node, arg0: ecflow.Cron) -> ecflow.Node :module: ecflow Add a :term:`cron` time dependency. See :py:class:`ecflow.Cron` @@ -275,192 +289,220 @@ Usage: Cron('+00:00 23:00 00:30',days_of_week=[0,1,2,3,4,5,6],days_of_month=[1,2,3,4,5,6],months=[1,2,3,4,5,6])) -.. py:method:: Node.add_date( (Node)arg1, (int)arg2, (int)arg3, (int)arg4) -> Node : +.. py:method:: Node.add_date(*args, **kwargs) :module: ecflow +Overloaded function. + +1. add_date(self: ecflow.Node, arg0: typing.SupportsInt | typing.SupportsIndex, arg1: typing.SupportsInt | typing.SupportsIndex, arg2: typing.SupportsInt | typing.SupportsIndex) -> ecflow.Node + Add a :term:`date` time dependency. See :py:class:`ecflow.Date` - - A value of zero for day,month,year means every day, every month, every year - - Exception: - - - Throws RuntimeError if an invalid date is added - - Usage: - - .. code-block:: python - - t1 = Task('t1', - Date('1.*.*'), - Date(1,1,2010))) # Create Date in place - - t1.add_date( Date(1,1,2010) ) # day,month,year - t1.add_date( 2,1,2010) # day,month,year - t1.add_date( 1,0,0) # day,month,year, the first of each month for every year - - -add_date( (Node)arg1, (Date)arg2) -> Node - - -.. py:method:: Node.add_day( (Node)arg1, (Days)arg2) -> Node : + +A value of zero for day,month,year means every day, every month, every year + +Exception: + +- Throws RuntimeError if an invalid date is added + +Usage: + +.. code-block:: python + + t1 = Task('t1', + Date('1.*.*'), + Date(1,1,2010))) # Create Date in place + + t1.add_date( Date(1,1,2010) ) # day,month,year + t1.add_date( 2,1,2010) # day,month,year + t1.add_date( 1,0,0) # day,month,year, the first of each month for every year + + +2. add_date(self: ecflow.Node, arg0: ecflow.Date) -> ecflow.Node + + +.. py:method:: Node.add_day(*args, **kwargs) :module: ecflow +Overloaded function. + +1. add_day(self: ecflow.Node, arg0: ecflow.Days) -> ecflow.Node + Add a :term:`day` time dependency. See :py:class:`ecflow.Day` - - - Usage: - - .. code-block:: python - - t1 = Task('t1', - Day('sunday')) # Create Day on Task creation - - t1.add_day( Day(Days.sunday) ) - t1.add_day( Days.monday) - t1.add_day( 'tuesday' ) - -add_day( (Node)arg1, (str)arg2) -> Node -add_day( (Node)arg1, (Day)arg2) -> Node +Usage: +.. code-block:: python -.. py:method:: Node.add_defstatus( (Node)arg1, (DState)arg2) -> Node : + t1 = Task('t1', + Day('sunday')) # Create Day on Task creation + + t1.add_day( Day(Days.sunday) ) + t1.add_day( Days.monday) + t1.add_day( 'tuesday' ) + + +2. add_day(self: ecflow.Node, arg0: str) -> ecflow.Node + +3. add_day(self: ecflow.Node, arg0: ecflow.Day) -> ecflow.Node + + +.. py:method:: Node.add_defstatus(*args, **kwargs) :module: ecflow +Overloaded function. + +1. add_defstatus(self: ecflow.Node, arg0: ecflow.DState) -> ecflow.Node + +Set the default status( :term:`defstatus` ) of node at begin or re queue. See :py:class:`ecflow.Defstatus` + +A :term:`defstatus` is useful in preventing suites from running automatically +once begun, or in setting Task's complete so they can be run selectively + +Usage: + +.. code-block:: python + + t1 = Task('t1') + Defstatus('complete') + t2 = Task('t2').add_defstatus( DState.suspended ) + + # we can also create a Defstatus in the Task constructor like any other attribute + t2 = Task('t3', + Defstatus('complete')) + + +2. add_defstatus(self: ecflow.Node, arg0: ecflow.Defstatus) -> ecflow.Node + Set the default status( :term:`defstatus` ) of node at begin or re queue. See :py:class:`ecflow.Defstatus` - - A :term:`defstatus` is useful in preventing suites from running automatically - once begun, or in setting Task's complete so they can be run selectively - - Usage: - - .. code-block:: python - - t1 = Task('t1') + Defstatus('complete') - t2 = Task('t2').add_defstatus( DState.suspended ) - - # we can also create a Defstatus in the Task constructor like any other attribute - t2 = Task('t3', - Defstatus('complete')) - - -add_defstatus( (Node)arg1, (Defstatus)arg2) -> Node : - Set the default status( :term:`defstatus` ) of node at begin or re queue. See :py:class:`ecflow.Defstatus` - - A :term:`defstatus` is useful in preventing suites from running automatically - once begun, or in setting Task's complete so they can be run selectively - - Usage: - - .. code-block:: python - - t1 = Task('t1') + Defstatus('complete') - t2 = Task('t2').add_defstatus( DState.suspended ) - - # we can also create a Defstatus in the Task constructor like any other attribute - t2 = Task('t3', - Defstatus('complete')) - - - -.. py:method:: Node.add_event( (Node)arg1, (Event)arg2) -> Node : + +A :term:`defstatus` is useful in preventing suites from running automatically +once begun, or in setting Task's complete so they can be run selectively + +Usage: + +.. code-block:: python + + t1 = Task('t1') + Defstatus('complete') + t2 = Task('t2').add_defstatus( DState.suspended ) + + # we can also create a Defstatus in the Task constructor like any other attribute + t2 = Task('t3', + Defstatus('complete')) + + +.. py:method:: Node.add_event(*args, **kwargs) :module: ecflow +Overloaded function. + +1. add_event(self: ecflow.Node, arg0: ecflow.Event) -> ecflow.Node + Add a :term:`event`. See :py:class:`ecflow.Event` - Events can be referenced in :term:`trigger` and :term:`complete expression`\ s - - - Exception: - - - Throws RuntimeError if a duplicate is added - - Usage: - - .. code-block:: python - - t1 = Task('t1', - Event(12), - Event(11,'eventx')) # Create events on Task creation - - t1.add_event( Event(10) ) # Create with function on Task - t1.add_event( Event(11,'Eventname') ) - t1.add_event( 12 ) - t1.add_event( 13, 'name') - - To reference event 'flag' in a trigger: - - .. code-block:: python - - t1.add_event('flag') - t2 = Task('t2', - Trigger('t1:flag == set')) +Events can be referenced in :term:`trigger` and :term:`complete expression`\ s + + +Exception: + +- Throws RuntimeError if a duplicate is added + +Usage: + +.. code-block:: python -add_event( (Node)arg1, (int)arg2) -> Node + t1 = Task('t1', + Event(12), + Event(11,'eventx')) # Create events on Task creation -add_event( (Node)arg1, (int)arg2, (str)arg3) -> Node + t1.add_event( Event(10) ) # Create with function on Task + t1.add_event( Event(11,'Eventname') ) + t1.add_event( 12 ) + t1.add_event( 13, 'name') -add_event( (Node)arg1, (str)arg2) -> Node +To reference event 'flag' in a trigger: +.. code-block:: python + + t1.add_event('flag') + t2 = Task('t2', + Trigger('t1:flag == set')) + +2. add_event(self: ecflow.Node, arg0: typing.SupportsInt | typing.SupportsIndex) -> ecflow.Node -.. py:method:: Node.add_generic( (Node)arg1, (Generic)arg2) -> Node +3. add_event(self: ecflow.Node, arg0: str) -> ecflow.Node + +4. add_event(self: ecflow.Node, arg0: typing.SupportsInt | typing.SupportsIndex, arg1: str) -> ecflow.Node + + +.. py:method:: Node.add_generic(*args, **kwargs) :module: ecflow -add_generic( (Node)arg1, (str)arg2, (list)arg3) -> Node +Overloaded function. + +1. add_generic(self: ecflow.Node, arg0: ecflow.Generic) -> ecflow.Node +2. add_generic(self: ecflow.Node, arg0: str, arg1: list) -> ecflow.Node -.. py:method:: Node.add_inlimit( (Node)arg1, (str)limit_name [, (str)path_to_node_containing_limit='' [, (int)tokens=1 [, (bool)limit_this_node_only=False]]]) -> Node : + +.. py:method:: Node.add_inlimit(*args, **kwargs) :module: ecflow +Overloaded function. + +1. add_inlimit(self: ecflow.Node, limit_name: str, path_to_node_containing_limit: str = '', tokens: typing.SupportsInt | typing.SupportsIndex = 1, limit_this_node_only: bool = False) -> ecflow.Node + Adds a :term:`inlimit` to a :term:`node`. See :py:class:`ecflow.InLimit` - - InLimit reference a :term:`limit`/:py:class:`ecflow.Limit`. Duplicate InLimits are not allowed - - Exception: - - - Throws RuntimeError if a duplicate is added - - Usage: - - .. code-block:: python - - task2.add_inlimit( InLimit('limitName','/s1/f1',2) ) - task2.add_inlimit( 'limitName','/s1/f1',2 ) - -add_inlimit( (Node)arg1, (InLimit)arg2) -> Node +InLimit reference a :term:`limit`/:py:class:`ecflow.Limit`. Duplicate InLimits are not allowed + +Exception: + +- Throws RuntimeError if a duplicate is added +Usage: -.. py:method:: Node.add_label( (Node)arg1, (str)arg2, (str)arg3) -> Node : +.. code-block:: python + + task2.add_inlimit( InLimit('limitName','/s1/f1',2) ) + task2.add_inlimit( 'limitName','/s1/f1',2 ) + + +2. add_inlimit(self: ecflow.Node, arg0: ecflow.InLimit) -> ecflow.Node + + +.. py:method:: Node.add_label(*args, **kwargs) :module: ecflow +Overloaded function. + +1. add_label(self: ecflow.Node, arg0: str, arg1: str) -> ecflow.Node + Adds a :term:`label` to a :term:`node`. See :py:class:`ecflow.Label` - - Labels can be updated from the jobs files, via :term:`child command` - - Exception: - - - Throws RuntimeError if a duplicate label name is added - - Usage: - - .. code-block:: python - - task.add_label( Label('TEA','/me/')) - task.add_label( 'Joe','/me/') - - The corresponding child command in the .ecf script file might be: - - .. code-block:: shell - - ecflow_client --label=TEA time - ecflow_client --label=Joe ninety - - -add_label( (Node)arg1, (Label)arg2) -> Node - - -.. py:method:: Node.add_late( (Node)arg1, (Late)arg2) -> Node : + +Labels can be updated from the jobs files, via :term:`child command` + +Exception: + +- Throws RuntimeError if a duplicate label name is added + +Usage: + +.. code-block:: python + + task.add_label( Label('TEA','/me/')) + task.add_label( 'Joe','/me/') + +The corresponding child command in the .ecf script file might be: + +.. code-block:: shell + + ecflow_client --label=TEA time + ecflow_client --label=Joe ninety + + +2. add_label(self: ecflow.Node, arg0: ecflow.Label) -> ecflow.Node + + +.. py:method:: Node.add_late(self: ecflow.Node, arg0: ecflow.Late) -> ecflow.Node :module: ecflow Add a :term:`late` attribute. See :py:class:`ecflow.Late` @@ -486,466 +528,536 @@ Usage: Late(submitted='20:10',active='20:10',complete='+20:10')) -.. py:method:: Node.add_limit( (Node)arg1, (str)arg2, (int)arg3) -> Node : +.. py:method:: Node.add_limit(*args, **kwargs) :module: ecflow +Overloaded function. + +1. add_limit(self: ecflow.Node, arg0: str, arg1: typing.SupportsInt | typing.SupportsIndex) -> ecflow.Node + Adds a :term:`limit` to a :term:`node` for simple load management. See :py:class:`ecflow.Limit` - - Multiple limits can be added, however the limit name must be unique. - For a node to be in a limit, a :term:`inlimit` must be used. - - Exception: - - - Throws RuntimeError if a duplicate limit name is added - - Usage: - - .. code-block:: python - - family.add_limit( Limit('load',12) ) - family.add_limit( 'load',12 ) - -add_limit( (Node)arg1, (Limit)arg2) -> Node +Multiple limits can be added, however the limit name must be unique. +For a node to be in a limit, a :term:`inlimit` must be used. + +Exception: + +- Throws RuntimeError if a duplicate limit name is added + +Usage: +.. code-block:: python -.. py:method:: Node.add_meter( (Node)arg1, (Meter)arg2) -> Node : + family.add_limit( Limit('load',12) ) + family.add_limit( 'load',12 ) + + +2. add_limit(self: ecflow.Node, arg0: ecflow.Limit) -> ecflow.Node + + +.. py:method:: Node.add_meter(*args, **kwargs) :module: ecflow +Overloaded function. + +1. add_meter(self: ecflow.Node, arg0: ecflow.Meter) -> ecflow.Node + Add a :term:`meter`. See :py:class:`ecflow.Meter` - Meters can be referenced in :term:`trigger` and :term:`complete expression`\ s - - - Exception: - - - Throws RuntimeError if a duplicate is added - - Usage: - - .. code-block:: python - - t1 = Task('t1', - Meter('met',0,50)) # create Meter on Task creation - t1.add_meter( Meter('metername',0,100,50) ) # create Meter using function - t1.add_meter( 'meter',0,200) - - To reference in a trigger: - - .. code-block:: python - - t2 = Task('t2') - t2.add_trigger('t1:meter >= 10') - - -add_meter( (Node)arg1, (str)arg2, (int)arg3, (int)arg4 [, (int)arg5]) -> Node - - -.. py:method:: Node.add_mirror( (Node)arg1, (MirrorAttr)arg2) -> Node : +Meters can be referenced in :term:`trigger` and :term:`complete expression`\ s + + +Exception: + +- Throws RuntimeError if a duplicate is added + +Usage: + +.. code-block:: python + + t1 = Task('t1', + Meter('met',0,50)) # create Meter on Task creation + t1.add_meter( Meter('metername',0,100,50) ) # create Meter using function + t1.add_meter( 'meter',0,200) + +To reference in a trigger: + +.. code-block:: python + + t2 = Task('t2') + t2.add_trigger('t1:meter >= 10') + + +2. add_meter(self: ecflow.Node, arg0: str, arg1: typing.SupportsInt | typing.SupportsIndex, arg2: typing.SupportsInt | typing.SupportsIndex, arg3: typing.SupportsInt | typing.SupportsIndex) -> ecflow.Node + +3. add_meter(self: ecflow.Node, arg0: str, arg1: typing.SupportsInt | typing.SupportsIndex, arg2: typing.SupportsInt | typing.SupportsIndex) -> ecflow.Node + + +.. py:method:: Node.add_mirror(self: ecflow.Node, arg0: ecflow.MirrorAttr) -> ecflow.Node :module: ecflow Adds a :term:`mirror` to a :term:`node`. See :py:class:`ecflow.Mirror` - -.. py:method:: Node.add_part_complete( (Node)arg1, (PartExpression)arg2) -> Node : +.. py:method:: Node.add_part_complete(*args, **kwargs) :module: ecflow +Overloaded function. + +1. add_part_complete(self: ecflow.Node, arg0: ecflow.PartExpression) -> ecflow.Node + Add a :term:`trigger` or :term:`complete expression`.Also see :py:class:`ecflow.Trigger` - - This defines a dependency for a :term:`node`. - There can only be one :term:`trigger` or :term:`complete expression` dependency per node. - A :term:`node` with a trigger can only be activated when the trigger has expired. - A trigger holds a node as long as the expression returns false. - - Exception: - - - Will throw RuntimeError if multiple trigger or complete expression are added - - Will throw RuntimeError if first expression is added as 'AND' or 'OR' expression - Like wise second and subsequent expression must have 'AND' or 'OR' booleans set - - Usage: - - Note we cannot make multiple add_trigger(..) calls on the same :term:`task`! - to add a simple trigger: - - .. code-block:: python - - task1.add_trigger( 't2 == active' ) - task2.add_trigger( 't1 == complete or t4 == complete' ) - task3.add_trigger( 't5 == active' ) - - Long expression can be broken up using add_part_trigger: - - .. code-block:: python - - task2.add_part_trigger( 't1 == complete or t4 == complete') - task2.add_part_trigger( 't5 == active',True) # True means AND - task2.add_part_trigger( 't7 == active',False) # False means OR - - The trigger for task2 is equivalent to: - 't1 == complete or t4 == complete and t5 == active or t7 == active' - -add_part_complete( (Node)arg1, (str)arg2) -> Node - -add_part_complete( (Node)arg1, (str)arg2, (bool)arg3) -> Node - - -.. py:method:: Node.add_part_trigger( (Node)arg1, (PartExpression)arg2) -> Node : + +This defines a dependency for a :term:`node`. +There can only be one :term:`trigger` or :term:`complete expression` dependency per node. +A :term:`node` with a trigger can only be activated when the trigger has expired. +A trigger holds a node as long as the expression returns false. + +Exception: + +- Will throw RuntimeError if multiple trigger or complete expression are added +- Will throw RuntimeError if first expression is added as 'AND' or 'OR' expression + Like wise second and subsequent expression must have 'AND' or 'OR' booleans set + +Usage: + +Note we cannot make multiple add_trigger(..) calls on the same :term:`task`! +to add a simple trigger: + +.. code-block:: python + + task1.add_trigger( 't2 == active' ) + task2.add_trigger( 't1 == complete or t4 == complete' ) + task3.add_trigger( 't5 == active' ) + +Long expression can be broken up using add_part_trigger: + +.. code-block:: python + + task2.add_part_trigger( 't1 == complete or t4 == complete') + task2.add_part_trigger( 't5 == active',True) # True means AND + task2.add_part_trigger( 't7 == active',False) # False means OR + +The trigger for task2 is equivalent to: +'t1 == complete or t4 == complete and t5 == active or t7 == active' + +2. add_part_complete(self: ecflow.Node, arg0: str) -> ecflow.Node + +3. add_part_complete(self: ecflow.Node, arg0: str, arg1: bool) -> ecflow.Node + + +.. py:method:: Node.add_part_trigger(*args, **kwargs) :module: ecflow +Overloaded function. + +1. add_part_trigger(self: ecflow.Node, arg0: ecflow.PartExpression) -> ecflow.Node + Add a :term:`trigger` or :term:`complete expression`.Also see :py:class:`ecflow.Trigger` - - This defines a dependency for a :term:`node`. - There can only be one :term:`trigger` or :term:`complete expression` dependency per node. - A :term:`node` with a trigger can only be activated when the trigger has expired. - A trigger holds a node as long as the expression returns false. - - Exception: - - - Will throw RuntimeError if multiple trigger or complete expression are added - - Will throw RuntimeError if first expression is added as 'AND' or 'OR' expression - Like wise second and subsequent expression must have 'AND' or 'OR' booleans set - - Usage: - - Note we cannot make multiple add_trigger(..) calls on the same :term:`task`! - to add a simple trigger: - - .. code-block:: python - - task1.add_trigger( 't2 == active' ) - task2.add_trigger( 't1 == complete or t4 == complete' ) - task3.add_trigger( 't5 == active' ) - - Long expression can be broken up using add_part_trigger: - - .. code-block:: python - - task2.add_part_trigger( 't1 == complete or t4 == complete') - task2.add_part_trigger( 't5 == active',True) # True means AND - task2.add_part_trigger( 't7 == active',False) # False means OR - - The trigger for task2 is equivalent to: - 't1 == complete or t4 == complete and t5 == active or t7 == active' - -add_part_trigger( (Node)arg1, (str)arg2) -> Node - -add_part_trigger( (Node)arg1, (str)arg2, (bool)arg3) -> Node - - -.. py:method:: Node.add_queue( (Node)arg1, (Queue)arg2) -> Node - :module: ecflow - -add_queue( (Node)arg1, (str)arg2, (list)arg3) -> Node - - -.. py:method:: Node.add_repeat( (Node)arg1, (RepeatDate)arg2) -> Node : + +This defines a dependency for a :term:`node`. +There can only be one :term:`trigger` or :term:`complete expression` dependency per node. +A :term:`node` with a trigger can only be activated when the trigger has expired. +A trigger holds a node as long as the expression returns false. + +Exception: + +- Will throw RuntimeError if multiple trigger or complete expression are added +- Will throw RuntimeError if first expression is added as 'AND' or 'OR' expression + Like wise second and subsequent expression must have 'AND' or 'OR' booleans set + +Usage: + +Note we cannot make multiple add_trigger(..) calls on the same :term:`task`! +to add a simple trigger: + +.. code-block:: python + + task1.add_trigger( 't2 == active' ) + task2.add_trigger( 't1 == complete or t4 == complete' ) + task3.add_trigger( 't5 == active' ) + +Long expression can be broken up using add_part_trigger: + +.. code-block:: python + + task2.add_part_trigger( 't1 == complete or t4 == complete') + task2.add_part_trigger( 't5 == active',True) # True means AND + task2.add_part_trigger( 't7 == active',False) # False means OR + +The trigger for task2 is equivalent to: +'t1 == complete or t4 == complete and t5 == active or t7 == active' + +2. add_part_trigger(self: ecflow.Node, arg0: str) -> ecflow.Node + +3. add_part_trigger(self: ecflow.Node, arg0: str, arg1: bool) -> ecflow.Node + + +.. py:method:: Node.add_queue(*args, **kwargs) :module: ecflow +Overloaded function. + +1. add_queue(self: ecflow.Node, arg0: ecflow.Queue) -> ecflow.Node + +2. add_queue(self: ecflow.Node, arg0: str, arg1: list) -> ecflow.Node + + +.. py:method:: Node.add_repeat(*args, **kwargs) + :module: ecflow + +Overloaded function. + +1. add_repeat(self: ecflow.Node, arg0: ecflow.RepeatDate) -> ecflow.Node + Add a RepeatDate attribute. See :py:class:`ecflow.RepeatDate` - - A node can only have one repeat - Reference to a RepeatDate in a trigger will use date arithmetic in a sub expression. i.e. - Here (/suite/family:YMD + 1) uses date arithmetic only, the result is still an integer - - trigger /suite/family:YMD + 1 > 20190101 - - Exception: - - - Throws a RuntimeError if more than one repeat is added - - Usage: - - .. code-block:: python - - t1 = Task('t1') - t1.add_repeat( RepeatDate('YMD',20100111,20100115) ) - - # we can also create a repeat in Task constructor like any other attribute - t2 = Task('t2', - RepeatDate('YMD',20100111,20100115)) - - -add_repeat( (Node)arg1, (RepeatDateTime)arg2) -> Node : - Add a RepeatDateTime attribute. See :py:class:`ecflow.RepeatDateTime` - - A node can only have one repeat. - When a RepeatDateTime is used in a trigger expression, the arithmetic value of the Repeat decays to second. - For example, the expression `/suite/family:DATETIME + 1` is evaluated as the number of seconds represented by `/suite/family:DT` (since the reference epoch, i.e. 19700101T000000) plus 1.The result is an integer. - - trigger /suite/family:DT + 1 > 123456 - - Exception: - - - Throws a RuntimeError if more than one repeat is added - - Usage: - - .. code-block:: python - - t1 = Task('t1') - t1.add_repeat(RepeatDateTime('DT', '20100111T120000', '20100115T000000', '12:00:00')) - - # we can also create a repeat in Task constructor like any other attribute - t2 = Task('t2', - RepeatDateTime('DT', '20100101T000000', '20100115T000000', '1:00:00')) - - -add_repeat( (Node)arg1, (RepeatDateList)arg2) -> Node : - Add a RepeatDateList attribute. See :py:class:`ecflow.RepeatDateList` - - A node can only have one repeat - Reference to a RepeatDateList in a trigger will use date arithmetic. i.e. - Here (/suite/family:YMD + 1) uses date arithmetic only, the result is still an integer: - - .. code-block:: python - - trigger /suite/family:YMD + 1 > 20190101 - - - Exception: - - - Throws a RuntimeError if more than one repeat is added - - Usage: - - .. code-block:: python - - t1 = Task('t1') - t1.add_repeat( RepeatDateList('YMD',[20100111,20100115]) ) - - # we can also create a repeat in Task constructor like any other attribute - t2 = Task('t2', - RepeatDateList('YMD',[20100111,20100115])) - - -add_repeat( (Node)arg1, (RepeatInteger)arg2) -> Node : - Add a RepeatInteger attribute. See :py:class:`ecflow.RepeatInteger` - - A node can only have one :term:`repeat` - - Exception: - - - Throws a RuntimeError if more than one repeat is added - - Usage: - - .. code-block:: python - - t1 = Task('t1') - t1.add_repeat( RepeatInteger('testInteger',0,100,2) ) - - # we can also create a repeat in Task constructor like any other attribute - t2 = Task('t2', - RepeatInteger('testInteger',0,100,2)) - - -add_repeat( (Node)arg1, (RepeatString)arg2) -> Node : - Add a RepeatString attribute. See :py:class:`ecflow.RepeatString` - - A node can only have one :term:`repeat` - - Exception: - - - Throws a RuntimeError if more than one repeat is added - - Usage: - - .. code-block:: python - - t1 = Task('t1') - t1.add_repeat( RepeatString('test_string',['a', 'b', 'c' ] ) ) - - # we can also create a repeat in Task constructor like any other attribute - t2 = Task('t2', - RepeatString('test_string',['a', 'b', 'c' ] ) ) - - -add_repeat( (Node)arg1, (RepeatEnumerated)arg2) -> Node : - Add a RepeatEnumerated attribute. See :py:class:`ecflow.RepeatEnumerated` - - A node can only have one :term:`repeat` - - Exception: - - - Throws a RuntimeError if more than one repeat is added - - Usage: - - .. code-block:: python - - t1 = Task('t1') - t1.add_repeat( RepeatEnumerated('test_string', ['red', 'green', 'blue' ] ) ) - - # we can also create a repeat in Task constructor like any other attribute - t2 = Task('t2', - RepeatEnumerated('test_string', ['red', 'green', 'blue' ] ) ) - - -add_repeat( (Node)arg1, (RepeatDay)arg2) -> Node : - Add a RepeatDay attribute. See :py:class:`ecflow.RepeatDay` - - A node can only have one :term:`repeat` - - Exception: - - - Throws a RuntimeError if more than one repeat is added - - Usage: - - .. code-block:: python - - t2 = Task('t2', - RepeatDay(1)) - - - -.. py:method:: Node.add_time( (Node)arg1, (int)arg2, (int)arg3) -> Node : + +A node can only have one repeat +Reference to a RepeatDate in a trigger will use date arithmetic in a sub expression. i.e. +Here (/suite/family:YMD + 1) uses date arithmetic only, the result is still an integer + + trigger /suite/family:YMD + 1 > 20190101 + +Exception: + +- Throws a RuntimeError if more than one repeat is added + +Usage: + +.. code-block:: python + + t1 = Task('t1') + t1.add_repeat( RepeatDate('YMD',20100111,20100115) ) + + # we can also create a repeat in Task constructor like any other attribute + t2 = Task('t2', + RepeatDate('YMD',20100111,20100115)) + + +2. add_repeat(self: ecflow.Node, arg0: ecflow.RepeatDateTime) -> ecflow.Node + +Add a RepeatDateTime attribute. See :py:class:`ecflow.RepeatDateTime` + +A node can only have one repeat. +When a RepeatDateTime is used in a trigger expression, the arithmetic value of the Repeat decays to second. +For example, the expression `/suite/family:DATETIME + 1` is evaluated as the number of seconds represented by `/suite/family:DT` (since the reference epoch, i.e. 19700101T000000) plus 1.The result is an integer. + + trigger /suite/family:DT + 1 > 123456 + +Exception: + +- Throws a RuntimeError if more than one repeat is added + +Usage: + +.. code-block:: python + + t1 = Task('t1') + t1.add_repeat(RepeatDateTime('DT', '20100111T120000', '20100115T000000', '12:00:00')) + + # we can also create a repeat in Task constructor like any other attribute + t2 = Task('t2', + RepeatDateTime('DT', '20100101T000000', '20100115T000000', '1:00:00')) + + +3. add_repeat(self: ecflow.Node, arg0: ecflow.RepeatDateList) -> ecflow.Node + +Add a RepeatDateList attribute. See :py:class:`ecflow.RepeatDateList` + +A node can only have one repeat +Reference to a RepeatDateList in a trigger will use date arithmetic. i.e. +Here (/suite/family:YMD + 1) uses date arithmetic only, the result is still an integer: + +.. code-block:: python + + trigger /suite/family:YMD + 1 > 20190101 + + +Exception: + +- Throws a RuntimeError if more than one repeat is added + +Usage: + +.. code-block:: python + + t1 = Task('t1') + t1.add_repeat( RepeatDateList('YMD',[20100111,20100115]) ) + + # we can also create a repeat in Task constructor like any other attribute + t2 = Task('t2', + RepeatDateList('YMD',[20100111,20100115])) + + +4. add_repeat(self: ecflow.Node, arg0: ecflow.RepeatDateTimeList) -> ecflow.Node + +Add a RepeatDateTimeList attribute. See :py:class:`ecflow.RepeatDateTimeList` + +A node can only have one repeat + +Exception: + +- Throws a RuntimeError if more than one repeat is added + +Usage: + +.. code-block:: python + + t1 = Task('t1') + t1.add_repeat( RepeatDateTimeList('DT', ['20240101T000000', '20240102T120000']) ) + + # we can also create a repeat in Task constructor like any other attribute + t2 = Task('t2', + RepeatDateTimeList('DT', ['20240101T000000', '20240102T120000'])) + + +5. add_repeat(self: ecflow.Node, arg0: ecflow.RepeatInteger) -> ecflow.Node + +Add a RepeatInteger attribute. See :py:class:`ecflow.RepeatInteger` + +A node can only have one :term:`repeat` + +Exception: + +- Throws a RuntimeError if more than one repeat is added + +Usage: + +.. code-block:: python + + t1 = Task('t1') + t1.add_repeat( RepeatInteger('testInteger',0,100,2) ) + + # we can also create a repeat in Task constructor like any other attribute + t2 = Task('t2', + RepeatInteger('testInteger',0,100,2)) + + +6. add_repeat(self: ecflow.Node, arg0: ecflow.RepeatString) -> ecflow.Node + +Add a RepeatString attribute. See :py:class:`ecflow.RepeatString` + +A node can only have one :term:`repeat` + +Exception: + +- Throws a RuntimeError if more than one repeat is added + +Usage: + +.. code-block:: python + + t1 = Task('t1') + t1.add_repeat( RepeatString('test_string',['a', 'b', 'c' ] ) ) + + # we can also create a repeat in Task constructor like any other attribute + t2 = Task('t2', + RepeatString('test_string',['a', 'b', 'c' ] ) ) + + +7. add_repeat(self: ecflow.Node, arg0: ecflow.RepeatEnumerated) -> ecflow.Node + +Add a RepeatEnumerated attribute. See :py:class:`ecflow.RepeatEnumerated` + +A node can only have one :term:`repeat` + +Exception: + +- Throws a RuntimeError if more than one repeat is added + +Usage: + +.. code-block:: python + + t1 = Task('t1') + t1.add_repeat( RepeatEnumerated('test_string', ['red', 'green', 'blue' ] ) ) + + # we can also create a repeat in Task constructor like any other attribute + t2 = Task('t2', + RepeatEnumerated('test_string', ['red', 'green', 'blue' ] ) ) + + +8. add_repeat(self: ecflow.Node, arg0: ecflow.RepeatDay) -> ecflow.Node + +Add a RepeatDay attribute. See :py:class:`ecflow.RepeatDay` + +A node can only have one :term:`repeat` + +Exception: + +- Throws a RuntimeError if more than one repeat is added + +Usage: + +.. code-block:: python + + t2 = Task('t2', + RepeatDay(1)) + + +.. py:method:: Node.add_time(*args, **kwargs) :module: ecflow +Overloaded function. + +1. add_time(self: ecflow.Node, arg0: typing.SupportsInt | typing.SupportsIndex, arg1: typing.SupportsInt | typing.SupportsIndex) -> ecflow.Node + Add a :term:`time` dependency. See :py:class:`ecflow.Time` - - - Usage: - - .. code-block:: python - - t1 = Task('t1', Time('+00:30 20:00 01:00')) # Create Time in Task constructor - t1.add_time( '00:30' ) - t1.add_time( '+00:30' ) - t1.add_time( '+00:30 20:00 01:00' ) - t1.add_time( Time( 0,10 )) # hour,min,relative =false - t1.add_time( Time( 0,12,True )) # hour,min,relative - t1.add_time( Time(TimeSlot(20,20),False)) - t1.add_time( 0,1 )) # hour,min,relative=false - t1.add_time( 0,3,False )) # hour,min,relative=false - start = TimeSlot(0,0) - finish = TimeSlot(23,0) - incr = TimeSlot(0,30) - ts = TimeSeries( start, finish, incr, True) - task2.add_time( Time(ts) ) - - -add_time( (Node)arg1, (int)arg2, (int)arg3, (bool)arg4) -> Node - -add_time( (Node)arg1, (str)arg2) -> Node - -add_time( (Node)arg1, (Time)arg2) -> Node - - -.. py:method:: Node.add_today( (Node)arg1, (int)arg2, (int)arg3) -> Node : + + +Usage: + +.. code-block:: python + + t1 = Task('t1', Time('+00:30 20:00 01:00')) # Create Time in Task constructor + t1.add_time( '00:30' ) + t1.add_time( '+00:30' ) + t1.add_time( '+00:30 20:00 01:00' ) + t1.add_time( Time( 0,10 )) # hour,min,relative =false + t1.add_time( Time( 0,12,True )) # hour,min,relative + t1.add_time( Time(TimeSlot(20,20),False)) + t1.add_time( 0,1 )) # hour,min,relative=false + t1.add_time( 0,3,False )) # hour,min,relative=false + start = TimeSlot(0,0) + finish = TimeSlot(23,0) + incr = TimeSlot(0,30) + ts = TimeSeries( start, finish, incr, True) + task2.add_time( Time(ts) ) + + +2. add_time(self: ecflow.Node, arg0: typing.SupportsInt | typing.SupportsIndex, arg1: typing.SupportsInt | typing.SupportsIndex, arg2: bool) -> ecflow.Node + +3. add_time(self: ecflow.Node, arg0: str) -> ecflow.Node + +4. add_time(self: ecflow.Node, arg0: ecflow.Time) -> ecflow.Node + + +.. py:method:: Node.add_today(*args, **kwargs) :module: ecflow +Overloaded function. + +1. add_today(self: ecflow.Node, arg0: typing.SupportsInt | typing.SupportsIndex, arg1: typing.SupportsInt | typing.SupportsIndex) -> ecflow.Node + Add a :term:`today` time dependency. See :py:class:`ecflow.Today` - - - Usage: - - .. code-block:: python - - t1 = Task('t1', - Today('+00:30 20:00 01:00')) # Create Today in Task constructor - - t1.add_today( '00:30' ) - t1.add_today( '+00:30' ) - t1.add_today( '+00:30 20:00 01:00' ) - t1.add_today( Today( 0,10 )) # hour,min,relative =false - t1.add_today( Today( 0,12,True )) # hour,min,relative - t1.add_today( Today(TimeSlot(20,20),False)) - t1.add_today( 0,1 )) # hour,min,relative=false - t1.add_today( 0,3,False )) # hour,min,relative=false - start = TimeSlot(0,0) - finish = TimeSlot(23,0) - incr = TimeSlot(0,30) - ts = TimeSeries( start, finish, incr, True) - task2.add_today( Today(ts) ) - - -add_today( (Node)arg1, (int)arg2, (int)arg3, (bool)arg4) -> Node - -add_today( (Node)arg1, (str)arg2) -> Node - -add_today( (Node)arg1, (Today)arg2) -> Node - - -.. py:method:: Node.add_trigger( (Node)arg1, (str)arg2) -> Node : + + +Usage: + +.. code-block:: python + + t1 = Task('t1', + Today('+00:30 20:00 01:00')) # Create Today in Task constructor + + t1.add_today( '00:30' ) + t1.add_today( '+00:30' ) + t1.add_today( '+00:30 20:00 01:00' ) + t1.add_today( Today( 0,10 )) # hour,min,relative =false + t1.add_today( Today( 0,12,True )) # hour,min,relative + t1.add_today( Today(TimeSlot(20,20),False)) + t1.add_today( 0,1 )) # hour,min,relative=false + t1.add_today( 0,3,False )) # hour,min,relative=false + start = TimeSlot(0,0) + finish = TimeSlot(23,0) + incr = TimeSlot(0,30) + ts = TimeSeries( start, finish, incr, True) + task2.add_today( Today(ts) ) + + +2. add_today(self: ecflow.Node, arg0: typing.SupportsInt | typing.SupportsIndex, arg1: typing.SupportsInt | typing.SupportsIndex, arg2: bool) -> ecflow.Node + +3. add_today(self: ecflow.Node, arg0: str) -> ecflow.Node + +4. add_today(self: ecflow.Node, arg0: ecflow.Today) -> ecflow.Node + + +.. py:method:: Node.add_trigger(*args, **kwargs) :module: ecflow +Overloaded function. + +1. add_trigger(self: ecflow.Node, arg0: str) -> ecflow.Node + Add a :term:`trigger` or :term:`complete expression`.Also see :py:class:`ecflow.Trigger` - - This defines a dependency for a :term:`node`. - There can only be one :term:`trigger` or :term:`complete expression` dependency per node. - A :term:`node` with a trigger can only be activated when the trigger has expired. - A trigger holds a node as long as the expression returns false. - - Exception: - - - Will throw RuntimeError if multiple trigger or complete expression are added - - Will throw RuntimeError if first expression is added as 'AND' or 'OR' expression - Like wise second and subsequent expression must have 'AND' or 'OR' booleans set - - Usage: - - Note we cannot make multiple add_trigger(..) calls on the same :term:`task`! - to add a simple trigger: - - .. code-block:: python - - task1.add_trigger( 't2 == active' ) - task2.add_trigger( 't1 == complete or t4 == complete' ) - task3.add_trigger( 't5 == active' ) - - Long expression can be broken up using add_part_trigger: - - .. code-block:: python - - task2.add_part_trigger( 't1 == complete or t4 == complete') - task2.add_part_trigger( 't5 == active',True) # True means AND - task2.add_part_trigger( 't7 == active',False) # False means OR - - The trigger for task2 is equivalent to: - 't1 == complete or t4 == complete and t5 == active or t7 == active' - -add_trigger( (Node)arg1, (Expression)arg2) -> Node - - -.. py:method:: Node.add_variable( (Node)arg1, (str)arg2, (str)arg3) -> Node : + +This defines a dependency for a :term:`node`. +There can only be one :term:`trigger` or :term:`complete expression` dependency per node. +A :term:`node` with a trigger can only be activated when the trigger has expired. +A trigger holds a node as long as the expression returns false. + +Exception: + +- Will throw RuntimeError if multiple trigger or complete expression are added +- Will throw RuntimeError if first expression is added as 'AND' or 'OR' expression + Like wise second and subsequent expression must have 'AND' or 'OR' booleans set + +Usage: + +Note we cannot make multiple add_trigger(..) calls on the same :term:`task`! +to add a simple trigger: + +.. code-block:: python + + task1.add_trigger( 't2 == active' ) + task2.add_trigger( 't1 == complete or t4 == complete' ) + task3.add_trigger( 't5 == active' ) + +Long expression can be broken up using add_part_trigger: + +.. code-block:: python + + task2.add_part_trigger( 't1 == complete or t4 == complete') + task2.add_part_trigger( 't5 == active',True) # True means AND + task2.add_part_trigger( 't7 == active',False) # False means OR + +The trigger for task2 is equivalent to: +'t1 == complete or t4 == complete and t5 == active or t7 == active' + +2. add_trigger(self: ecflow.Node, arg0: ecflow.Expression) -> ecflow.Node + + +.. py:method:: Node.add_variable(*args, **kwargs) :module: ecflow +Overloaded function. + +1. add_variable(self: ecflow.Node, arg0: str, arg1: str) -> ecflow.Node + Adds a name value :term:`variable`. Also see :py:class:`ecflow.Edit` - - This defines a variable for use in :term:`variable substitution` in a :term:`ecf script` file. - There can be any number of variables. The variables are names inside a pair of - '%' characters in an :term:`ecf script`. The name are case sensitive. - Special character in the value, must be placed inside single quotes if misinterpretation - is to be avoided. - The value of the variable replaces the variable name in the :term:`ecf script` at `job creation` time. - The variable names for any given node must be unique. If duplicates are added then the - the last value added is kept. - - Exception: - - - Writes warning to standard output, if a duplicate variable name is added - - Usage: - - .. code-block:: python - - task.add_variable( Variable('ECF_HOME','/tmp/')) - task.add_variable( 'TMPDIR','/tmp/') - task.add_variable( 'COUNT',2) - a_dict = { 'name':'value', 'name2':'value2', 'name3':'value3' } - task.add_variable(a_dict) - - -add_variable( (Node)arg1, (str)arg2, (int)arg3) -> Node - -add_variable( (Node)arg1, (Variable)arg2) -> Node - -add_variable( (Node)arg1, (dict)arg2) -> Node - - -.. py:method:: Node.add_verify( (Node)arg1, (Verify)arg2) -> None : + +This defines a variable for use in :term:`variable substitution` in a :term:`ecf script` file. +There can be any number of variables. The variables are names inside a pair of +'%' characters in an :term:`ecf script`. The name are case sensitive. +Special character in the value, must be placed inside single quotes if misinterpretation +is to be avoided. +The value of the variable replaces the variable name in the :term:`ecf script` at `job creation` time. +The variable names for any given node must be unique. If duplicates are added then the +the last value added is kept. + +Exception: + +- Writes warning to standard output, if a duplicate variable name is added + +Usage: + +.. code-block:: python + + task.add_variable( Variable('ECF_HOME','/tmp/')) + task.add_variable( 'TMPDIR','/tmp/') + task.add_variable( 'COUNT',2) + a_dict = { 'name':'value', 'name2':'value2', 'name3':'value3' } + task.add_variable(a_dict) + + +2. add_variable(self: ecflow.Node, arg0: str, arg1: typing.SupportsInt | typing.SupportsIndex) -> ecflow.Node + +3. add_variable(self: ecflow.Node, arg0: ecflow.Variable) -> ecflow.Node + +4. add_variable(self: ecflow.Node, arg0: ecflow.Edit) -> ecflow.Node + +5. add_variable(self: ecflow.Node, arg0: object) -> ecflow.Node + + +.. py:method:: Node.add_verify(self: ecflow.Node, arg0: ecflow.Verify) -> None :module: ecflow Add a Verify attribute. @@ -954,7 +1066,7 @@ Used in python simulation used to assert that a particular state was reached. t Verify(State.complete, 6)) # verify task completes 6 times during simulation -.. py:method:: Node.add_zombie( (Node)arg1, (ZombieAttr)arg2) -> Node : +.. py:method:: Node.add_zombie(self: ecflow.Node, arg0: ecflow.ZombieAttr) -> ecflow.Node :module: ecflow The :term:`zombie` attribute defines how a :term:`zombie` should be handled in an automated fashion @@ -998,11 +1110,11 @@ Usage: Returns a list of :term:`aviso`\ s -.. py:method:: Node.change_complete( (Node)arg1, (str)arg2) -> None +.. py:method:: Node.change_complete(self: ecflow.Node, arg0: str) -> None :module: ecflow -.. py:method:: Node.change_trigger( (Node)arg1, (str)arg2) -> None +.. py:method:: Node.change_trigger(self: ecflow.Node, arg0: str) -> None :module: ecflow @@ -1024,93 +1136,117 @@ Returns a list of :term:`date`\ s Returns a list of :term:`day`\ s -.. py:method:: Node.delete_complete( (Node)arg1) -> None +.. py:method:: Node.delete_complete(self: ecflow.Node) -> None :module: ecflow -.. py:method:: Node.delete_cron( (Node)arg1, (str)arg2) -> None +.. py:method:: Node.delete_cron(*args, **kwargs) :module: ecflow -delete_cron( (Node)arg1, (Cron)arg2) -> None +Overloaded function. + +1. delete_cron(self: ecflow.Node, arg0: str) -> None + +2. delete_cron(self: ecflow.Node, arg0: ecflow.Cron) -> None -.. py:method:: Node.delete_date( (Node)arg1, (str)arg2) -> None +.. py:method:: Node.delete_date(*args, **kwargs) :module: ecflow -delete_date( (Node)arg1, (Date)arg2) -> None +Overloaded function. +1. delete_date(self: ecflow.Node, arg0: str) -> None -.. py:method:: Node.delete_day( (Node)arg1, (str)arg2) -> None +2. delete_date(self: ecflow.Node, arg0: ecflow.Date) -> None + + +.. py:method:: Node.delete_day(*args, **kwargs) :module: ecflow -delete_day( (Node)arg1, (Day)arg2) -> None +Overloaded function. + +1. delete_day(self: ecflow.Node, arg0: str) -> None + +2. delete_day(self: ecflow.Node, arg0: ecflow.Day) -> None -.. py:method:: Node.delete_event( (Node)arg1, (str)arg2) -> None +.. py:method:: Node.delete_event(self: ecflow.Node, arg0: str) -> None :module: ecflow -.. py:method:: Node.delete_generic( (Node)arg1, (str)arg2) -> None +.. py:method:: Node.delete_generic(self: ecflow.Node, arg0: str) -> None :module: ecflow -.. py:method:: Node.delete_inlimit( (Node)arg1, (str)arg2) -> None +.. py:method:: Node.delete_inlimit(self: ecflow.Node, arg0: str) -> None :module: ecflow -.. py:method:: Node.delete_label( (Node)arg1, (str)arg2) -> None +.. py:method:: Node.delete_label(self: ecflow.Node, arg0: str) -> None :module: ecflow -.. py:method:: Node.delete_limit( (Node)arg1, (str)arg2) -> None +.. py:method:: Node.delete_limit(self: ecflow.Node, arg0: str) -> None :module: ecflow -.. py:method:: Node.delete_meter( (Node)arg1, (str)arg2) -> None +.. py:method:: Node.delete_meter(self: ecflow.Node, arg0: str) -> None :module: ecflow -.. py:method:: Node.delete_queue( (Node)arg1, (str)arg2) -> None +.. py:method:: Node.delete_queue(self: ecflow.Node, arg0: str) -> None :module: ecflow -.. py:method:: Node.delete_repeat( (Node)arg1) -> None +.. py:method:: Node.delete_repeat(self: ecflow.Node) -> None :module: ecflow -.. py:method:: Node.delete_time( (Node)arg1, (str)arg2) -> None +.. py:method:: Node.delete_time(*args, **kwargs) :module: ecflow -delete_time( (Node)arg1, (Time)arg2) -> None +Overloaded function. +1. delete_time(self: ecflow.Node, arg0: str) -> None -.. py:method:: Node.delete_today( (Node)arg1, (str)arg2) -> None +2. delete_time(self: ecflow.Node, arg0: ecflow.Time) -> None + + +.. py:method:: Node.delete_today(*args, **kwargs) :module: ecflow -delete_today( (Node)arg1, (Today)arg2) -> None +Overloaded function. + +1. delete_today(self: ecflow.Node, arg0: str) -> None + +2. delete_today(self: ecflow.Node, arg0: ecflow.Today) -> None -.. py:method:: Node.delete_trigger( (Node)arg1) -> None +.. py:method:: Node.delete_trigger(self: ecflow.Node) -> None :module: ecflow -.. py:method:: Node.delete_variable( (Node)arg1, (str)arg2) -> None +.. py:method:: Node.delete_variable(self: ecflow.Node, arg0: str) -> None :module: ecflow -.. py:method:: Node.delete_zombie( (Node)arg1, (str)arg2) -> None +.. py:method:: Node.delete_zombie(*args, **kwargs) :module: ecflow -delete_zombie( (Node)arg1, (ZombieType)arg2) -> None +Overloaded function. +1. delete_zombie(self: ecflow.Node, arg0: str) -> None -.. py:method:: Node.evaluate_complete( (Node)arg1) -> bool : +2. delete_zombie(self: ecflow.Node, arg0: ecflow.ZombieType) -> None + + +.. py:method:: Node.evaluate_complete(self: ecflow.Node) -> bool :module: ecflow evaluate complete expression -.. py:method:: Node.evaluate_trigger( (Node)arg1) -> bool : +.. py:method:: Node.evaluate_trigger(self: ecflow.Node) -> bool :module: ecflow evaluate trigger expression @@ -1122,67 +1258,67 @@ evaluate trigger expression Returns a list of :term:`event`\ s -.. py:method:: Node.find_event( (Node)arg1, (str)arg2) -> Event : +.. py:method:: Node.find_event(self: ecflow.Node, arg0: str) -> ecflow.Event :module: ecflow Find the :term:`event` on the node only. Returns a object -.. py:method:: Node.find_gen_variable( (Node)arg1, (str)arg2) -> Variable : +.. py:method:: Node.find_gen_variable(self: ecflow.Node, arg0: str) -> ecflow.Variable :module: ecflow Find generated variable on the node only. Returns an object -.. py:method:: Node.find_generic( (Node)arg1, (str)arg2) -> Generic : +.. py:method:: Node.find_generic(self: ecflow.Node, arg0: str) -> ecflow.Generic :module: ecflow Find the :term:`generic` on the node only. Returns a Generic object -.. py:method:: Node.find_label( (Node)arg1, (str)arg2) -> Label : +.. py:method:: Node.find_label(self: ecflow.Node, arg0: str) -> ecflow.Label :module: ecflow Find the :term:`label` on the node only. Returns a object -.. py:method:: Node.find_limit( (Node)arg1, (str)arg2) -> Limit : +.. py:method:: Node.find_limit(self: ecflow.Node, arg0: str) -> ecflow.Limit :module: ecflow Find the :term:`limit` on the node only. returns a limit ptr -.. py:method:: Node.find_meter( (Node)arg1, (str)arg2) -> Meter : +.. py:method:: Node.find_meter(self: ecflow.Node, arg0: str) -> ecflow.Meter :module: ecflow Find the :term:`meter` on the node only. Returns an object -.. py:method:: Node.find_node_up_the_tree( (Node)arg1, (str)arg2) -> Node : +.. py:method:: Node.find_node_up_the_tree(self: ecflow.Node, arg0: str) -> ecflow.Node :module: ecflow Search immediate node, then up the node hierarchy -.. py:method:: Node.find_parent_variable( (Node)arg1, (str)arg2) -> Variable : +.. py:method:: Node.find_parent_variable(self: ecflow.Node, arg0: str) -> ecflow.Variable :module: ecflow Find user variable variable up the parent hierarchy. Returns an object -.. py:method:: Node.find_parent_variable_sub_value( (Node)arg1, (str)arg2) -> str : +.. py:method:: Node.find_parent_variable_sub_value(self: ecflow.Node, arg0: str) -> str :module: ecflow Find user variable *up* node tree, then variable substitute the value, otherwise return empty string -.. py:method:: Node.find_queue( (Node)arg1, (str)arg2) -> Queue : +.. py:method:: Node.find_queue(self: ecflow.Node, arg0: str) -> ecflow.Queue :module: ecflow Find the queue on the node only. Returns a queue object -.. py:method:: Node.find_variable( (Node)arg1, (str)arg2) -> Variable : +.. py:method:: Node.find_variable(self: ecflow.Node, arg0: str) -> ecflow.Variable :module: ecflow Find user variable on the node only. Returns an object @@ -1194,94 +1330,97 @@ Find user variable on the node only. Returns an object Returns a list of :term:`generic`\ s -.. py:method:: Node.get_abs_node_path( (Node)arg1) -> str : +.. py:method:: Node.get_abs_node_path(self: ecflow.Node) -> str :module: ecflow returns a string which holds the path to the node - - -.. py:method:: Node.get_all_nodes( (Node)arg1) -> NodeVec : +.. py:method:: Node.get_all_nodes(self: ecflow.Node) -> ecflow.NodeVec :module: ecflow Returns all the child nodes -.. py:method:: Node.get_autoarchive( (Node)arg1) -> Autoarchive +.. py:method:: Node.get_autoarchive(self: ecflow.Node) -> ecflow.Autoarchive :module: ecflow -.. py:method:: Node.get_autocancel( (Node)arg1) -> Autocancel +.. py:method:: Node.get_autocancel(self: ecflow.Node) -> ecflow.Autocancel :module: ecflow -.. py:method:: Node.get_autorestore( (Node)arg1) -> Autorestore +.. py:method:: Node.get_autorestore(self: ecflow.Node) -> ecflow.Autorestore :module: ecflow -.. py:method:: Node.get_complete( (Node)arg1) -> Expression +.. py:method:: Node.get_complete(self: ecflow.Node) -> ecflow.Expression :module: ecflow -.. py:method:: Node.get_defs( (Node)arg1) -> Defs +.. py:method:: Node.get_defs(self: ecflow.Node) -> Defs :module: ecflow -.. py:method:: Node.get_defstatus( (Node)arg1) -> DState +.. py:method:: Node.get_defstatus(self: ecflow.Node) -> ecflow.DState :module: ecflow -.. py:method:: Node.get_dstate( (Node)arg1) -> DState : +.. py:method:: Node.get_dstate(self: ecflow.Node) -> ecflow.DState :module: ecflow Returns the state of node. This will include suspended state -.. py:method:: Node.get_flag( (Node)arg1) -> Flag : +.. py:method:: Node.get_flag(self: ecflow.Node) -> ecflow.Flag :module: ecflow Return additional state associated with a node. -.. py:method:: Node.get_generated_variables( (Node)arg1) -> list : +.. py:method:: Node.get_generated_variables(*args, **kwargs) :module: ecflow +Overloaded function. + +1. get_generated_variables(self: ecflow.Node) -> list + Returns the list of generated variables. -get_generated_variables( (Node)arg1, (VariableList)arg2) -> None : - Retrieves the list of generated variables. Pass in ecflow.VariableList as argument to hold variables. +2. get_generated_variables(self: ecflow.Node, arg0: ecflow.VariableList) -> None +Retrieves the list of generated variables. Pass in ecflow.VariableList as argument to hold variables. -.. py:method:: Node.get_late( (Node)arg1) -> Late + +.. py:method:: Node.get_late(self: ecflow.Node) -> ecflow.Late :module: ecflow -.. py:method:: Node.get_parent( (Node)arg1) -> Node +.. py:method:: Node.get_parent(self: ecflow.Node) -> ecflow.Node :module: ecflow -.. py:method:: Node.get_repeat( (Node)arg1) -> Repeat +.. py:method:: Node.get_repeat(self: ecflow.Node) -> ecflow.Repeat :module: ecflow -.. py:method:: Node.get_state( (Node)arg1) -> State : +.. py:method:: Node.get_state(self: ecflow.Node) -> ecflow.State :module: ecflow Returns the state of the node. This excludes the suspended state -.. py:method:: Node.get_state_change_time( (Node)arg1 [, (str)format='iso_extended']) -> str : +.. py:method:: Node.get_state_change_time(self: ecflow.Node, format: str = 'iso_extended') -> str :module: ecflow Returns the time of the last state change as a string. Default format is iso_extended, (iso_extended, iso, simple) -.. py:method:: Node.get_trigger( (Node)arg1) -> Expression +.. py:method:: Node.get_trigger(self: ecflow.Node) -> ecflow.Expression :module: ecflow -.. py:method:: Node.has_time_dependencies( (Node)arg1) -> bool +.. py:method:: Node.has_time_dependencies(self: ecflow.Node) -> bool :module: ecflow @@ -1291,7 +1430,7 @@ Returns the time of the last state change as a string. Default format is iso_ext Returns a list of :term:`inlimit`\ s -.. py:method:: Node.is_suspended( (Node)arg1) -> bool : +.. py:method:: Node.is_suspended(self: ecflow.Node) -> bool :module: ecflow Returns true if the :term:`node` is in a :term:`suspended` state @@ -1321,7 +1460,7 @@ Returns a list of :term:`meter`\ s Returns a list of :term:`mirror`\ s -.. py:method:: Node.name( (Node)arg1) -> str +.. py:method:: Node.name(self: ecflow.Node) -> str :module: ecflow @@ -1331,37 +1470,48 @@ Returns a list of :term:`mirror`\ s Returns a list of :term:`queue`\ s -.. py:method:: Node.remove( (Node)arg1) -> Node : +.. py:method:: Node.remove(self: ecflow.Node) -> ecflow.Node :module: ecflow Remove the node from its parent. and returns it -.. py:method:: Node.replace_on_server( (Node)arg1 [, (bool)suspend_node_first=True [, (bool)force=True]]) -> None : +.. py:method:: Node.replace_on_server(*args, **kwargs) :module: ecflow +Overloaded function. + +1. replace_on_server(self: ecflow.Node, suspend_node_first: bool = True, force: bool = True) -> None + replace node on the server. -replace_on_server( (Node)arg1, (str)arg2, (str)arg3 [, (bool)suspend_node_first=True [, (bool)force=True]]) -> None : - replace node on the server. +2. replace_on_server(self: ecflow.Node, host: str, port: str, suspend_node_first: bool = True, force: bool = True) -> None -replace_on_server( (Node)arg1, (str)arg2 [, (bool)suspend_node_first=True [, (bool)force=True]]) -> None : - replace node on the server. +replace node on the server. -replace_on_server( (Node)arg1, (Client)arg2 [, (bool)suspend_node_first=True [, (bool)force=True]]) -> None : - replace node on the server. +3. replace_on_server(self: ecflow.Node, host_port: str, suspend_node_first: bool = True, force: bool = True) -> None +replace node on the server. + +4. replace_on_server(self: ecflow.Node, client: ClientInvoker, suspend_node_first: bool = True, force: bool = True) -> None + +replace node on the server. -.. py:method:: Node.sort_attributes( (Node)arg1, (AttrType)arg2) -> None + +.. py:method:: Node.sort_attributes(*args, **kwargs) :module: ecflow -sort_attributes( (Node)arg1, (AttrType)arg2, (bool)arg3) -> None +Overloaded function. + +1. sort_attributes(self: ecflow.Node, arg0: ecflow.AttrType) -> None + +2. sort_attributes(self: ecflow.Node, arg0: ecflow.AttrType, arg1: bool) -> None -sort_attributes( (Node)arg1, (AttrType)arg2, (bool)arg3, (list)arg4) -> None +3. sort_attributes(self: ecflow.Node, arg0: ecflow.AttrType, arg1: bool, arg2: list) -> None -sort_attributes( (Node)arg1, (str)attribute_type [, (bool)recursive=True [, (list)no_sort=[]]]) -> None +4. sort_attributes(self: ecflow.Node, attribute_type: str, recursive: bool = True, no_sort: list = []) -> None -sort_attributes( (Node)arg1, (AttrType)arg2, (bool)attribute_type [, (object)recursive=True]) -> None +5. sort_attributes(self: ecflow.Node, attribute_type: ecflow.AttrType, recursive: bool = True, no_sort: collections.abc.Sequence[str] = []) -> None .. py:property:: Node.times @@ -1376,7 +1526,7 @@ Returns a list of :term:`time`\ s Returns a list of :term:`today`\ s -.. py:method:: Node.update_generated_variables( (Node)arg1) -> None +.. py:method:: Node.update_generated_variables(self: ecflow.Node) -> None :module: ecflow diff --git a/docs/python_api/NodeContainer.rst b/docs/python_api/NodeContainer.rst index c1cbcf090..40d012aa2 100644 --- a/docs/python_api/NodeContainer.rst +++ b/docs/python_api/NodeContainer.rst @@ -12,68 +12,76 @@ NodeContainer is the abstract base class for a Suite and Family A NodeContainer can have Families and Tasks as children -.. py:method:: NodeContainer.add_family( (NodeContainer)arg1, (str)arg2) -> Family : +.. py:method:: NodeContainer.add_family(*args, **kwargs) :module: ecflow +Overloaded function. + +1. add_family(self: ecflow.NodeContainer, arg0: str) -> ecflow.Node + Add a :term:`family`. See :py:class:`ecflow.Family`. - - Multiple families can be added. However family names must be unique. - for a given parent. Families can be hierarchical. - - Exception: - - - Throws RuntimeError if a duplicate is added - - Usage: - - .. code-block:: python - - suite = Suite('suite') # create a suite - f1 = Family('f1') # create a family - suite.add_family(f1) # add family to suite - f2 = suite.add_family('f2') # create a family and add to suite - - -add_family( (NodeContainer)arg1, (Family)arg2) -> Family - - -.. py:method:: NodeContainer.add_task( (NodeContainer)arg1, (str)arg2) -> Task : + +Multiple families can be added. However family names must be unique. +for a given parent. Families can be hierarchical. + +Exception: + +- Throws RuntimeError if a duplicate is added + +Usage: + +.. code-block:: python + + suite = Suite('suite') # create a suite + f1 = Family('f1') # create a family + suite.add_family(f1) # add family to suite + f2 = suite.add_family('f2') # create a family and add to suite + + +2. add_family(self: ecflow.NodeContainer, arg0: Family) -> ecflow.Node + + +.. py:method:: NodeContainer.add_task(*args, **kwargs) :module: ecflow +Overloaded function. + +1. add_task(self: ecflow.NodeContainer, arg0: str) -> ecflow.Node + Add a :term:`task`. See :py:class:`ecflow.Task` - - Multiple Tasks can be added. However Task names must be unique, - for a given parent. Task can be added to Familiy's or Suites. - - Exception: - - - Throws RuntimeError if a duplicate is added - - Usage: - - .. code-block:: python - - f1 = Family('f1') # create a family - t1 = Task('t1') # create a task - f1.add_task(t1) # add task to family - t2 = f1.add_task('t2') # create task 't2' and add to family - -add_task( (NodeContainer)arg1, (Task)arg2) -> Task - - -.. py:method:: NodeContainer.find_family( (NodeContainer)arg1, (str)arg2) -> Family : + +Multiple Tasks can be added. However Task names must be unique, +for a given parent. Task can be added to Familiy's or Suites. + +Exception: + +- Throws RuntimeError if a duplicate is added + +Usage: + +.. code-block:: python + + f1 = Family('f1') # create a family + t1 = Task('t1') # create a task + f1.add_task(t1) # add task to family + t2 = f1.add_task('t2') # create task 't2' and add to family + +2. add_task(self: ecflow.NodeContainer, arg0: ecflow.Task) -> ecflow.Node + + +.. py:method:: NodeContainer.find_family(self: ecflow.NodeContainer, arg0: str) -> Family :module: ecflow Find a family given a name -.. py:method:: NodeContainer.find_node( (NodeContainer)arg1, (str)arg2) -> Node : +.. py:method:: NodeContainer.find_node(self: ecflow.NodeContainer, arg0: str) -> ecflow.Node :module: ecflow Find immediate child node given a name -.. py:method:: NodeContainer.find_task( (NodeContainer)arg1, (str)arg2) -> Task : +.. py:method:: NodeContainer.find_task(self: ecflow.NodeContainer, arg0: str) -> ecflow.Task :module: ecflow Find a task given a name diff --git a/docs/python_api/NodeVec.rst b/docs/python_api/NodeVec.rst index 4bb15fbe5..ef203c3eb 100644 --- a/docs/python_api/NodeVec.rst +++ b/docs/python_api/NodeVec.rst @@ -5,15 +5,65 @@ ecflow.NodeVec .. py:class:: NodeVec :module: ecflow - Bases: :py:class:`~Boost.Python.instance` + Bases: :py:class:`~pybind11_builtins.pybind11_object` Hold a list of Nodes (i.e :term:`suite`, :term:`family` or :term:`task`\ s) -.. py:method:: NodeVec.append( (NodeVec)arg1, (object)arg2) -> None +.. py:method:: NodeVec.append(self: ecflow.NodeVec, x: Node) -> None :module: ecflow +Add an item to the end of the list -.. py:method:: NodeVec.extend( (NodeVec)arg1, (object)arg2) -> None + +.. py:method:: NodeVec.clear(self: ecflow.NodeVec) -> None + :module: ecflow + +Clear the contents + + +.. py:method:: NodeVec.count(self: ecflow.NodeVec, x: Node) -> int + :module: ecflow + +Return the number of times ``x`` appears in the list + + +.. py:method:: NodeVec.extend(*args, **kwargs) + :module: ecflow + +Overloaded function. + +1. extend(self: ecflow.NodeVec, L: ecflow.NodeVec) -> None + +Extend the list by appending all the items in the given list + +2. extend(self: ecflow.NodeVec, L: collections.abc.Iterable) -> None + +Extend the list by appending all the items in the given list + + +.. py:method:: NodeVec.insert(self: ecflow.NodeVec, i: typing.SupportsInt | typing.SupportsIndex, x: Node) -> None + :module: ecflow + +Insert an item at a given position. + + +.. py:method:: NodeVec.pop(*args, **kwargs) :module: ecflow +Overloaded function. + +1. pop(self: ecflow.NodeVec) -> Node + +Remove and return the last item + +2. pop(self: ecflow.NodeVec, i: typing.SupportsInt | typing.SupportsIndex) -> Node + +Remove and return the item at index ``i`` + + +.. py:method:: NodeVec.remove(self: ecflow.NodeVec, x: Node) -> None + :module: ecflow + +Remove the first item from the list whose value is x. It is an error if there is no such item. + diff --git a/docs/python_api/PartExpression.rst b/docs/python_api/PartExpression.rst index 9a4e49704..1b753a9a8 100644 --- a/docs/python_api/PartExpression.rst +++ b/docs/python_api/PartExpression.rst @@ -5,7 +5,7 @@ ecflow.PartExpression .. py:class:: PartExpression :module: ecflow - Bases: :py:class:`~Boost.Python.instance` + Bases: :py:class:`~pybind11_builtins.pybind11_object` PartExpression holds part of a :term:`trigger` or :term:`complete expression`. @@ -47,16 +47,16 @@ The trigger for task2 is equivalent to 't1 == complete or t4 == complete and t5 == active or t7 == active' -.. py:method:: PartExpression.and_expr( (PartExpression)arg1) -> bool +.. py:method:: PartExpression.and_expr(self: ecflow.PartExpression) -> bool :module: ecflow -.. py:method:: PartExpression.get_expression( (PartExpression)arg1) -> str : +.. py:method:: PartExpression.get_expression(self: ecflow.PartExpression) -> str :module: ecflow returns the part expression as a string -.. py:method:: PartExpression.or_expr( (PartExpression)arg1) -> bool +.. py:method:: PartExpression.or_expr(self: ecflow.PartExpression) -> bool :module: ecflow diff --git a/docs/python_api/PrintStyle.rst b/docs/python_api/PrintStyle.rst index 80cf50329..601d78e6a 100644 --- a/docs/python_api/PrintStyle.rst +++ b/docs/python_api/PrintStyle.rst @@ -5,7 +5,7 @@ ecflow.PrintStyle .. py:class:: PrintStyle :module: ecflow - Bases: :py:class:`~Boost.Python.instance` + Bases: :py:class:`~pybind11_builtins.pybind11_object` Singleton used to control the print Style. See :py:class:`ecflow.Style` @@ -19,14 +19,14 @@ Usage:: PrintStyle.set_style(old_style) # reset previous style -.. py:method:: PrintStyle.get_style() -> Style : +.. py:method:: PrintStyle.get_style() -> ecflow.Style :module: ecflow :staticmethod: Returns the style, static method -.. py:method:: PrintStyle.set_style( (Style)arg1) -> None : +.. py:method:: PrintStyle.set_style(arg0: ecflow.Style) -> None :module: ecflow :staticmethod: diff --git a/docs/python_api/Queue.rst b/docs/python_api/Queue.rst index c0a8efb34..6758ec428 100644 --- a/docs/python_api/Queue.rst +++ b/docs/python_api/Queue.rst @@ -5,30 +5,30 @@ ecflow.Queue .. py:class:: Queue :module: ecflow - Bases: :py:class:`~Boost.Python.instance` + Bases: :py:class:`~pybind11_builtins.pybind11_object` Queue allows specification of queue on Task, Family and Suite nodes -.. py:method:: Queue.empty( (Queue)arg1) -> bool : +.. py:method:: Queue.empty(self: ecflow.Queue) -> bool :module: ecflow Return true if the Queue is empty. Used when returning a NULL Queue, from a find -.. py:method:: Queue.index( (Queue)arg1) -> int : +.. py:method:: Queue.index(self: ecflow.Queue) -> int :module: ecflow Return the queue current index as a integer -.. py:method:: Queue.name( (Queue)arg1) -> str : +.. py:method:: Queue.name(self: ecflow.Queue) -> str :module: ecflow Return the queue name as string -.. py:method:: Queue.value( (Queue)arg1) -> str : +.. py:method:: Queue.value(self: ecflow.Queue) -> str :module: ecflow Return the queue current value as string diff --git a/docs/python_api/Repeat.rst b/docs/python_api/Repeat.rst index 370966fb6..0eb958eec 100644 --- a/docs/python_api/Repeat.rst +++ b/docs/python_api/Repeat.rst @@ -5,42 +5,69 @@ ecflow.Repeat .. py:class:: Repeat :module: ecflow - Bases: :py:class:`~Boost.Python.instance` + Bases: :py:class:`~pybind11_builtins.pybind11_object` -Represents one of RepeatString,RepeatEnumerated,RepeatInteger,RepeatDate,RepeatDay +Represents one of RepeatString, RepeatEnumerated, RepeatInteger, RepeatDate, +RepeatDateTime, RepeatDateList, RepeatDateTimeList, or RepeatDay. +Accessor methods:: -.. py:method:: Repeat.empty( (Repeat)arg1) -> bool : + current_value() -> None # if empty + current_value() -> int # if holding RepeatDate/DateList/Integer/Day + current_value() -> str # if holdind RepeatDateTime/DateTimeList/Enumerated/RepeatString + + +.. py:method:: Repeat.current_index(self: ecflow.Repeat) -> int + :module: ecflow + +The current index of the repeat (as an integer) + + +.. py:method:: Repeat.current_value(self: ecflow.Repeat) -> object + :module: ecflow + +The current value of the repeat (as a string for RepeatDateTime, RepeatDateTimeList, RepeatEnumerated, RepeatString; as an integer for RepeatDate, RepeatDateList, RepeatInteger and RepeatDay) + + +.. py:method:: Repeat.empty(self: ecflow.Repeat) -> bool :module: ecflow Return true if the repeat is empty. -.. py:method:: Repeat.end( (Repeat)arg1) -> int : +.. py:method:: Repeat.end(self: ecflow.Repeat) -> int :module: ecflow The last value of the repeat, as an integer -.. py:method:: Repeat.name( (Repeat)arg1) -> str : +.. py:method:: Repeat.increment(self: ecflow.Repeat) -> None + :module: ecflow + +Increment the repeat to the next value + +n.b. this modifies the in-memory local object only --it does not update the ecFlow server. + + +.. py:method:: Repeat.name(self: ecflow.Repeat) -> str :module: ecflow The :term:`repeat` name, can be referenced in :term:`trigger` expressions -.. py:method:: Repeat.start( (Repeat)arg1) -> int : +.. py:method:: Repeat.start(self: ecflow.Repeat) -> int :module: ecflow The start value of the repeat, as an integer -.. py:method:: Repeat.step( (Repeat)arg1) -> int : +.. py:method:: Repeat.step(self: ecflow.Repeat) -> int :module: ecflow The increment for the repeat, as an integer -.. py:method:: Repeat.value( (Repeat)arg1) -> int : +.. py:method:: Repeat.value(self: ecflow.Repeat) -> int :module: ecflow The current value of the repeat as an integer diff --git a/docs/python_api/RepeatDate.rst b/docs/python_api/RepeatDate.rst index 722bd0783..7dea5cbf9 100644 --- a/docs/python_api/RepeatDate.rst +++ b/docs/python_api/RepeatDate.rst @@ -5,7 +5,7 @@ ecflow.RepeatDate .. py:class:: RepeatDate :module: ecflow - Bases: :py:class:`~Boost.Python.instance` + Bases: :py:class:`~pybind11_builtins.pybind11_object` Allows a :term:`node` to be repeated using a yyyymmdd format @@ -38,26 +38,51 @@ Usage: t = Task('t1', RepeatDate('YMD', 20050130, 20050203 )) +Accessor methods:: -.. py:method:: RepeatDate.end( (RepeatDate)arg1) -> int : + current_index() -> int + Zero-based position of the current date in the sequence. + Computed using calendar-day (Julian-day) arithmetic, + so it is correct even when the sequence crosses a month or year boundary. + Returns 0 at construction / after reset. + + current_value() -> int + The current instant as an integer in yyyymmdd format. + + +.. py:method:: RepeatDate.current_index(self: ecflow.RepeatDate) -> int + :module: ecflow + +Return the zero-based index of the current date position. +Uses calendar-day (Julian-day) arithmetic so the result is correct +even for sequences that cross month or year boundaries. + + +.. py:method:: RepeatDate.current_value(self: ecflow.RepeatDate) -> object + :module: ecflow + +Return the current date as an integer in yyyymmdd format. + + +.. py:method:: RepeatDate.end(self: ecflow.RepeatDate) -> int :module: ecflow Return the end date as an integer in yyyymmdd format -.. py:method:: RepeatDate.name( (RepeatDate)arg1) -> str : +.. py:method:: RepeatDate.name(self: ecflow.RepeatDate) -> str :module: ecflow Return the name of the repeat. -.. py:method:: RepeatDate.start( (RepeatDate)arg1) -> int : +.. py:method:: RepeatDate.start(self: ecflow.RepeatDate) -> int :module: ecflow Return the start date as an integer in yyyymmdd format -.. py:method:: RepeatDate.step( (RepeatDate)arg1) -> int : +.. py:method:: RepeatDate.step(self: ecflow.RepeatDate) -> int :module: ecflow Return the step increment. This is used to update the repeat, until end date is reached diff --git a/docs/python_api/RepeatDateList.rst b/docs/python_api/RepeatDateList.rst index 60df2ed8d..d82a7c971 100644 --- a/docs/python_api/RepeatDateList.rst +++ b/docs/python_api/RepeatDateList.rst @@ -5,7 +5,7 @@ ecflow.RepeatDateList .. py:class:: RepeatDateList :module: ecflow - Bases: :py:class:`~Boost.Python.instance` + Bases: :py:class:`~pybind11_builtins.pybind11_object` Allows a :term:`node` to be repeated using arbitrary list of yyyymmdd integers @@ -37,20 +37,39 @@ Usage: t = Task('t1', RepeatDateList('YMD',[20050130, 20050203] )) +Accessor methods:: -.. py:method:: RepeatDateList.end( (RepeatDateList)arg1) -> int : + current_index() -> int + Zero-based index of the current date in the list. + current_value() -> int + The current date as an integer in yyyymmdd format. + + +.. py:method:: RepeatDateList.current_index(self: ecflow.RepeatDateList) -> int + :module: ecflow + +Return the zero-based index of the current date in the list. + + +.. py:method:: RepeatDateList.current_value(self: ecflow.RepeatDateList) -> object + :module: ecflow + +Return the current date as an integer in yyyymmdd format. + + +.. py:method:: RepeatDateList.end(self: ecflow.RepeatDateList) -> int :module: ecflow Return the end date as an integer in yyyymmdd format -.. py:method:: RepeatDateList.name( (RepeatDateList)arg1) -> str : +.. py:method:: RepeatDateList.name(self: ecflow.RepeatDateList) -> str :module: ecflow Return the name of the repeat. -.. py:method:: RepeatDateList.start( (RepeatDateList)arg1) -> int : +.. py:method:: RepeatDateList.start(self: ecflow.RepeatDateList) -> int :module: ecflow Return the start date as an integer in yyyymmdd format diff --git a/docs/python_api/RepeatDateTime.rst b/docs/python_api/RepeatDateTime.rst index 7167cfc0a..c189d5602 100644 --- a/docs/python_api/RepeatDateTime.rst +++ b/docs/python_api/RepeatDateTime.rst @@ -5,7 +5,7 @@ ecflow.RepeatDateTime .. py:class:: RepeatDateTime :module: ecflow - Bases: :py:class:`~Boost.Python.instance` + Bases: :py:class:`~pybind11_builtins.pybind11_object` Allows a :term:`node` to be repeated based on date+time instants (using yyyymmddTHHMMSS format). @@ -40,26 +40,46 @@ Usage: t = Task('t1', RepeatDateTime('DATETIME', '20050130T000000', '20050203T120000', '1:00:00')) +Accessor methods:: -.. py:method:: RepeatDateTime.end( (RepeatDateTime)arg1) -> int : + current_index() -> int + Zero-based position: (seconds(current) - seconds(start)) / seconds(step). + current_value() -> str + The current instant as a string in yyyymmddTHHMMSS format. + + +.. py:method:: RepeatDateTime.current_index(self: ecflow.RepeatDateTime) -> int + :module: ecflow + +Return the zero-based index of the current instant position: +(seconds(current) - seconds(start)) / seconds(step). + + +.. py:method:: RepeatDateTime.current_value(self: ecflow.RepeatDateTime) -> object + :module: ecflow + +Return the current instant as a string in yyyymmddTHHMMSS format. + + +.. py:method:: RepeatDateTime.end(self: ecflow.RepeatDateTime) -> int :module: ecflow Return the end date as an integer (i.e. seconds since 19700101T000000) -.. py:method:: RepeatDateTime.name( (RepeatDateTime)arg1) -> str : +.. py:method:: RepeatDateTime.name(self: ecflow.RepeatDateTime) -> str :module: ecflow Return the name of the repeat. -.. py:method:: RepeatDateTime.start( (RepeatDateTime)arg1) -> int : +.. py:method:: RepeatDateTime.start(self: ecflow.RepeatDateTime) -> int :module: ecflow Return the start date as an integer (i.e. seconds since 19700101T000000) -.. py:method:: RepeatDateTime.step( (RepeatDateTime)arg1) -> int : +.. py:method:: RepeatDateTime.step(self: ecflow.RepeatDateTime) -> int :module: ecflow Return the step increment (in seconds). This is used to update the repeat, until end instant is reached diff --git a/docs/python_api/RepeatDateTimeList.rst b/docs/python_api/RepeatDateTimeList.rst new file mode 100644 index 000000000..3baca1b20 --- /dev/null +++ b/docs/python_api/RepeatDateTimeList.rst @@ -0,0 +1,70 @@ +ecflow.RepeatDateTimeList +///////////////////////// + + +.. py:class:: RepeatDateTimeList + :module: ecflow + + Bases: :py:class:`~pybind11_builtins.pybind11_object` + +Allows a :term:`node` to be repeated using an arbitrary list of date+time instants (yyyymmddTHHMMSS format). + +A node can only have one :term:`repeat`. +The repeat name can be referenced in :term:`trigger` expressions. + +Constructor:: + + RepeatDateTimeList(variable, list) + string variable: The name of the repeat. The current datetime can be referenced in + trigger expressions using the variable name + list list_of_str: Arbitrary list of datetime strings in yyyymmddTHHMMSS format + +Exception: + +- Throws a RuntimeError if any string is not a valid datetime + +Usage: + +.. code-block:: python + + rep = RepeatDateTimeList('DT', ['20240101T000000', '20240102T120000', '20240103T060000']) + t = Task('t1', + RepeatDateTimeList('DT', ['20240101T000000', '20240102T120000'])) + +Accessor methods:: + + current_index() -> int + Zero-based index of the current instant in the list. + current_value() -> str + The current instant as a string in yyyymmddTHHMMSS format, or '' if out of bounds. + + +.. py:method:: RepeatDateTimeList.current_index(self: ecflow.RepeatDateTimeList) -> int + :module: ecflow + +Return the zero-based index of the current instant in the list. + + +.. py:method:: RepeatDateTimeList.current_value(self: ecflow.RepeatDateTimeList) -> object + :module: ecflow + +Return the current instant as a string in yyyymmddTHHMMSS format. + + +.. py:method:: RepeatDateTimeList.end(self: ecflow.RepeatDateTimeList) -> int + :module: ecflow + +Return the end instant as seconds since epoch + + +.. py:method:: RepeatDateTimeList.name(self: ecflow.RepeatDateTimeList) -> str + :module: ecflow + +Return the name of the repeat. + + +.. py:method:: RepeatDateTimeList.start(self: ecflow.RepeatDateTimeList) -> int + :module: ecflow + +Return the start instant as seconds since epoch + diff --git a/docs/python_api/RepeatDay.rst b/docs/python_api/RepeatDay.rst index 1e2a07014..24b3402fb 100644 --- a/docs/python_api/RepeatDay.rst +++ b/docs/python_api/RepeatDay.rst @@ -5,7 +5,7 @@ ecflow.RepeatDay .. py:class:: RepeatDay :module: ecflow - Bases: :py:class:`~Boost.Python.instance` + Bases: :py:class:`~pybind11_builtins.pybind11_object` A repeat that is infinite. @@ -23,3 +23,22 @@ Usage: t = Task('t1', RepeatDay( 1 )) +Accessor methods:: + + current_index() -> int + RepeatDay has no position concept; returns the step value. + current_value() -> int + The step value as an integer. + + +.. py:method:: RepeatDay.current_index(self: ecflow.RepeatDay) -> int + :module: ecflow + +Return the step as an integer value (n.b. RepeatDay has no position concept; the step is its only numeric attribute). + + +.. py:method:: RepeatDay.current_value(self: ecflow.RepeatDay) -> object + :module: ecflow + +Return the step value as an integer value. + diff --git a/docs/python_api/RepeatEnumerated.rst b/docs/python_api/RepeatEnumerated.rst index 6b041790b..d95979d0e 100644 --- a/docs/python_api/RepeatEnumerated.rst +++ b/docs/python_api/RepeatEnumerated.rst @@ -5,7 +5,7 @@ ecflow.RepeatEnumerated .. py:class:: RepeatEnumerated :module: ecflow - Bases: :py:class:`~Boost.Python.instance` + Bases: :py:class:`~pybind11_builtins.pybind11_object` Allows a node to be repeated using a enumerated list. @@ -26,21 +26,40 @@ Usage: t = Task('t1', RepeatEnumerated('COLOR', [ 'red', 'green', 'blue' ] )) +Accessor methods:: -.. py:method:: RepeatEnumerated.end( (RepeatEnumerated)arg1) -> int + current_index() -> int + Zero-based index of the current enumeration value. + current_value() -> str + The enumeration string at the current index, or '' if out of bounds. + + +.. py:method:: RepeatEnumerated.current_index(self: ecflow.RepeatEnumerated) -> int + :module: ecflow + +Return the zero-based index of the current enumeration value. + + +.. py:method:: RepeatEnumerated.current_value(self: ecflow.RepeatEnumerated) -> object + :module: ecflow + +Return the enumeration string at the current index. + + +.. py:method:: RepeatEnumerated.end(self: ecflow.RepeatEnumerated) -> int :module: ecflow -.. py:method:: RepeatEnumerated.name( (RepeatEnumerated)arg1) -> str : +.. py:method:: RepeatEnumerated.name(self: ecflow.RepeatEnumerated) -> str :module: ecflow Return the name of the :term:`repeat`. -.. py:method:: RepeatEnumerated.start( (RepeatEnumerated)arg1) -> int +.. py:method:: RepeatEnumerated.start(self: ecflow.RepeatEnumerated) -> int :module: ecflow -.. py:method:: RepeatEnumerated.step( (RepeatEnumerated)arg1) -> int +.. py:method:: RepeatEnumerated.step(self: ecflow.RepeatEnumerated) -> int :module: ecflow diff --git a/docs/python_api/RepeatInteger.rst b/docs/python_api/RepeatInteger.rst index 4eaf78baf..89cdfa6bb 100644 --- a/docs/python_api/RepeatInteger.rst +++ b/docs/python_api/RepeatInteger.rst @@ -5,7 +5,7 @@ ecflow.RepeatInteger .. py:class:: RepeatInteger :module: ecflow - Bases: :py:class:`~Boost.Python.instance` + Bases: :py:class:`~pybind11_builtins.pybind11_object` Allows a :term:`node` to be repeated using a integer range. @@ -28,21 +28,40 @@ Usage: t = Task('t1', RepeatInteger('HOUR', 6, 24, 6 )) +Accessor methods:: -.. py:method:: RepeatInteger.end( (RepeatInteger)arg1) -> int + current_index() -> int + Zero-based position: (value - start) / step. + current_value() -> int + The current integer value. + + +.. py:method:: RepeatInteger.current_index(self: ecflow.RepeatInteger) -> int + :module: ecflow + +Return the zero-based index of the current value: (value - start) / step. + + +.. py:method:: RepeatInteger.current_value(self: ecflow.RepeatInteger) -> object + :module: ecflow + +Return the current integer value. + + +.. py:method:: RepeatInteger.end(self: ecflow.RepeatInteger) -> int :module: ecflow -.. py:method:: RepeatInteger.name( (RepeatInteger)arg1) -> str : +.. py:method:: RepeatInteger.name(self: ecflow.RepeatInteger) -> str :module: ecflow Return the name of the repeat. -.. py:method:: RepeatInteger.start( (RepeatInteger)arg1) -> int +.. py:method:: RepeatInteger.start(self: ecflow.RepeatInteger) -> int :module: ecflow -.. py:method:: RepeatInteger.step( (RepeatInteger)arg1) -> int +.. py:method:: RepeatInteger.step(self: ecflow.RepeatInteger) -> int :module: ecflow diff --git a/docs/python_api/RepeatString.rst b/docs/python_api/RepeatString.rst index 705e6d994..5bac6fc0a 100644 --- a/docs/python_api/RepeatString.rst +++ b/docs/python_api/RepeatString.rst @@ -5,7 +5,7 @@ ecflow.RepeatString .. py:class:: RepeatString :module: ecflow - Bases: :py:class:`~Boost.Python.instance` + Bases: :py:class:`~pybind11_builtins.pybind11_object` Allows a :term:`node` to be repeated using a string list. @@ -26,21 +26,40 @@ Usage: t = Task('t1', RepeatString('COLOR', [ 'red', 'green', 'blue' ] )) +Accessor methods:: -.. py:method:: RepeatString.end( (RepeatString)arg1) -> int + current_index() -> int + Zero-based index into the string list. + current_value() -> str + The string at the current index, or '' if out of bounds. + + +.. py:method:: RepeatString.current_index(self: ecflow.RepeatString) -> int + :module: ecflow + +Return the zero-based index into the string list. + + +.. py:method:: RepeatString.current_value(self: ecflow.RepeatString) -> object + :module: ecflow + +Return the string at the current index. + + +.. py:method:: RepeatString.end(self: ecflow.RepeatString) -> int :module: ecflow -.. py:method:: RepeatString.name( (RepeatString)arg1) -> str : +.. py:method:: RepeatString.name(self: ecflow.RepeatString) -> str :module: ecflow Return the name of the :term:`repeat`. -.. py:method:: RepeatString.start( (RepeatString)arg1) -> int +.. py:method:: RepeatString.start(self: ecflow.RepeatString) -> int :module: ecflow -.. py:method:: RepeatString.step( (RepeatString)arg1) -> int +.. py:method:: RepeatString.step(self: ecflow.RepeatString) -> int :module: ecflow diff --git a/docs/python_api/SState.rst b/docs/python_api/SState.rst index 4ca26b1d0..47b6db78e 100644 --- a/docs/python_api/SState.rst +++ b/docs/python_api/SState.rst @@ -5,34 +5,40 @@ ecflow.SState .. py:class:: SState :module: ecflow - Bases: :py:class:`~Boost.Python.enum` + Bases: :py:class:`~pybind11_builtins.pybind11_object` A SState holds the :term:`ecflow_server` state See :term:`server states` +Members: + + HALTED + + SHUTDOWN + + RUNNING + .. py:attribute:: SState.HALTED :module: ecflow - :value: ecflow.SState.HALTED + :value: .. py:attribute:: SState.RUNNING :module: ecflow - :value: ecflow.SState.RUNNING + :value: .. py:attribute:: SState.SHUTDOWN :module: ecflow - :value: ecflow.SState.SHUTDOWN + :value: -.. py:attribute:: SState.names +.. py:property:: SState.name :module: ecflow - :value: {'HALTED': ecflow.SState.HALTED, 'RUNNING': ecflow.SState.RUNNING, 'SHUTDOWN': ecflow.SState.SHUTDOWN} -.. py:attribute:: SState.values +.. py:property:: SState.value :module: ecflow - :value: {0: ecflow.SState.HALTED, 1: ecflow.SState.SHUTDOWN, 2: ecflow.SState.RUNNING} diff --git a/docs/python_api/State.rst b/docs/python_api/State.rst index 27ef89862..711995563 100644 --- a/docs/python_api/State.rst +++ b/docs/python_api/State.rst @@ -5,7 +5,7 @@ ecflow.State .. py:class:: State :module: ecflow - Bases: :py:class:`~Boost.Python.enum` + Bases: :py:class:`~pybind11_builtins.pybind11_object` Each :term:`node` can have a status, which reflects the life cycle of a node. @@ -23,43 +23,55 @@ It varies as follows: Jobs end by becoming either :term:`complete` or :term:`aborted` +Members: + + unknown + + complete + + queued + + aborted + + submitted + + active + .. py:attribute:: State.aborted :module: ecflow - :value: ecflow.State.aborted + :value: .. py:attribute:: State.active :module: ecflow - :value: ecflow.State.active + :value: .. py:attribute:: State.complete :module: ecflow - :value: ecflow.State.complete + :value: -.. py:attribute:: State.names +.. py:property:: State.name :module: ecflow - :value: {'aborted': ecflow.State.aborted, 'active': ecflow.State.active, 'complete': ecflow.State.complete, 'queued': ecflow.State.queued, 'submitted': ecflow.State.submitted, 'unknown': ecflow.State.unknown} .. py:attribute:: State.queued :module: ecflow - :value: ecflow.State.queued + :value: .. py:attribute:: State.submitted :module: ecflow - :value: ecflow.State.submitted + :value: .. py:attribute:: State.unknown :module: ecflow - :value: ecflow.State.unknown + :value: -.. py:attribute:: State.values +.. py:property:: State.value :module: ecflow - :value: {0: ecflow.State.unknown, 1: ecflow.State.complete, 2: ecflow.State.queued, 3: ecflow.State.aborted, 4: ecflow.State.submitted, 5: ecflow.State.active} diff --git a/docs/python_api/Style.rst b/docs/python_api/Style.rst index 9333b9b32..b03e210d3 100644 --- a/docs/python_api/Style.rst +++ b/docs/python_api/Style.rst @@ -5,7 +5,7 @@ ecflow.Style .. py:class:: Style :module: ecflow - Bases: :py:class:`~Boost.Python.enum` + Bases: :py:class:`~pybind11_builtins.pybind11_object` Style is used to control printing output for the definition @@ -30,32 +30,41 @@ The following shows a summary of the features associated with each choice ===================== ==== ===== ======= +Members: + + NOTHING + + DEFS + + STATE + + MIGRATE + + .. py:attribute:: Style.DEFS :module: ecflow - :value: ecflow.Style.DEFS + :value: .. py:attribute:: Style.MIGRATE :module: ecflow - :value: ecflow.Style.MIGRATE + :value: .. py:attribute:: Style.NOTHING :module: ecflow - :value: ecflow.Style.NOTHING + :value: .. py:attribute:: Style.STATE :module: ecflow - :value: ecflow.Style.STATE + :value: -.. py:attribute:: Style.names +.. py:property:: Style.name :module: ecflow - :value: {'DEFS': ecflow.Style.DEFS, 'MIGRATE': ecflow.Style.MIGRATE, 'NOTHING': ecflow.Style.NOTHING, 'STATE': ecflow.Style.STATE} -.. py:attribute:: Style.values +.. py:property:: Style.value :module: ecflow - :value: {0: ecflow.Style.NOTHING, 1: ecflow.Style.DEFS, 2: ecflow.Style.STATE, 3: ecflow.Style.MIGRATE} diff --git a/docs/python_api/Submittable.rst b/docs/python_api/Submittable.rst index dec02e999..f566dbf1c 100644 --- a/docs/python_api/Submittable.rst +++ b/docs/python_api/Submittable.rst @@ -12,31 +12,31 @@ Submittable is the abstract base class for a Task and Alias It provides a process id, password and try number -.. py:method:: Submittable.get_aborted_reason( (Submittable)arg1) -> str : +.. py:method:: Submittable.get_aborted_reason(self: ecflow.Submittable) -> str :module: ecflow If node was aborted and a reason was provided, return the string -.. py:method:: Submittable.get_int_try_no( (Submittable)arg1) -> int : +.. py:method:: Submittable.get_int_try_no(self: ecflow.Submittable) -> int :module: ecflow The current try number as integer. -.. py:method:: Submittable.get_jobs_password( (Submittable)arg1) -> str : +.. py:method:: Submittable.get_jobs_password(self: ecflow.Submittable) -> str :module: ecflow The password. This generated by server -.. py:method:: Submittable.get_process_or_remote_id( (Submittable)arg1) -> str : +.. py:method:: Submittable.get_process_or_remote_id(self: ecflow.Submittable) -> str :module: ecflow The process or remote id of the running job -.. py:method:: Submittable.get_try_no( (Submittable)arg1) -> str : +.. py:method:: Submittable.get_try_no(self: ecflow.Submittable) -> str :module: ecflow The current try number as a string. diff --git a/docs/python_api/Suite.rst b/docs/python_api/Suite.rst index d9b59a352..50215bb47 100644 --- a/docs/python_api/Suite.rst +++ b/docs/python_api/Suite.rst @@ -40,29 +40,29 @@ Usage: Task('t1')))) # create in in-place -.. py:method:: Suite.add_clock( (Suite)arg1, (Clock)arg2) -> Suite +.. py:method:: Suite.add_clock(self: ecflow.Suite, arg0: ecflow.Clock) -> ecflow.Suite :module: ecflow -.. py:method:: Suite.add_end_clock( (Suite)arg1, (Clock)arg2) -> Suite : +.. py:method:: Suite.add_end_clock(self: ecflow.Suite, arg0: ecflow.Clock) -> ecflow.Suite :module: ecflow End clock, used to mark end of simulation -.. py:method:: Suite.begun( (Suite)arg1) -> bool : +.. py:method:: Suite.begun(self: ecflow.Suite) -> bool :module: ecflow Returns true if the :term:`suite` has begun, false otherwise -.. py:method:: Suite.get_clock( (Suite)arg1) -> Clock : +.. py:method:: Suite.get_clock(self: ecflow.Suite) -> ecflow.Clock :module: ecflow Returns the :term:`suite` :term:`clock` -.. py:method:: Suite.get_end_clock( (Suite)arg1) -> Clock : +.. py:method:: Suite.get_end_clock(self: ecflow.Suite) -> ecflow.Clock :module: ecflow Return the suite's end clock. Can be NULL diff --git a/docs/python_api/SuiteVec.rst b/docs/python_api/SuiteVec.rst index 73fb8f84d..d4047c794 100644 --- a/docs/python_api/SuiteVec.rst +++ b/docs/python_api/SuiteVec.rst @@ -5,15 +5,65 @@ ecflow.SuiteVec .. py:class:: SuiteVec :module: ecflow - Bases: :py:class:`~Boost.Python.instance` + Bases: :py:class:`~pybind11_builtins.pybind11_object` Hold a list of :term:`suite` nodes's -.. py:method:: SuiteVec.append( (SuiteVec)arg1, (object)arg2) -> None +.. py:method:: SuiteVec.append(self: ecflow.SuiteVec, x: Suite) -> None :module: ecflow +Add an item to the end of the list -.. py:method:: SuiteVec.extend( (SuiteVec)arg1, (object)arg2) -> None + +.. py:method:: SuiteVec.clear(self: ecflow.SuiteVec) -> None + :module: ecflow + +Clear the contents + + +.. py:method:: SuiteVec.count(self: ecflow.SuiteVec, x: Suite) -> int + :module: ecflow + +Return the number of times ``x`` appears in the list + + +.. py:method:: SuiteVec.extend(*args, **kwargs) + :module: ecflow + +Overloaded function. + +1. extend(self: ecflow.SuiteVec, L: ecflow.SuiteVec) -> None + +Extend the list by appending all the items in the given list + +2. extend(self: ecflow.SuiteVec, L: collections.abc.Iterable) -> None + +Extend the list by appending all the items in the given list + + +.. py:method:: SuiteVec.insert(self: ecflow.SuiteVec, i: typing.SupportsInt | typing.SupportsIndex, x: Suite) -> None + :module: ecflow + +Insert an item at a given position. + + +.. py:method:: SuiteVec.pop(*args, **kwargs) :module: ecflow +Overloaded function. + +1. pop(self: ecflow.SuiteVec) -> Suite + +Remove and return the last item + +2. pop(self: ecflow.SuiteVec, i: typing.SupportsInt | typing.SupportsIndex) -> Suite + +Remove and return the item at index ``i`` + + +.. py:method:: SuiteVec.remove(self: ecflow.SuiteVec, x: Suite) -> None + :module: ecflow + +Remove the first item from the list whose value is x. It is an error if there is no such item. + diff --git a/docs/python_api/TaskVec.rst b/docs/python_api/TaskVec.rst index feef407ec..8b79148b0 100644 --- a/docs/python_api/TaskVec.rst +++ b/docs/python_api/TaskVec.rst @@ -5,15 +5,65 @@ ecflow.TaskVec .. py:class:: TaskVec :module: ecflow - Bases: :py:class:`~Boost.Python.instance` + Bases: :py:class:`~pybind11_builtins.pybind11_object` Hold a list of :term:`task` nodes -.. py:method:: TaskVec.append( (TaskVec)arg1, (object)arg2) -> None +.. py:method:: TaskVec.append(self: ecflow.TaskVec, x: Task) -> None :module: ecflow +Add an item to the end of the list -.. py:method:: TaskVec.extend( (TaskVec)arg1, (object)arg2) -> None + +.. py:method:: TaskVec.clear(self: ecflow.TaskVec) -> None + :module: ecflow + +Clear the contents + + +.. py:method:: TaskVec.count(self: ecflow.TaskVec, x: Task) -> int + :module: ecflow + +Return the number of times ``x`` appears in the list + + +.. py:method:: TaskVec.extend(*args, **kwargs) + :module: ecflow + +Overloaded function. + +1. extend(self: ecflow.TaskVec, L: ecflow.TaskVec) -> None + +Extend the list by appending all the items in the given list + +2. extend(self: ecflow.TaskVec, L: collections.abc.Iterable) -> None + +Extend the list by appending all the items in the given list + + +.. py:method:: TaskVec.insert(self: ecflow.TaskVec, i: typing.SupportsInt | typing.SupportsIndex, x: Task) -> None + :module: ecflow + +Insert an item at a given position. + + +.. py:method:: TaskVec.pop(*args, **kwargs) :module: ecflow +Overloaded function. + +1. pop(self: ecflow.TaskVec) -> Task + +Remove and return the last item + +2. pop(self: ecflow.TaskVec, i: typing.SupportsInt | typing.SupportsIndex) -> Task + +Remove and return the item at index ``i`` + + +.. py:method:: TaskVec.remove(self: ecflow.TaskVec, x: Task) -> None + :module: ecflow + +Remove the first item from the list whose value is x. It is an error if there is no such item. + diff --git a/docs/python_api/Time.rst b/docs/python_api/Time.rst index 5fa4bd934..9e8ef2750 100644 --- a/docs/python_api/Time.rst +++ b/docs/python_api/Time.rst @@ -5,7 +5,7 @@ ecflow.Time .. py:class:: Time :module: ecflow - Bases: :py:class:`~Boost.Python.instance` + Bases: :py:class:`~pybind11_builtins.pybind11_object` Is used to define a :term:`time` dependency @@ -52,7 +52,7 @@ Usage: Time('10:30 20:10 00:10')) # Create time in place -.. py:method:: Time.time_series( (Time)arg1) -> TimeSeries : +.. py:method:: Time.time_series(self: ecflow.Time) -> ecflow.TimeSeries :module: ecflow Return the Time attributes time series diff --git a/docs/python_api/TimeSeries.rst b/docs/python_api/TimeSeries.rst index 7ce18d9f4..9fb4b2dd9 100644 --- a/docs/python_api/TimeSeries.rst +++ b/docs/python_api/TimeSeries.rst @@ -5,7 +5,7 @@ ecflow.TimeSeries .. py:class:: TimeSeries :module: ecflow - Bases: :py:class:`~Boost.Python.instance` + Bases: :py:class:`~pybind11_builtins.pybind11_object` A TimeSeries can hold a single time slot or a series. @@ -44,31 +44,31 @@ Usage:: time_series = TimeSeries(TimeSlot(10,11),False) -.. py:method:: TimeSeries.finish( (TimeSeries)arg1) -> TimeSlot : +.. py:method:: TimeSeries.finish(self: ecflow.TimeSeries) -> ecflow.TimeSlot :module: ecflow returns the finish time if time series specified, else returns a NULL time slot -.. py:method:: TimeSeries.has_increment( (TimeSeries)arg1) -> bool : +.. py:method:: TimeSeries.has_increment(self: ecflow.TimeSeries) -> bool :module: ecflow distinguish between a single time slot and a series. returns true for a series -.. py:method:: TimeSeries.incr( (TimeSeries)arg1) -> TimeSlot : +.. py:method:: TimeSeries.incr(self: ecflow.TimeSeries) -> ecflow.TimeSlot :module: ecflow returns the increment time if time series specified, else returns a NULL time slot -.. py:method:: TimeSeries.relative( (TimeSeries)arg1) -> bool : +.. py:method:: TimeSeries.relative(self: ecflow.TimeSeries) -> bool :module: ecflow returns a boolean where true means that the time series is relative -.. py:method:: TimeSeries.start( (TimeSeries)arg1) -> TimeSlot : +.. py:method:: TimeSeries.start(self: ecflow.TimeSeries) -> ecflow.TimeSlot :module: ecflow returns the start time diff --git a/docs/python_api/TimeSlot.rst b/docs/python_api/TimeSlot.rst index c504a03a9..1c06779ba 100644 --- a/docs/python_api/TimeSlot.rst +++ b/docs/python_api/TimeSlot.rst @@ -5,7 +5,7 @@ ecflow.TimeSlot .. py:class:: TimeSlot :module: ecflow - Bases: :py:class:`~Boost.Python.instance` + Bases: :py:class:`~pybind11_builtins.pybind11_object` Represents a time slot. @@ -24,14 +24,14 @@ Usage:: ts = TimeSlot(10,11) -.. py:method:: TimeSlot.empty( (TimeSlot)arg1) -> bool +.. py:method:: TimeSlot.empty(self: ecflow.TimeSlot) -> bool :module: ecflow -.. py:method:: TimeSlot.hour( (TimeSlot)arg1) -> int +.. py:method:: TimeSlot.hour(self: ecflow.TimeSlot) -> int :module: ecflow -.. py:method:: TimeSlot.minute( (TimeSlot)arg1) -> int +.. py:method:: TimeSlot.minute(self: ecflow.TimeSlot) -> int :module: ecflow diff --git a/docs/python_api/Today.rst b/docs/python_api/Today.rst index 58553636a..eb6afe5d7 100644 --- a/docs/python_api/Today.rst +++ b/docs/python_api/Today.rst @@ -5,7 +5,7 @@ ecflow.Today .. py:class:: Today :module: ecflow - Bases: :py:class:`~Boost.Python.instance` + Bases: :py:class:`~pybind11_builtins.pybind11_object` :term:`today` is a time dependency that does not wrap to tomorrow. @@ -49,8 +49,8 @@ Usage: Today('10:30 20:10 00:10')) # Create today in place -.. py:method:: Today.time_series( (Today)arg1) -> TimeSeries : +.. py:method:: Today.time_series(self: ecflow.Today) -> ecflow.TimeSeries :module: ecflow -Return the Todays time series +Return the Today's time series diff --git a/docs/python_api/Trigger.rst b/docs/python_api/Trigger.rst index 959600137..f66c8ecc2 100644 --- a/docs/python_api/Trigger.rst +++ b/docs/python_api/Trigger.rst @@ -5,7 +5,7 @@ ecflow.Trigger .. py:class:: Trigger :module: ecflow - Bases: :py:class:`~Boost.Python.instance` + Bases: :py:class:`~pybind11_builtins.pybind11_object` Add a :term:`trigger` or :term:`complete expression`. @@ -64,7 +64,7 @@ If however node taskx has not yet been added to its parent, we use a relative na t2 ==complete and taskx == complete -.. py:method:: Trigger.get_expression( (Trigger)arg1) -> str : +.. py:method:: Trigger.get_expression(self: ecflow.Trigger) -> str :module: ecflow returns the trigger expression as a string diff --git a/docs/python_api/UrlCmd.rst b/docs/python_api/UrlCmd.rst index a092a153c..151b74dff 100644 --- a/docs/python_api/UrlCmd.rst +++ b/docs/python_api/UrlCmd.rst @@ -5,7 +5,7 @@ ecflow.UrlCmd .. py:class:: UrlCmd :module: ecflow - Bases: :py:class:`~Boost.Python.instance` + Bases: :py:class:`~pybind11_builtins.pybind11_object` Executes a command ECF_URL_CMD to display a url. @@ -48,7 +48,7 @@ Lets assume that the server has the following definition:: print(str(e)) -.. py:method:: UrlCmd.execute( (UrlCmd)arg1) -> None : +.. py:method:: UrlCmd.execute(self: ecflow.UrlCmd) -> None :module: ecflow Displays url in the chosen browser diff --git a/docs/python_api/Variable.rst b/docs/python_api/Variable.rst index 795add8cb..9e20dc708 100644 --- a/docs/python_api/Variable.rst +++ b/docs/python_api/Variable.rst @@ -5,7 +5,7 @@ ecflow.Variable .. py:class:: Variable :module: ecflow - Bases: :py:class:`~Boost.Python.instance` + Bases: :py:class:`~pybind11_builtins.pybind11_object` Defines a :term:`variable` on a :term:`node` for use in :term:`ecf script`. @@ -57,19 +57,19 @@ The following use example of using Edit, which allow multiple variables to added Edit(d='d') ] -.. py:method:: Variable.empty( (Variable)arg1) -> bool : +.. py:method:: Variable.empty(self: ecflow.Variable) -> bool :module: ecflow Return true if the variable is empty. Used when returning a Null variable, from a find -.. py:method:: Variable.name( (Variable)arg1) -> str : +.. py:method:: Variable.name(self: ecflow.Variable) -> str :module: ecflow Return the variable name as string -.. py:method:: Variable.value( (Variable)arg1) -> str : +.. py:method:: Variable.value(self: ecflow.Variable) -> str :module: ecflow Return the variable value as a string diff --git a/docs/python_api/VariableList.rst b/docs/python_api/VariableList.rst index cc31f66e1..dcc37a6ec 100644 --- a/docs/python_api/VariableList.rst +++ b/docs/python_api/VariableList.rst @@ -5,15 +5,65 @@ ecflow.VariableList .. py:class:: VariableList :module: ecflow - Bases: :py:class:`~Boost.Python.instance` + Bases: :py:class:`~pybind11_builtins.pybind11_object` Hold a list of Variables -.. py:method:: VariableList.append( (VariableList)arg1, (object)arg2) -> None +.. py:method:: VariableList.append(self: ecflow.VariableList, x: Variable) -> None :module: ecflow +Add an item to the end of the list -.. py:method:: VariableList.extend( (VariableList)arg1, (object)arg2) -> None + +.. py:method:: VariableList.clear(self: ecflow.VariableList) -> None + :module: ecflow + +Clear the contents + + +.. py:method:: VariableList.count(self: ecflow.VariableList, x: Variable) -> int + :module: ecflow + +Return the number of times ``x`` appears in the list + + +.. py:method:: VariableList.extend(*args, **kwargs) + :module: ecflow + +Overloaded function. + +1. extend(self: ecflow.VariableList, L: ecflow.VariableList) -> None + +Extend the list by appending all the items in the given list + +2. extend(self: ecflow.VariableList, L: collections.abc.Iterable) -> None + +Extend the list by appending all the items in the given list + + +.. py:method:: VariableList.insert(self: ecflow.VariableList, i: typing.SupportsInt | typing.SupportsIndex, x: Variable) -> None + :module: ecflow + +Insert an item at a given position. + + +.. py:method:: VariableList.pop(*args, **kwargs) :module: ecflow +Overloaded function. + +1. pop(self: ecflow.VariableList) -> Variable + +Remove and return the last item + +2. pop(self: ecflow.VariableList, i: typing.SupportsInt | typing.SupportsIndex) -> Variable + +Remove and return the item at index ``i`` + + +.. py:method:: VariableList.remove(self: ecflow.VariableList, x: Variable) -> None + :module: ecflow + +Remove the first item from the list whose value is x. It is an error if there is no such item. + diff --git a/docs/python_api/Verify.rst b/docs/python_api/Verify.rst index bd2348db1..4d7a73c19 100644 --- a/docs/python_api/Verify.rst +++ b/docs/python_api/Verify.rst @@ -5,5 +5,5 @@ ecflow.Verify .. py:class:: Verify :module: ecflow - Bases: :py:class:`~Boost.Python.instance` + Bases: :py:class:`~pybind11_builtins.pybind11_object` diff --git a/docs/python_api/WhyCmd.rst b/docs/python_api/WhyCmd.rst index 781cd0653..3a97ccaac 100644 --- a/docs/python_api/WhyCmd.rst +++ b/docs/python_api/WhyCmd.rst @@ -5,7 +5,7 @@ ecflow.WhyCmd .. py:class:: WhyCmd :module: ecflow - Bases: :py:class:`~Boost.Python.instance` + Bases: :py:class:`~pybind11_builtins.pybind11_object` The why command reports, the reason why a node is not running. @@ -35,7 +35,7 @@ Usage:: print(str(e)) -.. py:method:: WhyCmd.why( (WhyCmd)arg1) -> str : +.. py:method:: WhyCmd.why(self: ecflow.WhyCmd) -> str :module: ecflow returns a '/n' separated string, with reasons why node is not running diff --git a/docs/python_api/Zombie.rst b/docs/python_api/Zombie.rst index 2f2ea66a3..39309a2f5 100644 --- a/docs/python_api/Zombie.rst +++ b/docs/python_api/Zombie.rst @@ -5,99 +5,99 @@ ecflow.Zombie .. py:class:: Zombie :module: ecflow - Bases: :py:class:`~Boost.Python.instance` + Bases: :py:class:`~pybind11_builtins.pybind11_object` Represent a zombie process stored by the server -.. py:method:: Zombie.adopt( (Zombie)arg1) -> bool +.. py:method:: Zombie.adopt(self: ecflow.Zombie) -> bool :module: ecflow -.. py:method:: Zombie.allowed_age( (Zombie)arg1) -> int +.. py:method:: Zombie.allowed_age(self: ecflow.Zombie) -> int :module: ecflow -.. py:method:: Zombie.attr( (Zombie)arg1) -> ZombieAttr +.. py:method:: Zombie.attr(self: ecflow.Zombie) -> ecflow.ZombieAttr :module: ecflow -.. py:method:: Zombie.block( (Zombie)arg1) -> bool +.. py:method:: Zombie.block(self: ecflow.Zombie) -> bool :module: ecflow -.. py:method:: Zombie.calls( (Zombie)arg1) -> int +.. py:method:: Zombie.calls(self: ecflow.Zombie) -> int :module: ecflow -.. py:method:: Zombie.duration( (Zombie)arg1) -> int +.. py:method:: Zombie.duration(self: ecflow.Zombie) -> int :module: ecflow -.. py:method:: Zombie.empty( (Zombie)arg1) -> bool +.. py:method:: Zombie.empty(self: ecflow.Zombie) -> bool :module: ecflow -.. py:method:: Zombie.fail( (Zombie)arg1) -> bool +.. py:method:: Zombie.fail(self: ecflow.Zombie) -> bool :module: ecflow -.. py:method:: Zombie.fob( (Zombie)arg1) -> bool +.. py:method:: Zombie.fob(self: ecflow.Zombie) -> bool :module: ecflow -.. py:method:: Zombie.host( (Zombie)arg1) -> str +.. py:method:: Zombie.host(self: ecflow.Zombie) -> str :module: ecflow -.. py:method:: Zombie.jobs_password( (Zombie)arg1) -> str +.. py:method:: Zombie.jobs_password(self: ecflow.Zombie) -> str :module: ecflow -.. py:method:: Zombie.kill( (Zombie)arg1) -> bool +.. py:method:: Zombie.kill(self: ecflow.Zombie) -> bool :module: ecflow -.. py:method:: Zombie.last_child_cmd( (Zombie)arg1) -> ChildCmdType +.. py:method:: Zombie.last_child_cmd(self: ecflow.Zombie) -> ecflow.ChildCmdType :module: ecflow -.. py:method:: Zombie.manual_user_action( (Zombie)arg1) -> bool +.. py:method:: Zombie.manual_user_action(self: ecflow.Zombie) -> bool :module: ecflow -.. py:method:: Zombie.path_to_task( (Zombie)arg1) -> str +.. py:method:: Zombie.path_to_task(self: ecflow.Zombie) -> str :module: ecflow -.. py:method:: Zombie.process_or_remote_id( (Zombie)arg1) -> str +.. py:method:: Zombie.process_or_remote_id(self: ecflow.Zombie) -> str :module: ecflow -.. py:method:: Zombie.remove( (Zombie)arg1) -> bool +.. py:method:: Zombie.remove(self: ecflow.Zombie) -> bool :module: ecflow -.. py:method:: Zombie.try_no( (Zombie)arg1) -> int +.. py:method:: Zombie.try_no(self: ecflow.Zombie) -> int :module: ecflow -.. py:method:: Zombie.type( (Zombie)arg1) -> ZombieType +.. py:method:: Zombie.type(self: ecflow.Zombie) -> ecflow.ZombieType :module: ecflow -.. py:method:: Zombie.type_str( (Zombie)arg1) -> str +.. py:method:: Zombie.type_str(self: ecflow.Zombie) -> str :module: ecflow -.. py:method:: Zombie.user_action( (Zombie)arg1) -> ZombieUserActionType +.. py:method:: Zombie.user_action(self: ecflow.Zombie) -> ecflow.ZombieUserActionType :module: ecflow -.. py:method:: Zombie.user_action_str( (Zombie)arg1) -> str +.. py:method:: Zombie.user_action_str(self: ecflow.Zombie) -> str :module: ecflow -.. py:method:: Zombie.user_cmd( (Zombie)arg1) -> str +.. py:method:: Zombie.user_cmd(self: ecflow.Zombie) -> str :module: ecflow diff --git a/docs/python_api/ZombieAttr.rst b/docs/python_api/ZombieAttr.rst index 69f7c6d1e..cc25f2550 100644 --- a/docs/python_api/ZombieAttr.rst +++ b/docs/python_api/ZombieAttr.rst @@ -5,7 +5,7 @@ ecflow.ZombieAttr .. py:class:: ZombieAttr :module: ecflow - Bases: :py:class:`~Boost.Python.instance` + Bases: :py:class:`~pybind11_builtins.pybind11_object` The :term:`zombie` attribute defines how a :term:`zombie` should be handled in an automated fashion @@ -48,25 +48,25 @@ Usage: The list of child commands. If empty action applies to all child cmds -.. py:method:: ZombieAttr.empty( (ZombieAttr)arg1) -> bool : +.. py:method:: ZombieAttr.empty(self: ecflow.ZombieAttr) -> bool :module: ecflow Return true if the attribute is empty -.. py:method:: ZombieAttr.user_action( (ZombieAttr)arg1) -> ZombieUserActionType : +.. py:method:: ZombieAttr.user_action(self: ecflow.ZombieAttr) -> ecflow.ZombieUserActionType :module: ecflow The automated action to invoke, when zombies arise -.. py:method:: ZombieAttr.zombie_lifetime( (ZombieAttr)arg1) -> int : +.. py:method:: ZombieAttr.zombie_lifetime(self: ecflow.ZombieAttr) -> int :module: ecflow Returns the lifetime in seconds of :term:`zombie` in the server -.. py:method:: ZombieAttr.zombie_type( (ZombieAttr)arg1) -> ZombieType : +.. py:method:: ZombieAttr.zombie_type(self: ecflow.ZombieAttr) -> ecflow.ZombieType :module: ecflow Returns the :term:`zombie type` diff --git a/docs/python_api/ZombieType.rst b/docs/python_api/ZombieType.rst index 8e2158727..bf04d4b12 100644 --- a/docs/python_api/ZombieType.rst +++ b/docs/python_api/ZombieType.rst @@ -5,13 +5,28 @@ ecflow.ZombieType .. py:class:: ZombieType :module: ecflow - Bases: :py:class:`~Boost.Python.enum` + Bases: :py:class:`~pybind11_builtins.pybind11_object` :term:`zombie`\ s are running jobs that fail authentication when communicating with the :term:`ecflow_server`. See class :term:`zombie type` and :py:class:`ecflow.ZombieAttr` for further information. +Members: + + ecf + + ecf_pid + + ecf_pid_passwd + + ecf_passwd + + user + + path + + .. py:attribute:: ZombieType.ecf :module: ecflow :value: ecflow.ZombieType.ecf @@ -32,6 +47,10 @@ See class :term:`zombie type` and :py:class:`ecflow.ZombieAttr` for further info :value: ecflow.ZombieType.ecf_pid_passwd +.. py:property:: ZombieType.name + :module: ecflow + + .. py:attribute:: ZombieType.names :module: ecflow :value: {'ecf': ecflow.ZombieType.ecf, 'ecf_passwd': ecflow.ZombieType.ecf_passwd, 'ecf_pid': ecflow.ZombieType.ecf_pid, 'ecf_pid_passwd': ecflow.ZombieType.ecf_pid_passwd, 'path': ecflow.ZombieType.path, 'user': ecflow.ZombieType.user} @@ -47,6 +66,10 @@ See class :term:`zombie type` and :py:class:`ecflow.ZombieAttr` for further info :value: ecflow.ZombieType.user +.. py:property:: ZombieType.value + :module: ecflow + + .. py:attribute:: ZombieType.values :module: ecflow :value: {0: ecflow.ZombieType.user, 1: ecflow.ZombieType.ecf, 2: ecflow.ZombieType.ecf_pid, 3: ecflow.ZombieType.ecf_passwd, 4: ecflow.ZombieType.ecf_pid_passwd, 5: ecflow.ZombieType.path} diff --git a/docs/python_api/ZombieUserActionType.rst b/docs/python_api/ZombieUserActionType.rst index 5d2d7cec6..cff435e34 100644 --- a/docs/python_api/ZombieUserActionType.rst +++ b/docs/python_api/ZombieUserActionType.rst @@ -5,7 +5,7 @@ ecflow.ZombieUserActionType .. py:class:: ZombieUserActionType :module: ecflow - Bases: :py:class:`~Boost.Python.enum` + Bases: :py:class:`~pybind11_builtins.pybind11_object` ZombieUserActionType is used define an automated response. See class :py:class:`ZombieAttr` @@ -31,6 +31,21 @@ server side: Note: Only adopt will allow the :term:`child command` to continue and change the :term:`node` tree +Members: + + fob + + fail + + remove + + adopt + + block + + kill + + .. py:attribute:: ZombieUserActionType.adopt :module: ecflow :value: ecflow.ZombieUserActionType.adopt @@ -56,6 +71,10 @@ Note: Only adopt will allow the :term:`child command` to continue and change the :value: ecflow.ZombieUserActionType.kill +.. py:property:: ZombieUserActionType.name + :module: ecflow + + .. py:attribute:: ZombieUserActionType.names :module: ecflow :value: {'adopt': ecflow.ZombieUserActionType.adopt, 'block': ecflow.ZombieUserActionType.block, 'fail': ecflow.ZombieUserActionType.fail, 'fob': ecflow.ZombieUserActionType.fob, 'kill': ecflow.ZombieUserActionType.kill, 'remove': ecflow.ZombieUserActionType.remove} @@ -66,6 +85,10 @@ Note: Only adopt will allow the :term:`child command` to continue and change the :value: ecflow.ZombieUserActionType.remove +.. py:property:: ZombieUserActionType.value + :module: ecflow + + .. py:attribute:: ZombieUserActionType.values :module: ecflow :value: {0: ecflow.ZombieUserActionType.fob, 1: ecflow.ZombieUserActionType.fail, 2: ecflow.ZombieUserActionType.adopt, 3: ecflow.ZombieUserActionType.remove, 4: ecflow.ZombieUserActionType.block, 5: ecflow.ZombieUserActionType.kill} diff --git a/docs/python_api/ZombieVec.rst b/docs/python_api/ZombieVec.rst index 36ab6b92a..f1c352e95 100644 --- a/docs/python_api/ZombieVec.rst +++ b/docs/python_api/ZombieVec.rst @@ -5,15 +5,69 @@ ecflow.ZombieVec .. py:class:: ZombieVec :module: ecflow - Bases: :py:class:`~Boost.Python.instance` + Bases: :py:class:`~pybind11_builtins.pybind11_object` Hold a list of zombies -.. py:method:: ZombieVec.append( (ZombieVec)arg1, (object)arg2) -> None +.. py:method:: ZombieVec.append(self: ecflow.ZombieVec, x: Zombie) -> None :module: ecflow +Add an item to the end of the list -.. py:method:: ZombieVec.extend( (ZombieVec)arg1, (object)arg2) -> None + +.. py:method:: ZombieVec.clear(self: ecflow.ZombieVec) -> None + :module: ecflow + +Clear the contents + + +.. py:method:: ZombieVec.count(self: ecflow.ZombieVec, x: Zombie) -> int + :module: ecflow + +Return the number of times ``x`` appears in the list + + +.. py:method:: ZombieVec.extend(*args, **kwargs) + :module: ecflow + +Overloaded function. + +1. extend(self: ecflow.ZombieVec, L: ecflow.ZombieVec) -> None + +Extend the list by appending all the items in the given list + +2. extend(self: ecflow.ZombieVec, L: collections.abc.Iterable) -> None + +Extend the list by appending all the items in the given list + + +.. py:method:: ZombieVec.insert(self: ecflow.ZombieVec, i: typing.SupportsInt | typing.SupportsIndex, x: Zombie) -> None + :module: ecflow + +Insert an item at a given position. + + +.. py:method:: ZombieVec.pop(*args, **kwargs) + :module: ecflow + +Overloaded function. + +1. pop(self: ecflow.ZombieVec) -> Zombie + +Remove and return the last item + +2. pop(self: ecflow.ZombieVec, i: typing.SupportsInt | typing.SupportsIndex) -> Zombie + +Remove and return the item at index ``i`` + + +.. py:method:: ZombieVec.remove(self: ecflow.ZombieVec, x: Zombie) -> None + :module: ecflow + +Remove the first item from the list whose value is x. It is an error if there is no such item. + + +.. py:function:: debug_build() -> bool :module: ecflow diff --git a/docs/python_api/python_api.rst b/docs/python_api/python_api.rst index 9d7616dd7..cb4d4fc9e 100644 --- a/docs/python_api/python_api.rst +++ b/docs/python_api/python_api.rst @@ -40,6 +40,7 @@ Suite definition - :py:class:`ecflow.RepeatDate` - :py:class:`ecflow.RepeatDateList` - :py:class:`ecflow.RepeatDateTime` + - :py:class:`ecflow.RepeatDateTimeList` - :py:class:`ecflow.RepeatDay` - :py:class:`ecflow.RepeatEnumerated` - :py:class:`ecflow.RepeatInteger` diff --git a/docs/release_notes/index.rst b/docs/release_notes/index.rst index 6507f3534..6572d7bd9 100644 --- a/docs/release_notes/index.rst +++ b/docs/release_notes/index.rst @@ -5,6 +5,7 @@ Release notes .. toctree:: :maxdepth: 1 + version_5.17 version_5.16 version_5.15 version_5.14 diff --git a/docs/release_notes/version_5.16.rst b/docs/release_notes/version_5.16.rst index 37c924b8c..f344bbeab 100644 --- a/docs/release_notes/version_5.16.rst +++ b/docs/release_notes/version_5.16.rst @@ -12,7 +12,7 @@ Version 5.16 updates Version 5.16.0 ============== -* `Released `__\ on 2026-02-06 +* `Released `__\ on 2026-03-25 General ------- diff --git a/docs/release_notes/version_5.17.rst b/docs/release_notes/version_5.17.rst new file mode 100644 index 000000000..945aba1a3 --- /dev/null +++ b/docs/release_notes/version_5.17.rst @@ -0,0 +1,45 @@ +.. _version_5.17: + +Version 5.17 updates +******************** + +.. role:: jiraissue + :class: hidden + +.. role:: githubissue + :class: hidden + +Version 5.17.0 +============== + +* `Released `__\ on 2026-06-03 + +General +------- + +- **Feature** enable support for RepeatDateTimeList :jiraissue:`ECFLOW-2080` +- **Feature** enable support for Debian 13 (+ .deb package) :jiraissue:`ECFLOW-2082` +- **Feature** Enable Repeat.current_index()/_value() in Python API :jiraissue:`ECFLOW-2084` + +- **Improvement** review LICENSE + NOTICE content :jiraissue:`ECFLOW-2088` +- **Improvement** enable support for compilers GNU GCC 15.2/Intel 2025.3.1 :jiraissue:`ECFLOW-2078/ECFLOW-2079` +- **Improvement** refactor string/node path algorithms (i.e. remove boost::algorithm dependencies) :jiraissue:`ECFLOW-2076,ECFLOW-1922` +- **Improvement** review server log content to ensure no information disclosure :jiraissue:`ECFLOW-2004` +- **Improvement** review SSL server implementation to ensure no use of weak encryption algorithms :jiraissue:`ECFLOW-2003` + +- **Fix** correct parsing of Variables with whitespace/special characters in their value :jiraissue:`ECFLOW-2089` +- **Fix** correct handling of '-s'/'-n' flag when adding inlimit using ecflow_client :jiraissue:`ECFLOW-2094` +- **Fix** correct handling of Trigger subtraction expression :code:`:NONEXISTENT - 1` :jiraissue:`ECFLOW-2083` +- **Fix** correct optimisation for parsing of Simple Trigger Expressions :jiraissue:`ECFLOW-2075` +- **Fix** correct handling of :code:`--help` option when ECF_SSL is set :jiraissue:`ECFLOW-2081` +- **Fix** avoid clobbering of :code:`setup.py` and :code:`__init__.py` during multi-preset CMake configuration :jiraissue:`ECFLOW-2077` + +Python API +---------- + +- **Improvement** replace Boost.Python with Pybind11 for Python bindings :jiraissue:`ECFLOW-2090` + +ecFlowUI +-------- + +- **Fix** correct intermittent :code:`ecflow_ui` crash :jiraissue:`ECFLOW-2090` diff --git a/docs/tutorial/advanced_topics/repeating.rst b/docs/tutorial/advanced_topics/repeating.rst index 4b527a7cf..1358f1773 100644 --- a/docs/tutorial/advanced_topics/repeating.rst +++ b/docs/tutorial/advanced_topics/repeating.rst @@ -14,6 +14,7 @@ This can be achieved by defining a :term:`repeat` attribute, which enables itera * a sequence of time instants (:code:`repeat datetime`) * a list of string (:code:`repeat string` or :code:`repeat enumerated`) * a list of dates (:code:`repeat datelist`) +* a list of time instants (:code:`repeat datetimelist`) Sequences of integers, dates or time instants are created by specifying the first and last element, with an optional increment (the default is 1 for integers, 1 day for dates, and 24:00:00 for time instants). @@ -31,13 +32,15 @@ followed by the type of repeat, the name of the repeat, and the values to iterat repeat string COLOR red green blue yellow repeat enumerated FRUIT apple banana cherry repeat datelist YMD 20130101 20130102 20130103 20200101 20190101 + repeat datetimelist INSTANT 20240101T000000 20240102T120000 20240103T060000 In order to be used by the task script or :term:`trigger` expressions, each :term:`repeat` attribute generates ecFlow variable(s), with the name(s) corresponding to the name of the repeat. .. note:: - If :code:`repeat date`, or :code:`repeat datelist` are used in trigger expressions, they will use date arithmetic. + If :code:`repeat date` or :code:`repeat datelist` are used in trigger expressions, they will use date arithmetic. + If :code:`repeat datetime` or :code:`repeat datetimelist` are used in trigger expressions, they will use seconds-based arithmetic. Repeat with day/date ---------------------- diff --git a/docs/ug/cookbook/index.rst b/docs/ug/cookbook/index.rst index 50e9d24c6..3fd8367b2 100644 --- a/docs/ug/cookbook/index.rst +++ b/docs/ug/cookbook/index.rst @@ -31,6 +31,7 @@ This cookbook will provide real examples of using the ecFlow :ref:`python_api`. template_suite shell_suite_definition cron_repeat_day_1_repeat_date + repeat_using_time_instants one_task_wrapper_for_multiple_tasks what_is_the_most_efficient_way_to_access_my_suites ecflow_ui_web_menu diff --git a/docs/ug/cookbook/repeat_using_time_instants.rst b/docs/ug/cookbook/repeat_using_time_instants.rst new file mode 100644 index 000000000..a2f1b716c --- /dev/null +++ b/docs/ug/cookbook/repeat_using_time_instants.rst @@ -0,0 +1,133 @@ +.. _repeat_datetime_and_datetimelist: + +Repeat using time instants +************************** + +:code:`repeat datetime` and :code:`repeat datetimelist` allow iterating a node over a sequence of +date+time instants (formatted as :code:`yyyymmddTHHMMSS`). They complement :code:`repeat date` and +:code:`repeat datelist` when sub-daily precision is needed — for example, iterating over hourly or +six-hourly data. + +Generated variables +------------------- + +Both :code:`repeat datetime` and :code:`repeat datetimelist` generate the same set of variables, +which can be used in job scripts (:code:`%INSTANT%`, :code:`%INSTANT_DATE%`, …) and in trigger/complete expressions: + +.. list-table:: + :header-rows: 1 + + * - Variable + - Content + * - ``INSTANT`` + - Current instant, formatted as ``YYYYMMDDThhmmss`` + * - ``CYCLE_DATE`` + - Date part, formatted as ``YYYYMMDD`` + * - ``INSTANT_YYYY`` + - Four-digit year + * - ``INSTANT_MM`` + - Two-digit month + * - ``INSTANT_DD`` + - Two-digit day of month + * - ``INSTANT_DOW`` + - Day of week + * - ``INSTANT_TIME`` + - Time part, formatted as ``hhmmss`` + * - ``INSTANT_HOURS`` + - Two-digit hour + * - ``INSTANT_MINUTES`` + - Two-digit minute + * - ``INSTANT_SECONDS`` + - Two-digit second + * - ``INSTANT_JULIAN`` + - Julian day corresponding to the date + +Choosing between the two +------------------------ + +- Use **repeat datetime** when the instants follow a regular interval: a start, an end, and a fixed step (e.g. every 6 hours). +- Use **repeat datetimelist** when the instants are irregular or explicitly enumerated (e.g. 00Z on Monday, 12Z on Wednesday, 06Z on Friday). + +.. code-block:: shell + :caption: Regular interval — repeat datetime + + family cycles + repeat datetime INSTANT 20240101T000000 20240103T180000 06:00:00 + task forecast + task post + trigger forecast == complete + endfamily + +.. code-block:: shell + :caption: Irregular instants — repeat datetimelist + + family cycles + repeat datetimelist INSTANT 20240101T000000 20240101T120000 20240103T060000 + task forecast + task post + trigger forecast == complete + endfamily + +Time instance dependency +------------------------ + +A common pattern with :code:`repeat datetime` and :code:`repeat datetimelist` is to trigger an overall +post-processing task once a specific iteration — identified by its position in the list — has been processed. +Because the repeat variable is compared as seconds since epoch, any datetime literal in a trigger expression +acts as an index threshold: :code:`f:INSTANT >= 20240101T120000` becomes true the moment the repeat advances +to the second instant (index 1) or beyond. +Combining this with :code:`f/post == complete` ensures the per-cycle work has actually finished before the +overall task starts. + +.. code-block:: shell + :caption: Overall post-processing triggered at specific instants + + suite s + family f + repeat datetimelist INSTANT 20240101T000000 20240101T120000 20240103T060000 + task forecast + task post + trigger forecast == complete + endfamily + task overall_1 + # triggered once the index-1 instant (20240101T120000) has completed + trigger f:INSTANT >= 20240101T120000 and f/post == complete + task overall_2 + # triggered once the index-2 instant (20240103T060000) has completed + trigger overall_1 == complete and f:INSTANT >= 20240103T060000 and f/post == complete + endsuite + +Chaining :code:`overall_2` on :code:`overall_1 == complete` guarantees the intermediate +step always precedes the final one, even if the repeat advances through multiple instants quickly. + +Trigger expressions and arithmetic +----------------------------------- + +When a :code:`repeat datetime` or :code:`repeat datetimelist` variable is referenced in a trigger +expression, its value is the number of seconds since :code:`19700101T000000`. +Integer arithmetic (adding/subtracting seconds) applies: + +.. code-block:: shell + + # task starts only after t1 has started processing, at least, 20240101T060000 + trigger /suite/t1:INSTANT >= 20240101T060000 + + # task starts only after t1 has started processing at least 1 day past 20240101T060000 + trigger /suite/t1:INSTANT + 86400 > 20240101T000000 + +.. tip:: + + DateTime instant literals in trigger expressions (e.g. :code:`20240101T060000`) are also + interpreted as seconds since epoch, so comparisons between repeat variables and datetime + literals work directly without any conversion. + +See also: + +.. list-table:: + + * - :ref:`repeat_(for_loop)` + - Full description of all repeat types, generated variables, and trigger semantics + * - :ref:`cron_repeat_day_1_repeat_date` + - Analogous patterns using date-based repeats + * - :ref:`python_api` + - :py:class:`ecflow.RepeatDateTime`, :py:class:`ecflow.RepeatDateTimeList` diff --git a/docs/ug/user_manual/definition_file_grammar.rst b/docs/ug/user_manual/definition_file_grammar.rst index 893500d61..d172a5a80 100644 --- a/docs/ug/user_manual/definition_file_grammar.rst +++ b/docs/ug/user_manual/definition_file_grammar.rst @@ -98,12 +98,12 @@ Definition file grammar : !`late_option` >> +`nextline` late_option: "-c" >> `hh_mm` | ("-s" >> `hh_mm` ) | "-a" >> `hh_mm` repeat: "repeat" >> `repeat_type` >> +`nextline` - repeat_type: `repeat_datetime` | `repeat_date` | + repeat_type: `repeat_datetime` | `repeat_datetimelist` | `repeat_date` | : `repeat_day` | `repeat_month` | `repeat_year` | - : `repeat_integer` | `repeat_enumerated` | `repeat_string` | + : `repeat_integer` | `repeat_enumerated` | `repeat_string` | : `repeat_datelist` repeat_day: "day" >> unsigned integer >> !`ymd` - repeat_integer: "integer" >> `identifier` >> integer >> " " >> + repeat_integer: "integer" >> `identifier` >> integer >> " " >> : integer >> " " >> integer repeat_enumerated: "enumerated" >> `identifier` >> +`identifier` repeat_string: "string" >> `identifier` >> +`identifier` @@ -111,6 +111,7 @@ Definition file grammar repeat_datetime: "datetime" >> `identifier` >> : `instant` >> `instant` >> `duration` repeat_datelist: "datelist" >> `identifier` >> +( `ymd` ) + repeat_datetimelist: "datetimelist" >> `identifier` >> +( `instant` ) varvalue: `tickquotedstring` | `quotedstring` | `identifier` suiteName: `node_name` >> +`nextline` familyName: `node_name` >> +`nextline` diff --git a/docs/ug/user_manual/python_based_suite_definition/adding_attributes/adding_repeats.rst b/docs/ug/user_manual/python_based_suite_definition/adding_attributes/adding_repeats.rst index d2755142d..ada27b1ba 100644 --- a/docs/ug/user_manual/python_based_suite_definition/adding_attributes/adding_repeats.rst +++ b/docs/ug/user_manual/python_based_suite_definition/adding_attributes/adding_repeats.rst @@ -15,6 +15,7 @@ Adding Repeats RepeatString, RepeatInteger, RepeatDateList, + RepeatDateTimeList, ) def add_tasks(fam): @@ -49,6 +50,18 @@ Adding Repeats ) # arbitrary date list add_tasks(f6) + f7 = s1.add_family("f7") + f7.add_repeat( + RepeatDateTime("INSTANT", "20240101T000000", "20240102T000000", "12:00:00") + ) # sequence of datetime instants + add_tasks(f7) + + f8 = s1.add_family("f8") + f8.add_repeat( + RepeatDateTimeList("INSTANT", ["20240101T000000", "20240102T120000", "20240103T060000"]) + ) # arbitrary datetime instant list + add_tasks(f8) + The following examples show alternative styles that produces the same definition: .. code-block:: python @@ -82,6 +95,16 @@ The following examples show alternative styles that produces the same definition RepeatDateList("YMD", [20130101, 20130102, 20130103]), [Task("t{}".format(i)) for i in range(1, 3)], ), + Family( + "f8", + RepeatDateTime("INSTANT", "20240101T000000", "20240102T000000", "12:00:00"), + [Task("t{}".format(i)) for i in range(1, 3)], + ), + Family( + "f8", + RepeatDateTimeList("INSTANT", ["20240101T000000", "20240102T120000", "20240103T060000"]), + [Task("t{}".format(i)) for i in range(1, 3)], + ), ) ) @@ -90,7 +113,7 @@ The following examples show alternative styles that produces the same definition defs = Defs() + Suite("s1") defs.s1 += [ Family("f{}".format(i)).add([Task("t{}".format(i)) for i in range(1, 3)]) - for i in range(1, 6) + for i in range(1, 8) ] defs.s1.f1 += RepeatDate("YMD", 20100111, 20100115, 2) defs.s1.f2 += RepeatInteger("count", 0, 100, 2) @@ -98,8 +121,9 @@ The following examples show alternative styles that produces the same definition defs.s1.f4 += RepeatString("enum", ["a", "b", "c"]) defs.s1.f5 += RepeatDay(1) defs.s1.f6 += RepeatDateList("YMD", [20130101, 20130102, 20130103]) + defs.s1.f7 += RepeatDateTime("INSTANT", "20240101T000000", "20240102T000000", "12:00:00") + defs.s1.f8 += RepeatDateTimeList("INSTANT", ["20240101T000000", "20240102T120000", "20240103T060000"]) .. warning:: In the second example above we use 'defs.s1.*' to reference a node by name. This is useful in small designs but will produce maintenance issues in large designs IF the node names are changed. - diff --git a/docs/ug/user_manual/text_based_suite_definition/attributes/edit_add_variable.rst b/docs/ug/user_manual/text_based_suite_definition/attributes/edit_add_variable.rst index c2d4066b5..0c4e3ca39 100644 --- a/docs/ug/user_manual/text_based_suite_definition/attributes/edit_add_variable.rst +++ b/docs/ug/user_manual/text_based_suite_definition/attributes/edit_add_variable.rst @@ -3,24 +3,93 @@ variable //////// -This defines a variable for ecFlow job substitution in a node, -equivalent to an external variable. There can be any number of -variables. The variables are names inside a pair of \`%'(ECF_MICRO) -characters in an ecFlow script. However, remember that ecFlow is case -sensitive. - -The content of a variable replaces the variable name in the ecFlow -script at submission time. Special characters in a definition must be -placed inside single quotes if misinterpretation is to be avoided or -inside double quotes variable substitution is to be carried out. Quotes -are needed if defining as a list. +This defines a variable for ecFlow job substitution in a node, equivalent to an external variable. +There can be any number of variables defined at any node level: suite, family or task. + +The textual definition of a variable is as follows, where :code:`` is the name of the variable and +:code:`` is the value of the variable. Variable names are case sensitive. + +After the variable name and value, an optional comment can be provided. The comment must be preceded by +a :code:`#` character, after the variable value. The comment is ignored, and serves only as a note for +users reading the suite definition. .. code-block:: shell - edit ECF_JOB_CMD "/bin/sh %ECF_JOB% &" - edit ECF_JOB_CMD "/usr/local/bin/qsub %ECF_JOB%" - edit ECF_JOB_CMD "rsh %ECF_HOST% sh <%ECF_JOB% 1>%ECF_JOBOUT% 2>&1" + edit + edit # optional comment + edit '' + edit "" # optional comment + +.. important:: + + **Use single quotes to delimit the value of a variable.** + + Although not mandatory, it is a highly recommended practice to *always* use single quotes to enclose + the value of a variable, even if it does not contain spaces. This avoids any misinterpretation of special + characters, including whitespace, in the value and ensures that the variable value is treated as a single entity. + +.. hint:: + + Define each variable on a separate line, and avoid defining multiple variables on the same line. + This enhances readability and maintainability of the suite definition. + +To use the variable in an ecFlow task script refer to it by its name, inside a pair of :code:`%` (:code:`ECF_MICRO`) +characters e.g. :code:`VARIABLE`, taking care to exactly match the case of the variable name. +The replacement of the variable name, surrounded by :code:`ECF_MICRO`, by the value of the variable is performed +during the job script rendering at submission time. + +.. code-block:: shell + + edit ECF_JOB_CMD '/bin/sh %ECF_JOB% &' + edit ECF_JOB_CMD '/usr/local/bin/qsub %ECF_JOB%' + edit ECF_JOB_CMD 'rsh %ECF_HOST% sh <%ECF_JOB% 1>%ECF_JOBOUT% 2>&1' edit KEEPLOGS no +When defining a variable value, care is needed to ensure that the value is correctly interpreted. + +* An unquoted variable value is determined by the first whitespace character after the variable name, + and the comment character :code:`#` is expected to appear after the value when an optional + comment is provided. If more content appears after the unquoted value before the presence of a + comment character :code:`#` the variable definition is considered invalid. + +* If a quote is used, determined by the presence of either :code:`'` or :code:`"` after the variable name, + the variable value includes all the content between the first and last occurrence of the quotation marker + in the line, potentially making the comment character :code:`#` part of the variable value if it appears + within the quotes. + +The following examples present invalid variable definitions: + +.. code-block:: shell + + edit SCRIPT /bin/sh script.sh & + +Since the value above is unquoted, the variable value is interpreted as simply `/bin/sh` +and the expected comment marked `#` is missing after the value. + +.. code-block:: shell + + edit SCRIPT '/bin/sh script.sh &' # it's a comment + +Since the value above is single quoted, determined by the opening single quote of the value, +the value is interpreted as `/bin/sh script.sh &' # it` (delimited by the last `'` that +appears in the entire line) and the expected comment marked `#` is missing after the value. + +Server variables +---------------- + +Variables defined at the top level can either be considered user or server variables. +The definition of server variables is reserved for ecFlow administrators, and are +relevant only in the context of checkpoint files. + +.. warning:: + + Server variables should not be used by users defining their own suites. + +In checkpoint files, Server variables are identified by the presence of the comment +:code:`# server` after the variable value, as shown in the example below. + +.. code-block:: shell -Submission is done via a **system(3)** call which executes /bin/sh. + edit ECF_HOME '/home/ecflow' # server + edit ECF_PORT '3141' # server + edit ECF_HOST 'ecflow-hostname' # server diff --git a/docs/ug/user_manual/text_based_suite_definition/attributes/repeat_for_loop.rst b/docs/ug/user_manual/text_based_suite_definition/attributes/repeat_for_loop.rst index e88a80cf4..c5688dfb6 100644 --- a/docs/ug/user_manual/text_based_suite_definition/attributes/repeat_for_loop.rst +++ b/docs/ug/user_manual/text_based_suite_definition/attributes/repeat_for_loop.rst @@ -19,15 +19,16 @@ generated for each iteration): .. code-block:: shell - repeat enumerated [ [ [... ]]] + repeat enumerated [ [... ]] # are string values - a sequence of string values .. code-block:: shell - repeat string [ [ [... ]]] + repeat string [ [... ]] # are string values + # the iteration will follow the order of the list, and not the alphabetical order of the string values - a sequence of dates, determined by [begin, end] and a delta @@ -43,9 +44,10 @@ generated for each iteration): .. code-block:: shell - repeat datelist [ [ [... ]]] + repeat datelist [ [... ]] # are given as YYYYMMDD # date arithmetic is used when evaluating trigger/complete expressions + # the iteration will follow the order of the list, and not the chronological order of the dates - a sequence of time instants, determined by [begin, end] and a delta @@ -55,6 +57,16 @@ generated for each iteration): # and are given as YYYYMMDDTHHMMSS # is given as a duration in the format HH:MM:SS # if is not provided, the default value is '24:00:00' + # seconds-based arithmetic is used when evaluating trigger/complete expressions + +- a sequence of time instants, determined by an arbitrary list of time instants + +.. code-block:: shell + + repeat datetimelist [ [... ]] + # are given as YYYYMMDDTHHMMSS + # seconds-based arithmetic is used when evaluating trigger/complete expressions + # the iteration will follow the order of the list, and not the chronological order of the time instants .. code-block:: shell @@ -101,7 +113,7 @@ The value of these variables is automatically updated when the node associated w completes and the node is re-queued -- except, of course, when the iteration reaches the end. The set of generated variables depends on the type of repeat, but in general includes the current value of the -iteration, and for date and datetime repeats, components of the date/datetime. The following table specifies the +iteration, and for date and datetime repeats, components of the date/time instant. The following table specifies the generated variables for each repeat type: .. list-table:: Generated variables for each repeat type @@ -117,7 +129,7 @@ generated variables for each repeat type: * - ``enumerated`` - ````, the current string value (the item from the list for this iteration). - * - ``date`` + * - ``date``, ``datelist`` - ````, the current date value, formatted as ``YYYYMMDD``. ``_YYYY``, the Year component of the current date (four digits, ``YYYY``). @@ -129,40 +141,28 @@ generated variables for each repeat type: ``_DOW``, the Day of week of the current date. ``_JULIAN``, the Julian day corresponding to the current date. - * - ``datelist`` - - ````, the current date value from the list, formatted as ``YYYYMMDD``. - - ``_YYYY``, the Year component of the current date (four digits, ``YYYY``). - - ``_MM``, the Month component of the current date (two digits, ``MM``). - - ``_DD``, the Day component of the current date (two digits, ``DD``). - - ``_DOW``, the Day of week of the current date. - - ``_JULIAN``, the Julian day corresponding to the current date. - * - ``datetime`` - - ````, the current datetime value, formatted as ``YYYYMMDDThhmmss``. + * - ``datetime``, ``datetimelist`` + - ````, the current time instant value, formatted as ``YYYYMMDDThhmmss``. - ``_DATE``, the Date of the current datetime, formatted as ``YYYYMMDD``. + ``_DATE``, the Date of the current time instant, formatted as ``YYYYMMDD``. - ``_YYYY``, the Year component of the current datetime (four digits, ``YYYY``). + ``_YYYY``, the Year component of the current time instant (four digits, ``YYYY``). - ``_MM``, the Month component of the current datetime (two digits, ``MM``). + ``_MM``, the Month component of the current time instant (two digits, ``MM``). - ``_DD``, the Day component of the current datetime (two digits, ``DD``). + ``_DD``, the Day component of the current time instant (two digits, ``DD``). - ``_DOW``, the Day of week of the current datetime. + ``_DOW``, the Day of week of the current time instant. - ``_TIME``, the Time of the current datetime, formatted as ``hhmmss``. + ``_TIME``, the Time of the current time instant, formatted as ``hhmmss``. - ``_HOURS``, the Hour component of the current datetime (two digits, ``hh``). + ``_HOURS``, the Hour component of the current time instant (two digits, ``hh``). - ``_MINUTES``, the Minute component of the current datetime (two digits, ``mm``). + ``_MINUTES``, the Minute component of the current time instant (two digits, ``mm``). - ``_SECONDS``, the Seconds component of the current datetime (two digits, ``ss``). + ``_SECONDS``, the Seconds component of the current time instant (two digits, ``ss``). - ``_JULIAN``, the Julian day corresponding to the date of the current datetime. + ``_JULIAN``, the Julian day corresponding to the date of the current time instant. When used in trigger or complete expressions, for integer arithmetic, the interpretation of the variables value depends on the repeat type -- see the following table for details. @@ -179,15 +179,12 @@ on the repeat type -- see the following table for details. - The value of ```` is interpreted as the current index in the list (0-based). * - ``enumerated`` - The value of ```` is interpreted as the current enumeration value converted to integer, if convertible; otherwise, the value is the current index in the list (0-based). - * - ``date`` - - The value of ```` is interpreted as the current date value, formatted as ``YYYYMMDD``. - This value will be subject to date arithmetic (e.g ``20090101 - 1 == 20081231``). - * - ``datelist`` - - The value of ```` is interpreted as the current date value, formatted as ``YYYYMMDD``. - This value will be subject to date arithmetic (e.g ``20090101 - 1 == 20081231``). - * - ``datetime`` - - The value of ``_DATE`` is interpreted as the current date value, formatted as ``YYYYMMDD``. + * - ``date``, ``datelist`` + - The value of ```` is interpreted as the current date integer value, formatted as ``YYYYMMDD``. This value will be subject to date arithmetic (e.g ``20090101 - 1 == 20081231``). + * - ``datetime``, ``datetimelist`` + - The value of ```` is interpreted as the current time instant value, as integer seconds since ``19700101T000000``. + Integer arithmetic applies (e.g. adding ``86400`` advances the value by one day). Used in combination with relative time -------------------------------------- @@ -223,7 +220,7 @@ See also: * - :ref:`ecflow_cli` - :ref:`add_cli`, :ref:`alter_cli` * - :ref:`python_api` - - :py:class:`ecflow.Node.add_repeat`, :py:class:`ecflow.Repeat`, :py:class:`ecflow.RepeatDate`, :py:class:`ecflow.RepeatEnumerated`, :py:class:`ecflow.RepeatInteger`, :py:class:`ecflow.RepeatDay` + - :py:class:`ecflow.Node.add_repeat`, :py:class:`ecflow.Repeat`, :py:class:`ecflow.RepeatDate`, :py:class:`ecflow.RepeatDateList`, :py:class:`ecflow.RepeatDateTime`, :py:class:`ecflow.RepeatDateTimeList`, :py:class:`ecflow.RepeatEnumerated`, :py:class:`ecflow.RepeatInteger`, :py:class:`ecflow.RepeatDay` * - :ref:`grammar` - :token:`repeat` * - :ref:`glossary` diff --git a/libs/CMakeLists.txt b/libs/CMakeLists.txt index 87a2b0c3a..1c6ba88b3 100644 --- a/libs/CMakeLists.txt +++ b/libs/CMakeLists.txt @@ -287,6 +287,7 @@ set(srcs core/src/ecflow/core/TimeSeries.hpp core/src/ecflow/core/TimeSlot.hpp core/src/ecflow/core/TimeStamp.hpp + core/src/ecflow/core/TypeTraits.hpp core/src/ecflow/core/User.hpp core/src/ecflow/core/Version.hpp core/src/ecflow/core/WhiteListFile.hpp diff --git a/libs/attribute/src/ecflow/attribute/CronAttr.cpp b/libs/attribute/src/ecflow/attribute/CronAttr.cpp index e6f9bf30f..1fcef0b65 100644 --- a/libs/attribute/src/ecflow/attribute/CronAttr.cpp +++ b/libs/attribute/src/ecflow/attribute/CronAttr.cpp @@ -37,7 +37,7 @@ CronAttr::CronAttr(const std::string& str) { throw std::runtime_error("CronAttr::CronAttr : empty string passed"); } std::vector tokens; - Str::split(str, tokens); + ecf::algorithm::split_at(tokens, str); if (tokens.empty()) { throw std::runtime_error("CronAttr::CronAttr : incorrect time string ?"); } @@ -602,7 +602,7 @@ static bool isComment(const std::string& token) { } static bool isTimeSpec(const std::string& token) { - if (token.find(Str::COLON()) == std::string::npos) { + if (token.find(ecf::string_constants::colon) == std::string::npos) { return false; } return true; @@ -873,7 +873,7 @@ void CronAttr::parse(CronAttr& cronAttr, const std::vector& lineTok CronAttr CronAttr::create(const std::string& cronString) { std::vector lineTokens; - Str::split(cronString, lineTokens); + ecf::algorithm::split_at(lineTokens, cronString); CronAttr theCronAttr; if (lineTokens.empty()) { diff --git a/libs/attribute/src/ecflow/attribute/CronAttr.hpp b/libs/attribute/src/ecflow/attribute/CronAttr.hpp index ff1ff71be..f5d115d5e 100644 --- a/libs/attribute/src/ecflow/attribute/CronAttr.hpp +++ b/libs/attribute/src/ecflow/attribute/CronAttr.hpp @@ -77,14 +77,10 @@ class CronAttr { const TimeSeries& time() const { return timeSeries_; } const TimeSeries& time_series() const { return timeSeries_; } bool last_day_of_month() const { return last_day_of_month_; } - std::vector::const_iterator week_days_begin() const { return weekDays_.begin(); } - std::vector::const_iterator week_days_end() const { return weekDays_.end(); } - std::vector::const_iterator last_week_days_of_month_begin() const { return last_week_days_of_month_.begin(); } - std::vector::const_iterator last_week_days_end_of_month_end() const { return last_week_days_of_month_.end(); } - std::vector::const_iterator days_of_month_begin() const { return daysOfMonth_.begin(); } - std::vector::const_iterator days_of_month_end() const { return daysOfMonth_.end(); } - std::vector::const_iterator months_begin() const { return months_.begin(); } - std::vector::const_iterator months_end() const { return months_.end(); } + const std::vector& week_days() const { return weekDays_; } + const std::vector& last_week_days_of_month() const { return last_week_days_of_month_; } + const std::vector& days_of_month() const { return daysOfMonth_; } + const std::vector& months() const { return months_; } std::string name() const; // for display/gui std::string toString() const; diff --git a/libs/attribute/src/ecflow/attribute/GenericAttr.cpp b/libs/attribute/src/ecflow/attribute/GenericAttr.cpp index 5a85d7ae9..ddbb75252 100644 --- a/libs/attribute/src/ecflow/attribute/GenericAttr.cpp +++ b/libs/attribute/src/ecflow/attribute/GenericAttr.cpp @@ -26,7 +26,7 @@ GenericAttr::GenericAttr(const std::string& name, const std::vector : name_(name), values_(values) { std::string msg; - if (!Str::valid_name(name, msg)) { + if (!ecf::algorithm::is_valid_name(name, msg)) { throw std::runtime_error("GenericAttr::GenericAttr : Invalid generic name : " + msg); } } @@ -34,7 +34,7 @@ GenericAttr::GenericAttr(const std::string& name, const std::vector GenericAttr::GenericAttr(const std::string& name) : name_(name) { std::string msg; - if (!Str::valid_name(name, msg)) { + if (!ecf::algorithm::is_valid_name(name, msg)) { throw std::runtime_error("GenericAttr::GenericAttr : Invalid generic name : " + msg); } } diff --git a/libs/attribute/src/ecflow/attribute/LateAttr.cpp b/libs/attribute/src/ecflow/attribute/LateAttr.cpp index bf0fefb6b..14ac02933 100644 --- a/libs/attribute/src/ecflow/attribute/LateAttr.cpp +++ b/libs/attribute/src/ecflow/attribute/LateAttr.cpp @@ -240,7 +240,7 @@ void LateAttr::parse(LateAttr& lateAttr, LateAttr LateAttr::create(const std::string& lateString) { std::vector lineTokens; - Str::split(lateString, lineTokens); + ecf::algorithm::split_at(lineTokens, lateString); if (lineTokens.empty()) { throw std::runtime_error("LateParser::create: empty string no late specified ?" + lateString); diff --git a/libs/attribute/src/ecflow/attribute/NodeAttr.cpp b/libs/attribute/src/ecflow/attribute/NodeAttr.cpp index f995e341f..86b3da37c 100644 --- a/libs/attribute/src/ecflow/attribute/NodeAttr.cpp +++ b/libs/attribute/src/ecflow/attribute/NodeAttr.cpp @@ -51,7 +51,7 @@ Event::Event(int number, const std::string& eventName, bool iv, bool check_name) iv_(iv) { if (!eventName.empty() && check_name) { std::string msg; - if (!Str::valid_name(eventName, msg)) { + if (!ecf::algorithm::is_valid_name(eventName, msg)) { throw std::runtime_error("Event::Event: Invalid event name : " + msg); } } @@ -75,7 +75,7 @@ Event::Event(const std::string& eventName, bool iv) // which then did *not* load. // // Test for numeric, and then casting, is ****faster***** than relying on exception alone - if (eventName.find_first_of(Str::NUMERIC()) == 0) { + if (eventName.find_first_of(ecf::string_constants::numeric_chars) == 0) { try { number_ = ecf::convert_to(eventName); n_.clear(); @@ -87,7 +87,7 @@ Event::Event(const std::string& eventName, bool iv) } std::string msg; - if (!Str::valid_name(eventName, msg)) { + if (!ecf::algorithm::is_valid_name(eventName, msg)) { throw std::runtime_error("Event::Event: Invalid event name : " + msg); } } @@ -214,7 +214,7 @@ Meter::Meter(const std::string& name, int min, int max, int colorChange, int val cc_(colorChange), n_(name) { if (check) { - if (!Str::valid_name(name)) { + if (!ecf::algorithm::is_valid_name(name)) { throw std::runtime_error("Meter::Meter: Invalid Meter name: " + name); } } @@ -242,7 +242,7 @@ Meter Meter::make_from_value(const std::string& name, const std::string& value) // value is expected to be of the form "min,max,value", where min, max and value are integers std::vector tokens; - ecf::algorithm::split(tokens, value, ","); + ecf::algorithm::split_at(tokens, value, ","); if (tokens.size() != 3) { throw std::runtime_error( MESSAGE("Meter::make_from_value: Expect three comma-separated values, but found: '" << value << "'")); @@ -323,7 +323,7 @@ Label::Label(const std::string& name, const std::string& value, const std::strin : n_(name), v_(value), new_v_(new_value) { - if (check_name && !Str::valid_name(n_)) { + if (check_name && !ecf::algorithm::is_valid_name(n_)) { throw std::runtime_error(MESSAGE("Label::Label: Invalid Label name :" << n_)); } } @@ -347,7 +347,7 @@ void Label::write(std::string& ret) const { else { // replace \n, otherwise re-parse will fail std::string value = v_; - Str::replaceall(value, "\n", "\\n"); + ecf::algorithm::replace_all(value, "\n", "\\n"); ret += value; } ret += "\""; @@ -395,11 +395,11 @@ void Label::parse(const std::string& line, // parsing will always STRIP single or double quotes, print will add double quotes // label simple_label 'ecgems' if (line_token_size == 3) { - Str::removeQuotes(lineTokens[2]); - Str::removeSingleQuotes(lineTokens[2]); + ecf::algorithm::remove_double_quotes(lineTokens[2]); + ecf::algorithm::remove_single_quotes(lineTokens[2]); the_value = lineTokens[2]; if (the_value.find("\\n") != std::string::npos) { - Str::replaceall(the_value, "\\n", "\n"); + ecf::algorithm::replace_all(the_value, "\\n", "\n"); } } else { @@ -418,11 +418,11 @@ void Label::parse(const std::string& line, value += lineTokens[i]; } - Str::removeQuotes(value); - Str::removeSingleQuotes(value); + ecf::algorithm::remove_double_quotes(value); + ecf::algorithm::remove_single_quotes(value); the_value = value; if (the_value.find("\\n") != std::string::npos) { - Str::replaceall(the_value, "\\n", "\n"); + ecf::algorithm::replace_all(the_value, "\\n", "\n"); } // state @@ -450,7 +450,7 @@ void Label::parse(const std::string& line, the_new_value = new_value; if (the_new_value.find("\\n") != std::string::npos) { - Str::replaceall(the_new_value, "\\n", "\n"); + ecf::algorithm::replace_all(the_new_value, "\\n", "\n"); } } } diff --git a/libs/attribute/src/ecflow/attribute/QueueAttr.cpp b/libs/attribute/src/ecflow/attribute/QueueAttr.cpp index 0e78f53c9..3f6bf23b5 100644 --- a/libs/attribute/src/ecflow/attribute/QueueAttr.cpp +++ b/libs/attribute/src/ecflow/attribute/QueueAttr.cpp @@ -35,7 +35,7 @@ QueueAttr::QueueAttr(const std::string& name, const std::vector& th : theQueue_(theQueue), name_(name) { std::string msg; - if (!Str::valid_name(name, msg)) { + if (!ecf::algorithm::is_valid_name(name, msg)) { throw std::runtime_error("QueueAttr::QueueAttr: Invalid queue name : " + msg); } if (theQueue.empty()) { @@ -208,8 +208,11 @@ void QueueAttr::parse(QueueAttr& queAttr, if (theEnum[0] == '#') { break; } - Str::removeSingleQuotes(theEnum); // remove quotes, they get added back when we persist - Str::removeQuotes(theEnum); // remove quotes, they get added back when we persist + + // remove quotes, as they get added back when we persist + ecf::algorithm::remove_single_quotes(theEnum); + ecf::algorithm::remove_double_quotes(theEnum); + theEnums.push_back(theEnum); } if (theEnums.empty()) { @@ -273,7 +276,7 @@ void QueueAttr::set_state_vec(const std::vector& state_vec) { void QueueAttr::set_name(const std::string& name) { std::string msg; - if (!Str::valid_name(name, msg)) { + if (!ecf::algorithm::is_valid_name(name, msg)) { throw std::runtime_error("QueueAttr::set_name: Invalid queue name : " + msg); } name_ = name; diff --git a/libs/attribute/src/ecflow/attribute/RepeatAttr.cpp b/libs/attribute/src/ecflow/attribute/RepeatAttr.cpp index c6e09828a..a229be733 100644 --- a/libs/attribute/src/ecflow/attribute/RepeatAttr.cpp +++ b/libs/attribute/src/ecflow/attribute/RepeatAttr.cpp @@ -70,6 +70,10 @@ 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)) { } @@ -119,7 +123,7 @@ bool Repeat::operator==(const Repeat& rhs) const { } const std::string& Repeat::name() const { - return (type_.get()) ? type_->name() : Str::EMPTY(); + return (type_.get()) ? type_->name() : ecf::string_constants::empty; } // ========================================================================= @@ -156,7 +160,7 @@ RepeatDate::RepeatDate(const std::string& variable, int start, int end, int delt end_(end), delta_(delta), value_(start) { - if (!Str::valid_name(variable)) { + if (!ecf::algorithm::is_valid_name(variable)) { throw std::runtime_error("RepeatDate::RepeatDate: Invalid name: " + variable); } @@ -289,7 +293,8 @@ void RepeatDate::update_repeat_genvar_value() const { catch (std::exception& e) { log(Log::ERR, MESSAGE("RepeatDate::update_repeat_genvar_value : " << toString() << "\n The current date(" - << date_as_string << ") is not valid")); + << date_as_string + << ") is not valid, due to: " << e.what())); return; } } @@ -307,6 +312,14 @@ 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_); } @@ -500,7 +513,7 @@ RepeatDateTime::RepeatDateTime(const std::string& variable, Instant start, Insta end_(end), delta_(delta), value_(start) { - if (!Str::valid_name(variable)) { + if (!ecf::algorithm::is_valid_name(variable)) { throw std::runtime_error("RepeatDateTime::RepeatDateTime: Invalid name: " + variable); } @@ -615,7 +628,8 @@ void RepeatDateTime::update_repeat_genvar_value() const { catch (std::exception& e) { log(Log::ERR, MESSAGE("RepeatDateTime::update_repeat_genvar_value : " << toString() << "\n The current date(" - << date_as_string << ") is not valid")); + << date_as_string + << ") is not valid, due to: " << e.what())); return; } } @@ -806,7 +820,7 @@ void RepeatDateTime::set_value(long the_new_date) { RepeatDateList::RepeatDateList(const std::string& variable, const std::vector& l) : RepeatBase(variable), list_(l) { - if (!Str::valid_name(variable)) { + if (!ecf::algorithm::is_valid_name(variable)) { throw std::runtime_error("RepeatDateList: Invalid name: " + variable); } if (list_.empty()) { @@ -909,8 +923,8 @@ void RepeatDateList::update_repeat_genvar_value() const { } catch (std::exception& e) { log(Log::ERR, - MESSAGE("RepeatDateList::update_repeat_genvar_value(): " << toString() << "\n invalid current date: " - << date_as_string)); + MESSAGE("RepeatDateList::update_repeat_genvar_value(): " + << toString() << "\n invalid current date: " << date_as_string << ", due to: " << e.what())); } } } @@ -1124,6 +1138,312 @@ bool RepeatDateList::operator==(const RepeatDateList& rhs) const { //====================================================================================== +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), @@ -1131,7 +1451,7 @@ RepeatInteger::RepeatInteger(const std::string& variable, int start, int end, in delta_(delta), value_(start) { // cout << toString() << "\n"; - if (!Str::valid_name(variable)) { + if (!ecf::algorithm::is_valid_name(variable)) { throw std::runtime_error("RepeatInteger: Invalid name: " + variable); } } @@ -1325,7 +1645,7 @@ std::string RepeatInteger::prev_value_as_string() const { RepeatEnumerated::RepeatEnumerated(const std::string& variable, const std::vector& theEnums) : RepeatBase(variable), theEnums_(theEnums) { - if (!Str::valid_name(variable)) { + if (!ecf::algorithm::is_valid_name(variable)) { throw std::runtime_error("RepeatEnumerated: Invalid name: " + variable); } if (theEnums.empty()) { @@ -1543,7 +1863,7 @@ bool RepeatEnumerated::operator==(const RepeatEnumerated& rhs) const { RepeatString::RepeatString(const std::string& variable, const std::vector& theEnums) : RepeatBase(variable), theStrings_(theEnums) { - if (!Str::valid_name(variable)) { + if (!ecf::algorithm::is_valid_name(variable)) { throw std::runtime_error("RepeatString:: Invalid name: " + variable); } if (theEnums.empty()) { @@ -1766,6 +2086,11 @@ 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_)); @@ -1785,6 +2110,7 @@ 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); @@ -1794,6 +2120,7 @@ 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) diff --git a/libs/attribute/src/ecflow/attribute/RepeatAttr.hpp b/libs/attribute/src/ecflow/attribute/RepeatAttr.hpp index 84ab9d176..a0a008365 100644 --- a/libs/attribute/src/ecflow/attribute/RepeatAttr.hpp +++ b/libs/attribute/src/ecflow/attribute/RepeatAttr.hpp @@ -25,11 +25,14 @@ #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. @@ -47,6 +50,20 @@ class RepeatBase { 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 { @@ -108,6 +125,7 @@ class RepeatBase { 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; } @@ -146,6 +164,20 @@ class RepeatDate final : public RepeatBase { 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; @@ -228,6 +260,20 @@ class RepeatDateTime final : public RepeatBase { 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(); } @@ -320,12 +366,30 @@ class RepeatDateList final : public RepeatBase { 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())); } @@ -372,6 +436,100 @@ class RepeatDateList final : public RepeatBase { 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); @@ -388,6 +546,20 @@ class RepeatInteger final : public RepeatBase { 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_); } @@ -446,6 +618,22 @@ class RepeatEnumerated final : public RepeatBase { 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_); } @@ -499,6 +687,23 @@ class RepeatString final : public RepeatBase { 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())); } @@ -577,10 +782,30 @@ class RepeatDay final : public RepeatBase { 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 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{}; } @@ -621,6 +846,7 @@ class Repeat { Repeat(const RepeatDate&); Repeat(const RepeatDateTime&); Repeat(const RepeatDateList&); + Repeat(const RepeatDateTimeList&); Repeat(const RepeatInteger&); Repeat(const RepeatEnumerated&); Repeat(const RepeatString&); @@ -664,6 +890,20 @@ class Repeat { 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_) { @@ -729,4 +969,122 @@ class Repeat { 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 */ diff --git a/libs/attribute/src/ecflow/attribute/RepeatRange.hpp b/libs/attribute/src/ecflow/attribute/RepeatRange.hpp index 960410639..cea7185f9 100644 --- a/libs/attribute/src/ecflow/attribute/RepeatRange.hpp +++ b/libs/attribute/src/ecflow/attribute/RepeatRange.hpp @@ -110,6 +110,30 @@ struct Range 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 { @@ -294,6 +318,10 @@ Limits limits_of(const RepeatBase* 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()}; diff --git a/libs/attribute/src/ecflow/attribute/TimeAttr.cpp b/libs/attribute/src/ecflow/attribute/TimeAttr.cpp index d936b2936..5adf024ef 100644 --- a/libs/attribute/src/ecflow/attribute/TimeAttr.cpp +++ b/libs/attribute/src/ecflow/attribute/TimeAttr.cpp @@ -28,7 +28,7 @@ TimeAttr::TimeAttr(const std::string& str) { throw std::runtime_error("Time::Time: empty string passed"); } std::vector tokens; - Str::split(str, tokens); + ecf::algorithm::split_at(tokens, str); if (tokens.empty()) { throw std::runtime_error("Time::Time: incorrect time string ?"); } diff --git a/libs/attribute/src/ecflow/attribute/TodayAttr.cpp b/libs/attribute/src/ecflow/attribute/TodayAttr.cpp index ee15c1379..2b4041e1e 100644 --- a/libs/attribute/src/ecflow/attribute/TodayAttr.cpp +++ b/libs/attribute/src/ecflow/attribute/TodayAttr.cpp @@ -27,7 +27,7 @@ TodayAttr::TodayAttr(const std::string& str) { throw std::runtime_error("Today::Today: empty string passed"); } std::vector tokens; - Str::split(str, tokens); + ecf::algorithm::split_at(tokens, str); if (tokens.empty()) { throw std::runtime_error("Today::Today: incorrect time string ?"); } diff --git a/libs/attribute/src/ecflow/attribute/Variable.cpp b/libs/attribute/src/ecflow/attribute/Variable.cpp index ccabc98ac..b5272521e 100644 --- a/libs/attribute/src/ecflow/attribute/Variable.cpp +++ b/libs/attribute/src/ecflow/attribute/Variable.cpp @@ -31,22 +31,22 @@ Variable::Variable(const std::string& name, const std::string& value) : n_(name), v_(value) { std::string msg; - if (!Str::valid_name(name, msg)) { + if (!ecf::algorithm::is_valid_name(name, msg)) { throw std::runtime_error("Variable::Variable: Invalid Variable name: " + msg); } } void Variable::set_name(const std::string& v) { std::string msg; - if (!Str::valid_name(v, msg)) { + if (!ecf::algorithm::is_valid_name(v, msg)) { throw std::runtime_error("Variable::set_name: Invalid Variable name: " + msg); } n_ = v; } int Variable::value() const { - // see if the value is convertible to an integer - return Str::to_int(v_, 0 /* value to return if conversion fails*/); + // check if the value is convertible to an integer + return ecf::algorithm::to_int(v_, 0 /* value to return if conversion fails*/); } bool Variable::operator==(const Variable& rhs) const { @@ -76,7 +76,7 @@ void Variable::write(std::string& ret) const { else { // replace \n, otherwise re-parse will fail std::string value = v_; - Str::replaceall(value, "\n", "\\n"); + ecf::algorithm::replace_all(value, "\n", "\\n"); ret += value; } ret += "'"; diff --git a/libs/attribute/src/ecflow/attribute/VerifyAttr.cpp b/libs/attribute/src/ecflow/attribute/VerifyAttr.cpp index 4a4f69660..5f0908ef0 100644 --- a/libs/attribute/src/ecflow/attribute/VerifyAttr.cpp +++ b/libs/attribute/src/ecflow/attribute/VerifyAttr.cpp @@ -50,11 +50,12 @@ void VerifyAttr::reset() { } std::string VerifyAttr::toString() const { - return MESSAGE("verify " << NState::toString(state_) << Str::COLON() << expected_); + return MESSAGE("verify " << NState::toString(state_) << ecf::string_constants::colon << expected_); } std::string VerifyAttr::dump() const { - return MESSAGE("verify " << NState::toString(state_) << Str::COLON() << expected_ << " actual(" << actual_ << ")"); + return MESSAGE("verify " << NState::toString(state_) << ecf::string_constants::colon << expected_ << " actual(" + << actual_ << ")"); } template diff --git a/libs/attribute/src/ecflow/attribute/ZombieAttr.cpp b/libs/attribute/src/ecflow/attribute/ZombieAttr.cpp index 5e44e525f..d399edb02 100644 --- a/libs/attribute/src/ecflow/attribute/ZombieAttr.cpp +++ b/libs/attribute/src/ecflow/attribute/ZombieAttr.cpp @@ -95,11 +95,11 @@ void ZombieAttr::write(std::string& ret) const { /// format is zombie_type : child_cmds(optional) : action : zombie_lifetime_(optional) ret += "zombie "; ret += Child::to_string(zombie_type_); - ret += Str::COLON(); + ret += ecf::string_constants::colon; ret += ecf::to_string(action_); - ret += Str::COLON(); + ret += ecf::string_constants::colon; ret += Child::to_string(child_cmds_); - ret += Str::COLON(); + ret += ecf::string_constants::colon; ret += ecf::convert_to(zombie_lifetime_); } diff --git a/libs/attribute/test/TestAttrSerialization.cpp b/libs/attribute/test/TestAttrSerialization.cpp index 20e8c0da0..34e4dccfa 100644 --- a/libs/attribute/test/TestAttrSerialization.cpp +++ b/libs/attribute/test/TestAttrSerialization.cpp @@ -47,6 +47,7 @@ BOOST_AUTO_TEST_CASE(test_AttrDefaultConstructor_serialisation) { doSaveAndRestore(fileName); doSaveAndRestore(fileName); doSaveAndRestore(fileName); + doSaveAndRestore(fileName); doSaveAndRestore(fileName); doSaveAndRestore(fileName); doSaveAndRestore(fileName); diff --git a/libs/attribute/test/TestCron.cpp b/libs/attribute/test/TestCron.cpp index 3630e8958..d6858bc1c 100644 --- a/libs/attribute/test/TestCron.cpp +++ b/libs/attribute/test/TestCron.cpp @@ -219,7 +219,7 @@ BOOST_AUTO_TEST_CASE(test_cron_state_parsing) { { std::string line = "cron 04:30 # isValid:false"; std::vector lineTokens; - Str::split(line, lineTokens); + ecf::algorithm::split_at(lineTokens, line); bool parse_state = true; CronAttr parsed_cronAttr; CronAttr::parse(parsed_cronAttr, lineTokens, index, parse_state); @@ -236,7 +236,7 @@ BOOST_AUTO_TEST_CASE(test_cron_state_parsing) { { std::string line = "cron 04:30 # free isValid:false"; std::vector lineTokens; - Str::split(line, lineTokens); + ecf::algorithm::split_at(lineTokens, line); bool parse_state = true; CronAttr parsed_cronAttr; CronAttr::parse(parsed_cronAttr, lineTokens, index, parse_state); @@ -254,7 +254,7 @@ BOOST_AUTO_TEST_CASE(test_cron_state_parsing) { { std::string line = "cron 00:01 23:59 01:00 # nextTimeSlot/12:01"; std::vector lineTokens; - Str::split(line, lineTokens); + ecf::algorithm::split_at(lineTokens, line); bool parse_state = true; CronAttr parsed_cronAttr; CronAttr::parse(parsed_cronAttr, lineTokens, index, parse_state); @@ -274,7 +274,7 @@ BOOST_AUTO_TEST_CASE(test_cron_state_parsing) { { std::string line = "cron 00:01 23:59 01:00 # free nextTimeSlot/12:01"; std::vector lineTokens; - Str::split(line, lineTokens); + ecf::algorithm::split_at(lineTokens, line); bool parse_state = true; CronAttr parsed_cronAttr; CronAttr::parse(parsed_cronAttr, lineTokens, index, parse_state); @@ -294,7 +294,7 @@ BOOST_AUTO_TEST_CASE(test_cron_state_parsing) { { std::string line = "cron 00:00 18:00 06:00 # isValid:false nextTimeSlot/24:00"; std::vector lineTokens; - Str::split(line, lineTokens); + ecf::algorithm::split_at(lineTokens, line); bool parse_state = true; CronAttr parsed_cronAttr; CronAttr::parse(parsed_cronAttr, lineTokens, index, parse_state); @@ -317,7 +317,7 @@ BOOST_AUTO_TEST_CASE(test_cron_state_parsing) { // Could not parse 'cron +00:00 23:59 00:01 # isValid:false nextTimeSlot/523:40' around line number 654 std::string line = "cron +00:00 23:59 00:01 # isValid:false nextTimeSlot/523:40"; std::vector lineTokens; - Str::split(line, lineTokens); + ecf::algorithm::split_at(lineTokens, line); bool parse_state = true; CronAttr parsed_cronAttr; CronAttr::parse(parsed_cronAttr, lineTokens, index, parse_state); diff --git a/libs/attribute/test/TestDateAttr.cpp b/libs/attribute/test/TestDateAttr.cpp index 7d3b45178..b62a2d15e 100644 --- a/libs/attribute/test/TestDateAttr.cpp +++ b/libs/attribute/test/TestDateAttr.cpp @@ -79,7 +79,7 @@ static DateAttr print_and_parse_attr(DateAttr& date) { output.erase(output.begin() + output.size() - 1); // remove trailing newline std::vector tokens; - Str::split_orig(output, tokens); + ecf::algorithm::split_at(tokens, output); return DateAttr::create(tokens, true /*read state*/); } diff --git a/libs/attribute/test/TestDayAttr.cpp b/libs/attribute/test/TestDayAttr.cpp index 20376434b..afebdbb49 100644 --- a/libs/attribute/test/TestDayAttr.cpp +++ b/libs/attribute/test/TestDayAttr.cpp @@ -105,7 +105,7 @@ static DayAttr print_and_parse_attr(DayAttr& day) { output.erase(output.begin() + output.size() - 1); // remove trailing newline std::vector tokens; - Str::split_orig(output, tokens); + ecf::algorithm::split_at(tokens, output); return DayAttr::create(tokens, true /*read state*/); } diff --git a/libs/attribute/test/TestLabel.cpp b/libs/attribute/test/TestLabel.cpp index b6bd6b98b..346b750e2 100644 --- a/libs/attribute/test/TestLabel.cpp +++ b/libs/attribute/test/TestLabel.cpp @@ -29,7 +29,7 @@ BOOST_AUTO_TEST_CASE(test_label_parsing) { { std::string line = "label name \"value\""; std::vector linetokens; - Str::split(line, linetokens); + ecf::algorithm::split_at(linetokens, line); Label label; label.parse(line, linetokens, false); @@ -42,7 +42,7 @@ BOOST_AUTO_TEST_CASE(test_label_parsing) { { std::string line = R"(label name "value\nvalue")"; std::vector linetokens; - Str::split(line, linetokens); + ecf::algorithm::split_at(linetokens, line); Label label; label.parse(line, linetokens, false); @@ -55,7 +55,7 @@ BOOST_AUTO_TEST_CASE(test_label_parsing) { { std::string line = "label name \"value that is multiple token !!!! 23445 !^ & * ( )\""; std::vector linetokens; - Str::split(line, linetokens); + ecf::algorithm::split_at(linetokens, line); Label label; label.parse(line, linetokens, false); @@ -68,7 +68,7 @@ BOOST_AUTO_TEST_CASE(test_label_parsing) { { std::string line = R"(label name "value\n that\n is\n multiple\n token\n and\n new\n \nlines")"; std::vector linetokens; - Str::split(line, linetokens); + ecf::algorithm::split_at(linetokens, line); Label label; label.parse(line, linetokens, false); diff --git a/libs/attribute/test/TestRepeat.cpp b/libs/attribute/test/TestRepeat.cpp index a140ebdf7..2ab5b34ca 100644 --- a/libs/attribute/test/TestRepeat.cpp +++ b/libs/attribute/test/TestRepeat.cpp @@ -64,6 +64,111 @@ BOOST_AUTO_TEST_CASE(construction) { BOOST_CHECK_MESSAGE(empty.state_change_no() == 0, " empty failed"); } +BOOST_AUTO_TEST_CASE(current_index_current_value_delegates_to_concrete_type) { + ECF_NAME_THIS_TEST(); + + using namespace ecf::repeat; + + // Repeat is a type-erasing wrapper; current_index / current_value must + // delegate correctly to each wrapped type. + { + // RepeatDate: index increments with Julian-day arithmetic + Repeat r{RepeatDate("YMD", 20241129, 20241207, 3)}; + BOOST_CHECK_EQUAL(r.current_index(), 0L); + BOOST_CHECK_EQUAL(r.current_value(), "20241129"); + { + auto value = current_value_as(r); + BOOST_CHECK(value.has_value() && value.value() == 20241129); + } + { + auto value = current_value_as(r); + BOOST_CHECK(!value.has_value()); + } + r.increment(); + BOOST_CHECK_EQUAL(r.current_index(), 1L); + BOOST_CHECK_EQUAL(r.current_value(), "20241202"); + { + auto value = current_value_as(r); + BOOST_CHECK(value.has_value() && value.value() == 20241202); + } + { + auto value = current_value_as(r); + BOOST_CHECK(!value.has_value()); + } + } + { + // RepeatInteger + Repeat r{RepeatInteger("N", 0, 4, 2)}; + BOOST_CHECK_EQUAL(r.current_index(), 0L); + BOOST_CHECK_EQUAL(r.current_value(), "0"); + { + auto value = current_value_as(r); + BOOST_CHECK(value.has_value() && value.value() == 0); + } + { + auto value = current_value_as(r); + BOOST_CHECK(!value.has_value()); + } + r.increment(); + BOOST_CHECK_EQUAL(r.current_index(), 1L); + BOOST_CHECK_EQUAL(r.current_value(), "2"); + { + auto value = current_value_as(r); + BOOST_CHECK(value.has_value() && value.value() == 2); + } + { + auto value = current_value_as(r); + BOOST_CHECK(!value.has_value()); + } + } + { + // RepeatString + using namespace std::string_literals; + Repeat r{RepeatString("S", {"x"s, "y"s})}; + BOOST_CHECK_EQUAL(r.current_index(), 0L); + BOOST_CHECK_EQUAL(r.current_value(), "x"); + { + auto value = current_value_as(r); + BOOST_CHECK(!value.has_value()); + } + { + auto value = current_value_as(r); + BOOST_CHECK(value.has_value() && value.value() == "x"); + } + r.increment(); + BOOST_CHECK_EQUAL(r.current_index(), 1L); + BOOST_CHECK_EQUAL(r.current_value(), "y"); + { + auto value = current_value_as(r); + BOOST_CHECK(!value.has_value()); + } + { + auto value = current_value_as(r); + BOOST_CHECK(value.has_value() && value.value() == "y"); + } + } + { + // RepeatDay: index == step + Repeat r{RepeatDay(5)}; + BOOST_CHECK_EQUAL(r.current_index(), 5L); + BOOST_CHECK_EQUAL(r.current_value(), "5"); + { + auto value = current_value_as(r); + BOOST_CHECK(value.has_value() && value.value() == 5); + } + { + auto value = current_value_as(r); + BOOST_CHECK(!value.has_value()); + } + } + { + // Empty Repeat: returns 0 / "" + Repeat empty; + BOOST_CHECK_EQUAL(empty.current_index(), 0L); + BOOST_CHECK_EQUAL(empty.current_value(), ""); + } +} + BOOST_AUTO_TEST_SUITE_END() // test_repeat /* @@ -310,8 +415,252 @@ BOOST_AUTO_TEST_CASE(generated_variables) { } } +BOOST_AUTO_TEST_CASE(iterate_over_datelist_current_index_current_value) { + ECF_NAME_THIS_TEST(); + + using namespace ecf::repeat; + + RepeatDateList r("DL", {20100101, 20100201, 20100301}); + BOOST_CHECK_EQUAL(r.current_index(), 0L); + BOOST_CHECK_EQUAL(r.current_value(), "20100101"); + { + auto value = current_value_as(r); + BOOST_CHECK(value.has_value() && value.value() == 20100101); + } + { + auto value = current_value_as(r); + BOOST_CHECK(!value.has_value()); + } + r.increment(); + BOOST_CHECK_EQUAL(r.current_index(), 1L); + BOOST_CHECK_EQUAL(r.current_value(), "20100201"); + { + auto value = current_value_as(r); + BOOST_CHECK(value.has_value() && value.value() == 20100201); + } + { + auto value = current_value_as(r); + BOOST_CHECK(!value.has_value()); + } + r.increment(); + BOOST_CHECK_EQUAL(r.current_index(), 2L); + BOOST_CHECK_EQUAL(r.current_value(), "20100301"); + { + auto value = current_value_as(r); + BOOST_CHECK(value.has_value() && value.value() == 20100301); + } + { + auto value = current_value_as(r); + BOOST_CHECK(!value.has_value()); + } +} + BOOST_AUTO_TEST_SUITE_END() // test_repeat_datelist +/* + * Test Suite: ::test_repeat_datetimelist + * ************************************************************ */ + +BOOST_AUTO_TEST_SUITE(test_repeat_datetimelist) + +BOOST_AUTO_TEST_CASE(invariants) { + ECF_NAME_THIS_TEST(); + + { + using ecf::Instant; + Instant dt0 = Instant::parse("20240101T000000"); + Instant dt1 = Instant::parse("20240102T120000"); + Instant dt2 = Instant::parse("20240103T060000"); + + Repeat rep(RepeatDateTimeList("DT", {dt0, dt1, dt2})); + BOOST_CHECK_MESSAGE(!rep.empty(), "Repeat should not be empty"); + BOOST_CHECK_MESSAGE(rep.name() == "DT", "name not as expected"); + BOOST_CHECK_MESSAGE(rep.step() == 1, "step should be 1"); + BOOST_CHECK_MESSAGE(rep.index_or_value() == 0, "index_or_value should be 0"); + BOOST_CHECK_MESSAGE(rep.valid(), "RepeatDateTimeList should be valid"); + BOOST_CHECK_MESSAGE(rep.valueAsString() == "20240101T000000", + "valueAsString not as expected: " << rep.valueAsString()); + + Repeat cloned = Repeat(rep); + BOOST_CHECK_MESSAGE(cloned == rep, "Equality failed"); + BOOST_CHECK_MESSAGE(cloned.name() == "DT", "not as expected"); + BOOST_CHECK_MESSAGE(cloned.step() == 1, "not as expected"); + BOOST_CHECK_MESSAGE(cloned.index_or_value() == 0, "not as expected"); + BOOST_CHECK_MESSAGE(cloned.valueAsString() == "20240101T000000", "not as expected"); + } + { + RepeatDateTimeList empty; + BOOST_CHECK_MESSAGE(empty.start() == 0, "Start should be 0"); + BOOST_CHECK_MESSAGE(empty.end() == 0, "end should be 0"); + BOOST_CHECK_MESSAGE(empty.step() == 1, "step should be 1"); + BOOST_CHECK_MESSAGE(empty.value() == 0, "value should be 0"); + BOOST_CHECK_MESSAGE(empty.name().empty(), "name should be empty"); + BOOST_CHECK_MESSAGE(empty.valueAsString() == "", "expected '' but found " << empty.valueAsString()); + } +} + +BOOST_AUTO_TEST_CASE(increment_and_validity) { + ECF_NAME_THIS_TEST(); + + using ecf::Instant; + Instant dt0 = Instant::parse("20240101T000000"); + Instant dt1 = Instant::parse("20240102T120000"); + + Repeat rep(RepeatDateTimeList("DT", {dt0, dt1})); + BOOST_CHECK_MESSAGE(rep.valid(), "should be valid"); + BOOST_CHECK_MESSAGE(rep.index_or_value() == 0, "index should be 0"); + BOOST_CHECK_MESSAGE(rep.valueAsString() == "20240101T000000", "not as expected"); + + rep.increment(); + BOOST_CHECK_MESSAGE(rep.valid(), "should be valid after first increment"); + BOOST_CHECK_MESSAGE(rep.index_or_value() == 1, "index should be 1"); + BOOST_CHECK_MESSAGE(rep.valueAsString() == "20240102T120000", "not as expected"); + + rep.increment(); + BOOST_CHECK_MESSAGE(!rep.valid(), "should not be valid after second increment"); + + rep.reset(); + BOOST_CHECK_MESSAGE(rep.valid(), "should be valid after reset"); + BOOST_CHECK_MESSAGE(rep.index_or_value() == 0, "index should be 0 after reset"); + + rep.setToLastValue(); + BOOST_CHECK_MESSAGE(rep.valid(), "should be valid at last value"); + BOOST_CHECK_MESSAGE(rep.index_or_value() == 1, "index should be 1 at last value"); + BOOST_CHECK_MESSAGE(rep.valueAsString() == "20240102T120000", "not as expected at last value"); +} + +BOOST_AUTO_TEST_CASE(change_and_change_value) { + ECF_NAME_THIS_TEST(); + + using ecf::Instant; + Instant dt0 = Instant::parse("20240101T000000"); + Instant dt1 = Instant::parse("20240102T120000"); + Instant dt2 = Instant::parse("20240103T060000"); + + { + Repeat rep(RepeatDateTimeList("DT", {dt0, dt1, dt2})); + rep.change("20240103T060000"); + BOOST_CHECK_MESSAGE(rep.index_or_value() == 2, "index should be 2 after change"); + BOOST_CHECK_MESSAGE(rep.valueAsString() == "20240103T060000", "not as expected"); + } + { + Repeat rep(RepeatDateTimeList("DT", {dt0, dt1, dt2})); + BOOST_CHECK_THROW(rep.change("20240105T000000"), std::runtime_error); + } + { + Repeat rep(RepeatDateTimeList("DT", {dt0, dt1, dt2})); + rep.changeValue(2); + BOOST_CHECK_MESSAGE(rep.index_or_value() == 2, "index should be 2 after changeValue"); + } + { + Repeat rep(RepeatDateTimeList("DT", {dt0, dt1, dt2})); + BOOST_CHECK_THROW(rep.changeValue(3), std::runtime_error); + BOOST_CHECK_THROW(rep.changeValue(-1), std::runtime_error); + } +} + +BOOST_AUTO_TEST_CASE(generated_variables) { + ECF_NAME_THIS_TEST(); + + using ecf::Instant; + Instant dt0 = Instant::parse("20240315T103045"); + + Repeat rep(RepeatDateTimeList("DT", {dt0})); + rep.update_repeat_genvar(); + + std::vector vec; + rep.gen_variables(vec); + // 9 datetime components + 1 base = 10 + BOOST_CHECK_MESSAGE(vec.size() == 10, "expected 10 generated variables but found " << vec.size()); + + { + const Variable& var = rep.find_gen_variable("DT"); + BOOST_CHECK_MESSAGE(!var.empty(), "Did not find DT"); + BOOST_CHECK_MESSAGE(var.theValue() == "20240315T103045", + "expected '20240315T103045' but found " << var.theValue()); + } + { + const Variable& var = rep.find_gen_variable("DT_DATE"); + BOOST_CHECK_MESSAGE(!var.empty(), "Did not find DT_DATE"); + BOOST_CHECK_MESSAGE(var.theValue() == "20240315", "expected '20240315' but found " << var.theValue()); + } + { + const Variable& var = rep.find_gen_variable("DT_YYYY"); + BOOST_CHECK_MESSAGE(!var.empty(), "Did not find DT_YYYY"); + BOOST_CHECK_MESSAGE(var.theValue() == "2024", "expected '2024' but found " << var.theValue()); + } + { + const Variable& var = rep.find_gen_variable("DT_MM"); + BOOST_CHECK_MESSAGE(!var.empty(), "Did not find DT_MM"); + BOOST_CHECK_MESSAGE(var.theValue() == "03", "expected '03' but found " << var.theValue()); + } + { + const Variable& var = rep.find_gen_variable("DT_DD"); + BOOST_CHECK_MESSAGE(!var.empty(), "Did not find DT_DD"); + BOOST_CHECK_MESSAGE(var.theValue() == "15", "expected '15' but found " << var.theValue()); + } + { + const Variable& var = rep.find_gen_variable("DT_HOURS"); + BOOST_CHECK_MESSAGE(!var.empty(), "Did not find DT_HOURS"); + BOOST_CHECK_MESSAGE(var.theValue() == "10", "expected '10' but found " << var.theValue()); + } + { + const Variable& var = rep.find_gen_variable("DT_MINUTES"); + BOOST_CHECK_MESSAGE(!var.empty(), "Did not find DT_MINUTES"); + BOOST_CHECK_MESSAGE(var.theValue() == "30", "expected '30' but found " << var.theValue()); + } + { + const Variable& var = rep.find_gen_variable("DT_SECONDS"); + BOOST_CHECK_MESSAGE(!var.empty(), "Did not find DT_SECONDS"); + BOOST_CHECK_MESSAGE(var.theValue() == "45", "expected '45' but found " << var.theValue()); + } +} + +BOOST_AUTO_TEST_CASE(iterate_over_datetimelist_current_index_current_value) { + ECF_NAME_THIS_TEST(); + + using namespace ecf::repeat; + + RepeatDateTimeList r("DTL", + {ecf::Instant::parse("19700101T000000"), + ecf::Instant::parse("19700101T120000"), + ecf::Instant::parse("19700102T000000")}); + BOOST_CHECK_EQUAL(r.current_index(), 0L); + BOOST_CHECK_EQUAL(r.current_value(), "19700101T000000"); + { + auto value = current_value_as(r); + BOOST_CHECK(!value.has_value()); + } + { + auto value = current_value_as(r); + BOOST_CHECK(value.has_value() && value.value() == "19700101T000000"); + } + r.increment(); + BOOST_CHECK_EQUAL(r.current_index(), 1L); + BOOST_CHECK_EQUAL(r.current_value(), "19700101T120000"); + { + auto value = current_value_as(r); + BOOST_CHECK(!value.has_value()); + } + { + auto value = current_value_as(r); + BOOST_CHECK(value.has_value() && value.value() == "19700101T120000"); + } + r.increment(); + BOOST_CHECK_EQUAL(r.current_index(), 2L); + BOOST_CHECK_EQUAL(r.current_value(), "19700102T000000"); + { + auto value = current_value_as(r); + BOOST_CHECK(!value.has_value()); + } + { + auto value = current_value_as(r); + BOOST_CHECK(value.has_value() && value.value() == "19700102T000000"); + } +} + +BOOST_AUTO_TEST_SUITE_END() // test_repeat_datetimelist + /* * Test Suite: ::test_repeat_date * ************************************************************ */ @@ -825,6 +1174,140 @@ BOOST_AUTO_TEST_CASE(more_generated_variables) { } } +BOOST_AUTO_TEST_CASE(iterate_over_date_current_index_current_value_within_month) { + ECF_NAME_THIS_TEST(); + + using namespace ecf::repeat; + + RepeatDate r("YMD", 20100101, 20100110, 3); + BOOST_CHECK_EQUAL(r.current_index(), 0L); + BOOST_CHECK_EQUAL(r.current_value(), "20100101"); + { + auto value = current_value_as(r); + BOOST_CHECK(value.has_value() && value.value() == 20100101); + } + { + auto value = current_value_as(r); + BOOST_CHECK(!value.has_value()); + } + r.increment(); + BOOST_CHECK_EQUAL(r.current_index(), 1L); + BOOST_CHECK_EQUAL(r.current_value(), "20100104"); + { + auto value = current_value_as(r); + BOOST_CHECK(value.has_value() && value.value() == 20100104); + } + { + auto value = current_value_as(r); + BOOST_CHECK(!value.has_value()); + } + r.increment(); + BOOST_CHECK_EQUAL(r.current_index(), 2L); + BOOST_CHECK_EQUAL(r.current_value(), "20100107"); + { + auto value = current_value_as(r); + BOOST_CHECK(value.has_value() && value.value() == 20100107); + } + { + auto value = current_value_as(r); + BOOST_CHECK(!value.has_value()); + } + r.increment(); + BOOST_CHECK_EQUAL(r.current_index(), 3L); + BOOST_CHECK_EQUAL(r.current_value(), "20100110"); + { + auto value = current_value_as(r); + BOOST_CHECK(value.has_value() && value.value() == 20100110); + } + { + auto value = current_value_as(r); + BOOST_CHECK(!value.has_value()); + } +} + +BOOST_AUTO_TEST_CASE(iterate_over_date_current_index_current_value_over_month_boundary) { + ECF_NAME_THIS_TEST(); + + using namespace ecf::repeat; + + RepeatDate r("YMD", 20241129, 20241207, 3); + BOOST_CHECK_EQUAL(r.current_index(), 0L); + BOOST_CHECK_EQUAL(r.current_value(), "20241129"); + { + auto value = current_value_as(r); + BOOST_CHECK(value.has_value() && value.value() == 20241129); + } + r.increment(); + BOOST_CHECK_EQUAL(r.current_index(), 1L); + BOOST_CHECK_EQUAL(r.current_value(), "20241202"); + { + auto value = current_value_as(r); + BOOST_CHECK(value.has_value() && value.value() == 20241202); + } + r.increment(); + BOOST_CHECK_EQUAL(r.current_index(), 2L); + BOOST_CHECK_EQUAL(r.current_value(), "20241205"); + { + auto value = current_value_as(r); + BOOST_CHECK(value.has_value() && value.value() == 20241205); + } +} + +BOOST_AUTO_TEST_CASE(iterate_over_date_current_index_current_value_over_year_boundary) { + ECF_NAME_THIS_TEST(); + + using namespace ecf::repeat; + + // Another cross-boundary regression: Jan 1st is 2 days after Dec 30th. + RepeatDate r("YMD", 20231230, 20240103, 2); + BOOST_CHECK_EQUAL(r.current_index(), 0L); + BOOST_CHECK_EQUAL(r.current_value(), "20231230"); + { + auto value = current_value_as(r); + BOOST_CHECK(value.has_value() && value.value() == 20231230); + } + r.increment(); + BOOST_CHECK_EQUAL(r.current_index(), 1L); + BOOST_CHECK_EQUAL(r.current_value(), "20240101"); + { + auto value = current_value_as(r); + BOOST_CHECK(value.has_value() && value.value() == 20240101); + } + r.increment(); + BOOST_CHECK_EQUAL(r.current_index(), 2L); + BOOST_CHECK_EQUAL(r.current_value(), "20240103"); + { + auto value = current_value_as(r); + BOOST_CHECK(value.has_value() && value.value() == 20240103); + } +} + +BOOST_AUTO_TEST_CASE(iterate_over_date_current_index_negative_delta) { + ECF_NAME_THIS_TEST(); + + RepeatDate r("YMD", 20240103, 20231230, -2); + BOOST_CHECK_EQUAL(r.current_index(), 0L); + BOOST_CHECK_EQUAL(r.current_value(), "20240103"); + { + auto value = ecf::repeat::current_value_as(r); + BOOST_CHECK(value.has_value() && value.value() == 20240103); + } + r.increment(); + BOOST_CHECK_EQUAL(r.current_index(), 1L); + BOOST_CHECK_EQUAL(r.current_value(), "20240101"); + { + auto value = ecf::repeat::current_value_as(r); + BOOST_CHECK(value.has_value() && value.value() == 20240101); + } + r.increment(); + BOOST_CHECK_EQUAL(r.current_index(), 2L); + BOOST_CHECK_EQUAL(r.current_value(), "20231230"); + { + auto value = ecf::repeat::current_value_as(r); + BOOST_CHECK(value.has_value() && value.value() == 20231230); + } +} + BOOST_AUTO_TEST_CASE(convert_xref_to_boost_date) { ECF_NAME_THIS_TEST(); @@ -1122,6 +1605,46 @@ BOOST_AUTO_TEST_CASE(generated_variables) { } } +BOOST_AUTO_TEST_CASE(iterate_over_datetime_current_index_current_value) { + ECF_NAME_THIS_TEST(); + + using namespace ecf::repeat; + + RepeatDateTime r("DT", "19700101T000000", "19700102T000000", "12:00:00"); + BOOST_CHECK_EQUAL(r.current_index(), 0L); + BOOST_CHECK_EQUAL(r.current_value(), "19700101T000000"); + { + auto value = current_value_as(r); + BOOST_CHECK(!value.has_value()); + } + { + auto value = current_value_as(r); + BOOST_CHECK(value.has_value() && value.value() == "19700101T000000"); + } + r.increment(); + BOOST_CHECK_EQUAL(r.current_index(), 1L); + BOOST_CHECK_EQUAL(r.current_value(), "19700101T120000"); + { + auto value = current_value_as(r); + BOOST_CHECK(!value.has_value()); + } + { + auto value = current_value_as(r); + BOOST_CHECK(value.has_value() && value.value() == "19700101T120000"); + } + r.increment(); + BOOST_CHECK_EQUAL(r.current_index(), 2L); + BOOST_CHECK_EQUAL(r.current_value(), "19700102T000000"); + { + auto value = current_value_as(r); + BOOST_CHECK(!value.has_value()); + } + { + auto value = current_value_as(r); + BOOST_CHECK(value.has_value() && value.value() == "19700102T000000"); + } +} + BOOST_AUTO_TEST_SUITE_END() // test_repeat_datetime /* @@ -1305,6 +1828,47 @@ BOOST_AUTO_TEST_CASE(handling_errors) { BOOST_REQUIRE_THROW(RepeatEnumerated("AEnum", empty), std::runtime_error); // empty enumerations } +BOOST_AUTO_TEST_CASE(iterate_over_enumerated_current_index_current_value) { + ECF_NAME_THIS_TEST(); + + using namespace ecf::repeat; + + using namespace std::string_literals; + RepeatEnumerated r("E", {"red"s, "green"s, "blue"s}); + BOOST_CHECK_EQUAL(r.current_index(), 0L); + BOOST_CHECK_EQUAL(r.current_value(), "red"); + { + auto value = current_value_as(r); + BOOST_CHECK(!value.has_value()); + } + { + auto value = current_value_as(r); + BOOST_CHECK(value.has_value() && value.value() == "red"); + } + r.increment(); + BOOST_CHECK_EQUAL(r.current_index(), 1L); + BOOST_CHECK_EQUAL(r.current_value(), "green"); + { + auto value = current_value_as(r); + BOOST_CHECK(!value.has_value()); + } + { + auto value = current_value_as(r); + BOOST_CHECK(value.has_value() && value.value() == "green"); + } + r.increment(); + BOOST_CHECK_EQUAL(r.current_index(), 2L); + BOOST_CHECK_EQUAL(r.current_value(), "blue"); + { + auto value = current_value_as(r); + BOOST_CHECK(!value.has_value()); + } + { + auto value = current_value_as(r); + BOOST_CHECK(value.has_value() && value.value() == "blue"); + } +} + BOOST_AUTO_TEST_SUITE_END() // test_repeat_enumerated /* @@ -1424,6 +1988,46 @@ BOOST_AUTO_TEST_CASE(increment) { BOOST_CHECK_MESSAGE(rep.last_valid_value() == 10, " Expected 10 but found " << rep.last_valid_value()); } +BOOST_AUTO_TEST_CASE(iterate_over_integer_current_index_current_value) { + ECF_NAME_THIS_TEST(); + + using namespace ecf::repeat; + + RepeatInteger r("N", 1, 6, 2); + BOOST_CHECK_EQUAL(r.current_index(), 0L); + BOOST_CHECK_EQUAL(r.current_value(), "1"); + { + auto value = current_value_as(r); + BOOST_CHECK(value.has_value() && value.value() == 1); + } + { + auto value = current_value_as(r); + BOOST_CHECK(!value.has_value()); + } + r.increment(); + BOOST_CHECK_EQUAL(r.current_index(), 1L); + BOOST_CHECK_EQUAL(r.current_value(), "3"); + { + auto value = current_value_as(r); + BOOST_CHECK(value.has_value() && value.value() == 3); + } + { + auto value = current_value_as(r); + BOOST_CHECK(!value.has_value()); + } + r.increment(); + BOOST_CHECK_EQUAL(r.current_index(), 2L); + BOOST_CHECK_EQUAL(r.current_value(), "5"); + { + auto value = current_value_as(r); + BOOST_CHECK(value.has_value() && value.value() == 5); + } + { + auto value = current_value_as(r); + BOOST_CHECK(!value.has_value()); + } +} + BOOST_AUTO_TEST_SUITE_END() // test_repeat_integer /* @@ -1458,6 +2062,36 @@ BOOST_AUTO_TEST_CASE(invariants) { } } +BOOST_AUTO_TEST_CASE(iterate_over_day_current_index_current_value) { + ECF_NAME_THIS_TEST(); + + using namespace ecf::repeat; + + // RepeatDay has no position concept: current_index() returns the step value. + RepeatDay r(3); + BOOST_CHECK_EQUAL(r.current_index(), 3L); + BOOST_CHECK_EQUAL(r.current_value(), "3"); + { + auto value = current_value_as(r); + BOOST_CHECK(value.has_value() && value.value() == 3); + } + { + auto value = current_value_as(r); + BOOST_CHECK(!value.has_value()); + } + r.increment(); + BOOST_CHECK_EQUAL(r.current_index(), 3L); + BOOST_CHECK_EQUAL(r.current_value(), "3"); + { + auto value = current_value_as(r); + BOOST_CHECK(value.has_value() && value.value() == 3); + } + { + auto value = current_value_as(r); + BOOST_CHECK(!value.has_value()); + } +} + BOOST_AUTO_TEST_SUITE_END() // test_repeat_day /* @@ -1542,6 +2176,47 @@ BOOST_AUTO_TEST_CASE(handling_errors) { BOOST_REQUIRE_THROW(RepeatString("AEnum", empty), std::runtime_error); // empty string list } +BOOST_AUTO_TEST_CASE(iterate_over_string_current_index_current_value) { + ECF_NAME_THIS_TEST(); + + using namespace ecf::repeat; + using namespace std::string_literals; + + RepeatString r("S", {"a"s, "b"s, "c"s}); + BOOST_CHECK_EQUAL(r.current_index(), 0L); + BOOST_CHECK_EQUAL(r.current_value(), "a"); + { + auto value = current_value_as(r); + BOOST_CHECK(!value.has_value()); + } + { + auto value = current_value_as(r); + BOOST_CHECK(value.has_value() && value.value() == "a"); + } + r.increment(); + BOOST_CHECK_EQUAL(r.current_index(), 1L); + BOOST_CHECK_EQUAL(r.current_value(), "b"); + { + auto value = current_value_as(r); + BOOST_CHECK(!value.has_value()); + } + { + auto value = current_value_as(r); + BOOST_CHECK(value.has_value() && value.value() == "b"); + } + r.increment(); + BOOST_CHECK_EQUAL(r.current_index(), 2L); + BOOST_CHECK_EQUAL(r.current_value(), "c"); + { + auto value = current_value_as(r); + BOOST_CHECK(!value.has_value()); + } + { + auto value = current_value_as(r); + BOOST_CHECK(value.has_value() && value.value() == "c"); + } +} + BOOST_AUTO_TEST_SUITE_END() // test_repeat_string BOOST_AUTO_TEST_SUITE_END() diff --git a/libs/attribute/test/TestSizeOf.cpp b/libs/attribute/test/TestSizeOf.cpp index f90df85eb..26390126e 100644 --- a/libs/attribute/test/TestSizeOf.cpp +++ b/libs/attribute/test/TestSizeOf.cpp @@ -48,7 +48,7 @@ TYPE_STRING(DayAttr) template void inspect_size_of(T t = T{}) { #if PRINT_SIZEOF_RESULTS - ECF_TEST_DBG(<< " * sizeof(" << typeName() << ") = " << sizeof(T)); + ECF_TEST_DBG(" * sizeof(" << typeName() << ") = " << sizeof(T)); #endif BOOST_REQUIRE_EQUAL(sizeof(T), sizeof(T)); } diff --git a/libs/base/CMakeLists.txt b/libs/base/CMakeLists.txt index 007807301..25bf14ad0 100644 --- a/libs/base/CMakeLists.txt +++ b/libs/base/CMakeLists.txt @@ -16,6 +16,7 @@ set(test_srcs test/TestArchiveAndRestoreCmd.cpp test/TestBase_main.cpp # test entry point test/TestClientHandleCmd.cpp + test/TestClientOptionsParser.cpp test/TestCmd.cpp test/TestDeleteNodeCmd.cpp test/TestForceCmd.cpp diff --git a/libs/base/src/ecflow/base/Algorithms.hpp b/libs/base/src/ecflow/base/Algorithms.hpp index 1322e4452..960233a18 100644 --- a/libs/base/src/ecflow/base/Algorithms.hpp +++ b/libs/base/src/ecflow/base/Algorithms.hpp @@ -13,9 +13,6 @@ #include -#include -#include - #include "ecflow/base/AbstractServer.hpp" #include "ecflow/core/Result.hpp" #include "ecflow/node/Defs.hpp" @@ -44,10 +41,8 @@ struct Path } std::vector tokens; - boost::tokenizer> tokenizer(path, boost::char_separator("/ ")); - for (const auto& token : tokenizer) { - tokens.push_back(token); - } + ecf::algorithm::split_at(tokens, path, "/ "); + return Result::success(Path(std::move(tokens))); } diff --git a/libs/base/src/ecflow/base/AuthenticationDetails.hpp b/libs/base/src/ecflow/base/AuthenticationDetails.hpp index ea9d2efd6..d367e94c7 100644 --- a/libs/base/src/ecflow/base/AuthenticationDetails.hpp +++ b/libs/base/src/ecflow/base/AuthenticationDetails.hpp @@ -72,9 +72,7 @@ inline authentication_t verify_user_authentication_rules(const AbstractServer& s return authentication_t::success("Authentication (user command) successful"); } - return authentication_t::failure("Authentication (user command) failed, due to: Incorrect credentials for (" + - command.identity().username() + " / " + command.identity().password() + - ") is not authentic"); + return authentication_t::failure("Authentication (user command) failed, due to: Incorrect credentials detected"); } template diff --git a/libs/base/src/ecflow/base/ClientOptionsParser.cpp b/libs/base/src/ecflow/base/ClientOptionsParser.cpp index 139af0454..cd50bb5d7 100644 --- a/libs/base/src/ecflow/base/ClientOptionsParser.cpp +++ b/libs/base/src/ecflow/base/ClientOptionsParser.cpp @@ -16,7 +16,7 @@ namespace ecf { namespace { -bool is_valid_path(const std::string& path) { +bool is_valid_path(std::string_view path) { return !path.empty() && path[0] == '/'; } @@ -101,9 +101,32 @@ void parse_alter(ClientOptionsParser::option_set_t& processed_options, ClientOpt // This effectively always collects an argument (i.e. the operation) parse_option(alter, processed_options, args); - // Collect up to 4 positional arguments, that are not paths - parse_positional_arguments( - alter, processed_options, args, 4, [](const std::string& arg) { return !is_valid_path(arg); }); + // + // The selector collects up to 4 positional arguments. + // + // In general, arguments are processed until the first valid node path (one + // starting with '/') is found and collection stops. + // + // Special case — "add inlimit": the inlimit name is a reference to the + // associated limit and can take the form [/path/to/node:]limit_name. + // + // In the specific case of "inlimit", the inlimit "name" is actually a reference + // to the associated limit which can take the form [:]. + // To prevent the leading '/' from stopping collection early, the first 3 + // positional arguments are always accepted when the attribute type is "inlimit". + // + bool is_inlimit = false; + size_t selector_index = 0; + auto selector = [&is_inlimit, &selector_index](std::string_view arg) -> bool { + if (selector_index == 0 && arg == "inlimit") { + is_inlimit = true; + } + bool is_selected = (is_inlimit && selector_index < 3) ? true : !is_valid_path(arg); + ++selector_index; + return is_selected; + }; + + parse_positional_arguments(alter, processed_options, args, 4, selector); // Collect remaining positional arguments, that are paths parse_positional_arguments( diff --git a/libs/base/src/ecflow/base/Gnuplot.cpp b/libs/base/src/ecflow/base/Gnuplot.cpp index a85415bc8..1a72cea0c 100644 --- a/libs/base/src/ecflow/base/Gnuplot.cpp +++ b/libs/base/src/ecflow/base/Gnuplot.cpp @@ -125,10 +125,10 @@ std::string Gnuplot::create_gnuplot_file(std::vector& suite_vec, cons bool child_cmd = false; bool user_cmd = false; - if (line.find(Str::CHILD_CMD()) != std::string::npos) { + if (line.find(ecf::string_constants::child_cmd) != std::string::npos) { child_cmd = true; } - else if (line.find(Str::USER_CMD()) != std::string::npos) { + else if (line.find(ecf::string_constants::user_cmd) != std::string::npos) { user_cmd = true; } if (!child_cmd && !user_cmd) { @@ -155,7 +155,7 @@ std::string Gnuplot::create_gnuplot_file(std::vector& suite_vec, cons } std::string time_stamp = line.substr(0, first_closed_bracket); - Str::split(time_stamp, new_time_stamp); + ecf::algorithm::split_at(new_time_stamp, time_stamp); if (new_time_stamp.size() != 2) { continue; } @@ -384,7 +384,7 @@ bool Gnuplot::extract_suite_path(const std::string& line, std::vector theNodeNames; theNodeNames.reserve(4); - NodePath::split(path, theNodeNames); + ecf::node::split_path(path, theNodeNames); if (!theNodeNames.empty()) { for (size_t n = 0; n < suite_vec.size(); n++) { if (suite_vec[n].suite_name_ == theNodeNames[0]) { diff --git a/libs/base/src/ecflow/base/Openssl.cpp b/libs/base/src/ecflow/base/Openssl.cpp index e70662559..0e10392a8 100644 --- a/libs/base/src/ecflow/base/Openssl.cpp +++ b/libs/base/src/ecflow/base/Openssl.cpp @@ -36,7 +36,7 @@ std::string Openssl::selected_crt() const { } bool Openssl::enable_no_throw(std::string host, const std::string& port, const std::string& ecf_ssl_env) { - if (host == Str::LOCALHOST()) { + if (host == ecf::string_constants::localhost) { host = Host().name(); } @@ -73,7 +73,7 @@ bool Openssl::enable_no_throw(std::string host, const std::string& port, const s } void Openssl::enable(std::string host, const std::string& port) { - if (host == Str::LOCALHOST()) { + if (host == ecf::string_constants::localhost) { host = Host().name(); } @@ -87,7 +87,7 @@ void Openssl::enable_if_defined(std::string host, const std::string& port) { if (auto ecf_ssl = ecf::environment::fetch(ecf::environment::ECF_SSL); ecf_ssl) { std::string ecf_ssl_env = ecf_ssl.value(); - if (host == Str::LOCALHOST()) { + if (host == ecf::string_constants::localhost) { host = Host().name(); } @@ -116,6 +116,49 @@ void Openssl::init_for_server() { if (enabled()) { check_server_certificates(); + // --- Server-side SSL context initialisation --- + // + // The context is created with `tls_server`, the generic server-side + // TLS method. On its own this permits any TLS version that the + // underlying OpenSSL library supports. The `set_options` call below + // then explicitly disables every protocol version below TLS 1.3 + // (SSLv2, SSLv3, TLS 1.0, TLS 1.1, TLS 1.2), leaving TLS 1.3 as + // the *only* version the server will accept. + // + // The practical consequence is that any use of weak cryptography is eliminated: + // - SSLv2/SSLv3 are broken (POODLE, DROWN) -- disabled. + // - TLS 1.0/1.1 rely on SHA-1 and RC4 that are no longer acceptable -- disabled. + // - TLS 1.2 cipher suites can include DES/3DES, RC4 and other weak + // algorithms that are no longer acceptable -- disabled. + // + // - TLS 1.3 mandates AEAD cipher suites (AES-GCM, ChaCha20-Poly1305) + // and forward secrecy for every connection. + // ** TLS 1.3 is the effective (and only) option after set_options. ** + // + // Notice that using `tls_server` enables the automatic use of future TLS versions, simply by recompiling + // against a newer OpenSSL library that supports them. The server will automatically negotiate the highest + // mutually supported TLS version with the client, always guaranteeing the TLS 1.3 is the lowest accepted in + // the handshake with the client. This is a future-proof design that does not require code changes to support + // new TLS versions as they are released, while still enforcing the use of strong cryptography. + // Even so, changes will be need if TLS 1.3 becomes deprecated. + // + // Client compliance requirement: + // During the TLS handshake the client proposes a list of protocol + // versions and cipher suites it supports. Because the server + // advertises *only* TLS 1.3, any client that does not support + // TLS 1.3 will fail the handshake with a "protocol version" alert. + // The counterpart `init_for_client()` therefore uses the generic + // `tls_client` method (which supports TLS 1.3) so that the + // negotiation succeeds. Clients built against older OpenSSL versions + // that lack TLS 1.3 support will be rejected. + // + // Other options: + // - `default_workarounds` enables OpenSSL bug-compatibility shims + // that are harmless on TLS 1.3 but kept for robustness. + // - `single_dh_use` ensures a fresh DH key pair is generated for + // each handshake, preserving forward secrecy for the DH key + // exchange used by the loaded dh2048.pem parameters. + // ssl_context_ = std::make_unique(boost::asio::ssl::context::tls_server); // clang-format off @@ -129,7 +172,8 @@ void Openssl::init_for_server() { boost::asio::ssl::context::single_dh_use); // clang-format on - // this must be done before loading any keys. as below + // The password callback is registered before any private key is loaded. + // OpenSSL calls it immediately during `use_private_key_file` if the key file is passphrase-protected. ssl_context_->set_password_callback( [this](std::size_t size, boost::asio::ssl::context_base::password_purpose purpose) { return this->get_password(); @@ -144,6 +188,22 @@ void Openssl::init_for_client() { if (!init_for_client_ && enabled()) { init_for_client_ = true; + // --- Client-side SSL context initialisation --- + // + // `tls_client` is the generic, version-flexible client method: it + // allows OpenSSL to negotiate the highest mutually supported TLS + // version. Because the server is configured exclusively for TLS 1.3 + // (see `init_for_server`), the handshake will always settle on + // TLS 1.3; the flexibility here is on the client side only and does + // not weaken security. + // + // Certificate verification: + // `load_verify_file` loads the self-signed server certificate (CRT) + // as a trusted CA. During the handshake OpenSSL verifies the + // server's presented certificate against this file, ensuring the + // client is talking to the expected server and not an impostor + // (i.e. protection against man-in-the-middle attacks). + // ssl_context_ = std::make_unique(boost::asio::ssl::context::tls_client); ssl_context_->load_verify_file(crt()); } diff --git a/libs/base/src/ecflow/base/cts/ClientToServerCmd.cpp b/libs/base/src/ecflow/base/cts/ClientToServerCmd.cpp index 0a86587ed..76cf85ce0 100644 --- a/libs/base/src/ecflow/base/cts/ClientToServerCmd.cpp +++ b/libs/base/src/ecflow/base/cts/ClientToServerCmd.cpp @@ -72,7 +72,7 @@ STC_Cmd_ptr ClientToServerCmd::handleRequest(AbstractServer* as) const { // mark edited nodes, with edit history. relies on doHandleRequest to populate edit_history_nodes_/paths // hence must at the same scope level - EditHistoryMgr edit_history_mgr(this, as); + EditHistoryMgr edit_history_mgr(*this, *as); // Handle the request, and return the reply back to the client STC_Cmd_ptr server_to_client_ptr = doHandleRequest(as); @@ -173,7 +173,7 @@ void ClientToServerCmd::add_edit_history(Defs* defs) const { if (edit_history_nodes_.empty() && edit_history_node_paths_.empty()) { defs->flag().set(ecf::Flag::MESSAGE); - add_edit_history(defs, Str::ROOT_PATH()); + add_edit_history(defs, ecf::string_constants::root_path); } else { // edit_history_node_paths_ is only populated by the delete command @@ -220,10 +220,10 @@ void ClientToServerCmd::add_edit_history(Defs* defs, const std::string& path) co } void ClientToServerCmd::add_delete_edit_history(Defs* defs, const std::string& path) const { - // History is added to Str::ROOT_PATH(), but the path must show deleted node path + // History is added to ecf::string_constants::root_path, but the path must show deleted node path std::string ss("MSG:"); ss += Log::instance()->get_cached_time_stamp(); print(ss, path); // custom print - defs->add_edit_history(Str::ROOT_PATH(), ss); + defs->add_edit_history(ecf::string_constants::root_path, ss); } diff --git a/libs/base/src/ecflow/base/cts/ClientToServerCmd.hpp b/libs/base/src/ecflow/base/cts/ClientToServerCmd.hpp index d4502b58b..f5bd507d0 100644 --- a/libs/base/src/ecflow/base/cts/ClientToServerCmd.hpp +++ b/libs/base/src/ecflow/base/cts/ClientToServerCmd.hpp @@ -155,8 +155,40 @@ class ClientToServerCmd { virtual bool show_cmd() const { return false; } virtual void add_edit_history(Defs*) const; - // used by group_cmd to postfix syncCmd on all user commands that modify defs - virtual void set_client_handle(int /*client_handle*/) {} // used by group_cmd + /// + /// @brief Sets the client handle for this command, used during group command execution. + /// + /// This function is part of the client handle synchronisation mechanism used when commands + /// are executed as part of a GroupCTSCmd. It allows commands that create, modify, or drop + /// client handles to propagate that information to subsequent commands in the group + /// (typically a sync command). + /// + /// Client handles are identifiers that allow clients to register interest in a specific + /// subset of suites on the server, enabling efficient synchronisation: + /// - client_handle > 0: References a specific registered set of suites + /// - client_handle == 0: References all suites (global scope) + /// + /// Call flow example when a group contains [ClientHandleCmd, CSyncCmd]: + /// 1. ClientHandleCmd creates a new handle (e.g., 42) + /// 2. ClientHandleCmd calls group_cmd_->set_client_handle(42) + /// 3. GroupCTSCmd propagates this to the next command (CSyncCmd) + /// 4. CSyncCmd stores the handle and uses it to sync only the relevant suites + /// + /// This function is const because it's called from const member functions (doHandleRequest). + /// It doesn't modify the command's observable state, only propagates information or updates + /// internal synchronisation state (via mutable members in derived classes). + /// + /// @param client_handle The client handle to set. Use 0 for global scope (all suites), + /// or a positive value for a specific registered set of suites. + /// + /// @see GroupCTSCmd::set_client_handle() for propagation logic + /// @see CSyncCmd::set_client_handle() for storage and usage + /// @see ClientHandleCmd for client handle creation/management + /// + virtual void set_client_handle([[maybe_unused]] int client_handle) const { + // Default implementation: no-op, ignored by most command types + } + virtual void set_group_cmd(const GroupCTSCmd*) {} // CLIENT side Parse and command construction, create can throw std::runtime_error for errors diff --git a/libs/base/src/ecflow/base/cts/EditHistoryMgr.cpp b/libs/base/src/ecflow/base/cts/EditHistoryMgr.cpp index ea30577e5..7bcf0ebe9 100644 --- a/libs/base/src/ecflow/base/cts/EditHistoryMgr.cpp +++ b/libs/base/src/ecflow/base/cts/EditHistoryMgr.cpp @@ -15,36 +15,39 @@ #include "ecflow/core/Ecf.hpp" #include "ecflow/node/Defs.hpp" -EditHistoryMgr::EditHistoryMgr(const ClientToServerCmd* c, AbstractServer* a) - : cts_cmd_(c), - as_(a), +EditHistoryMgr::EditHistoryMgr(const ClientToServerCmd& cmd, const AbstractServer& server) + : cts_cmd_(cmd), + as_(server), state_change_no_(Ecf::state_change_no()), modify_change_no_(Ecf::modify_change_no()) { - assert(cts_cmd_->edit_history_nodes_.empty()); - assert(cts_cmd_->edit_history_node_paths_.empty()); + assert(cts_cmd_.edit_history_nodes_.empty()); + assert(cts_cmd_.edit_history_node_paths_.empty()); } EditHistoryMgr::~EditHistoryMgr() { - // check if state changed + // Detect a state/modify change if (state_change_no_ != Ecf::state_change_no() || modify_change_no_ != Ecf::modify_change_no()) { - // Ignore child commands for edit history, where only interested in user commands - if (!cts_cmd_->task_cmd()) { + // Ignore child commands for edit history -- only user commands are considered + if (!cts_cmd_.task_cmd()) { - // *ONLY* record edit history to commands that change the data model - // Otherwise we will end up making a data model change for read only commands - // If there has been a change in defs state then the command must return true from isWrite - if (cts_cmd_->isWrite()) { - cts_cmd_->add_edit_history(as_->defs().get()); + // The goal is to *ONLY* record edit history to commands that already change the data model + // Otherwise, all read-only commands would actually become write commands! + if (cts_cmd_.isWrite()) { + cts_cmd_.add_edit_history(as_.defs().get()); } else { - // Read only command, that is making data model changes, oops ? - // Can happen with check pt command, when ecf_home can't be written to, (exceptional) - // i.e set late flag( when saving takes more the 30 seconds) *OR* Flag::CHECKPT_ERROR | Flag::LOG_ERROR - // even though its read only command. In which case is_mutable() should return true. - if (!cts_cmd_->is_mutable()) { + // Read-only commands should not produce any data model changes! + // + // Exception to the rule: + // This can happen in very exceptional cases (e.g. when the ECF_HOME_home cannot be written to, + // the checkpt command eventually sets a late or error flag because technically saving is late or was + // not successful). + // These commands are identified as being "mutable", which is gives them a "get out of free" card. + // + if (!cts_cmd_.is_mutable()) { std::string ss; - cts_cmd_->print(ss); + cts_cmd_.print(ss); std::cout << "cmd " << ss << " should return true from isWrite() ******************\n"; std::cout << "Read only command is making data changes to defs ?????\n"; std::cout << "Ecf::state_change_no() " << Ecf::state_change_no() << " Ecf::modify_change_no() " diff --git a/libs/base/src/ecflow/base/cts/EditHistoryMgr.hpp b/libs/base/src/ecflow/base/cts/EditHistoryMgr.hpp index 9de2c9dff..e8b5a2b60 100644 --- a/libs/base/src/ecflow/base/cts/EditHistoryMgr.hpp +++ b/libs/base/src/ecflow/base/cts/EditHistoryMgr.hpp @@ -15,31 +15,46 @@ class ClientToServerCmd; class AbstractServer; /// -/// \brief This class manages the edit history, for the commands. -/// It determines if there was a state change, if there was, it adds edit -/// history to the stored nodes. -/// Additionally we check that if there was an edit then the command must -/// return ClientToServerCmd::isWrite() true. +/// @brief The EditHistoryMgr class manages the history of changes performed by the commands. +/// +/// The goal is to determine if there was an actual state change performed by the command, +/// and if there was adds them to the history associated to the nodes. +/// The history only considers changes performed by user commands. +/// +/// This also performs validation of the changes, and ensures that only commands that +/// return ClientToServerCmd::isWrite() true actually make changes to the node tree. +/// +/// This class is used in the following sequence of steps: +/// 1) An object of EditHistoryMgr is created before the command is handled and retrieves before state/modify counters +/// 2) The server handles the command and the command makes changes to the node tree (if any) +/// n.b. this must update the list of edit_history_nodes/edit_history_node_paths in the comamnd itself +/// 3) The dtor of EditHistoryMgr retrieves after state/modify counters +/// and if there is a change in either of the counters, then adds the edit history to the nodes in the command /// - class EditHistoryMgr { public: - EditHistoryMgr() = delete; - + /// + /// @brief Construct an EditHistoryMgr object. + /// + /// @param cmd The command to track the history for. + /// @param server The server handling the request + /// + EditHistoryMgr(const ClientToServerCmd& cmd, const AbstractServer& server); + + EditHistoryMgr() = delete; EditHistoryMgr(const EditHistoryMgr&) = delete; EditHistoryMgr& operator=(const EditHistoryMgr&) = delete; EditHistoryMgr(EditHistoryMgr&&) = delete; EditHistoryMgr& operator=(EditHistoryMgr&&) = delete; - EditHistoryMgr(const ClientToServerCmd*, AbstractServer*); - ~EditHistoryMgr(); private: - const ClientToServerCmd* cts_cmd_; - AbstractServer* as_; - mutable unsigned int state_change_no_; // detect state change in defs - mutable unsigned int modify_change_no_; // detect state change in defs + const ClientToServerCmd& cts_cmd_; + const AbstractServer& as_; + + mutable unsigned int state_change_no_; + mutable unsigned int modify_change_no_; }; #endif /* ecflow_base_cts_EditHistoryMgr_HPP */ diff --git a/libs/base/src/ecflow/base/cts/task/AbortCmd.cpp b/libs/base/src/ecflow/base/cts/task/AbortCmd.cpp index 1e8b1676e..e127324b7 100644 --- a/libs/base/src/ecflow/base/cts/task/AbortCmd.cpp +++ b/libs/base/src/ecflow/base/cts/task/AbortCmd.cpp @@ -37,13 +37,13 @@ AbortCmd::AbortCmd(const std::string& pathToTask, if (!reason_.empty()) { // Do not use "\n" | ';' in Submittable::abr_, as this can mess up, --migrate output // Which would then affect --load. - Str::replace(reason_, "\n", ""); - Str::replace(reason_, ";", " "); + ecf::algorithm::replace(reason_, "\n", ""); + ecf::algorithm::replace(reason_, ";", " "); } } void AbortCmd::print(std::string& os) const { - os += Str::CHILD_CMD(); + os += ecf::string_constants::child_cmd; os += "abort "; os += path_to_node(); os += " "; diff --git a/libs/base/src/ecflow/base/cts/task/CompleteCmd.cpp b/libs/base/src/ecflow/base/cts/task/CompleteCmd.cpp index 1227d725a..cb0adb666 100644 --- a/libs/base/src/ecflow/base/cts/task/CompleteCmd.cpp +++ b/libs/base/src/ecflow/base/cts/task/CompleteCmd.cpp @@ -25,7 +25,7 @@ using namespace ecf; void CompleteCmd::print(std::string& os) const { - os += Str::CHILD_CMD(); + os += ecf::string_constants::child_cmd; os += "complete "; os += path_to_node(); if (!var_to_del_.empty()) { diff --git a/libs/base/src/ecflow/base/cts/task/CtsWaitCmd.cpp b/libs/base/src/ecflow/base/cts/task/CtsWaitCmd.cpp index ab79a4956..9a9e46121 100644 --- a/libs/base/src/ecflow/base/cts/task/CtsWaitCmd.cpp +++ b/libs/base/src/ecflow/base/cts/task/CtsWaitCmd.cpp @@ -44,7 +44,7 @@ CtsWaitCmd::CtsWaitCmd(const std::string& pathToTask, } void CtsWaitCmd::print(std::string& os) const { - os += Str::CHILD_CMD(); + os += ecf::string_constants::child_cmd; os += "wait "; os += expression_; os += " "; diff --git a/libs/base/src/ecflow/base/cts/task/EventCmd.cpp b/libs/base/src/ecflow/base/cts/task/EventCmd.cpp index 81714af45..dce9c21f9 100644 --- a/libs/base/src/ecflow/base/cts/task/EventCmd.cpp +++ b/libs/base/src/ecflow/base/cts/task/EventCmd.cpp @@ -48,7 +48,7 @@ ecf::authorisation_t EventCmd::authorise(AbstractServer& server) const { } void EventCmd::print(std::string& os) const { - os += Str::CHILD_CMD(); + os += ecf::string_constants::child_cmd; os += "event "; os += name_; os += " "; diff --git a/libs/base/src/ecflow/base/cts/task/InitCmd.cpp b/libs/base/src/ecflow/base/cts/task/InitCmd.cpp index 162cd08c0..f5e21b926 100644 --- a/libs/base/src/ecflow/base/cts/task/InitCmd.cpp +++ b/libs/base/src/ecflow/base/cts/task/InitCmd.cpp @@ -27,7 +27,7 @@ using namespace ecf; void InitCmd::print(std::string& os) const { - os += Str::CHILD_CMD(); + os += ecf::string_constants::child_cmd; os += "init "; os += path_to_node(); if (!var_to_add_.empty()) { @@ -135,7 +135,7 @@ void InitCmd::create(Cmd_ptr& cmd, boost::program_options::variables_map& vm, Ab variable_vec.reserve(var_args.size()); for (const auto& v : var_args) { std::vector tokens; - Str::split(v, tokens, "="); + ecf::algorithm::split_at(tokens, v, "="); if (tokens.size() != 2) { throw std::runtime_error( "Could not parse variable provided to --add; Expected var1=value1 var2=value2 but found " + v); diff --git a/libs/base/src/ecflow/base/cts/task/LabelCmd.cpp b/libs/base/src/ecflow/base/cts/task/LabelCmd.cpp index 50024db28..bc0a34849 100644 --- a/libs/base/src/ecflow/base/cts/task/LabelCmd.cpp +++ b/libs/base/src/ecflow/base/cts/task/LabelCmd.cpp @@ -48,7 +48,7 @@ ecf::authorisation_t LabelCmd::authorise(AbstractServer& server) const { } void LabelCmd::print(std::string& os) const { - os += Str::CHILD_CMD(); + os += ecf::string_constants::child_cmd; os += "label "; os += name_; os += " '"; diff --git a/libs/base/src/ecflow/base/cts/task/MeterCmd.cpp b/libs/base/src/ecflow/base/cts/task/MeterCmd.cpp index 69337e1d5..7d8fff5cc 100644 --- a/libs/base/src/ecflow/base/cts/task/MeterCmd.cpp +++ b/libs/base/src/ecflow/base/cts/task/MeterCmd.cpp @@ -49,7 +49,7 @@ ecf::authorisation_t MeterCmd::authorise(AbstractServer& server) const { } void MeterCmd::print(std::string& os) const { - os += Str::CHILD_CMD(); + os += ecf::string_constants::child_cmd; os += "meter "; os += name_; os += " "; diff --git a/libs/base/src/ecflow/base/cts/task/QueueCmd.cpp b/libs/base/src/ecflow/base/cts/task/QueueCmd.cpp index 7f09f86f3..ec5674adc 100644 --- a/libs/base/src/ecflow/base/cts/task/QueueCmd.cpp +++ b/libs/base/src/ecflow/base/cts/task/QueueCmd.cpp @@ -56,7 +56,7 @@ ecf::authorisation_t QueueCmd::authorise(AbstractServer& server) const { } void QueueCmd::print(std::string& os) const { - os += Str::CHILD_CMD(); + os += ecf::string_constants::child_cmd; os += TaskApi::queue_arg(); os += " "; os += name_; @@ -277,7 +277,7 @@ void QueueCmd::create(Cmd_ptr& cmd, boost::program_options::variables_map& vm, A } std::string msg; - if (!Str::valid_name(queue_name, msg)) { + if (!ecf::algorithm::is_valid_name(queue_name, msg)) { throw std::runtime_error("QueueCmd: Invalid queue name : " + msg); } diff --git a/libs/base/src/ecflow/base/cts/user/AlterCmd.cpp b/libs/base/src/ecflow/base/cts/user/AlterCmd.cpp index 28b14b918..87eb1a315 100644 --- a/libs/base/src/ecflow/base/cts/user/AlterCmd.cpp +++ b/libs/base/src/ecflow/base/cts/user/AlterCmd.cpp @@ -57,51 +57,6 @@ static std::string dump_args(const std::vector& options, const std: namespace ecf { -InlimitOptions parse_inlimit_value(std::string value) { - if (value.empty()) { - return InlimitOptions{/* tokens = */ 1, /* limited_submission = */ false, /* limited_node = */ false}; - } - - bool limited_submission = false; - constexpr const char* submission_flag = "-s"; - if (ecf::algorithm::contains(value, submission_flag)) { - limited_submission = true; - ecf::algorithm::remove_all(value, submission_flag); - } - - bool limited_node = false; - constexpr const char* node_flag = "-n"; - if (ecf::algorithm::contains(value, node_flag)) { - limited_node = true; - ecf::algorithm::remove_all(value, node_flag); - } - - if (limited_submission && limited_node) { - throw std::runtime_error("AlterCmd: an inlimit cannot be limited for both submission and node"); - } - - ecf::algorithm::trim(value); - - if (value.empty()) { - return InlimitOptions{/* tokens = */ 1, limited_submission, limited_node}; - } - - try { - int tokens = ecf::convert_to(value); - - if (tokens <= 0) { - throw std::runtime_error( - MESSAGE("AlterCmd: the inlimit value must be > 0, but value was: '" << tokens << "'")); - } - - return InlimitOptions{tokens, limited_submission, limited_node}; - } - catch (const ecf::bad_conversion&) { - throw std::runtime_error( - MESSAGE("AlterCmd: the inlimit value, '" << value << "', cannot be converted to an integer")); - } -} - namespace detail { template <> @@ -615,19 +570,8 @@ STC_Cmd_ptr AlterCmd::doHandleRequest(AbstractServer* as) const { break; } case AlterCmd::ADD_INLIMIT: { - // Parse the limit path and name, which the inlimit depends on. - std::string limit_path; // This can be empty - std::string limit_name; - if (!Extract::pathAndName(name_, limit_path, limit_name)) { - throw std::runtime_error("AlterCmd::ADD_INLIMIT: Invalid inlimit : " + name_); - } - - // Parse the inlimit options, including token value - auto [tokens, limited_submission, limited_node] = parse_inlimit_value(value_); - - // Add the inlimit to the node - // Note: the InLimit ctor performs validation of the parameters and can throw if they are not valid - node->addInLimit(InLimit(limit_name, limit_path, tokens, limited_node, limited_submission)); + auto inlimit = InLimit::make_from_name_and_value(name_, value_); + node->addInLimit(inlimit); break; } case AlterCmd::ADD_ATTR_ND: @@ -635,7 +579,8 @@ STC_Cmd_ptr AlterCmd::doHandleRequest(AbstractServer* as) const { } } catch (std::exception& e) { - ss << "Alter (add) failed for " << paths_[i] << ": Could not parse " << name_ << " : " << e.what() << "\n"; + ss << "Alter (add) failed for paths " << paths_[i] << ", unable to handle request for " << name_ + << " due to : " << e.what() << "\n"; } // Change flags @@ -1008,9 +953,9 @@ void AlterCmd::extract_name_and_value_for_add(AlterCmd::Add_attr_type theAttrTyp } case AlterCmd::ADD_INLIMIT: { // inlimit /obs/limits:hpcd 2 name=hpcd, path=/obs/limits, tokens=2(optional) // options[0] - add - // options[1] - [ inlimit ] - // options[2] - [ path_to_limit:limit_name ] --> name - // options[3] - integer (optional) --> value + // options[1] - inlimit + // options[2] - [/path/to/limit:]limit_name --> name + // options[3] - [integer [-s] [-n ]] --> value if (options.size() < 3) { throw std::runtime_error(MESSAGE( "AlterCmd: add: Expected 'add inlimit (optional) . Not " @@ -1093,27 +1038,8 @@ void AlterCmd::check_for_add(AlterCmd::Add_attr_type theAttrType, Limit check(name, int_value); // will throw if not valid break; } - case AlterCmd::ADD_INLIMIT: { // inlimit /obs/limits:hpcd 2 name=hpcd, path=/obs/limits, tokens=2(optional) - // options[0] - add - // options[1] - [ inlimit ] - // options[2] - [ path_to_limit:limit_name ] ---> name - // options[3] - integer (optional) ---> value - std::string path_to_limit; // This can be empty - std::string limitName; - if (!Extract::pathAndName(name, path_to_limit, limitName)) { - throw std::runtime_error("AlterCmd add inlimit Invalid inlimit : " + name); - } - int token_value = 1; - if (!value.empty()) { - try { - token_value = ecf::convert_to(value); - } - catch (const ecf::bad_conversion&) { - throw std::runtime_error(MESSAGE("AlterCmd add inlimit expected optional limit token '" - << value << "' to be convertible to an integer\n")); - } - } - InLimit inlimit(limitName, path_to_limit, token_value); // will throw if not valid + case AlterCmd::ADD_INLIMIT: { + InLimit::make_from_name_and_value(name, value); // throws if either name or value are invalid break; } case AlterCmd::ADD_ATTR_ND: @@ -1511,7 +1437,7 @@ void AlterCmd::extract_name_and_value_for_change(AlterCmd::Change_attr_type theA } value = options[3]; if (value.find("\\n") != std::string::npos) { - Str::replaceall(value, "\\n", "\n"); + ecf::algorithm::replace_all(value, "\\n", "\n"); } } name = options[2]; diff --git a/libs/base/src/ecflow/base/cts/user/AlterCmd.hpp b/libs/base/src/ecflow/base/cts/user/AlterCmd.hpp index a20690653..737444d36 100644 --- a/libs/base/src/ecflow/base/cts/user/AlterCmd.hpp +++ b/libs/base/src/ecflow/base/cts/user/AlterCmd.hpp @@ -14,43 +14,6 @@ #include "ecflow/base/cts/user/UserCmd.hpp" #include "ecflow/node/Flag.hpp" -namespace ecf { - -struct InlimitOptions -{ - /// - /// @ brief the number of tokens considered for the inlimit, the minimum/default is 1 - /// - int tokens = 1; - - /// - /// @ brief flag indicating whether the inlimit is limited for submission - /// - bool limited_submission = false; - - /// - /// @ brief flag indicating whether the inlimit is limited for node - /// - bool limited_node = false; -}; - -/// -/// @brief Parses the inlimit options from a given value and returns the corresponding InlimitOptions struct. -/// -/// @param value the value to parse, which can be in the following formats: -/// - "" (tokens = 1, limited_submission = false, limited_node = false) -/// - "10" (tokens = 10, limited_submission = false, limited_node = false) -/// - "-s 10" (tokens = 10, limited_submission = true, limited_node = false) -/// - "-n 10" (tokens = 10, limited_submission = false, limited_node = true) -/// @return the InlimitOptions containing the tokens, limited_submission, and limited_node values -/// @throws std::runtime_error for the following cases: -/// - the value, after stripped of '-n' and '-s' options, and 'spacces' trimmed, is not a valid positive integer -/// - both '-n' and '-s' options are specified at the same time -/// -InlimitOptions parse_inlimit_value(std::string value); - -} // namespace ecf - class AlterCmd final : public UserCmd { public: enum Delete_attr_type { diff --git a/libs/base/src/ecflow/base/cts/user/BeginCmd.cpp b/libs/base/src/ecflow/base/cts/user/BeginCmd.cpp index 3391d2b8b..746597a76 100644 --- a/libs/base/src/ecflow/base/cts/user/BeginCmd.cpp +++ b/libs/base/src/ecflow/base/cts/user/BeginCmd.cpp @@ -73,12 +73,12 @@ STC_Cmd_ptr BeginCmd::doHandleRequest(AbstractServer* as) const { // If no suite name begin all suites, else begin the the specific suite if (suiteName_.empty()) { - const std::vector& suiteVec = defs->suiteVec(); - size_t theSuiteVecSize = suiteVec.size(); + const auto& suites = defs->suites(); if (!force_) { - for (size_t s = 0; s < theSuiteVecSize; s++) { + size_t size = suites.size(); + for (size_t s = 0; s < size; s++) { /// check_suite_can_begin will throw if suite can't begin - defs->check_suite_can_begin(suiteVec[s]); + defs->check_suite_can_begin(suites[s]); } } else { @@ -152,7 +152,8 @@ void BeginCmd::addOption(boost::program_options::options_description& desc) cons } void BeginCmd::create(Cmd_ptr& cmd, boost::program_options::variables_map& vm, AbstractClientEnv* ace) const { std::string beginArg = vm[arg()].as(); - Str::removeQuotes(beginArg); + + ecf::algorithm::remove_double_quotes(beginArg); if (ace->debug()) { std::cout << " BeginCmd::create arg = " << beginArg << "\n"; @@ -163,7 +164,7 @@ void BeginCmd::create(Cmd_ptr& cmd, boost::program_options::variables_map& vm, A if (!beginArg.empty()) { std::vector lineTokens; - Str::split(beginArg, lineTokens); + ecf::algorithm::split_at(lineTokens, beginArg); if (lineTokens.size() == 1) { if (lineTokens[0] == "--force") { force = true; diff --git a/libs/base/src/ecflow/base/cts/user/CFileCmd.cpp b/libs/base/src/ecflow/base/cts/user/CFileCmd.cpp index c7d744372..ad0b3f987 100644 --- a/libs/base/src/ecflow/base/cts/user/CFileCmd.cpp +++ b/libs/base/src/ecflow/base/cts/user/CFileCmd.cpp @@ -318,7 +318,7 @@ STC_Cmd_ptr CFileCmd::doHandleRequest(AbstractServer* as) const { } /// The file could get very large, hence truncate at the start - if (Str::truncate_at_start(fileContents, max_lines_)) { + if (ecf::algorithm::tail(fileContents, max_lines_)) { fileContents += MESSAGE("\n# >>>>>>>> File truncated down to " << max_lines_ << ". Truncated from the end of the file <<<<<<<<<\n"); } diff --git a/libs/base/src/ecflow/base/cts/user/CSyncCmd.hpp b/libs/base/src/ecflow/base/cts/user/CSyncCmd.hpp index 1ab1491ba..f488784d9 100644 --- a/libs/base/src/ecflow/base/cts/user/CSyncCmd.hpp +++ b/libs/base/src/ecflow/base/cts/user/CSyncCmd.hpp @@ -39,7 +39,7 @@ class CSyncCmd final : public UserCmd { int client_modify_change_no() const { return client_modify_change_no_; } int client_handle() const { return client_handle_; } - void set_client_handle(int client_handle) override { client_handle_ = client_handle; } // used by group_cmd + void set_client_handle(int client_handle) const override { client_handle_ = client_handle; } // used by group_cmd void print(std::string&) const override; std::string print_short() const override; void print_only(std::string&) const override; @@ -61,7 +61,7 @@ class CSyncCmd final : public UserCmd { STC_Cmd_ptr doHandleRequest(AbstractServer*) const override; Api api_{SYNC}; - int client_handle_{0}; + mutable int client_handle_{0}; int client_state_change_no_{0}; int client_modify_change_no_{0}; diff --git a/libs/base/src/ecflow/base/cts/user/CtsCmd.cpp b/libs/base/src/ecflow/base/cts/user/CtsCmd.cpp index 548975fe2..052739ef1 100644 --- a/libs/base/src/ecflow/base/cts/user/CtsCmd.cpp +++ b/libs/base/src/ecflow/base/cts/user/CtsCmd.cpp @@ -404,7 +404,7 @@ STC_Cmd_ptr CtsCmd::doHandleRequest(AbstractServer* as) const { case CtsCmd::STATS: { as->update_stats().stats_++; as->stats().update_for_serialisation(); - as->stats().no_of_suites_ = as->defs()->suiteVec().size(); + as->stats().no_of_suites_ = as->defs()->suites().size(); std::ostringstream ss; as->stats().show(ss); // ECFLOW-880, allow stats to be changed in server, by only returning string return PreAllocatedReply::string_cmd(ss.str()); diff --git a/libs/base/src/ecflow/base/cts/user/CtsNodeCmd.cpp b/libs/base/src/ecflow/base/cts/user/CtsNodeCmd.cpp index 729d91e72..c4e0dd597 100644 --- a/libs/base/src/ecflow/base/cts/user/CtsNodeCmd.cpp +++ b/libs/base/src/ecflow/base/cts/user/CtsNodeCmd.cpp @@ -110,7 +110,7 @@ bool CtsNodeCmd::equals(ClientToServerCmd* rhs) const { if (api_ != the_rhs->api()) { return false; } - if (absNodePath_ != the_rhs->absNodePath()) { + if (absNodePath_ != the_rhs->pathToNode()) { return false; } return UserCmd::equals(rhs); diff --git a/libs/base/src/ecflow/base/cts/user/EditScriptCmd.cpp b/libs/base/src/ecflow/base/cts/user/EditScriptCmd.cpp index d5efa28d0..df8710607 100644 --- a/libs/base/src/ecflow/base/cts/user/EditScriptCmd.cpp +++ b/libs/base/src/ecflow/base/cts/user/EditScriptCmd.cpp @@ -32,7 +32,7 @@ bool EditScriptCmd::equals(ClientToServerCmd* rhs) const { if (!the_rhs) { return false; } - if (path_to_node_ != the_rhs->path_to_node()) { + if (path_to_node_ != the_rhs->pathToNode()) { return false; } if (edit_type_ != the_rhs->edit_type()) { diff --git a/libs/base/src/ecflow/base/cts/user/ForceCmd.cpp b/libs/base/src/ecflow/base/cts/user/ForceCmd.cpp index 7155672c2..cba96f4a3 100644 --- a/libs/base/src/ecflow/base/cts/user/ForceCmd.cpp +++ b/libs/base/src/ecflow/base/cts/user/ForceCmd.cpp @@ -278,10 +278,10 @@ void ForceCmd::create(Cmd_ptr& cmd, boost::program_options::variables_map& vm, A std::string stateOrEvent; size_t options_size = options.size(); for (size_t i = 0; i < options_size; i++) { - if (Str::caseInsCompare(options[i], "recursive")) { + if (ecf::algorithm::case_insensitive_compare(options[i], "recursive")) { recursive = true; } - else if (Str::caseInsCompare(options[i], "full")) { + else if (ecf::algorithm::case_insensitive_compare(options[i], "full")) { setRepeatToLastValue = true; } else if (NState::isValid(options[i])) { diff --git a/libs/base/src/ecflow/base/cts/user/GroupCTSCmd.cpp b/libs/base/src/ecflow/base/cts/user/GroupCTSCmd.cpp index 8bf593013..4522a31a2 100644 --- a/libs/base/src/ecflow/base/cts/user/GroupCTSCmd.cpp +++ b/libs/base/src/ecflow/base/cts/user/GroupCTSCmd.cpp @@ -36,7 +36,7 @@ using namespace ecf; GroupCTSCmd::GroupCTSCmd(const std::string& cmdSeries, AbstractClientEnv* clientEnv) { std::vector individualCmdVec; - Str::split(cmdSeries, individualCmdVec, ";"); + ecf::algorithm::split_at(individualCmdVec, cmdSeries, ";"); if (individualCmdVec.empty()) { throw std::runtime_error("GroupCTSCmd::GroupCTSCmd: Please provide a list of ';' separated commands\n"); } @@ -88,7 +88,7 @@ GroupCTSCmd::GroupCTSCmd(const std::string& cmdSeries, AbstractClientEnv* client // Each sub command can have, many args std::vector subCmdArgs; - Str::split(subCmd, subCmdArgs); + ecf::algorithm::split_at(subCmdArgs, subCmd); if (replaced_spaces) { for (auto& str : subCmdArgs) { diff --git a/libs/base/src/ecflow/base/cts/user/GroupCTSCmd.hpp b/libs/base/src/ecflow/base/cts/user/GroupCTSCmd.hpp index 84220743d..d5244d677 100644 --- a/libs/base/src/ecflow/base/cts/user/GroupCTSCmd.hpp +++ b/libs/base/src/ecflow/base/cts/user/GroupCTSCmd.hpp @@ -45,7 +45,7 @@ class GroupCTSCmd final : public UserCmd { bool why_cmd(std::string&) const override; bool group_cmd() const override { return true; } - void set_client_handle(int client_handle) const; // used in group sync with client register + void set_client_handle(int client_handle) const override; // used in group sync with client register void print(std::string&) const override; std::string print_short() const override; diff --git a/libs/base/src/ecflow/base/cts/user/LoadDefsCmd.cpp b/libs/base/src/ecflow/base/cts/user/LoadDefsCmd.cpp index b5b948275..0bd824c8d 100644 --- a/libs/base/src/ecflow/base/cts/user/LoadDefsCmd.cpp +++ b/libs/base/src/ecflow/base/cts/user/LoadDefsCmd.cpp @@ -136,7 +136,7 @@ STC_Cmd_ptr LoadDefsCmd::doHandleRequest(AbstractServer* as) const { // all errors, references are not resolved. as->updateDefs(defs, force_); - LOG_ASSERT(defs->suiteVec().size() == 0, "Expected suites to be transferred to server defs"); + LOG_ASSERT(defs->suites().size() == 0, "Expected suites to be transferred to server defs"); } LOG_ASSERT(as->defs()->externs().size() == 0, "Expected server to have no externs"); diff --git a/libs/base/src/ecflow/base/cts/user/PlugCmd.cpp b/libs/base/src/ecflow/base/cts/user/PlugCmd.cpp index 41444968c..d4cc7dfbc 100644 --- a/libs/base/src/ecflow/base/cts/user/PlugCmd.cpp +++ b/libs/base/src/ecflow/base/cts/user/PlugCmd.cpp @@ -140,7 +140,7 @@ STC_Cmd_ptr PlugCmd::doHandleRequest(AbstractServer* as) const { // Dest could still be on the same server. Extract host and port // expect: host:port/suite/family/node - if (!NodePath::extractHostPort(dest_, host, port)) { + if (!ecf::node::extract_host_and_port_from_path(dest_, host, port)) { std::string errorMsg = "Plug command failed. The destination path "; errorMsg += dest_; errorMsg += " does not exist on server, and could not extract host/port from the destination path"; @@ -148,7 +148,7 @@ STC_Cmd_ptr PlugCmd::doHandleRequest(AbstractServer* as) const { } // Remove the host:port from the path - destPath = NodePath::removeHostPortFromPath(dest_); + destPath = ecf::node::remove_host_and_port_from_path(dest_); std::pair hostPortPair = as->hostPort(); if ((hostPortPair.first == host || host == "localhost") && hostPortPair.second == port) { diff --git a/libs/base/src/ecflow/base/cts/user/UserCmd.cpp b/libs/base/src/ecflow/base/cts/user/UserCmd.cpp index 903ede9c2..5af1cc898 100644 --- a/libs/base/src/ecflow/base/cts/user/UserCmd.cpp +++ b/libs/base/src/ecflow/base/cts/user/UserCmd.cpp @@ -173,7 +173,7 @@ bool UserCmd::setup_user_authentification(AbstractClientEnv& clientEnv) { void UserCmd::setup_user_authentification() { if (user_.empty()) { - setup_user_authentification(get_login_name(), Str::EMPTY()); + setup_user_authentification(get_login_name(), ecf::string_constants::empty); } } diff --git a/libs/base/src/ecflow/base/stc/SStatsCmd.cpp b/libs/base/src/ecflow/base/stc/SStatsCmd.cpp index 47fb089d4..f53ae716b 100644 --- a/libs/base/src/ecflow/base/stc/SStatsCmd.cpp +++ b/libs/base/src/ecflow/base/stc/SStatsCmd.cpp @@ -22,7 +22,7 @@ SStatsCmd::SStatsCmd(AbstractServer* as) { void SStatsCmd::init(AbstractServer* as) { as->stats().update_for_serialisation(); stats_ = as->stats(); - stats_.no_of_suites_ = as->defs()->suiteVec().size(); + stats_.no_of_suites_ = as->defs()->suites().size(); } bool SStatsCmd::equals(ServerToClientCmd* rhs) const { diff --git a/libs/base/src/ecflow/base/stc/SSuitesCmd.cpp b/libs/base/src/ecflow/base/stc/SSuitesCmd.cpp index f3450d961..df0394c5b 100644 --- a/libs/base/src/ecflow/base/stc/SSuitesCmd.cpp +++ b/libs/base/src/ecflow/base/stc/SSuitesCmd.cpp @@ -25,11 +25,11 @@ void SSuitesCmd::init(AbstractServer* as) { // This command can be re-used hence clear existing data members suites_.clear(); - const std::vector& suiteVec = as->defs()->suiteVec(); - size_t suite_vec_size = suiteVec.size(); - suites_.reserve(suite_vec_size); - for (size_t i = 0; i < suite_vec_size; i++) { - suites_.push_back(suiteVec[i]->name()); + const auto& suites = as->defs()->suites(); + size_t size = suites.size(); + suites_.reserve(size); + for (size_t i = 0; i < size; i++) { + suites_.push_back(suites[i]->name()); } } diff --git a/libs/base/src/ecflow/base/stc/SSyncCmd.cpp b/libs/base/src/ecflow/base/stc/SSyncCmd.cpp index ff9e407ec..4301744b6 100644 --- a/libs/base/src/ecflow/base/stc/SSyncCmd.cpp +++ b/libs/base/src/ecflow/base/stc/SSyncCmd.cpp @@ -237,7 +237,7 @@ void SSyncCmd::full_sync(unsigned int client_handle, AbstractServer* as) { #ifdef DEBUG_SERVER_SYNC if (the_server_defs) { - cout << ": no of suites(" << the_server_defs->suiteVec().size() << ")" << endl; + cout << ": no of suites(" << the_server_defs->suites().size() << ")" << endl; } else { cout << ": NULL defs!" << endl; diff --git a/libs/base/src/ecflow/base/stc/ServerToClientCmd.cpp b/libs/base/src/ecflow/base/stc/ServerToClientCmd.cpp index c473d66a6..fee3411d6 100644 --- a/libs/base/src/ecflow/base/stc/ServerToClientCmd.cpp +++ b/libs/base/src/ecflow/base/stc/ServerToClientCmd.cpp @@ -17,5 +17,5 @@ using namespace ecf; ServerToClientCmd::~ServerToClientCmd() = default; const std::string& ServerToClientCmd::get_string() const { - return Str::EMPTY(); + return ecf::string_constants::empty; } diff --git a/libs/base/src/ecflow/base/stc/ServerToClientCmd.hpp b/libs/base/src/ecflow/base/stc/ServerToClientCmd.hpp index 6353c4881..8f9cdc41a 100644 --- a/libs/base/src/ecflow/base/stc/ServerToClientCmd.hpp +++ b/libs/base/src/ecflow/base/stc/ServerToClientCmd.hpp @@ -28,10 +28,9 @@ class ServerToClientCmd { virtual std::string print() const = 0; virtual bool equals(ServerToClientCmd*) const { return true; } - virtual const std::string& - get_string() const; /// Used by group command, can return any string, including file contents - virtual bool ok() const { return true; } /// Used by group command - virtual bool is_returnable_in_group_cmd() const { return true; } /// used by group command + virtual const std::string& get_string() const; + virtual bool ok() const { return true; } + virtual bool is_returnable_in_group_cmd() const { return true; } virtual std::string error() const { return std::string{}; } /// Used by test diff --git a/libs/base/test/TestArchiveAndRestoreCmd.cpp b/libs/base/test/TestArchiveAndRestoreCmd.cpp index a20bcca47..d6f764f73 100644 --- a/libs/base/test/TestArchiveAndRestoreCmd.cpp +++ b/libs/base/test/TestArchiveAndRestoreCmd.cpp @@ -60,26 +60,26 @@ BOOST_AUTO_TEST_CASE(test_archive_and_restore_suite) { TestHelper::invokeRequest(&theDefs, Cmd_ptr(new PathsCmd(PathsCmd::ARCHIVE, suite->absNodePath()))); BOOST_CHECK_MESSAGE(suite->get_flag().is_set(ecf::Flag::ARCHIVED), "Archived flag not set"); BOOST_CHECK_MESSAGE(fs::exists(suite->archive_path()), "Archive path" << suite->archive_path() << " not created"); - BOOST_CHECK_MESSAGE(suite->nodeVec().empty(), "Children not removed"); + BOOST_CHECK_MESSAGE(suite->children().empty(), "Children not removed"); // cout << theDefs << "\n"; TestHelper::invokeRequest(&theDefs, Cmd_ptr(new PathsCmd(PathsCmd::RESTORE, suite->absNodePath()))); BOOST_CHECK_MESSAGE(suite->get_flag().is_set(ecf::Flag::RESTORED), "Restored flag not set"); BOOST_CHECK_MESSAGE(!suite->get_flag().is_set(ecf::Flag::ARCHIVED), "Archived flag not *cleared"); BOOST_CHECK_MESSAGE(!fs::exists(suite->archive_path()), "Archived file has not been deleted after restore"); - BOOST_CHECK_MESSAGE(!suite->nodeVec().empty(), "Children are not restored"); + BOOST_CHECK_MESSAGE(!suite->children().empty(), "Children are not restored"); // Archive again but restore via begin TestHelper::invokeRequest(&theDefs, Cmd_ptr(new PathsCmd(PathsCmd::ARCHIVE, suite->absNodePath()))); BOOST_CHECK_MESSAGE(suite->get_flag().is_set(ecf::Flag::ARCHIVED), "Archived flag not set"); BOOST_CHECK_MESSAGE(fs::exists(suite->archive_path()), "Archive path" << suite->archive_path() << " not created"); - BOOST_CHECK_MESSAGE(suite->nodeVec().empty(), "Children not removed"); + BOOST_CHECK_MESSAGE(suite->children().empty(), "Children not removed"); TestHelper::invokeRequest(&theDefs, Cmd_ptr(new BeginCmd(suite->absNodePath(), true))); // nodes will be active BOOST_CHECK_MESSAGE(!suite->get_flag().is_set(ecf::Flag::ARCHIVED), "Archived flag not *cleared"); BOOST_CHECK_MESSAGE(!suite->get_flag().is_set(ecf::Flag::RESTORED), "restored flag not *cleared"); BOOST_CHECK_MESSAGE(!fs::exists(suite->archive_path()), "Archived file has not been deleted after restore"); - BOOST_CHECK_MESSAGE(!suite->nodeVec().empty(), "Children are not restored"); + BOOST_CHECK_MESSAGE(!suite->children().empty(), "Children are not restored"); // PrintStyle::setStyle(PrintStyle::MIGRATE); // cout << theDefs << "\n"; @@ -106,26 +106,26 @@ BOOST_AUTO_TEST_CASE(test_archive_and_restore_family) { TestHelper::invokeRequest(&theDefs, Cmd_ptr(new PathsCmd(PathsCmd::ARCHIVE, f3->absNodePath()))); BOOST_CHECK_MESSAGE(f3->get_flag().is_set(ecf::Flag::ARCHIVED), "Archived flag not set"); BOOST_CHECK_MESSAGE(fs::exists(f3->archive_path()), "Archive path" << f3->archive_path() << " not created"); - BOOST_CHECK_MESSAGE(f3->nodeVec().empty(), "Children not removed"); + BOOST_CHECK_MESSAGE(f3->children().empty(), "Children not removed"); TestHelper::invokeRequest(&theDefs, Cmd_ptr(new PathsCmd(PathsCmd::RESTORE, f3->absNodePath()))); BOOST_CHECK_MESSAGE(f3->get_flag().is_set(ecf::Flag::RESTORED), "restored flag not set"); BOOST_CHECK_MESSAGE(!f3->get_flag().is_set(ecf::Flag::ARCHIVED), "Archived flag not *cleared"); BOOST_CHECK_MESSAGE(!fs::exists(f3->archive_path()), "Archived file has not been deleted after restore"); - BOOST_CHECK_MESSAGE(!f3->nodeVec().empty(), "Children are not restored"); + BOOST_CHECK_MESSAGE(!f3->children().empty(), "Children are not restored"); // Archive again but restore via begin TestHelper::invokeRequest(&theDefs, Cmd_ptr(new PathsCmd(PathsCmd::ARCHIVE, f3->absNodePath()))); BOOST_CHECK_MESSAGE(f3->get_flag().is_set(ecf::Flag::ARCHIVED), "Archived flag not set"); BOOST_CHECK_MESSAGE(!f3->get_flag().is_set(ecf::Flag::RESTORED), "Restored flag not cleared"); BOOST_CHECK_MESSAGE(fs::exists(f3->archive_path()), "Archive path" << suite->archive_path() << " not created"); - BOOST_CHECK_MESSAGE(f3->nodeVec().empty(), "Children not removed"); + BOOST_CHECK_MESSAGE(f3->children().empty(), "Children not removed"); TestHelper::invokeRequest(&theDefs, Cmd_ptr(new BeginCmd(suite->absNodePath(), true))); // nodes will be active BOOST_CHECK_MESSAGE(!f3->get_flag().is_set(ecf::Flag::ARCHIVED), "Archived flag not *cleared"); BOOST_CHECK_MESSAGE(!f3->get_flag().is_set(ecf::Flag::RESTORED), "Restored flag not *cleared"); BOOST_CHECK_MESSAGE(!fs::exists(f3->archive_path()), "Archived file has not been deleted after restore"); - BOOST_CHECK_MESSAGE(!f3->nodeVec().empty(), "Children are not restored"); + BOOST_CHECK_MESSAGE(!f3->children().empty(), "Children are not restored"); // PrintStyle::setStyle(PrintStyle::MIGRATE); // cout << theDefs << "\n"; @@ -190,7 +190,7 @@ BOOST_AUTO_TEST_CASE(test_archive_and_restore_all) { BOOST_CHECK_MESSAGE(nc->get_flag().is_set(ecf::Flag::ARCHIVED), "Archived flag not set " << nc->absNodePath()); BOOST_CHECK_MESSAGE(fs::exists(nc->archive_path()), "Archive path" << nc->archive_path() << " not created"); - BOOST_CHECK_MESSAGE(nc->nodeVec().empty(), "Children not removed " << nc->absNodePath()); + BOOST_CHECK_MESSAGE(nc->children().empty(), "Children not removed " << nc->absNodePath()); TestHelper::invokeRequest(&theDefs, Cmd_ptr(new PathsCmd(PathsCmd::RESTORE, i))); node = theDefs.findAbsNode(i); @@ -202,7 +202,7 @@ BOOST_AUTO_TEST_CASE(test_archive_and_restore_all) { "Archived flag not *cleared " << nc->absNodePath()); BOOST_CHECK_MESSAGE(!fs::exists(nc->archive_path()), "Archived file has not been deleted after restore " << nc->absNodePath()); - BOOST_CHECK_MESSAGE(!nc->nodeVec().empty(), "Children are not restored " << nc->absNodePath()); + BOOST_CHECK_MESSAGE(!nc->children().empty(), "Children are not restored " << nc->absNodePath()); } { // Archive again but restore via re-queue @@ -215,7 +215,7 @@ BOOST_AUTO_TEST_CASE(test_archive_and_restore_all) { BOOST_CHECK_MESSAGE(!nc->get_flag().is_set(ecf::Flag::RESTORED), "Restored flag should be clear " << nc->absNodePath()); BOOST_CHECK_MESSAGE(fs::exists(nc->archive_path()), "Archive path" << nc->archive_path() << " not created"); - BOOST_CHECK_MESSAGE(nc->nodeVec().empty(), "Children not removed " << nc->absNodePath()); + BOOST_CHECK_MESSAGE(nc->children().empty(), "Children not removed " << nc->absNodePath()); TestHelper::invokeRequest(&theDefs, Cmd_ptr(new RequeueNodeCmd(i, RequeueNodeCmd::FORCE))); node = theDefs.findAbsNode(i); @@ -227,7 +227,7 @@ BOOST_AUTO_TEST_CASE(test_archive_and_restore_all) { "Archived flag not *cleared " << nc->absNodePath()); BOOST_CHECK_MESSAGE(!fs::exists(nc->archive_path()), "Archived file has not been deleted after restore " << nc->absNodePath()); - BOOST_CHECK_MESSAGE(!nc->nodeVec().empty(), "Children are not restored " << nc->absNodePath()); + BOOST_CHECK_MESSAGE(!nc->children().empty(), "Children are not restored " << nc->absNodePath()); } } // cout << theDefs << "\n"; @@ -263,14 +263,14 @@ BOOST_AUTO_TEST_CASE(test_archive_and_restore_overlap) { Cmd_ptr(new PathsCmd(PathsCmd::ARCHIVE, paths))); // family_ptr f1 removed from the suite BOOST_CHECK_MESSAGE(suite->get_flag().is_set(ecf::Flag::ARCHIVED), "Archived flag not set"); BOOST_CHECK_MESSAGE(fs::exists(suite->archive_path()), "Archive path " << suite->archive_path() << " not created"); - BOOST_CHECK_MESSAGE(suite->nodeVec().empty(), "Children not removed"); + BOOST_CHECK_MESSAGE(suite->children().empty(), "Children not removed"); BOOST_CHECK_MESSAGE(!theDefs.findAbsNode(f1_abs_node_path), "f1 should have been removed"); TestHelper::invokeRequest(&theDefs, Cmd_ptr(new PathsCmd(PathsCmd::RESTORE, suite->absNodePath()))); BOOST_CHECK_MESSAGE(suite->get_flag().is_set(ecf::Flag::RESTORED), "Restored flag not set"); BOOST_CHECK_MESSAGE(!suite->get_flag().is_set(ecf::Flag::ARCHIVED), "Archived flag not *cleared"); BOOST_CHECK_MESSAGE(!fs::exists(suite->archive_path()), "Archived file has not been deleted after restore"); - BOOST_CHECK_MESSAGE(!suite->nodeVec().empty(), "Children are not restored"); + BOOST_CHECK_MESSAGE(!suite->children().empty(), "Children are not restored"); node_ptr f1 = theDefs.findAbsNode(f1_abs_node_path); BOOST_CHECK_MESSAGE(f1, "f1 should have been restored"); BOOST_CHECK_MESSAGE(!f1->get_flag().is_set(ecf::Flag::RESTORED), "Family f1 should not have restored flag set"); @@ -301,14 +301,14 @@ BOOST_AUTO_TEST_CASE(test_archive_and_delete_suite) { BOOST_CHECK_MESSAGE(family->has_archive(), "Archived flag not set on family"); std::string family_archive_path = family->archive_path(); BOOST_CHECK_MESSAGE(fs::exists(family_archive_path), "Archive path" << family->archive_path() << " not created"); - BOOST_CHECK_MESSAGE(family->nodeVec().empty(), "Children of family not removed after archive"); + BOOST_CHECK_MESSAGE(family->children().empty(), "Children of family not removed after archive"); TestHelper::invokeRequest(&theDefs, Cmd_ptr(new PathsCmd(PathsCmd::ARCHIVE, suite->absNodePath()))); BOOST_CHECK_MESSAGE(suite->get_flag().is_set(ecf::Flag::ARCHIVED), "Archived flag not set on suite"); BOOST_CHECK_MESSAGE(suite->has_archive(), "Archived flag not set on family"); std::string suite_archive_path = suite->archive_path(); BOOST_CHECK_MESSAGE(fs::exists(suite_archive_path), "Archive path" << suite->archive_path() << " not created"); - BOOST_CHECK_MESSAGE(suite->nodeVec().empty(), "Children of suite not removed after archive"); + BOOST_CHECK_MESSAGE(suite->children().empty(), "Children of suite not removed after archive"); TestHelper::invokeRequest(&theDefs, Cmd_ptr(new DeleteCmd(suite->absNodePath()))); BOOST_CHECK_MESSAGE(!fs::exists(suite_archive_path), "Suite Archived file not removed after DeleteCmd"); diff --git a/libs/base/test/TestClientHandleCmd.cpp b/libs/base/test/TestClientHandleCmd.cpp index d0f81370e..80acbbe45 100644 --- a/libs/base/test/TestClientHandleCmd.cpp +++ b/libs/base/test/TestClientHandleCmd.cpp @@ -258,7 +258,7 @@ BOOST_AUTO_TEST_CASE(is_able_to_handle_cmd_add_remove) { defs_ptr created_defs = defs->client_suite_mgr().create_defs(1, defs); // clear the handle change BOOST_CHECK_MESSAGE(created_defs, "Expected defs to be created"); - BOOST_CHECK_MESSAGE(created_defs->suiteVec().empty(), "Expected no suites"); + BOOST_CHECK_MESSAGE(created_defs->suites().empty(), "Expected no suites"); BOOST_CHECK_MESSAGE(!defs->client_suite_mgr().handle_changed(1), "Expected handle changed to be cleared after create_defs()"); } @@ -280,7 +280,7 @@ BOOST_AUTO_TEST_CASE(is_able_to_handle_cmd_add_remove) { BOOST_CHECK_MESSAGE(created_defs, "Expected defs to be created"); BOOST_CHECK_MESSAGE(created_defs.get() == defs.get(), "When *ALL* suites registered, the returned defs should be the same"); - BOOST_CHECK_MESSAGE(created_defs->suiteVec().size() == suite_names.size(), "Not all suites created"); + BOOST_CHECK_MESSAGE(created_defs->suites().size() == suite_names.size(), "Not all suites created"); } // Now remove the suites from the handle @@ -299,7 +299,7 @@ BOOST_AUTO_TEST_CASE(is_able_to_handle_cmd_add_remove) { defs_ptr created_defs = defs->client_suite_mgr().create_defs(1, defs); // clear the handle change BOOST_CHECK_MESSAGE(created_defs, "Expected defs to be created"); - BOOST_CHECK_MESSAGE(created_defs->suiteVec().empty(), "Expected no suites"); + BOOST_CHECK_MESSAGE(created_defs->suites().empty(), "Expected no suites"); BOOST_CHECK_MESSAGE(!defs->client_suite_mgr().handle_changed(1), "Expected handle changed to be cleared after create_defs()"); } @@ -307,9 +307,9 @@ BOOST_AUTO_TEST_CASE(is_able_to_handle_cmd_add_remove) { static bool check_ordering(Defs& defs) { // make sure order of suites in handles is the same as server order - const std::vector& suite_vec = defs.suiteVec(); + const auto& suites = defs.suites(); std::vector suite_names; - for (const auto& i : suite_vec) { + for (const auto& i : suites) { suite_names.push_back(i->name()); } diff --git a/libs/base/test/TestClientOptionsParser.cpp b/libs/base/test/TestClientOptionsParser.cpp new file mode 100644 index 000000000..bbab16aba --- /dev/null +++ b/libs/base/test/TestClientOptionsParser.cpp @@ -0,0 +1,171 @@ +/* + * 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 +#include + +#include + +#include "ecflow/base/ClientOptionsParser.hpp" +#include "ecflow/test/scaffold/Naming.hpp" + +BOOST_AUTO_TEST_SUITE(U_Base) + +BOOST_AUTO_TEST_SUITE(T_ClientOptionsParser) + +namespace { + +std::vector parse_alter(std::vector args) { + ecf::ClientOptionsParser parser; + auto options = parser(args); + BOOST_REQUIRE_EQUAL(options.size(), 1u); + BOOST_REQUIRE_EQUAL(options[0].string_key, "alter"); + BOOST_REQUIRE_MESSAGE(args.empty(), "expected all args to be consumed, but " << args.size() << " remain"); + return options[0].value; +} + +} // namespace + +BOOST_AUTO_TEST_SUITE(AddInLimit) + +BOOST_AUTO_TEST_CASE(add_inlimit_with_path_prefixed_limit_collects_all_tokens) { + ECF_NAME_THIS_TEST(); + + const auto values = parse_alter({"--alter", "add", "inlimit", "/s/limits:cpu", "5", "/s/f/t"}); + + const std::vector expected = {"add", "inlimit", "/s/limits:cpu", "5", "/s/f/t"}; + BOOST_CHECK_EQUAL_COLLECTIONS(values.begin(), values.end(), expected.begin(), expected.end()); +} + +BOOST_AUTO_TEST_CASE(add_inlimit_with_path_prefixed_limit_and_flag_value) { + ECF_NAME_THIS_TEST(); + + const auto values = parse_alter({"--alter", "add", "inlimit", "/s/limits:cpu", "1 -s", "/s/f/t"}); + + const std::vector expected = {"add", "inlimit", "/s/limits:cpu", "1 -s", "/s/f/t"}; + BOOST_CHECK_EQUAL_COLLECTIONS(values.begin(), values.end(), expected.begin(), expected.end()); +} + +BOOST_AUTO_TEST_CASE(add_inlimit_with_path_prefixed_limit_and_node_flag_value) { + ECF_NAME_THIS_TEST(); + + const auto values = parse_alter({"--alter", "add", "inlimit", "/s/limits:cpu", "-n 5", "/s/f/t"}); + + const std::vector expected = {"add", "inlimit", "/s/limits:cpu", "-n 5", "/s/f/t"}; + BOOST_CHECK_EQUAL_COLLECTIONS(values.begin(), values.end(), expected.begin(), expected.end()); +} + +BOOST_AUTO_TEST_CASE(add_inlimit_without_path_prefix_collects_all_tokens) { + ECF_NAME_THIS_TEST(); + + const auto values = parse_alter({"--alter", "add", "inlimit", "cpu", "-s 5", "/s/f/t"}); + + const std::vector expected = {"add", "inlimit", "cpu", "-s 5", "/s/f/t"}; + BOOST_CHECK_EQUAL_COLLECTIONS(values.begin(), values.end(), expected.begin(), expected.end()); +} + +BOOST_AUTO_TEST_CASE(add_inlimit_with_default_token_count) { + ECF_NAME_THIS_TEST(); + + const auto values = parse_alter({"--alter", "add", "inlimit", "/s/limits:cpu", "/s/f/t"}); + + const std::vector expected = {"add", "inlimit", "/s/limits:cpu", "/s/f/t"}; + BOOST_CHECK_EQUAL_COLLECTIONS(values.begin(), values.end(), expected.begin(), expected.end()); +} + +BOOST_AUTO_TEST_CASE(add_inlimit_with_multiple_target_nodes) { + ECF_NAME_THIS_TEST(); + + const auto values = parse_alter({"--alter", "add", "inlimit", "/s/limits:cpu", "2", "/s/f/t1", "/s/f/t2"}); + + const std::vector expected = {"add", "inlimit", "/s/limits:cpu", "2", "/s/f/t1", "/s/f/t2"}; + BOOST_CHECK_EQUAL_COLLECTIONS(values.begin(), values.end(), expected.begin(), expected.end()); +} + +BOOST_AUTO_TEST_CASE(add_inlimit_using_equals_form) { + ECF_NAME_THIS_TEST(); + + const auto values = parse_alter({"--alter=add", "inlimit", "/s/limits:cpu", "5", "/s/f/t"}); + + const std::vector expected = {"add", "inlimit", "/s/limits:cpu", "5", "/s/f/t"}; + BOOST_CHECK_EQUAL_COLLECTIONS(values.begin(), values.end(), expected.begin(), expected.end()); +} + +BOOST_AUTO_TEST_SUITE_END() // AddInLimit + +BOOST_AUTO_TEST_SUITE(DeleteInLimit) + +BOOST_AUTO_TEST_CASE(delete_inlimit_unnamed_collects_all_tokens) { + ECF_NAME_THIS_TEST(); + + const auto values = parse_alter({"--alter", "delete", "inlimit", "/s/f/t"}); + + const std::vector expected = {"delete", "inlimit", "/s/f/t"}; + BOOST_CHECK_EQUAL_COLLECTIONS(values.begin(), values.end(), expected.begin(), expected.end()); +} + +BOOST_AUTO_TEST_CASE(delete_inlimit_by_plain_name_collects_all_tokens) { + ECF_NAME_THIS_TEST(); + + const auto values = parse_alter({"--alter", "delete", "inlimit", "cpu", "/s/f/t"}); + + const std::vector expected = {"delete", "inlimit", "cpu", "/s/f/t"}; + BOOST_CHECK_EQUAL_COLLECTIONS(values.begin(), values.end(), expected.begin(), expected.end()); +} + +BOOST_AUTO_TEST_CASE(delete_inlimit_by_path_prefixed_name_collects_all_tokens) { + ECF_NAME_THIS_TEST(); + + const auto values = parse_alter({"--alter", "delete", "inlimit", "/s/limits:cpu", "/s/f/t"}); + + const std::vector expected = {"delete", "inlimit", "/s/limits:cpu", "/s/f/t"}; + BOOST_CHECK_EQUAL_COLLECTIONS(values.begin(), values.end(), expected.begin(), expected.end()); +} + +BOOST_AUTO_TEST_CASE(delete_inlimit_unnamed_multiple_nodes) { + ECF_NAME_THIS_TEST(); + + const auto values = parse_alter({"--alter", "delete", "inlimit", "/s/f/t1", "/s/f/t2"}); + + const std::vector expected = {"delete", "inlimit", "/s/f/t1", "/s/f/t2"}; + BOOST_CHECK_EQUAL_COLLECTIONS(values.begin(), values.end(), expected.begin(), expected.end()); +} + +BOOST_AUTO_TEST_SUITE_END() + +BOOST_AUTO_TEST_SUITE(AddLabel) + +BOOST_AUTO_TEST_CASE(add_label_selector_does_not_interfere) { + ECF_NAME_THIS_TEST(); + + const auto values = parse_alter({"--alter", "add", "label", "my_label", "my_value", "/s/f/t"}); + + const std::vector expected = {"add", "label", "my_label", "my_value", "/s/f/t"}; + BOOST_CHECK_EQUAL_COLLECTIONS(values.begin(), values.end(), expected.begin(), expected.end()); +} + +BOOST_AUTO_TEST_SUITE_END() + +BOOST_AUTO_TEST_SUITE(DeleteLabel) + +BOOST_AUTO_TEST_CASE(delete_label_selector_does_not_interfere) { + ECF_NAME_THIS_TEST(); + + const auto values = parse_alter({"--alter", "delete", "label", "my_label", "/s/f/t"}); + + const std::vector expected = {"delete", "label", "my_label", "/s/f/t"}; + BOOST_CHECK_EQUAL_COLLECTIONS(values.begin(), values.end(), expected.begin(), expected.end()); +} + +BOOST_AUTO_TEST_SUITE_END() + +BOOST_AUTO_TEST_SUITE_END() // T_ClientOptionsParser + +BOOST_AUTO_TEST_SUITE_END() // U_Base diff --git a/libs/base/test/TestDeleteNodeCmd.cpp b/libs/base/test/TestDeleteNodeCmd.cpp index b467ba513..69d220335 100644 --- a/libs/base/test/TestDeleteNodeCmd.cpp +++ b/libs/base/test/TestDeleteNodeCmd.cpp @@ -49,7 +49,8 @@ BOOST_AUTO_TEST_CASE(test_delete_node_cmd) { paths.push_back(alias->absNodePath()); } - size_t edit_history_size_before = fixtureDef.defsfile_.get_edit_history(Str::ROOT_PATH()).size(); + size_t edit_history_size_before = + fixtureDef.defsfile_.get_edit_history(ecf::string_constants::root_path).size(); BOOST_CHECK_MESSAGE(!paths.empty(), "Expected paths to be specified, *OTHERWISE* we delete all nodes"); DeleteCmd cmd(paths); cmd.setup_user_authentification(); @@ -57,8 +58,9 @@ BOOST_AUTO_TEST_CASE(test_delete_node_cmd) { BOOST_CHECK_MESSAGE(returnCmd->ok(), "Failed to delete aliases"); { // ECFLOW-434 - const std::vector& edit_history = fixtureDef.defsfile_.get_edit_history(Str::ROOT_PATH()); - size_t edit_history_size_after = edit_history_size_before + paths.size(); + const std::vector& edit_history = + fixtureDef.defsfile_.get_edit_history(ecf::string_constants::root_path); + size_t edit_history_size_after = edit_history_size_before + paths.size(); if (edit_history_size_after > Defs::max_edit_history_size_per_node()) { edit_history_size_after = Defs::max_edit_history_size_per_node(); } @@ -84,14 +86,16 @@ BOOST_AUTO_TEST_CASE(test_delete_node_cmd) { } BOOST_CHECK_MESSAGE(!paths.empty(), "Expected paths to be specified, *OTHERWISE* we delete all nodes"); - size_t edit_history_size_before = fixtureDef.defsfile_.get_edit_history(Str::ROOT_PATH()).size(); + size_t edit_history_size_before = + fixtureDef.defsfile_.get_edit_history(ecf::string_constants::root_path).size(); DeleteCmd cmd(paths); cmd.setup_user_authentification(); STC_Cmd_ptr returnCmd = cmd.handleRequest(&mockServer); BOOST_CHECK_MESSAGE(returnCmd->ok(), "Failed to delete tasks"); { - const std::vector& edit_history = fixtureDef.defsfile_.get_edit_history(Str::ROOT_PATH()); - size_t edit_history_size_after = edit_history_size_before + paths.size(); + const std::vector& edit_history = + fixtureDef.defsfile_.get_edit_history(ecf::string_constants::root_path); + size_t edit_history_size_after = edit_history_size_before + paths.size(); if (edit_history_size_after > Defs::max_edit_history_size_per_node()) { edit_history_size_after = Defs::max_edit_history_size_per_node(); } @@ -115,9 +119,9 @@ BOOST_AUTO_TEST_CASE(test_delete_node_cmd) { // Delete all Suites { - std::vector vec = fixtureDef.defsfile_.suiteVec(); - BOOST_CHECK_MESSAGE(vec.size() > 0, "Expected > 0 Suites but found " << vec.size()); - for (suite_ptr s : vec) { + auto suites = fixtureDef.defsfile_.suites(); + BOOST_CHECK_MESSAGE(suites.size() > 0, "Expected > 0 Suites but found " << suites.size()); + for (auto s : suites) { { // Delete all Families // ********************************************************************************************* @@ -129,13 +133,14 @@ BOOST_AUTO_TEST_CASE(test_delete_node_cmd) { // for(family_ptr f: s->familyVec()) { // As with this will invalidate the iterators. std::vector paths; - paths.reserve(vec.size()); + paths.reserve(suites.size()); for (family_ptr f : familyVec) { paths.push_back(f->absNodePath()); } if (!paths.empty()) { - size_t edit_history_size_before = fixtureDef.defsfile_.get_edit_history(Str::ROOT_PATH()).size(); + size_t edit_history_size_before = + fixtureDef.defsfile_.get_edit_history(ecf::string_constants::root_path).size(); DeleteCmd cmd(paths); cmd.setup_user_authentification(); STC_Cmd_ptr returnCmd = cmd.handleRequest(&mockServer); @@ -144,7 +149,7 @@ BOOST_AUTO_TEST_CASE(test_delete_node_cmd) { "Expected all Families to be deleted but found " << s->familyVec().size()); { const std::vector& edit_history = - fixtureDef.defsfile_.get_edit_history(Str::ROOT_PATH()); + fixtureDef.defsfile_.get_edit_history(ecf::string_constants::root_path); size_t edit_history_size_after = edit_history_size_before + paths.size(); if (edit_history_size_after > Defs::max_edit_history_size_per_node()) { edit_history_size_after = Defs::max_edit_history_size_per_node(); @@ -157,15 +162,17 @@ BOOST_AUTO_TEST_CASE(test_delete_node_cmd) { } // delete the suite - size_t edit_history_size_before = fixtureDef.defsfile_.get_edit_history(Str::ROOT_PATH()).size(); - std::string absNodePath = s->absNodePath(); + size_t edit_history_size_before = + fixtureDef.defsfile_.get_edit_history(ecf::string_constants::root_path).size(); + std::string absNodePath = s->absNodePath(); DeleteCmd cmd(absNodePath); cmd.setup_user_authentification(); STC_Cmd_ptr returnCmd = cmd.handleRequest(&mockServer); BOOST_CHECK_MESSAGE(returnCmd->ok(), "Failed to delete suite at path " << absNodePath); { - const std::vector& edit_history = fixtureDef.defsfile_.get_edit_history(Str::ROOT_PATH()); - size_t edit_history_size_after = edit_history_size_before + 1; + const std::vector& edit_history = + fixtureDef.defsfile_.get_edit_history(ecf::string_constants::root_path); + size_t edit_history_size_after = edit_history_size_before + 1; if (edit_history_size_after > Defs::max_edit_history_size_per_node()) { edit_history_size_after = Defs::max_edit_history_size_per_node(); } @@ -175,8 +182,8 @@ BOOST_AUTO_TEST_CASE(test_delete_node_cmd) { } } - BOOST_REQUIRE_MESSAGE(fixtureDef.defsfile_.suiteVec().empty(), - "Expected all Suites to be deleted but found " << fixtureDef.defsfile_.suiteVec().size()); + BOOST_REQUIRE_MESSAGE(fixtureDef.defsfile_.suites().empty(), + "Expected all Suites to be deleted but found " << fixtureDef.defsfile_.suites().size()); } { @@ -200,8 +207,9 @@ BOOST_AUTO_TEST_CASE(test_delete_node_edit_history_ECFLOW_1684) { // suspend all suites and tasks, then check we have some edit history { - size_t edit_history_root_size_before = fixtureDef.defsfile_.get_edit_history(Str::ROOT_PATH()).size(); - size_t edit_history_before = fixtureDef.defsfile_.get_edit_history().size(); + size_t edit_history_root_size_before = + fixtureDef.defsfile_.get_edit_history(ecf::string_constants::root_path).size(); + size_t edit_history_before = fixtureDef.defsfile_.get_edit_history().size(); BOOST_CHECK_MESSAGE(edit_history_root_size_before == 0, "Expected edit_history_root_size_before == 0 but found " << edit_history_root_size_before); BOOST_CHECK_MESSAGE(edit_history_before == 0, @@ -216,9 +224,9 @@ BOOST_AUTO_TEST_CASE(test_delete_node_edit_history_ECFLOW_1684) { } BOOST_CHECK_MESSAGE(!paths.empty(), "Expected paths to be specified, *OTHERWISE* we delete all nodes"); - std::vector suite_vec = fixtureDef.defsfile_.suiteVec(); - for (suite_ptr s : suite_vec) { - paths.push_back(s->absNodePath()); + auto suites = fixtureDef.defsfile_.suites(); + for (suite_ptr suite : suites) { + paths.push_back(suite->absNodePath()); } PathsCmd cmd(PathsCmd::SUSPEND, paths); @@ -226,8 +234,9 @@ BOOST_AUTO_TEST_CASE(test_delete_node_edit_history_ECFLOW_1684) { STC_Cmd_ptr returnCmd = cmd.handleRequest(&mockServer); BOOST_CHECK_MESSAGE(returnCmd->ok(), "Failed to suspend tasks"); { - size_t edit_history_root_size_after = fixtureDef.defsfile_.get_edit_history(Str::ROOT_PATH()).size(); - size_t edit_history_after = fixtureDef.defsfile_.get_edit_history().size(); + size_t edit_history_root_size_after = + fixtureDef.defsfile_.get_edit_history(ecf::string_constants::root_path).size(); + size_t edit_history_after = fixtureDef.defsfile_.get_edit_history().size(); BOOST_CHECK_MESSAGE(edit_history_root_size_after == 0, "Expected edit_history_root_size_after == 0 but found " @@ -240,24 +249,25 @@ BOOST_AUTO_TEST_CASE(test_delete_node_edit_history_ECFLOW_1684) { // Delete all Suites, this should delete edit history associated with suites and child tasks { - std::vector vec = fixtureDef.defsfile_.suiteVec(); - BOOST_CHECK_MESSAGE(vec.size() > 0, "Expected > 0 Suites but found " << vec.size()); - for (suite_ptr s : vec) { - std::string absNodePath = s->absNodePath(); + auto suites = fixtureDef.defsfile_.suites(); + BOOST_CHECK_MESSAGE(suites.size() > 0, "Expected > 0 Suites but found " << suites.size()); + for (suite_ptr suite : suites) { + std::string absNodePath = suite->absNodePath(); DeleteCmd cmd(absNodePath); cmd.setup_user_authentification(); STC_Cmd_ptr returnCmd = cmd.handleRequest(&mockServer); BOOST_CHECK_MESSAGE(returnCmd->ok(), "Failed to delete suite at path " << absNodePath); } - BOOST_REQUIRE_MESSAGE(fixtureDef.defsfile_.suiteVec().empty(), - "Expected all Suites to be deleted but found " << fixtureDef.defsfile_.suiteVec().size()); + BOOST_REQUIRE_MESSAGE(fixtureDef.defsfile_.suites().empty(), + "Expected all Suites to be deleted but found " << fixtureDef.defsfile_.suites().size()); - size_t edit_history_root_size_after = fixtureDef.defsfile_.get_edit_history(Str::ROOT_PATH()).size(); - size_t edit_history_after = fixtureDef.defsfile_.get_edit_history().size(); + size_t edit_history_root_size_after = + fixtureDef.defsfile_.get_edit_history(ecf::string_constants::root_path).size(); + size_t edit_history_after = fixtureDef.defsfile_.get_edit_history().size(); - BOOST_CHECK_MESSAGE(edit_history_root_size_after == vec.size(), + BOOST_CHECK_MESSAGE(edit_history_root_size_after == suites.size(), "Expected edit_history size of root node to be same as number of deleted suites(" - << vec.size() << ") but found " << edit_history_root_size_after); + << suites.size() << ") but found " << edit_history_root_size_after); BOOST_CHECK_MESSAGE(edit_history_after == 1, "Expected edit_history_after == 1 to *ONLY* have root node, but found " << edit_history_after << "\n" diff --git a/libs/base/test/TestPermissions.cpp b/libs/base/test/TestPermissions.cpp index ad2e4b4e3..7681af8d6 100644 --- a/libs/base/test/TestPermissions.cpp +++ b/libs/base/test/TestPermissions.cpp @@ -21,6 +21,8 @@ BOOST_AUTO_TEST_SUITE(T_Perms) BOOST_AUTO_TEST_CASE(test_file_automatic_name_exists) { ECF_NAME_THIS_TEST(); + using namespace ecf::test::scaffold; + std::vector paths; for (int i = 0; i < 10; i++) { WithTestFile file; @@ -38,6 +40,8 @@ BOOST_AUTO_TEST_CASE(test_file_automatic_name_exists) { BOOST_AUTO_TEST_CASE(test_file_automatic_prefix_name_exists) { ECF_NAME_THIS_TEST(); + using namespace ecf::test::scaffold; + std::vector paths; for (int i = 0; i < 10; i++) { auto prefix = "test_file_" + std::to_string(i); diff --git a/libs/base/test/TestSSyncCmd.cpp b/libs/base/test/TestSSyncCmd.cpp index 98ed1ddd0..5f96243d8 100644 --- a/libs/base/test/TestSSyncCmd.cpp +++ b/libs/base/test/TestSSyncCmd.cpp @@ -265,7 +265,7 @@ void remove_a_family(defs_ptr defs) { } void change_clock_gain(defs_ptr defs) { - for (suite_ptr suite : defs->suiteVec()) { + for (suite_ptr suite : defs->suites()) { if (suite->clockAttr().get()) { SuiteChanged changed(suite); suite->changeClockGain("100001"); @@ -274,7 +274,7 @@ void change_clock_gain(defs_ptr defs) { } void change_clock_type_to_real(defs_ptr defs) { - for (suite_ptr suite : defs->suiteVec()) { + for (suite_ptr suite : defs->suites()) { if (suite->clockAttr().get()) { SuiteChanged changed(suite); suite->changeClockType("real"); @@ -283,7 +283,7 @@ void change_clock_type_to_real(defs_ptr defs) { } void change_clock_type_to_hybrid(defs_ptr defs) { - for (suite_ptr suite : defs->suiteVec()) { + for (suite_ptr suite : defs->suites()) { if (suite->clockAttr().get()) { SuiteChanged changed(suite); suite->changeClockType("hybrid"); @@ -292,7 +292,7 @@ void change_clock_type_to_hybrid(defs_ptr defs) { } void change_clock_date(defs_ptr defs) { - for (suite_ptr suite : defs->suiteVec()) { + for (suite_ptr suite : defs->suites()) { if (suite->clockAttr().get()) { SuiteChanged changed(suite); suite->changeClockDate("1.1.2001"); @@ -301,7 +301,7 @@ void change_clock_date(defs_ptr defs) { } void change_clock_sync(defs_ptr defs) { - for (suite_ptr suite : defs->suiteVec()) { + for (suite_ptr suite : defs->suites()) { if (suite->clockAttr().get()) { SuiteChanged changed(suite); suite->changeClockSync(); @@ -314,25 +314,25 @@ void change_clock_sync(defs_ptr defs) { /// max value, however because we had, changed value as well it got masked. void change_limit_max(defs_ptr defs) { - for (suite_ptr s : defs->suiteVec()) { - std::vector theLimits = s->limits(); + for (suite_ptr suite : defs->suites()) { + std::vector theLimits = suite->limits(); for (limit_ptr l : theLimits) { // std::cout << "found " << l->toString() << "\n"; - TestHelper::invokeRequest(defs.get(), - Cmd_ptr(new AlterCmd(s->absNodePath(), AlterCmd::LIMIT_MAX, l->name(), "90"))); - limit_ptr v = s->find_limit(l->name()); + TestHelper::invokeRequest( + defs.get(), Cmd_ptr(new AlterCmd(suite->absNodePath(), AlterCmd::LIMIT_MAX, l->name(), "90"))); + limit_ptr v = suite->find_limit(l->name()); BOOST_CHECK_MESSAGE(v.get() && v->theLimit() == 90, "expected to find limit with max value of 90"); } } } void change_limit_value(defs_ptr defs) { - for (suite_ptr s : defs->suiteVec()) { - std::vector theLimits = s->limits(); + for (suite_ptr suite : defs->suites()) { + std::vector theLimits = suite->limits(); for (limit_ptr l : theLimits) { - TestHelper::invokeRequest(defs.get(), - Cmd_ptr(new AlterCmd(s->absNodePath(), AlterCmd::LIMIT_VAL, l->name(), "33"))); - limit_ptr v = s->find_limit(l->name()); + TestHelper::invokeRequest( + defs.get(), Cmd_ptr(new AlterCmd(suite->absNodePath(), AlterCmd::LIMIT_VAL, l->name(), "33"))); + limit_ptr v = suite->find_limit(l->name()); BOOST_CHECK_MESSAGE(v.get() && v->value() == 33, "expected to find limit with value of 33"); } } @@ -359,7 +359,7 @@ void update_calendar(defs_ptr defs) { defs->updateCalendar(p); // Currently updating the calendar, does not cause change, Hence force a change - for (suite_ptr suite : defs->suiteVec()) { + for (suite_ptr suite : defs->suites()) { SuiteChanged changed(suite); suite->add_variable("name", "value"); } @@ -371,9 +371,9 @@ void update_calendar(defs_ptr defs) { } void delete_suite(defs_ptr defs) { - std::vector vec = defs->suiteVec(); - BOOST_REQUIRE_MESSAGE(!vec.empty(), "Expected suites"); - vec[0]->remove(); + auto suites = defs->suites(); + BOOST_REQUIRE_MESSAGE(!suites.empty(), "Expected suites"); + suites[0]->remove(); } void set_server_state_shutdown(defs_ptr defs) { @@ -401,21 +401,21 @@ void delete_server_variable(defs_ptr defs) { } void reorder_suites(defs_ptr defs) { - std::vector suiteVec = defs->suiteVec(); - BOOST_REQUIRE_MESSAGE(!suiteVec.empty(), "Expected suites"); - std::string path = "/" + suiteVec[0]->name(); + auto suites = defs->suites(); + BOOST_REQUIRE_MESSAGE(!suites.empty(), "Expected suites"); + std::string path = "/" + suites[0]->name(); TestHelper::invokeRequest(defs.get(), Cmd_ptr(new OrderNodeCmd(path, NOrder::ALPHA))); } void move_peers(defs_ptr defs) { - std::vector suiteVec = defs->suiteVec(); - BOOST_REQUIRE_MESSAGE(suiteVec.size() >= 2, "Expected suites"); - TestHelper::invokeRequest(defs.get(), Cmd_ptr(new PlugCmd(suiteVec[0]->absNodePath(), suiteVec[1]->absNodePath()))); + auto suites = defs->suites(); + BOOST_REQUIRE_MESSAGE(suites.size() >= 2, "Expected suites"); + TestHelper::invokeRequest(defs.get(), Cmd_ptr(new PlugCmd(suites[0]->absNodePath(), suites[1]->absNodePath()))); } void move_peers1(defs_ptr defs) { - std::vector suiteVec = defs->suiteVec(); - BOOST_REQUIRE_MESSAGE(suiteVec.size() >= 2, "Expected suites"); - TestHelper::invokeRequest(defs.get(), Cmd_ptr(new PlugCmd(suiteVec[1]->absNodePath(), suiteVec[0]->absNodePath()))); + auto suites = defs->suites(); + BOOST_REQUIRE_MESSAGE(suites.size() >= 2, "Expected suites"); + TestHelper::invokeRequest(defs.get(), Cmd_ptr(new PlugCmd(suites[1]->absNodePath(), suites[0]->absNodePath()))); } void set_defs_flag(defs_ptr defs) { diff --git a/libs/base/test/TestSSyncCmdOrder.cpp b/libs/base/test/TestSSyncCmdOrder.cpp index 8b5f7b7ad..fc8f39204 100644 --- a/libs/base/test/TestSSyncCmdOrder.cpp +++ b/libs/base/test/TestSSyncCmdOrder.cpp @@ -33,28 +33,18 @@ static bool bypass_state_modify_change_check = false; BOOST_AUTO_TEST_SUITE(U_Base) +auto to_name = [](const auto& node) { return node->name(); }; + BOOST_AUTO_TEST_SUITE(T_SSyncCmdOrder) /// define a function which returns nothing, and takes a defs_ptr parameter using defs_change_cmd = boost::function; static std::vector vector_abcd() { - std::vector names; - names.reserve(4); - names.emplace_back("a"); - names.emplace_back("b"); - names.emplace_back("c"); - names.emplace_back("d"); - return names; + return std::vector{"a", "b", "c", "d"}; } static std::vector vector_dcba() { - std::vector names; - names.reserve(4); - names.emplace_back("d"); - names.emplace_back("c"); - names.emplace_back("b"); - names.emplace_back("a"); - return names; + return std::vector{"d", "c", "b", "a"}; } static defs_ptr create_defs() { @@ -67,9 +57,6 @@ static defs_ptr create_defs() { family_ptr f = s->add_family(names[k]); for (const auto& name : names) { task_ptr t = f->add_task(name); - // t->add_alias_only(); // alias0 - // t->add_alias_only(); // alias1 - // t->add_alias_only(); // alias2 } } } @@ -111,9 +98,6 @@ static void test_sync_scaffold(defs_change_cmd the_defs_change_command, Ecf::set_server(false); } - // cout << "test_sync_scaffold AFTER Command before SYNC: Server:\n" << *server_defs << "\n"; - // cout << "test_sync_scaffold AFTER Command before SYNC: Client:\n" << *server_reply.client_defs() << "\n"; - MockServer mock_server(server_defs); SSyncCmd cmd(client_handle, client_state_change_no, client_modify_change_no, &mock_server); std::string error_msg; @@ -127,31 +111,30 @@ static void test_sync_scaffold(defs_change_cmd the_defs_change_command, BOOST_CHECK_MESSAGE(*server_defs == *server_reply.client_defs(), "Server and client should be same after sync"); } else { - BOOST_CHECK_MESSAGE(server_reply.client_defs()->suiteVec().size() == test_equality, + BOOST_CHECK_MESSAGE(server_reply.client_defs()->suites().size() == test_equality, "Expected suite of size " << test_equality << " but found " - << server_reply.client_defs()->suiteVec().size()); + << server_reply.client_defs()->suites().size()); } } static void reorder_suites(defs_ptr theDefs) { TestHelper::invokeRequest(theDefs.get(), Cmd_ptr(new OrderNodeCmd("/a", NOrder::ALPHA))); - BOOST_REQUIRE_MESSAGE(ecf::algorithm::transform_to_name_vector(theDefs->suiteVec()) == vector_abcd(), - "NOrder::ALPHA expected " - << ecf::algorithm::join(vector_abcd()) << " but found: " - << ecf::algorithm::join(ecf::algorithm::transform_to_name_vector(theDefs->suiteVec()))); + auto names = ecf::algorithm::transform_to_vector(theDefs->suites(), to_name); + BOOST_REQUIRE_MESSAGE(names == vector_abcd(), + "NOrder::ALPHA expected " << ecf::algorithm::join(vector_abcd()) + << " but found: " << ecf::algorithm::join(names)); } static void reorder_family(defs_ptr theDefs) { - // std::cout << "reorder_family\n" << *theDefs << "\n"; TestHelper::invokeRequest(theDefs.get(), Cmd_ptr(new OrderNodeCmd("/d/d", NOrder::ALPHA))); auto families = ecf::get_all_families(*theDefs->findAbsNode("/d")); - BOOST_REQUIRE_MESSAGE(ecf::algorithm::transform_to_name_vector(families) == vector_abcd(), - "NOrder::ALPHA expected " - << ecf::algorithm::join(vector_abcd()) << " but found: " - << ecf::algorithm::join(ecf::algorithm::transform_to_name_vector(families))); + auto names = ecf::algorithm::transform_to_vector(families, to_name); + BOOST_REQUIRE_MESSAGE(names == vector_abcd(), + "NOrder::ALPHA expected " << ecf::algorithm::join(vector_abcd()) + << " but found: " << ecf::algorithm::join(names)); } static void reorder_task(defs_ptr theDefs) { @@ -160,24 +143,12 @@ static void reorder_task(defs_ptr theDefs) { TestHelper::invokeRequest(theDefs.get(), Cmd_ptr(new OrderNodeCmd("/d/d/d", NOrder::ALPHA))); auto tasks = ecf::get_all_tasks(*theDefs->findAbsNode("/d/d")); - BOOST_REQUIRE_MESSAGE(ecf::algorithm::transform_to_name_vector(tasks) == vector_abcd(), - "NOrder::ALPHA expected " - << ecf::algorithm::join(vector_abcd()) << " but found: " - << ecf::algorithm::join(ecf::algorithm::transform_to_name_vector(tasks))); + auto names = ecf::algorithm::transform_to_vector(tasks, to_name); + BOOST_REQUIRE_MESSAGE(names == vector_abcd(), + "NOrder::ALPHA expected " << ecf::algorithm::join(vector_abcd()) + << " but found: " << ecf::algorithm::join(names)); } -// static void reorder_alias(defs_ptr theDefs) { -// //std::cout << "reorder_task\n" << *theDefs << "\n"; -// -// TestHelper::invokeRequest(theDefs.get(),Cmd_ptr( new OrderNodeCmd("/d/d/d/alias0",NOrder::ALPHA))); -// -// std::vector aliases; -// theDefs->findAbsNode("/d/d/d")->get_all_aliases(aliases); -// BOOST_REQUIRE_MESSAGE( ecf::algorithm::transform_to_name_vector(aliases) == vector_abcd(),"NOrder::ALPHA expected -// " << ecf::algorithm::join(vector_abcd())<< " but found: " << -// ecf::algorithm::join(ecf::algorithm::transform_to_name_vector(tasks))); -// } - static void reorder_suites_using_handles(defs_ptr theDefs) { // *** NOTE ****: Whenever we register a handle, we get a *FULL* sync @@ -193,41 +164,45 @@ static void reorder_suites_using_handles(defs_ptr theDefs) { "Expected 1 Client suites but found " << theDefs->client_suite_mgr().clientSuites().size()); TestHelper::invokeRequest(theDefs.get(), Cmd_ptr(new OrderNodeCmd("/a", NOrder::ALPHA))); - BOOST_REQUIRE_MESSAGE(ecf::algorithm::transform_to_name_vector(theDefs->suiteVec()) == vector_abcd(), - "NOrder::ALPHA expected " - << ecf::algorithm::join(vector_abcd()) << " but found: " - << ecf::algorithm::join(ecf::algorithm::transform_to_name_vector(theDefs->suiteVec()))); + auto names = ecf::algorithm::transform_to_vector(theDefs->suites(), to_name); + BOOST_REQUIRE_MESSAGE(names == vector_abcd(), + "NOrder::ALPHA expected " << ecf::algorithm::join(vector_abcd()) + << " but found: " << ecf::algorithm::join(names)); } static void reorder_family_using_handles(defs_ptr theDefs) { // *** NOTE ****: Whenever we register a handle, we get a *FULL* sync + { + // create client handle which references all the suites + // It should be noted that invokeRequest, uses a MockServer, which set/unsets + // Hence after this call Ecf::server_ is false. Hence we need to ensure that following + // commands/ DM function set Ecf::server_ to true. + std::vector suite_names{"d"}; // client handle for suite 'd' ONLY + TestHelper::invokeRequest( + theDefs.get(), Cmd_ptr(new ClientHandleCmd(0, suite_names, false)), bypass_state_modify_change_check); + BOOST_CHECK_MESSAGE(theDefs->client_suite_mgr().clientSuites().size() == 1, + "Expected 1 Client suites but found " << theDefs->client_suite_mgr().clientSuites().size()); + } - // create client handle which references all the suites - // It should be noted that invokeRequest, uses a MockServer, which set/unsets - // Hence after this call Ecf::server_ is false. Hence we need to ensure that following - // commands/ DM function set Ecf::server_ to true. - std::vector suite_names; - suite_names.emplace_back("d"); // clinet handle for suite 'd' ONLY - TestHelper::invokeRequest( - theDefs.get(), Cmd_ptr(new ClientHandleCmd(0, suite_names, false)), bypass_state_modify_change_check); - BOOST_CHECK_MESSAGE(theDefs->client_suite_mgr().clientSuites().size() == 1, - "Expected 1 Client suites but found " << theDefs->client_suite_mgr().clientSuites().size()); + { + /// Don't call, data model function directly, since Ecf::server_ is false. *here* + /// The suite should stay the same, only suite d's family should change + TestHelper::invokeRequest(theDefs.get(), Cmd_ptr(new OrderNodeCmd("/d/d", NOrder::ALPHA))); + auto names = ecf::algorithm::transform_to_vector(theDefs->suites(), to_name); + BOOST_REQUIRE_MESSAGE(names == vector_dcba(), + "expected " << ecf::algorithm::join(vector_dcba()) + << " but found: " << ecf::algorithm::join(names)); + } - /// Don't call, data model function directly, since Ecf::server_ is false. *here* - /// The suite should stay the same, only suite d's family should change - TestHelper::invokeRequest(theDefs.get(), Cmd_ptr(new OrderNodeCmd("/d/d", NOrder::ALPHA))); - BOOST_REQUIRE_MESSAGE( - ecf::algorithm::transform_to_name_vector(theDefs->suiteVec()) == vector_dcba(), - "expected " << ecf::algorithm::join(vector_dcba()) << " but found: " - << ecf::algorithm::join(ecf::algorithm::transform_to_name_vector(theDefs->suiteVec()))); - - suite_ptr suite_a = theDefs->findSuite("d"); - auto families = ecf::get_all_families(*suite_a); - BOOST_REQUIRE_MESSAGE(ecf::algorithm::transform_to_name_vector(families) == vector_abcd(), - "NOrder::ALPHA expected " - << ecf::algorithm::join(vector_abcd()) << " but found: " - << ecf::algorithm::join(ecf::algorithm::transform_to_name_vector(families))); + { + suite_ptr suite_a = theDefs->findSuite("d"); + auto families = ecf::get_all_families(*suite_a); + auto names = ecf::algorithm::transform_to_vector(families, to_name); + BOOST_REQUIRE_MESSAGE(names == vector_abcd(), + "NOrder::ALPHA expected " << ecf::algorithm::join(vector_abcd()) + << " but found: " << ecf::algorithm::join(names)); + } } BOOST_AUTO_TEST_CASE(test_ssync_cmd_test_order) { diff --git a/libs/base/test/TestSSyncCmd_CH1.cpp b/libs/base/test/TestSSyncCmd_CH1.cpp index dd5b3089b..fc6048820 100644 --- a/libs/base/test/TestSSyncCmd_CH1.cpp +++ b/libs/base/test/TestSSyncCmd_CH1.cpp @@ -125,7 +125,7 @@ void test_sync_scaffold(defs_change_cmd the_defs_change_command, const std::stri /// called create Defs should clear the flag. make sure server state, is synced defs_ptr the_client_defs = server_defs->client_suite_mgr().create_defs(client_handle, server_defs); - BOOST_CHECK_MESSAGE(the_client_defs->suiteVec().size() == 2, test_name << ": Expected 2 suites"); + BOOST_CHECK_MESSAGE(the_client_defs->suites().size() == 2, test_name << ": Expected 2 suites"); BOOST_CHECK_MESSAGE(server_defs->client_suite_mgr().handle_changed(client_handle) == false, test_name << ": Expected handle_changed to be cleared after create_defs()"); BOOST_CHECK_MESSAGE(server_defs->server_state().compare(the_client_defs->server_state()), @@ -582,11 +582,11 @@ BOOST_AUTO_TEST_CASE(test_ssync_using_handle) { // ============================================================================================= static defs_ptr create_the_server_defs() { - defs_ptr defs = create_server_defs(); - std::vector suite_vec = defs->suiteVec(); - for (size_t j = 0; j < suite_vec.size(); j++) { - suite_vec[j]->set_state_change_no(j); - suite_vec[j]->set_modify_change_no(j); + defs_ptr defs = create_server_defs(); + auto suites = defs->suites(); + for (size_t j = 0; j < suites.size(); j++) { + suites[j]->set_state_change_no(j); + suites[j]->set_modify_change_no(j); } return defs; } diff --git a/libs/client/src/ecflow/client/ClientEnvironment.cpp b/libs/client/src/ecflow/client/ClientEnvironment.cpp index 9d506644a..b835dc5f0 100644 --- a/libs/client/src/ecflow/client/ClientEnvironment.cpp +++ b/libs/client/src/ecflow/client/ClientEnvironment.cpp @@ -102,7 +102,7 @@ void ClientEnvironment::init() { // If no host specified default to local host and default port number if (host_vec_.empty()) { - host_vec_.emplace_back(ecf::Str::LOCALHOST(), ecf::Str::DEFAULT_PORT_NUMBER()); + host_vec_.emplace_back(ecf::string_constants::localhost, ecf::string_constants::default_port_number); } // Program options are read in last, and will override any previous setting @@ -215,7 +215,7 @@ std::string ClientEnvironment::toString() const { << "\n"; if (!host_vec_.empty()) { for (std::pair i : host_vec_) { - ss << " " << i.first << ecf::Str::COLON() << i.second << "\n"; + ss << " " << i.first << ecf::string_constants::colon << i.second << "\n"; } } ss << " " << ecf::environment::ECF_NAME << " = " << task_path_ << "\n"; @@ -250,7 +250,7 @@ std::string ClientEnvironment::hostSpecified() { } std::string ClientEnvironment::portSpecified() { - std::string specified_port = ecf::Str::DEFAULT_PORT_NUMBER(); + std::string specified_port = ecf::string_constants::default_port_number; ecf::environment::get(ecf::environment::ECF_PORT, specified_port); return specified_port; } @@ -310,8 +310,8 @@ void ClientEnvironment::read_environment_variables() { } /// Override the config settings *IF* any - std::string port = ecf::Str::DEFAULT_PORT_NUMBER(); - std::string host = ecf::Str::LOCALHOST(); + std::string port = ecf::string_constants::default_port_number; + std::string host = ecf::string_constants::localhost; if (!host_vec_.empty()) { const auto& selected = host_vec_[0]; // the first entry holds the selected host/port host = selected.first; @@ -336,7 +336,7 @@ void ClientEnvironment::read_environment_variables() { bool ClientEnvironment::parseHostsFile(std::string& errorMsg) { - std::string configured_port = host_vec_.empty() ? ecf::Str::DEFAULT_PORT_NUMBER() : host_vec_[0].second; + std::string configured_port = host_vec_.empty() ? ecf::string_constants::default_port_number : host_vec_[0].second; int port = ecf::convert_to(configured_port); try { @@ -388,7 +388,7 @@ const std::string& ClientEnvironment::get_password(const char* env, const std::s } } - return ecf::Str::EMPTY(); + return ecf::string_constants::empty; } struct TokenFile diff --git a/libs/client/src/ecflow/client/ClientInvoker.cpp b/libs/client/src/ecflow/client/ClientInvoker.cpp index 43cfe0f96..67ae2e591 100644 --- a/libs/client/src/ecflow/client/ClientInvoker.cpp +++ b/libs/client/src/ecflow/client/ClientInvoker.cpp @@ -1724,7 +1724,7 @@ int ClientInvoker::edit_script_submit(const std::string& path_to_task, std::string ClientInvoker::client_env_host_port() const { std::string host_port = clientEnv_.host(); - host_port += Str::COLON(); + host_port += ecf::string_constants::colon; host_port += clientEnv_.port(); return host_port; } diff --git a/libs/client/src/ecflow/client/ClientInvoker.hpp b/libs/client/src/ecflow/client/ClientInvoker.hpp index 74ad2ec21..21273d06c 100644 --- a/libs/client/src/ecflow/client/ClientInvoker.hpp +++ b/libs/client/src/ecflow/client/ClientInvoker.hpp @@ -458,6 +458,8 @@ class ClientInvoker { */ bool is_not_retrying(const ClientToServerCmd& cmd) const; + const auto& changed_node_paths() const { return server_reply_.changed_nodes(); } + private: /** * @return 1 when command is selected; 0 if no command is selected (e.g. --help) @@ -479,15 +481,6 @@ class ClientInvoker { friend class RoundTripRecorder; friend class RequestLogger; - /// For use by python interface, - std::vector::const_iterator changed_node_paths_begin() const { - return server_reply_.changed_nodes().begin(); - } - std::vector::const_iterator changed_node_paths_end() const { - return server_reply_.changed_nodes().end(); - } - friend void export_Client(); - private: #ifdef DEBUG inline static auto RETRY_CONNECTION_PERIOD = std::chrono::seconds{1}; diff --git a/libs/client/src/ecflow/client/ClientOptions.cpp b/libs/client/src/ecflow/client/ClientOptions.cpp index 7c6212795..8ea487516 100644 --- a/libs/client/src/ecflow/client/ClientOptions.cpp +++ b/libs/client/src/ecflow/client/ClientOptions.cpp @@ -137,6 +137,19 @@ Cmd_ptr ClientOptions::parse(const CommandLine& cl, ClientEnvironment* env) cons env->set_debug(true); } + // If the `--help` option is provided, display the requested help message and exit + if (vm.count("help")) { + std::string topic = vm["help"].as(); + std::cout << Help{*desc_, topic}; + return nullptr; + } + + // If the `--version` option is provided, display the version information and exit + if (vm.count("version")) { + std::cout << Version::description() << "\n"; + return nullptr; + } + // Check to see if host or port, specified. This will override the environment variables std::string host, port; if (vm.count("port")) { @@ -166,10 +179,10 @@ Cmd_ptr ClientOptions::parse(const CommandLine& cl, ClientEnvironment* env) cons port = env->portSpecified(); // get the environment variable ECF_PORT || Str::DEFAULT_PORT_NUMBER() } if (host.empty()) { - host = Str::LOCALHOST(); // if ECF_HOST not specified default to localhost + host = ecf::string_constants::localhost; // if ECF_HOST not specified default to localhost } if (port.empty()) { - port = Str::DEFAULT_PORT_NUMBER(); // if ECF_PORT not specified use default + port = ecf::string_constants::default_port_number; // if ECF_PORT not specified use default } env->set_host_port(host, port); } @@ -248,16 +261,6 @@ Cmd_ptr ClientOptions::parse(const CommandLine& cl, ClientEnvironment* env) cons // For example: // --server_load // this is sent to server // --server_load= // no command returned, command executed by client - if (vm.count("help")) { - std::string topic = vm["help"].as(); - std::cout << Help{*desc_, topic}; - return client_request; - } - - if (vm.count("version")) { - std::cout << Version::description() << "\n"; - exit(0); - } std::ostringstream ss; ss << print_variable_map(vm) << "\n"; diff --git a/libs/client/src/ecflow/client/ClientOptions.hpp b/libs/client/src/ecflow/client/ClientOptions.hpp index 3b572ceff..424343329 100644 --- a/libs/client/src/ecflow/client/ClientOptions.hpp +++ b/libs/client/src/ecflow/client/ClientOptions.hpp @@ -40,9 +40,17 @@ class ClientOptions { ~ClientOptions(); - /// parse the arguments and create the client request that is to be sent - /// to the server. Will throw std::runtime_error if invalid arguments specified - Cmd_ptr parse(const CommandLine& cl, ClientEnvironment*) const; + /// + /// @brief Parse the command line arguments and create the client request that is to be sent to the server. + /// + /// @param cl The command line arguments to parse + /// @param environment The client environment, which may be used by some commands to construct the client request + /// @return A pointer to the client request to be sent to the server. + /// The pointer can be nullptr if the command is client specific (e.g. --help, --version) + /// and does not need to send a request to the server. + /// @throw std::runtime_error if invalid arguments are specified + /// + Cmd_ptr parse(const CommandLine& cl, ClientEnvironment* environment) const; private: CtsCmdRegistry cmdRegistry_; diff --git a/libs/client/src/ecflow/client/Help.cpp b/libs/client/src/ecflow/client/Help.cpp index b410efbcf..ffe38c058 100644 --- a/libs/client/src/ecflow/client/Help.cpp +++ b/libs/client/src/ecflow/client/Help.cpp @@ -345,7 +345,7 @@ void Documentation::show_summary(std::ostream& os, PREDICATE select) const { int max_width = get_options_max_width(options); for (const auto& option : options) { std::vector lines; - ecf::Str::split(option->description(), lines, "\n"); + ecf::algorithm::split_at(lines, option->description(), "\n"); if (!lines.empty()) { std::string name = option->long_name(); os << " " << std::left << std::setw(max_width) << name << " "; diff --git a/libs/client/src/ecflow/client/HostsFile.cpp b/libs/client/src/ecflow/client/HostsFile.cpp index ff808e473..a79666f86 100644 --- a/libs/client/src/ecflow/client/HostsFile.cpp +++ b/libs/client/src/ecflow/client/HostsFile.cpp @@ -40,7 +40,7 @@ HostsFile HostsFile::parse(std::istream& is, const int default_port) { for (const auto& line : lines) { std::vector tokens; - ecf::Str::split(line, tokens); + ecf::algorithm::split_at(tokens, line); if (tokens.empty() || tokens[0].empty() || tokens[0][0] == '#') { continue; // skip empty lines and comments } @@ -58,7 +58,7 @@ HostsFile HostsFile::parse(std::istream& is, const int default_port) { // The port can be empty, in which case we use the default port port = std::to_string(default_port); } - else if (!ecf::Str::is_int(port)) { + else if (!ecf::algorithm::is_int(port)) { // If the port must be an integer, otherwise throw an exception THROW_EXCEPTION(HostsFileFailure, "Non-integer port number in hosts file: " << port); } diff --git a/libs/client/test/TestCheckPtDefsCmd.cpp b/libs/client/test/TestCheckPtDefsCmd.cpp index b3ee1d0e4..2213572ec 100644 --- a/libs/client/test/TestCheckPtDefsCmd.cpp +++ b/libs/client/test/TestCheckPtDefsCmd.cpp @@ -222,9 +222,9 @@ BOOST_AUTO_TEST_CASE(test_restore_from_check_pt) { BOOST_REQUIRE_MESSAGE(theClient.haltServer() == 0, "Expected halt server to succeed\n" << theClient.errorMsg()); BOOST_REQUIRE_MESSAGE(theClient.restoreDefsFromCheckPt() == 0, "Expected restoreDefsFromCheckPt succeed\n"); BOOST_REQUIRE_MESSAGE(theClient.getDefs() == 0, "Expected getDefs() to succeed, i.e expected empty defs\n"); - BOOST_REQUIRE_MESSAGE(theClient.defs()->suiteVec().size() == expected_no_of_suites, + BOOST_REQUIRE_MESSAGE(theClient.defs()->suites().size() == expected_no_of_suites, "Expected " << expected_no_of_suites << " suites, after restoreDefsFromCheckPt but found " - << theClient.defs()->suiteVec().size() << "\n"); + << theClient.defs()->suites().size() << "\n"); std::string suite = "/s" + ecf::convert_to(i); BOOST_REQUIRE_MESSAGE(theClient.delete_node(suite) == 0, @@ -250,7 +250,7 @@ BOOST_AUTO_TEST_CASE(test_restore_from_check_pt_using_new_server) { MyDefsFixture theDefsFixture; // make sure generated server variable use this port. defs_ptr defs_to_be_check_pointed = theDefsFixture.create_defs(port); - BOOST_REQUIRE_MESSAGE(defs_to_be_check_pointed->suiteVec().size() >= 2, "expected at least 2 suites"); + BOOST_REQUIRE_MESSAGE(defs_to_be_check_pointed->suites().size() >= 2, "expected at least 2 suites"); { // Start a new server. However make sure that on server exit, we not delete check pt files @@ -320,9 +320,10 @@ BOOST_AUTO_TEST_CASE(test_check_pt_edit_history) { "Server failed to start on " << invokeServer.host() << ":" << invokeServer.port()); ClientInvoker theClient(invokeServer.host(), invokeServer.port()); - BOOST_REQUIRE_MESSAGE(theClient.edit_history(Str::ROOT_PATH()) == 0, - CtsApi::to_string(CtsApi::edit_history(Str::ROOT_PATH())) << " should return 0\n" - << theClient.errorMsg()); + BOOST_REQUIRE_MESSAGE(theClient.edit_history(ecf::string_constants::root_path) == 0, + CtsApi::to_string(CtsApi::edit_history(ecf::string_constants::root_path)) + << " should return 0\n" + << theClient.errorMsg()); BOOST_REQUIRE_MESSAGE(theClient.server_reply().get_string_vec().size() == 0, "Expected edit history of size 0 after server start, but found " << theClient.server_reply().get_string_vec().size()); @@ -344,9 +345,10 @@ BOOST_AUTO_TEST_CASE(test_check_pt_edit_history) { << theClient.errorMsg()); // make sure edit history updated - BOOST_REQUIRE_MESSAGE(theClient.edit_history(Str::ROOT_PATH()) == 0, - CtsApi::to_string(CtsApi::edit_history(Str::ROOT_PATH())) << " should return 0\n" - << theClient.errorMsg()); + BOOST_REQUIRE_MESSAGE(theClient.edit_history(ecf::string_constants::root_path) == 0, + CtsApi::to_string(CtsApi::edit_history(ecf::string_constants::root_path)) + << " should return 0\n" + << theClient.errorMsg()); BOOST_REQUIRE_MESSAGE(theClient.server_reply().get_string_vec().size() == 5, "Expected edit history of size 5, but found " << theClient.server_reply().get_string_vec().size()); @@ -355,9 +357,9 @@ BOOST_AUTO_TEST_CASE(test_check_pt_edit_history) { BOOST_REQUIRE_MESSAGE(theClient.getDefs() == 0, CtsApi::get() << " failed should return 0\n" << theClient.errorMsg()); - BOOST_REQUIRE_MESSAGE(theClient.defs()->get_edit_history(Str::ROOT_PATH()).size() == 0, + BOOST_REQUIRE_MESSAGE(theClient.defs()->get_edit_history(ecf::string_constants::root_path).size() == 0, "Expected edit history of size 0, but found " - << theClient.defs()->get_edit_history(Str::ROOT_PATH()).size()); + << theClient.defs()->get_edit_history(ecf::string_constants::root_path).size()); // This should write the edit history BOOST_REQUIRE_MESSAGE(theClient.checkPtDefs() == 0, @@ -374,9 +376,9 @@ BOOST_AUTO_TEST_CASE(test_check_pt_edit_history) { { Defs defs; defs.restore(invokeServer.ecf_checkpt_file()); // restore defs from checkpoint - BOOST_REQUIRE_MESSAGE(defs.get_edit_history(Str::ROOT_PATH()).size() == 5, + BOOST_REQUIRE_MESSAGE(defs.get_edit_history(ecf::string_constants::root_path).size() == 5, "Expected edit history of size 5, but found " - << defs.get_edit_history(Str::ROOT_PATH()).size()); + << defs.get_edit_history(ecf::string_constants::root_path).size()); } { diff --git a/libs/client/test/TestClientConfigurations.cpp b/libs/client/test/TestClientConfigurations.cpp index dafd8af57..f9b4e0566 100644 --- a/libs/client/test/TestClientConfigurations.cpp +++ b/libs/client/test/TestClientConfigurations.cpp @@ -37,6 +37,8 @@ BOOST_AUTO_TEST_SUITE(S_Client) BOOST_AUTO_TEST_SUITE(T_ClientConfiguration) +using namespace ecf::test::scaffold; + /* * The following exports an environment variable used for tests, which changes the location of the SSL certificates. * Instead of the default location (HOME/.ecflowrc/ssl), we use the current test directory. diff --git a/libs/client/test/TestClientEnvironment.cpp b/libs/client/test/TestClientEnvironment.cpp index 43bbcf562..40965ba3a 100644 --- a/libs/client/test/TestClientEnvironment.cpp +++ b/libs/client/test/TestClientEnvironment.cpp @@ -34,7 +34,7 @@ BOOST_AUTO_TEST_CASE(test_client_environment_host_file_parsing) { // local host should be implicitly added to internal host list std::string the_host = ClientEnvironment::hostSpecified(); if (the_host.empty()) { - the_host = Str::LOCALHOST(); + the_host = ecf::string_constants::localhost; } std::vector expectedHost; @@ -85,7 +85,7 @@ BOOST_AUTO_TEST_CASE(test_client_environment_host_file_defaults) { // local host should be implicitly added to internal host list std::vector> expectedHost; - expectedHost.emplace_back(Str::LOCALHOST(), std::string("5111")); // here 5111 is job supplied port + expectedHost.emplace_back(ecf::string_constants::localhost, std::string("5111")); // here 5111 is job supplied port expectedHost.emplace_back(std::string("host1"), std::string("3142")); expectedHost.emplace_back(std::string("host2"), std::string("3141")); expectedHost.emplace_back(std::string("host3"), @@ -96,10 +96,11 @@ BOOST_AUTO_TEST_CASE(test_client_environment_host_file_defaults) { expectedHost.emplace_back(std::string("host6"), std::string("4081")); // Create the ClientEnvironment overriding the config & environment. To specify host and port - ClientEnvironment client_env(good_host_file, Str::LOCALHOST(), "5111"); + ClientEnvironment client_env(good_host_file, ecf::string_constants::localhost, "5111"); std::string home_host = client_env.host(); std::string home_port = client_env.port(); - BOOST_CHECK_MESSAGE(Str::LOCALHOST() == home_host && "5111" == home_port, "host host & port not as expected"); + BOOST_CHECK_MESSAGE(ecf::string_constants::localhost == home_host && "5111" == home_port, + "host host & port not as expected"); std::string host; size_t count = 0; @@ -145,6 +146,8 @@ BOOST_AUTO_TEST_CASE(test_client_environment_empty_host_file) { BOOST_AUTO_TEST_CASE(test_client_environment_host_file_policy) { ECF_NAME_THIS_TEST(); + using namespace ecf::test::scaffold; + { // When no host file policy is specified... diff --git a/libs/client/test/TestClientHandleCmd.cpp b/libs/client/test/TestClientHandleCmd.cpp index 0aa05b11b..369cce698 100644 --- a/libs/client/test/TestClientHandleCmd.cpp +++ b/libs/client/test/TestClientHandleCmd.cpp @@ -41,14 +41,14 @@ BOOST_AUTO_TEST_CASE(test_client_handle_cmd) { } BOOST_REQUIRE_MESSAGE(theClient.load(defs) == 0, "load defs failed \n" << theClient.errorMsg()); - BOOST_REQUIRE_MESSAGE(theClient.defs()->suiteVec().size() == suites.size(), "load failed"); + BOOST_REQUIRE_MESSAGE(theClient.defs()->suites().size() == suites.size(), "load failed"); BOOST_REQUIRE_MESSAGE(theClient.ch1_register(true, suites) == 0, "ch1_register failed\n" << theClient.errorMsg()); - BOOST_REQUIRE_MESSAGE(theClient.defs()->suiteVec().size() == suites.size(), "register group sync failed"); + BOOST_REQUIRE_MESSAGE(theClient.defs()->suites().size() == suites.size(), "register group sync failed"); BOOST_REQUIRE_MESSAGE(theClient.server_reply().client_handle() == 1, " ch1_register failed expected handle 1"); BOOST_REQUIRE_MESSAGE(theClient.delete_all() == 0, "delete_all failed\n" << theClient.errorMsg()); - BOOST_REQUIRE_MESSAGE(theClient.defs()->suiteVec().size() == 0, "delete all failed"); + BOOST_REQUIRE_MESSAGE(theClient.defs()->suites().size() == 0, "delete all failed"); BOOST_REQUIRE_MESSAGE(theClient.server_reply().client_handle() == 0, "delete all failed expected handle 0 but found " << theClient.server_reply().client_handle()); @@ -56,16 +56,16 @@ BOOST_AUTO_TEST_CASE(test_client_handle_cmd) { theClient.set_auto_sync(false); BOOST_REQUIRE_MESSAGE(theClient.load(defs) == 0, "load defs failed \n" << theClient.errorMsg()); BOOST_REQUIRE_MESSAGE(theClient.sync_local() == 0, "sync_local failed \n" << theClient.errorMsg()); - BOOST_REQUIRE_MESSAGE(theClient.defs()->suiteVec().size() == suites.size(), "load failed"); + BOOST_REQUIRE_MESSAGE(theClient.defs()->suites().size() == suites.size(), "load failed"); BOOST_REQUIRE_MESSAGE(theClient.ch1_register(true, suites) == 0, "ch1_register failed\n" << theClient.errorMsg()); BOOST_REQUIRE_MESSAGE(theClient.sync_local() == 0, "sync_local failed \n" << theClient.errorMsg()); - BOOST_REQUIRE_MESSAGE(theClient.defs()->suiteVec().size() == suites.size(), "register group sync failed"); + BOOST_REQUIRE_MESSAGE(theClient.defs()->suites().size() == suites.size(), "register group sync failed"); BOOST_REQUIRE_MESSAGE(theClient.server_reply().client_handle() == 1, " ch1_register failed expected handle 1"); BOOST_REQUIRE_MESSAGE(theClient.delete_all() == 0, "delete_all failed\n" << theClient.errorMsg()); BOOST_REQUIRE_MESSAGE(theClient.sync_local() == 0, "sync_local failed \n" << theClient.errorMsg()); - BOOST_REQUIRE_MESSAGE(theClient.defs()->suiteVec().size() == 0, "delete all failed"); + BOOST_REQUIRE_MESSAGE(theClient.defs()->suites().size() == 0, "delete all failed"); BOOST_REQUIRE_MESSAGE(theClient.server_reply().client_handle() == 0, "delete all failed expected handle 0 but found " << theClient.server_reply().client_handle()); diff --git a/libs/client/test/TestClientRetry.cpp b/libs/client/test/TestClientRetry.cpp index 45b5b4c59..4b5828190 100644 --- a/libs/client/test/TestClientRetry.cpp +++ b/libs/client/test/TestClientRetry.cpp @@ -44,6 +44,8 @@ BOOST_AUTO_TEST_CASE(test_client_does_not_retry_user_command_by_default) { BOOST_AUTO_TEST_CASE(test_client_does_not_retry_when_denied_is_set) { ECF_NAME_THIS_TEST(); + using namespace ecf::test::scaffold; + WithTestEnvironmentVariable env_var(ecf::environment::ECF_DENIED, "true"); ClientInvoker client; BOOST_CHECK(client.is_not_retrying(ping)); @@ -71,6 +73,8 @@ BOOST_AUTO_TEST_CASE(test_client_retries_task_command_by_default) { BOOST_AUTO_TEST_CASE(test_client_follows_host_file_policy) { ECF_NAME_THIS_TEST(); + using namespace ecf::test::scaffold; + { WithTestEnvironmentVariable env_var(ecf::environment::ECF_HOSTFILE_POLICY, "task"); diff --git a/libs/client/test/TestClientTimeout.cpp b/libs/client/test/TestClientTimeout.cpp index 5da22472d..240c9ebbc 100644 --- a/libs/client/test/TestClientTimeout.cpp +++ b/libs/client/test/TestClientTimeout.cpp @@ -65,7 +65,7 @@ BOOST_AUTO_TEST_CASE(test_client_timeout, *boost::unit_test::disabled()) { /// Now see what timeout value we succeed with bool loaded_defs = false; for (int i = 2; i < 30; ++i) { - theClient.set_connect_timeout(i); + theClient.set_connect_timeout(std::chrono::seconds{i}); try { std::cout << "Trying with timeout of " << i << " seconds\n"; theClient.loadDefs(path); diff --git a/libs/client/test/TestGroupCmd.cpp b/libs/client/test/TestGroupCmd.cpp index 87eb0cd44..7f4d3b548 100644 --- a/libs/client/test/TestGroupCmd.cpp +++ b/libs/client/test/TestGroupCmd.cpp @@ -76,8 +76,8 @@ BOOST_AUTO_TEST_CASE(test_client_group_lifecyle) { << theClient.errorMsg()); defs_ptr serverDefs = theClient.defs(); BOOST_REQUIRE_MESSAGE(serverDefs.get(), "Server returned a NULL defs"); - BOOST_REQUIRE_MESSAGE(serverDefs->suiteVec().size() >= 1, " no suite ?"); - const std::vector& suites = serverDefs->suiteVec(); + BOOST_REQUIRE_MESSAGE(serverDefs->suites().size() >= 1, " no suite ?"); + const auto& suites = serverDefs->suites(); const std::vector& variables = suites[0]->variables(); expected_var = variables.size(); } @@ -92,7 +92,7 @@ BOOST_AUTO_TEST_CASE(test_client_group_lifecyle) { << theClient.errorMsg()); defs_ptr serverDefs = theClient.defs(); BOOST_REQUIRE_MESSAGE(serverDefs.get(), "Server returned a NULL defs"); - const std::vector& suites = serverDefs->suiteVec(); + const auto& suites = serverDefs->suites(); BOOST_REQUIRE_MESSAGE(suites.size() >= 1, " no suite ?"); const std::vector& variables = suites[0]->variables(); BOOST_REQUIRE_MESSAGE(variables.size() == expected_var, @@ -245,8 +245,8 @@ BOOST_AUTO_TEST_CASE(test_client_group_lifecyle) { BOOST_REQUIRE_MESSAGE(!theEvent.empty(), "Could not find the event myEvent"); BOOST_REQUIRE_MESSAGE(theEvent.value(), "The event was not set"); - const std::vector& suiteVec = serverDefs->suiteVec(); - suite_ptr suite = suiteVec.back(); + const auto& suites = serverDefs->suites(); + suite_ptr suite = suites.back(); BOOST_REQUIRE_MESSAGE(suite->state() == NState::COMPLETE, "Suite expected NState::COMPLETE, but found to be " << NState::toString(suite->state())); } diff --git a/libs/client/test/TestHostsFile.cpp b/libs/client/test/TestHostsFile.cpp index de1d7d479..e30f96cc9 100644 --- a/libs/client/test/TestHostsFile.cpp +++ b/libs/client/test/TestHostsFile.cpp @@ -137,6 +137,8 @@ host1:invalid BOOST_AUTO_TEST_CASE(test_can_parse_from_file) { ECF_NAME_THIS_TEST(); + using namespace ecf::test::scaffold; + auto content = fs::path{"test_hostsfile.txt"}; WithTestFile hostsfile(NamedTestFile{content.c_str()}, R"( @@ -169,6 +171,8 @@ longnamehost5 BOOST_AUTO_TEST_CASE(test_can_parse_from_file_with_comments) { ECF_NAME_THIS_TEST(); + using namespace ecf::test::scaffold; + auto content = fs::path{"test_hostsfile.txt"}; WithTestFile hostsfile(NamedTestFile{content.c_str()}, R"( # Some comment diff --git a/libs/client/test/TestLifeCycle.cpp b/libs/client/test/TestLifeCycle.cpp index 62fed268c..537f67065 100644 --- a/libs/client/test/TestLifeCycle.cpp +++ b/libs/client/test/TestLifeCycle.cpp @@ -68,8 +68,8 @@ BOOST_AUTO_TEST_CASE(test_node_tree_lifecycle) { // endsuite // get the suite, before we do anything initial state should be UNKNOWN - const std::vector& suiteVec = defs.suiteVec(); - suite_ptr suite = suiteVec.back(); + const auto& suites = defs.suites(); + auto suite = suites.back(); BOOST_CHECK_MESSAGE(suite->state() == NState::UNKNOWN, " Initial suite state should be NState::UNKNOWN"); std::string suite1_family1_a = "suite1/family1/a"; diff --git a/libs/client/test/TestLoadDefsCmd.cpp b/libs/client/test/TestLoadDefsCmd.cpp index 35b059b30..e459bb34f 100644 --- a/libs/client/test/TestLoadDefsCmd.cpp +++ b/libs/client/test/TestLoadDefsCmd.cpp @@ -54,7 +54,7 @@ BOOST_AUTO_TEST_CASE(test_load_defs_cmd_handleRequest) { BOOST_CHECK_MESSAGE(parse, "Parse failed. " << errorMsg); firstDefs->clear_externs(); // server defs should not have externs. } - size_t noOfSuites = firstDefs->suiteVec().size(); + size_t noOfSuites = firstDefs->suites().size(); // load the SECOND file, which should resolve the externs std::string secondDef = File::test_data("libs/client/test/data/second.def", "libs/client"); @@ -64,7 +64,7 @@ BOOST_AUTO_TEST_CASE(test_load_defs_cmd_handleRequest) { bool parse = secondDefs.restore(secondDef, errorMsg, warningMsg); BOOST_CHECK_MESSAGE(parse, "Parse failed. " << errorMsg); } - noOfSuites += secondDefs.suiteVec().size(); + noOfSuites += secondDefs.suites().size(); // Create a LoadDefsCmd. This capable of merging defs files // Externs are NOT loaded into the server. @@ -80,13 +80,13 @@ BOOST_AUTO_TEST_CASE(test_load_defs_cmd_handleRequest) { STC_Cmd_ptr requestStatus = cmd.handleRequest(&mockServer); BOOST_CHECK_MESSAGE(requestStatus, "Handle Request " << cmd << " returned NULL\n"); BOOST_CHECK_MESSAGE(requestStatus->error().empty(), requestStatus->error()); - BOOST_CHECK_MESSAGE(secondDefs.suiteVec().size() == noOfSuites, "Merge failed to add suites"); + BOOST_CHECK_MESSAGE(secondDefs.suites().size() == noOfSuites, "Merge failed to add suites"); // Modify the Defs file to add a task/trigger that references the undefined // extern path defined in file 'first.def' This should fail. task_ptr task = Task::create("AMadeUpName"); task->add_trigger("/a/b/c/d/e/f/g/h/j == complete"); - secondDefs.suiteVec().back()->familyVec().back()->addTask(task); + secondDefs.suites().back()->familyVec().back()->addTask(task); // we just added an expression, re-parse to create AST // This should also attempt to resolve the extern node path /a/b/c/d/e/f/g/h/j @@ -115,7 +115,7 @@ BOOST_AUTO_TEST_CASE(test_load_defs_check_only) { BOOST_REQUIRE_MESSAGE(theClient.sync_local() == 0, "Expected sync to succeed\n" << theClient.errorMsg()); // Note: when running with ECF_HOST=localhost the defs may exist, but the number of suites should be empty - BOOST_REQUIRE_MESSAGE(!theClient.defs() || theClient.defs()->suiteVec().empty(), + BOOST_REQUIRE_MESSAGE(!theClient.defs() || theClient.defs()->suites().empty(), "Expected no defs, since nothing should have been loaded\n" << theClient.errorMsg()); } @@ -124,7 +124,7 @@ BOOST_AUTO_TEST_CASE(test_load_defs_check_only) { BOOST_REQUIRE_MESSAGE(theClient.loadDefs(path, false, true /* check only*/) == 0, "Expected load to succeed\n" << theClient.errorMsg()); - BOOST_REQUIRE_MESSAGE(!theClient.defs() || theClient.defs()->suiteVec().empty(), + BOOST_REQUIRE_MESSAGE(!theClient.defs() || theClient.defs()->suites().empty(), "Expected no defs, since nothing should have been loaded\n" << theClient.errorMsg()); } diff --git a/libs/client/test/TestPlugCmd.cpp b/libs/client/test/TestPlugCmd.cpp index bfcd193c6..9a8b2cbe2 100644 --- a/libs/client/test/TestPlugCmd.cpp +++ b/libs/client/test/TestPlugCmd.cpp @@ -299,7 +299,7 @@ static void test_plug_on_multiple_server(const std::string& host1, CtsApi::get() << " failed should return 0\n" << server2Client.errorMsg()); BOOST_REQUIRE_MESSAGE(server2Client.defs().get(), "Server returned a NULL defs"); - BOOST_REQUIRE_MESSAGE(server2Client.defs()->suiteVec().size() == 0, " Expected server2 to have zero suite"); + BOOST_REQUIRE_MESSAGE(server2Client.defs()->suites().size() == 0, " Expected server2 to have zero suite"); // Move the suite FROM the FIRST server TO the SECOND server and check that it worked destPath = secondServerHostPort; @@ -310,13 +310,13 @@ static void test_plug_on_multiple_server(const std::string& host1, CtsApi::get() << " failed should return 0\n" << server1Client.errorMsg()); BOOST_REQUIRE_MESSAGE(server1Client.defs().get(), "Server returned a NULL defs"); - BOOST_REQUIRE_MESSAGE(server1Client.defs()->suiteVec().size() == 0, " Expected server1 to have no suites"); + BOOST_REQUIRE_MESSAGE(server1Client.defs()->suites().size() == 0, " Expected server1 to have no suites"); BOOST_REQUIRE_MESSAGE(server2Client.getDefs() == 0, CtsApi::get() << " failed should return 0\n" << server2Client.errorMsg()); BOOST_REQUIRE_MESSAGE(server2Client.defs().get(), "Server returned a NULL defs"); - BOOST_REQUIRE_MESSAGE(server2Client.defs()->suiteVec().size() == 1, " Expected server2 to have one suite"); + BOOST_REQUIRE_MESSAGE(server2Client.defs()->suites().size() == 1, " Expected server2 to have one suite"); // ========================================================================== // Do it again, but with no defs file in second server. reload defs into server1 @@ -336,13 +336,13 @@ static void test_plug_on_multiple_server(const std::string& host1, CtsApi::get() << " failed should return 0\n" << server1Client.errorMsg()); BOOST_REQUIRE_MESSAGE(server1Client.defs().get(), "Server returned a NULL defs"); - BOOST_REQUIRE_MESSAGE(server1Client.defs()->suiteVec().size() == 0, " Expected server1 to have no suites"); + BOOST_REQUIRE_MESSAGE(server1Client.defs()->suites().size() == 0, " Expected server1 to have no suites"); BOOST_REQUIRE_MESSAGE(server2Client.getDefs() == 0, CtsApi::get() << " failed should return 0\n" << server2Client.errorMsg()); BOOST_REQUIRE_MESSAGE(server2Client.defs().get(), "Server returned a NULL defs"); - BOOST_REQUIRE_MESSAGE(server2Client.defs()->suiteVec().size() == 1, " Expected server2 to have one suite"); + BOOST_REQUIRE_MESSAGE(server2Client.defs()->suites().size() == 1, " Expected server2 to have one suite"); } BOOST_AUTO_TEST_CASE(test_server_plug_cmd) { @@ -382,8 +382,10 @@ BOOST_AUTO_TEST_CASE(test_server_plug_cmd) { BOOST_REQUIRE_MESSAGE(invokeServer2.server_started(), "Server failed to start on " << invokeServer2.host() << ":" << invokeServer2.port()); - test_plug_on_multiple_server( - ClientEnvironment::hostSpecified(), ClientEnvironment::portSpecified(), Str::LOCALHOST(), port2); + test_plug_on_multiple_server(ClientEnvironment::hostSpecified(), + ClientEnvironment::portSpecified(), + ecf::string_constants::localhost, + port2); } } diff --git a/libs/client/test/TestServer.cpp b/libs/client/test/TestServer.cpp index 3906ecaf8..eabbeeaca 100644 --- a/libs/client/test/TestServer.cpp +++ b/libs/client/test/TestServer.cpp @@ -146,9 +146,10 @@ BOOST_AUTO_TEST_CASE(test_server_state_changes) { // This check only valid if server was invoked locally. Ignore for remote servers // make sure edit history updated - BOOST_REQUIRE_MESSAGE(theClient.edit_history(Str::ROOT_PATH()) == 0, - CtsApi::to_string(CtsApi::edit_history(Str::ROOT_PATH())) << " should return 0\n" - << theClient.errorMsg()); + BOOST_REQUIRE_MESSAGE(theClient.edit_history(ecf::string_constants::root_path) == 0, + CtsApi::to_string(CtsApi::edit_history(ecf::string_constants::root_path)) + << " should return 0\n" + << theClient.errorMsg()); BOOST_REQUIRE_MESSAGE(theClient.server_reply().get_string_vec().size() == 7, "Expected edit history of size 7, but found " << theClient.server_reply().get_string_vec().size()); @@ -157,17 +158,18 @@ BOOST_AUTO_TEST_CASE(test_server_state_changes) { BOOST_REQUIRE_MESSAGE(theClient.getDefs() == 0, CtsApi::get() << " failed should return 0\n" << theClient.errorMsg()); - BOOST_REQUIRE_MESSAGE(theClient.defs()->get_edit_history(Str::ROOT_PATH()).size() == 0, + BOOST_REQUIRE_MESSAGE(theClient.defs()->get_edit_history(ecf::string_constants::root_path).size() == 0, "Expected edit history of size 0, but found " - << theClient.defs()->get_edit_history(Str::ROOT_PATH()).size()); + << theClient.defs()->get_edit_history(ecf::string_constants::root_path).size()); // clear edit history BOOST_REQUIRE_MESSAGE(theClient.edit_history("clear") == 0, CtsApi::to_string(CtsApi::edit_history("clear")) << " should return 0\n" << theClient.errorMsg()); - BOOST_REQUIRE_MESSAGE(theClient.edit_history(Str::ROOT_PATH()) == 0, - CtsApi::to_string(CtsApi::edit_history(Str::ROOT_PATH())) << " should return 0\n" - << theClient.errorMsg()); + BOOST_REQUIRE_MESSAGE(theClient.edit_history(ecf::string_constants::root_path) == 0, + CtsApi::to_string(CtsApi::edit_history(ecf::string_constants::root_path)) + << " should return 0\n" + << theClient.errorMsg()); BOOST_REQUIRE_MESSAGE(theClient.server_reply().get_string_vec().size() == 0, "Expected no edit history " << theClient.server_reply().get_string_vec().size()); } @@ -219,23 +221,24 @@ BOOST_AUTO_TEST_CASE(test_server_state_changes_with_auto_sync) { // This check only valid if server was invoked locally. Ignore for remote servers // make sure edit history updated - BOOST_REQUIRE_MESSAGE(theClient.edit_history(Str::ROOT_PATH()) == 0, - CtsApi::to_string(CtsApi::edit_history(Str::ROOT_PATH())) << " should return 0\n" - << theClient.errorMsg()); + BOOST_REQUIRE_MESSAGE(theClient.edit_history(ecf::string_constants::root_path) == 0, + CtsApi::to_string(CtsApi::edit_history(ecf::string_constants::root_path)) + << " should return 0\n" + << theClient.errorMsg()); BOOST_REQUIRE_MESSAGE(theClient.server_reply().get_string_vec().size() == 8 && theClient.server_reply().get_string_vec().size() <= Defs::max_edit_history_size_per_node(), "Expected edit history of size 8, but found " << theClient.server_reply().get_string_vec().size() << "\n" - << Str::dump_string_vec(theClient.server_reply().get_string_vec())); + << ecf::algorithm::as_string(theClient.server_reply().get_string_vec())); // make sure edit history was *NOT* serialized, It is only serialized when check pointing BOOST_REQUIRE_MESSAGE(theClient.getDefs() == 0, CtsApi::get() << " failed should return 0\n" << theClient.errorMsg()); - BOOST_REQUIRE_MESSAGE(theClient.defs()->get_edit_history(Str::ROOT_PATH()).size() == 0, + BOOST_REQUIRE_MESSAGE(theClient.defs()->get_edit_history(ecf::string_constants::root_path).size() == 0, "Expected edit history of size 0, but found " - << theClient.defs()->get_edit_history(Str::ROOT_PATH()).size()); + << theClient.defs()->get_edit_history(ecf::string_constants::root_path).size()); } } @@ -291,7 +294,7 @@ BOOST_AUTO_TEST_CASE(test_server_stress_test) { BOOST_REQUIRE_MESSAGE(theClient.sync_local() == 0, "failed should return 0\n" << theClient.errorMsg()); BOOST_REQUIRE_MESSAGE(theClient.defs().get(), "Server returned a NULL defs"); - BOOST_REQUIRE_MESSAGE(theClient.defs()->suiteVec().size() >= 1, " no suite ?"); + BOOST_REQUIRE_MESSAGE(theClient.defs()->suites().size() >= 1, " no suite ?"); } std::cout << " Server handled " << load * 16 << " requests in CPU timer(" << timer << ")" << " Duration timer(" << DurationTimer::to_simple_string(duration_timer.elapsed()) << ")" << " Chrono timer(" @@ -325,7 +328,7 @@ BOOST_AUTO_TEST_CASE(test_server_stress_test) { << theClient.errorMsg()); BOOST_REQUIRE_MESSAGE(theClient.defs().get(), "Server returned a NULL defs"); - BOOST_REQUIRE_MESSAGE(theClient.defs()->suiteVec().size() >= 1, " no suite ?"); + BOOST_REQUIRE_MESSAGE(theClient.defs()->suites().size() >= 1, " no suite ?"); } std::cout << " Server handled " << load * 8 << " requests in boost_timer(" << timer << ")" << " DurationTimer(" << DurationTimer::to_simple_string(duration_timer.elapsed()) << ")" << " Chrono_timer(" @@ -379,7 +382,7 @@ BOOST_AUTO_TEST_CASE(test_server_group_stress_test) { "Group request " << CtsApi::group(groupRequest) << " failed should return 0\n" << theClient.errorMsg()); BOOST_REQUIRE_MESSAGE(theClient.defs().get(), "Server returned a NULL defs"); - BOOST_REQUIRE_MESSAGE(theClient.defs()->suiteVec().size() >= 1, " no suite ?"); + BOOST_REQUIRE_MESSAGE(theClient.defs()->suites().size() >= 1, " no suite ?"); } std::cout << " Server handled " << load * 8 << " commands using " << load << " group requests in boost_timer(" << timer << ") DurationTimer(" << DurationTimer::to_simple_string(duration_timer.elapsed()) << ")" @@ -605,7 +608,7 @@ BOOST_AUTO_TEST_CASE(test_server_stress_test_2) { CtsApi::get() << " failed should return 0\n" << theClient.errorMsg()); // 60 BOOST_REQUIRE_MESSAGE(theClient.defs().get(), "Server returned a NULL defs"); - BOOST_REQUIRE_MESSAGE(theClient.defs()->suiteVec().size() >= 1, " no suite ?"); + BOOST_REQUIRE_MESSAGE(theClient.defs()->suites().size() >= 1, " no suite ?"); } int no_of_client_calls = 74; diff --git a/libs/client/test/TestServerAndLifeCycle.cpp b/libs/client/test/TestServerAndLifeCycle.cpp index 48560c94f..2e12249bf 100644 --- a/libs/client/test/TestServerAndLifeCycle.cpp +++ b/libs/client/test/TestServerAndLifeCycle.cpp @@ -77,7 +77,7 @@ BOOST_AUTO_TEST_CASE(test_client_lifecyle) { << theClient.errorMsg()); defs_ptr serverDefs = theClient.defs(); BOOST_REQUIRE_MESSAGE(serverDefs.get(), "Server returned a NULL defs"); - BOOST_REQUIRE_MESSAGE(serverDefs->suiteVec().size() >= 1, " no suite ?"); + BOOST_REQUIRE_MESSAGE(serverDefs->suites().size() >= 1, " no suite ?"); } // Now go through and simulate client request to change Node tree state. @@ -267,8 +267,8 @@ BOOST_AUTO_TEST_CASE(test_client_lifecyle) { BOOST_REQUIRE_MESSAGE(!theEvent.empty(), "Could not find the event myEvent"); BOOST_REQUIRE_MESSAGE(theEvent.value(), "The event was not set"); - const std::vector& suiteVec = serverDefs->suiteVec(); - suite_ptr suite = suiteVec.back(); + const auto& suites = serverDefs->suites(); + suite_ptr suite = suites.back(); BOOST_REQUIRE_MESSAGE(suite->state() == NState::COMPLETE, "Suite expected NState::COMPLETE, but found to be " << NState::toString(suite->state())); } diff --git a/libs/client/test/harness/InvokeServer.hpp b/libs/client/test/harness/InvokeServer.hpp index 01ee96885..5736104a0 100644 --- a/libs/client/test/harness/InvokeServer.hpp +++ b/libs/client/test/harness/InvokeServer.hpp @@ -32,7 +32,7 @@ class InvokeServer { InvokeServer& operator=(InvokeServer&&) = delete; explicit InvokeServer(const std::string& msg, - const std::string& port = ecf::Str::DEFAULT_PORT_NUMBER(), + const std::string& port = ecf::string_constants::default_port_number, bool disable_job_generation = false, bool remove_checkpt_file_before_server_start = true, bool remove_checkpt_file_after_server_exit = true) @@ -58,7 +58,7 @@ class InvokeServer { std::string test_name = msg; test_name += " on "; test_name += host_; - test_name += ecf::Str::COLON(); + test_name += ecf::string_constants::colon; test_name += port_; std::cout << test_name << std::endl; @@ -88,7 +88,7 @@ class InvokeServer { // This will also remove the generated files. // Will only terminate local server, host_ is *EMPTY* for local server, using two constructors above if (host_.empty()) { - doEnd(ecf::Str::LOCALHOST(), + doEnd(ecf::string_constants::localhost, port_, remove_checkpt_file_after_server_exit_, remove_log_file_after_server_exit_); @@ -98,7 +98,7 @@ class InvokeServer { const std::string& port() const { return port_; } const std::string& host() const { if (host_.empty()) { - return ecf::Str::LOCALHOST(); + return ecf::string_constants::localhost; } return host_; } @@ -146,7 +146,7 @@ class InvokeServer { (void)system(theServerInvokePath.c_str()); // Allow time for server process to kick in. - ClientInvoker theClient(ecf::Str::LOCALHOST(), port); + ClientInvoker theClient(ecf::string_constants::localhost, port); if (theClient.wait_for_server_reply()) { server_started = true; if (!msg.empty()) { @@ -162,8 +162,6 @@ class InvokeServer { const std::string& port, bool remove_checkpt_file_after_server_exit, bool remove_log_file_after_server_exit) { - // std::cout << "*****InvokeServer::doEnd Closing server on " << host << ecf::Str::COLON() << port << - // "\n"; { ClientInvoker theClient(host, port); BOOST_REQUIRE_NO_THROW(theClient.terminateServer()); diff --git a/libs/client/test/harness/SCPort.cpp b/libs/client/test/harness/SCPort.cpp index 56a967216..28795f08c 100644 --- a/libs/client/test/harness/SCPort.cpp +++ b/libs/client/test/harness/SCPort.cpp @@ -59,7 +59,7 @@ std::string SCPort::next() { std::cout << " ECF_HOST('" << host << "')"; } - if (host == Str::LOCALHOST()) { + if (host == ecf::string_constants::localhost) { if (auto port = ecf::environment::fetch(ecf::environment::ECF_PORT); port) { if (!port.value().empty()) { if (debug) { @@ -96,7 +96,7 @@ std::string SCPort::next_only(bool debug) { if (debug) { std::cout << " SCPort::next_only() seed_port(" << thePort_ << ")\n"; } - return SCPort::find_free_port(thePort_, debug); + return SCPort::find_available_port(std::to_string(thePort_)); } bool SCPort::is_free_port(int port, bool debug) { @@ -111,15 +111,16 @@ bool SCPort::is_free_port(int port, bool debug) { } ClientInvoker client; - client.set_retry_connection_period(1); // avoid long wait - client.set_connection_attempts(1); // avoid long wait + client.set_retry_connection_period(std::chrono::seconds{1}); // avoid long wait + client.set_connection_attempts(1); // avoid long wait const auto the_port = ecf::convert_to(port); try { if (debug) { - std::cout << " Trying to connect to server on '" << Str::LOCALHOST() << ":" << the_port << "'\n"; + std::cout << " Trying to connect to server on '" << ecf::string_constants::localhost << ":" << the_port + << "'\n"; } - client.set_host_port(Str::LOCALHOST(), the_port); + client.set_host_port(ecf::string_constants::localhost, the_port); client.pingServer(); if (debug) { std::cout << " Connected to server on port " << the_port << ". Returning FALSE\n"; @@ -166,15 +167,16 @@ std::string SCPort::find_free_port(int seed_port_number, bool debug) { int the_port = seed_port_number; std::string free_port; ClientInvoker client; - client.set_retry_connection_period(1); // avoid long wait - client.set_connection_attempts(1); // avoid long wait + client.set_retry_connection_period(std::chrono::seconds{1}); // avoid long wait + client.set_connection_attempts(1); // avoid long wait while (true) { free_port = ecf::convert_to(the_port); try { if (debug) { - std::cout << " Trying to connect to server on '" << Str::LOCALHOST() << ":" << free_port << "'\n"; + std::cout << " Trying to connect to server on '" << ecf::string_constants::localhost << ":" + << free_port << "'\n"; } - client.set_host_port(Str::LOCALHOST(), free_port); + client.set_host_port(ecf::string_constants::localhost, free_port); client.pingServer(); if (debug) { std::cout << " Connected to server on port " << free_port << " trying next port\n"; diff --git a/libs/core/CMakeLists.txt b/libs/core/CMakeLists.txt index 4d9e5f887..202338f03 100644 --- a/libs/core/CMakeLists.txt +++ b/libs/core/CMakeLists.txt @@ -12,17 +12,26 @@ ## Notice: test_scaffold is an INTERFACE-only test utility library. ## +if(${SELECTED_BOOST_VERSION} VERSION_GREATER_EQUAL 1.86.0) + set(ADDITIONAL_BOOST_COMPONENTS Boost::process) +endif() + ecbuild_add_library( TARGET test_scaffold NOINSTALL - TYPE INTERFACE + TYPE STATIC SOURCES test/scaffold/ecflow/test/scaffold/Naming.hpp + test/scaffold/ecflow/test/scaffold/Process.hpp test/scaffold/ecflow/test/scaffold/Provisioning.hpp test/scaffold/ecflow/test/scaffold/Serialisation.hpp + test/scaffold/ecflow/test/scaffold/Process.cpp PUBLIC_INCLUDES test/scaffold + PUBLIC_LIBS + Boost::boost + ${ADDITIONAL_BOOST_COMPONENTS} ) target_clangformat(test_scaffold) @@ -47,6 +56,7 @@ set(test_srcs test/TestLog.cpp test/TestMessage.cpp test/TestMigration.cpp + test/TestNodeNameValidity.cpp test/TestNodePath.cpp test/TestPasswdFile.cpp test/TestPasswordEncryption.cpp diff --git a/libs/core/src/ecflow/core/Base64.cpp b/libs/core/src/ecflow/core/Base64.cpp index 8af273ea6..69317c224 100644 --- a/libs/core/src/ecflow/core/Base64.cpp +++ b/libs/core/src/ecflow/core/Base64.cpp @@ -10,33 +10,176 @@ #include "ecflow/core/Base64.hpp" -#include +#include +#include +#include -#include -#include -#include -#include +namespace { + +constexpr std::array make_encoding_table() { + return std::array{ + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', + 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', + 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'}; +} + +constexpr auto encoding_table = make_encoding_table(); + +constexpr unsigned char padding_marker = '='; +constexpr uint8_t invalid_marker = 0xFF; + +constexpr std::array make_decoding_table() { + std::array table{}; + for (auto& v : table) { + v = invalid_marker; + } + for (uint8_t i = 0; i < static_cast(encoding_table.size()); ++i) { + table[encoding_table[i]] = i; + } + table[padding_marker] = 0; + return table; +} + +constexpr auto decoding_table = make_decoding_table(); + +bool is_base64_char(unsigned char c) { + return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || (c == '+' || c == '/'); +} + +bool is_base64_padding(unsigned char c) { + return c == padding_marker; +} + +bool is_base64(unsigned char c) { + return is_base64_char(c) || is_base64_padding(c); +} + +} // namespace namespace ecf { -std::string decode_base64(const std::string& val) { - using namespace boost::archive::iterators; - using It = transform_width, 8, 6>; - return boost::algorithm::trim_right_copy_if(std::string(It(std::begin(val)), It(std::end(val))), - [](char c) { return c == '\0'; }); +std::string decode_base64(std::string_view value) { + if (value.empty()) { + return {}; + } + + const auto len = value.size(); + if (len % 4 != 0) { + throw std::invalid_argument("Invalid base64 input length"); + } + + // Determine output size, provisioning padding at the end when necessary + size_t out_len = (len / 4) * 3; + if (value[len - 1] == '=') { + --out_len; + } + if (value[len - 2] == '=') { + --out_len; + + // Detect invalid padding, with a non-equal sign after the first equals (e.g. Zg=a) + if (value[len - 1] != '=') { + throw std::invalid_argument("Invalid base64 padding"); + } + } + + std::string result; + result.reserve(out_len); + + for (size_t i = 0; i < len; i += 4) { + + auto a = static_cast(value[i]); + auto b = static_cast(value[i + 1]); + auto c = static_cast(value[i + 2]); + auto d = static_cast(value[i + 3]); + + if (!is_base64(a) || !is_base64(b) || !is_base64(c) || !is_base64(d)) { + throw std::invalid_argument("Invalid base64 input"); + } + + uint32_t sextet_a = decoding_table[a]; + uint32_t sextet_b = decoding_table[b]; + uint32_t sextet_c = decoding_table[c]; + uint32_t sextet_d = decoding_table[d]; + + uint32_t triple = (sextet_a << 18) | (sextet_b << 12) | (sextet_c << 6) | sextet_d; + + result += static_cast((triple >> 16) & 0xFF); + if (c != '=') { + result += static_cast((triple >> 8) & 0xFF); + } + if (d != '=') { + result += static_cast(triple & 0xFF); + } + } + + return result; } -std::string encode_base64(const std::string& val) { - using namespace boost::archive::iterators; - using It = base64_from_binary>; - auto tmp = std::string(It(std::begin(val)), It(std::end(val))); - return tmp.append((3 - val.size() % 3) % 3, '='); +std::string encode_base64(std::string_view value) { + if (value.empty()) { + return {}; + } + + const auto len = value.size(); + // Output size: 4 characters for every 3 input bytes, rounded up + size_t out_len = 4 * ((len + 2) / 3); + + std::string result; + result.reserve(out_len); + + for (size_t i = 0; i < len; i += 3) { + auto a = static_cast(value[i]); + auto b = (i + 1 < len) ? static_cast(value[i + 1]) : uint8_t{0}; + auto c = (i + 2 < len) ? static_cast(value[i + 2]) : uint8_t{0}; + + uint32_t triple = (static_cast(a) << 16) | (static_cast(b) << 8) | static_cast(c); + + result += encoding_table[(triple >> 18) & 0x3F]; + result += encoding_table[(triple >> 12) & 0x3F]; + result += (i + 1 < len) ? encoding_table[(triple >> 6) & 0x3F] : '='; + result += (i + 2 < len) ? encoding_table[triple & 0x3F] : '='; + } + + return result; } -bool validate_base64(const std::string& value) { - static const std::regex pattern("^([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{4}|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)$"); +bool validate_base64(std::string_view value) { + // An empty string is considered valid base64 (represents empty data) + if (value.empty()) { + return true; + } + + // An invalid sized string is considered invalid base64 (must be padded with '=' to multiple of 4) + if (value.size() % 4 != 0) { + return false; + } + + // Check that all characters are valid base64 characters + // Padding ('=') is only allowed at the end (last 1 or 2 characters) + size_t padding_start = value.size(); + for (size_t i = 0; i < value.size(); ++i) { + auto c = static_cast(value[i]); + if (value[i] == '=') { + padding_start = i; + break; + } + if (decoding_table[c] == 0xFF) { + return false; // invalid character + } + } + + // Verify padding: only 0, 1, or 2 '=' characters at the very end + size_t padding_len = value.size() - padding_start; + if (padding_len > 2) { + return false; + } + for (size_t i = padding_start; i < value.size(); ++i) { + if (value[i] != '=') { + return false; // non-padding character after padding + } + } - return value.size() % 4 == 0 && std::regex_match(value, pattern); + return true; } } // namespace ecf diff --git a/libs/core/src/ecflow/core/Base64.hpp b/libs/core/src/ecflow/core/Base64.hpp index 606f039ea..24379e729 100644 --- a/libs/core/src/ecflow/core/Base64.hpp +++ b/libs/core/src/ecflow/core/Base64.hpp @@ -16,35 +16,38 @@ namespace ecf { -/** - * @brief Decode a base64 encoded string. - * - * @param value the base64 encoded string - * @return the decoded string - */ -std::string decode_base64(const std::string& value); - -/** - * @brief Encode a string to base64. - * - * @param value the string to encode - * @return the base64 encoded string - */ -std::string encode_base64(const std::string& value); - -/** - * @brief Simple validation of base64 encoded string. - * - * Checks the following conditions: - * - length has to be multiple of 4 - * - valid characters are A-Za-z0-9+/ - * padding of 0-2 characters at the end of string can be = - * - * @param value the base64 encoded string - * @return true if valid, false otherwise - */ - -bool validate_base64(const std::string& value); +/// +/// @brief Decode a base64 encoded string. +/// +/// @param value the base64 encoded string +/// @return the decoded string +/// +/// @throws std::invalid_argument if the input has size not multiple of 4 +/// @throws std::invalid_argument if any character in the input is invalid (not in A-Za-z0-9+/ or '=' for padding) +/// @throws std::invalid_argument if padding is invalid (more than 2 '=', or non-padding characters after first '=') +/// +std::string decode_base64(std::string_view value); + +/// +/// @brief Encode a string to base64. +/// +/// @param value the string to encode +/// @return the base64 encoded string +/// +std::string encode_base64(std::string_view value); + +/// +/// @brief Simple validation of base64 encoded string. +/// +/// Checks the following conditions: +/// - length has to be multiple of 4 (n.b. 0 if a valid lenght) +/// - valid characters are A-Za-z0-9+/ +/// - '=' can be used for padding (0, 1, or 2 characters) at the end of string +/// +/// @param value the base64 encoded string +/// @return true if valid, false otherwise +/// +bool validate_base64(std::string_view value); } // namespace ecf diff --git a/libs/core/src/ecflow/core/Child.cpp b/libs/core/src/ecflow/core/Child.cpp index 6d032012d..2a627ad32 100644 --- a/libs/core/src/ecflow/core/Child.cpp +++ b/libs/core/src/ecflow/core/Child.cpp @@ -97,7 +97,7 @@ std::string Child::to_string(Child::CmdType ct) { std::vector Child::child_cmds(const std::string& s) { // expect single or , separated tokens std::vector tokens; - Str::split(s, tokens, ","); + ecf::algorithm::split_at(tokens, s, ","); std::vector ret; ret.reserve(tokens.size()); for (const auto& token : tokens) { @@ -122,7 +122,7 @@ bool Child::valid_child_cmds(const std::string& s) { // expect single or , separated tokens std::vector tokens; - Str::split(s, tokens, ","); + ecf::algorithm::split_at(tokens, s, ","); for (const auto& token : tokens) { if (!valid_child_cmd(token)) { return false; diff --git a/libs/core/src/ecflow/core/CommandLine.hpp b/libs/core/src/ecflow/core/CommandLine.hpp index 15e752260..0e5308956 100644 --- a/libs/core/src/ecflow/core/CommandLine.hpp +++ b/libs/core/src/ecflow/core/CommandLine.hpp @@ -15,12 +15,12 @@ #include #include -/** - * CommandLine is a thin wrapper/container for the contents of an ecFlow client command line. - * - * Note: This class is related to class ArgvCreator, which takes the vector of the command - * line tokens and converts them into argc+argv. - */ +/// +/// @brief CommandLine is a thin wrapper/container for the contents of an ecFlow client command line. +/// +/// Note: This class is related to class ArgvCreator, which takes the vector of the command +/// line tokens and converts them into argc+argv. +/// class CommandLine { public: using cl_t = std::string; @@ -28,37 +28,40 @@ class CommandLine { using size_t = tokens_t::size_type; public: - /** - * Create a Command Line instance based on argc + argv - * - * @param argc the number of tokens in the command line - * @param argv the tokens in the command line - */ + /// + /// @brief Create a Command Line instance based on argc + argv + /// + /// @param argc the number of tokens in the command line + /// @param argv the tokens in the command line + /// CommandLine(int argc, char** argv); CommandLine(int argc, const char** argv); - /** - * Create a Command Line instance based on a sequence of characters, - * considering that it might include single/double quotes. - * - * @param cl the command line as a sequence of characters - */ + /// + /// @brief Create a Command Line instance based on a sequence of characters, + /// considering that it might include single/double quotes. + /// + /// @param cl the command line as a sequence of characters + /// @throws std::runtime_error if the command line contains an invalid escape sequence + /// or unmatched quotation marks + /// explicit CommandLine(const cl_t& cl); - /** - * Create a Command Line instance based on a sequence of tokens. - * - * @param tokens the command line as a sequence of tokens - */ + /// + /// @brief Create a Command Line instance based on a sequence of tokens. + /// + /// @param tokens the command line as a sequence of tokens + /// explicit CommandLine(tokens_t tokens); - /** - * Create a Command Line instance based on multiple tokens. - * Tokens can be either strings or collections of strings. In the latter case, the collection of strings are - * added to the command line element-by-element. - * - * @param tokens the command line as multiple tokens - */ + /// + /// @brief Create a Command Line instance based on multiple tokens. + /// Tokens can be either strings or collections of strings. In the latter case, the collection of strings are + /// added to the command line element-by-element. + /// + /// @param tokens the command line as multiple tokens + /// @return a CommandLine instance composed of the provided tokens + /// template static CommandLine make_command_line(TOKENS... tokens) { auto push = Overload{ @@ -72,28 +75,34 @@ class CommandLine { return CommandLine(ts); } - /** - * Access the original content of the command line. - */ + /// + /// @brief Access the original content of the command line. + /// + /// @return the command line as a single string, with tokens separated by spaces + /// [[nodiscard]] cl_t original() const; - /** - * Access the number of tokens composing the command line. - * - * @return the number of tokens - */ + /// + /// @brief Access the number of tokens composing the command line. + /// + /// @return the number of tokens + /// [[nodiscard]] size_t size() const; - /** - * Access the sequence of tokens composing the command line. - * - * @return the command line tokens - */ + /// + /// @brief Access the sequence of tokens composing the command line. + /// + /// @return the command line tokens + /// [[nodiscard]] const tokens_t& tokens() const; - /** - * Pretty print the command line - */ + /// + /// @brief Pretty print the command line + /// + /// @param os the output stream to write to + /// @param cl the command line to print + /// @return the output stream @p os + /// friend std::ostream& operator<<(std::ostream& os, const CommandLine& cl); private: diff --git a/libs/core/src/ecflow/core/EcfPortLock.hpp b/libs/core/src/ecflow/core/EcfPortLock.hpp index 2894ea9f2..061245232 100644 --- a/libs/core/src/ecflow/core/EcfPortLock.hpp +++ b/libs/core/src/ecflow/core/EcfPortLock.hpp @@ -22,6 +22,8 @@ #include #include +#include + #include "ecflow/core/Converter.hpp" #include "ecflow/core/Environment.hpp" #include "ecflow/core/File.hpp" @@ -109,11 +111,55 @@ class EcfPortLock { fs::remove(the_file); } + /// + /// @brief Checks whether the given TCP port can be bound on the local machine. + /// + /// @param port The TCP port number to check. + /// @return true if port is free, false if it is occupied or if any error occurs during the check. + /// + static bool is_tcp_port_free(unsigned short port) { + using namespace boost::asio; + + io_context io; + ip::tcp::acceptor a(io); + + boost::system::error_code ec; + a.open(ip::tcp::v4(), ec); + if (ec) { + // If we cannot open a socket, assume the port is in use. + std::cout << " EcfPortLock::is_port_free(" << port << ") : FALSE (unable to open socket)\n "; + return false; + } + a.bind({ip::tcp::v4(), port}, ec); + if (ec) { + if (ec == error::address_in_use) { + std::cout << " EcfPortLock::is_port_free(" << port + << ") : FALSE (unable to bind, due to port already in use)\n "; + } + else { + std::cout << " EcfPortLock::is_port_free(" << port + << ") : FALSE (unable to bind, due to unknown issue)\n "; + } + return false; + } + return true; + } + static bool is_free(int port, bool debug = false) { std::string the_port = ecf::convert_to(port); + // 1. File-lock check (fast path) if (fs::exists(port_file(the_port))) { if (debug) { - std::cout << " EcfPortLock::is_free(" << port << ") returning FALSE\n "; + std::cout << " EcfPortLock::is_free(" << port << ") returning FALSE (lock file exists)\n "; + } + return false; + } + + // 2. TCP socket check + // This ensures the port is free, by actually trying to binding to it (and immediatelly releasing it). + if (!is_tcp_port_free(port)) { + if (debug) { + std::cout << " EcfPortLock::is_free(" << port << ") returning FALSE (TCP port occupied)\n "; } return false; } diff --git a/libs/core/src/ecflow/core/Enumerate.hpp b/libs/core/src/ecflow/core/Enumerate.hpp index dd1fe6b40..0c240c959 100644 --- a/libs/core/src/ecflow/core/Enumerate.hpp +++ b/libs/core/src/ecflow/core/Enumerate.hpp @@ -24,16 +24,15 @@ namespace ecf { namespace detail { -/** - * EnumTraits defines the mapping between a set of enum values and their designation. - * - * EnumTraits must define: - * - the mapping `map`, provided as a std::array composed of a std::pair for each enum value - * - the `size`, holding the number os entries in `map` - * - * @tparam E - */ - +/// +/// @brief EnumTraits defines the mapping between a set of enum values and their designation. +/// +/// EnumTraits must define: +/// - the mapping `map`, provided as a std::array composed of a std::pair for each enum value +/// - the `size`, holding the number of entries in `map` +/// +/// @tparam E the enum type for which the traits are defined +/// template struct EnumTraits { @@ -41,6 +40,12 @@ struct EnumTraits } // namespace detail +/// +/// @brief Enumerate provides bidirectional mapping between enum values and their string designations. +/// +/// @tparam E the enum type to map +/// @tparam TRAITS the traits type providing the mapping; defaults to detail::EnumTraits +/// template > struct Enumerate { @@ -48,15 +53,15 @@ struct Enumerate using enum_t = E; using string_t = std::string_view; - /** - * Convert the given enum value to its designation - * - * This is an "unsafe" operation, as it assumes that the given enum value exists in the mapping. - * If the enum value does not exist, an assertion failure is raised. - * - * @param e the enum value - * @return the associated designation - */ + /// + /// @brief Convert the given enum value to its designation. + /// + /// This is an "unsafe" operation, as it assumes that the given enum value exists in the mapping. + /// If the enum value does not exist, an assertion failure is raised. + /// + /// @param e the enum value + /// @return the associated designation + /// static constexpr string_t as_string(enum_t e) noexcept { auto found = std::find_if( std::begin(TRAITS::map), std::end(TRAITS::map), [&](const auto& item) { return item.first == e; }); @@ -66,12 +71,12 @@ struct Enumerate return found->second; } - /** - * Convert the given enum value to its designation - * - * @param e the enum value - * @return the associated designation, in case it exists; an empty optional, otherwise - */ + /// + /// @brief Convert the given enum value to its designation. + /// + /// @param e the enum value + /// @return the associated designation, in case it exists; an empty optional, otherwise + /// static constexpr std::optional to_string(enum_t e) noexcept { if (auto found = std::find_if( std::begin(TRAITS::map), std::end(TRAITS::map), [&](const auto& item) { return item.first == e; }); @@ -82,12 +87,12 @@ struct Enumerate return std::nullopt; } - /** - * Convert the given designation to the related enum value - * - * @param s the designation - * @return the enum value, in case it exists; an empty optional, otherwise - */ + /// + /// @brief Convert the given designation to the related enum value. + /// + /// @param s the designation + /// @return the enum value, in case it exists; an empty optional, otherwise + /// static constexpr std::optional to_enum(string_t s) noexcept { if (auto found = std::find_if( std::begin(TRAITS::map), std::end(TRAITS::map), [&](const auto& item) { return item.second == s; }); @@ -98,26 +103,28 @@ struct Enumerate return std::nullopt; } - /** - * Checks if the given designation is valid (i.e. has a related enum value) - * - * @param s the designation - * @return true, if valid; false, otherwise - */ + /// + /// @brief Check if the given designation is valid (i.e. has a related enum value). + /// + /// @param s the designation + /// @return true, if valid; false, otherwise + /// static constexpr bool is_valid(string_t s) { auto found = std::find_if( std::begin(TRAITS::map), std::end(TRAITS::map), [&](const auto& item) { return item.second == s; }); return found != std::end(TRAITS::map); } - /** - * The number of mapped enum values - */ + /// + /// @brief The number of mapped enum values. + /// static const size_t size = TRAITS::size; - /** - * The vector of mapped enum values - */ + /// + /// @brief Collect all mapped enum values. + /// + /// @return a vector containing all mapped enum values + /// static auto enums() { std::vector result; result.reserve(TRAITS::size); @@ -128,9 +135,11 @@ struct Enumerate return result; } - /** - * The vector of mapped designations - */ + /// + /// @brief Collect all mapped designations. + /// + /// @return a vector containing all mapped designations + /// static auto designations() { std::vector result; result.reserve(TRAITS::size); diff --git a/libs/core/src/ecflow/core/Environment.hpp b/libs/core/src/ecflow/core/Environment.hpp index 674d34973..63e982dca 100644 --- a/libs/core/src/ecflow/core/Environment.hpp +++ b/libs/core/src/ecflow/core/Environment.hpp @@ -143,23 +143,26 @@ struct Wrapper } // namespace +/// +/// @brief Exception thrown when a required environment variable is not found. +/// struct EnvVarNotFound : public std::runtime_error { explicit EnvVarNotFound(std::basic_string what) : std::runtime_error(what) {} }; -/** - * @brief Retrieves the environment variable value and stores it in the given variable. - * If the environment variable is not found, the variable is left unchanged. - * - * In case of integral types, the environment variable is converted to the corresponding type. - * In case of bool type, if the environment variable is set, the variable is set to true. - * - * @tparam T - * @param name - * @param value - */ +/// +/// @brief Retrieve the environment variable value and store it in the given variable. +/// If the environment variable is not found, the variable is left unchanged. +/// +/// For integral types, the environment variable value is converted to the corresponding type. +/// For bool type, if the environment variable is set, the variable is set to true. +/// +/// @tparam T the type to convert the environment variable value to +/// @param name the name of the environment variable +/// @param value the variable to store the retrieved value in +/// template void get(const char* name, T& value) { if (auto found = Wrapper::get(name); found) { @@ -167,16 +170,18 @@ void get(const char* name, T& value) { } } -/** - * @brief Retrieves the environment variable value and returns it. - * If the environment variable is not found, an exception is thrown. - * - * In case of integral types, the environment variable is converted to the corresponding type. - * In case of bool type, if the environment variable is set, the result is true. - * - * @tparam T - * @param name - */ +/// +/// @brief Retrieve the environment variable value and return it. +/// If the environment variable is not found, an exception is thrown. +/// +/// For integral types, the environment variable value is converted to the corresponding type. +/// For bool type, if the environment variable is set, the result is true. +/// +/// @tparam T the type to convert the environment variable value to; defaults to std::string +/// @param name the name of the environment variable +/// @return the value of the environment variable, converted to type T +/// @throws EnvVarNotFound if the environment variable is not set +/// template T get(const char* name) { if (auto found = Wrapper::get(name); found) { @@ -186,27 +191,28 @@ T get(const char* name) { throw EnvVarNotFound(Message(name).str()); } -/** - * @brief Retrieves the environment variable value and returns it (wrapped in a std::optional<>). - * If the environment variable is not found, std::nullopt is returned. - * - * In case of integral types, the environment variable is converted to the corresponding type. - * In case of bool type, if the environment variable is set, the result is true. - * - * @tparam T - * @param name - */ +/// +/// @brief Retrieve the environment variable value and return it wrapped in a std::optional. +/// If the environment variable is not found, std::nullopt is returned. +/// +/// For integral types, the environment variable value is converted to the corresponding type. +/// For bool type, if the environment variable is set, the result is true. +/// +/// @tparam T the type to convert the environment variable value to; defaults to std::string +/// @param name the name of the environment variable +/// @return the value of the environment variable converted to type T, or std::nullopt if not set +/// template std::optional fetch(const char* name) { return Wrapper::get(name); } -/** - * @brief Checks if an environment variable is set. - * - * @tparam T - * @param name - */ +/// +/// @brief Check if an environment variable is set. +/// +/// @param name the name of the environment variable +/// @return true if the environment variable is set; false otherwise +/// inline bool has(const char* name) { return std::getenv(name) != nullptr; } diff --git a/libs/core/src/ecflow/core/Extract.hpp b/libs/core/src/ecflow/core/Extract.hpp index 6bee6fbd8..f6b01db14 100644 --- a/libs/core/src/ecflow/core/Extract.hpp +++ b/libs/core/src/ecflow/core/Extract.hpp @@ -23,31 +23,38 @@ class Extract { Extract() = delete; /// - /// Extract path and name from given token. + /// @brief Extract path and name from given token. /// - /// Given + /// Given: /// "/suite/family:obj", extracts path = "/suite/family", and name = "obj" /// "/suite/family", extracts path = "/suite/family", and name = "" /// "obj", extracts path = "", and name = "obj" - /// @returns true if extraction succeeded; false, otherwise + /// + /// @param token the input token to extract from + /// @param path the extracted path component + /// @param name the extracted name component + /// @return true if extraction succeeded; false, otherwise /// static bool pathAndName(const std::string& token, std::string& path, std::string& name); /// - /// Extract 2nd token from the given string (considering provided separator) + /// @brief Extract the 2nd token from the given string, using the provided separator. /// - /// Given str = "HH:MM", extracts ret = "MM" + /// Given str = "HH:MM", extracts ret = "MM". /// - /// @returns true if extraction succeeded; false, otherwise + /// @param str the input string to split + /// @param ret the extracted second token + /// @param separator the character used to split the string + /// @return true if extraction succeeded; false, otherwise /// static bool split_get_second(const std::string& str, std::string& ret, char separator = ':'); /// - /// Extract a value of the given type from the provided token + /// @brief Extract a value of the given arithmetic type from the provided token. /// - /// @tparam TO the type of the value to extract - /// @param token the input token - /// @return a Result object, containing either the extracted value, or an error message + /// @tparam TO the arithmetic type of the value to extract + /// @param token the input token to convert + /// @return a Result object containing either the extracted value, or an error message /// template >> static ecf::Result value(const std::string& token) { @@ -61,12 +68,13 @@ class Extract { } /// - /// Extract a value of the given type from the provided token + /// @brief Extract a value of the given type from the provided token, throwing on failure. /// /// @tparam TO the type of the value to extract - /// @param token the input token - /// @param error the error message to the included in the thrown exception - /// @return the extracted value, or throws std::runtime_error exception with the specified error message + /// @param token the input token to convert + /// @param error the error message to include in the thrown exception + /// @return the extracted value + /// @throws std::runtime_error if the token cannot be converted to the requested type /// template static TO value(const std::string& token, const std::string& error) { @@ -78,32 +86,39 @@ class Extract { } /// - /// Extract YMD integer, of the form yyyymmdd, from the given token + /// @brief Extract a YMD integer of the form yyyymmdd from the given token. /// - /// @throws std::runtime_error if extractions fails, with the provided error message included in exception message + /// @param ymdToken the input token containing the date in yyyymmdd format + /// @param errorMsg the error message to include in the thrown exception on failure + /// @return the extracted date as an integer of the form yyyymmdd + /// @throws std::runtime_error if the token is not a valid 8-character date /// static int ymd(const std::string& ymdToken, std::string& errorMsg); /// - /// Extract (optional) integer, from token in index pos + /// @brief Extract an optional integer from the token at the given index. /// - /// Notice: when first character of selected token is '#', the return is the provided default value + /// When the first character of the selected token is '#', the default value is returned. /// - /// Example of tokens: + /// Example: /// ["repeat", "integer", "variable", "1", "2", "#a", "comment"] /// - extracting from "repeat", "integer", "variable", or "comment" throws std::runtime_error /// - extracting from "1", returns 1 - /// - extracting from "2", returns 3 + /// - extracting from "2", returns 2 /// - extracting from "#a", returns the given default value /// ["repeat", "integer", "variable", "1", "2", "#", "a", "comment"] /// - extracting from "repeat", "integer", "variable", "a", or "comment" throws std::runtime_error /// - extracting from "1", returns 1 - /// - extracting from "2", returns 3 + /// - extracting from "2", returns 2 /// - extracting from "#", returns the given default value /// - /// @returns the extracted integer, if extraction succeeded; default value if selected token starts with '#' - /// @throws std::runtime_error if extractions fails, with the provided error message included in exception message - /// + /// @tparam TO the type to use for the conversion + /// @param tokens the sequence of tokens to extract from + /// @param pos the index of the token to extract + /// @param defaultValue the value to return when the selected token starts with '#' + /// @param errorMsg the error message to include in the thrown exception on failure + /// @return the extracted integer, or @p defaultValue if the selected token starts with '#' + /// @throws std::runtime_error if extraction fails /// template static int diff --git a/libs/core/src/ecflow/core/File.cpp b/libs/core/src/ecflow/core/File.cpp index ec38dea21..3b27417a4 100644 --- a/libs/core/src/ecflow/core/File.cpp +++ b/libs/core/src/ecflow/core/File.cpp @@ -56,7 +56,7 @@ std::string File::which(const std::string& file) { if (!env_paths.empty()) { std::string path; std::vector paths; - Str::split(env_paths, paths, ":"); + ecf::algorithm::split_at(paths, env_paths, ":"); size_t path_size = paths.size(); for (size_t i = 0; i < path_size; ++i) { path.clear(); @@ -473,7 +473,7 @@ bool File::createMissingDirectories(const std::string& pathToFileOrDir) { } std::vector thePath; - NodePath::split(pathToFileOrDir, thePath); + ecf::node::split_path(pathToFileOrDir, thePath); try { if (!thePath.empty()) { @@ -495,7 +495,7 @@ bool File::createMissingDirectories(const std::string& pathToFileOrDir) { // if original path had leading slash then add it here, to preserve path if (pathToFileOrDir[0] == '/') { - pathToCreate += Str::PATH_SEPARATOR(); + pathToCreate += ecf::string_constants::path_separator; } for (const auto& i : thePath) { @@ -506,7 +506,7 @@ bool File::createMissingDirectories(const std::string& pathToFileOrDir) { #endif fs::create_directory(pathToCreate); } - pathToCreate += Str::PATH_SEPARATOR(); + pathToCreate += ecf::string_constants::path_separator; } } else { @@ -642,7 +642,7 @@ File::backwardSearch(const std::string& rootPath, const std::string& nodePath, c // /task.ecf std::vector nodePathTokens; - NodePath::split(nodePath, nodePathTokens); + ecf::node::split_path(nodePath, nodePathTokens); LOG_ASSERT(!nodePathTokens.empty(), ""); std::string leafName; // i.e. task in the example above @@ -657,7 +657,7 @@ File::backwardSearch(const std::string& rootPath, const std::string& nodePath, c while (nodePathTokens.size() > 0) { // Reconstitute the path - std::string path = NodePath::createPath(nodePathTokens); + std::string path = ecf::node::create_node_path(nodePathTokens); path += fileExtn; // .ecf, .man , etc std::string combinedPath = rootPath; @@ -712,7 +712,7 @@ std::string File::forwardSearch(const std::string& rootPath, const std::string& /// /task.ecf std::vector nodePathTokens; - NodePath::split(nodePath, nodePathTokens); + ecf::node::split_path(nodePath, nodePathTokens); LOG_ASSERT(!nodePathTokens.empty(), ""); std::string leafName; @@ -727,7 +727,7 @@ std::string File::forwardSearch(const std::string& rootPath, const std::string& while (nodePathTokens.size() > 0) { // Reconstitute the path - std::string path = NodePath::createPath(nodePathTokens); + std::string path = ecf::node::create_node_path(nodePathTokens); path += fileExtn; // .ecf, .man , etc std::string combinedPath = rootPath; diff --git a/libs/core/src/ecflow/core/Filesystem.hpp b/libs/core/src/ecflow/core/Filesystem.hpp index 2db589bd0..a0dcecedd 100644 --- a/libs/core/src/ecflow/core/Filesystem.hpp +++ b/libs/core/src/ecflow/core/Filesystem.hpp @@ -18,21 +18,23 @@ namespace fs = std::filesystem; namespace ecf { namespace fsx { -/** - * Generate a unique path from the given model. - * The one or more '%' characters in the model which will be replaced by random hexadecimal characters (0-9, A-F). - * - * @param model the model for the unique path - * @return the unique path - */ +/// +/// @brief Generate a unique path from the given model. +/// +/// Each '%' character in @p model is replaced by a random hexadecimal digit (0-9, A-F). +/// +/// @param model the path model, with '%' characters as placeholders for random hex digits +/// @return the generated path with all '%' characters replaced by random hex digits +/// std::filesystem::path unique_path(std::string model); -/** - * Get the last write time of the given path, as a std::chrono::system_clock::time_point. - * - * @param path the path - * @return the last write time - */ +/// +/// @brief Get the last write time of the given path as a std::chrono::system_clock::time_point. +/// +/// @param path the path to query +/// @return the last write time of @p path +/// @throws std::filesystem::filesystem_error if @p path does not exist or cannot be accessed +/// std::chrono::system_clock::time_point last_write_time(const std::filesystem::path& path); } // namespace fsx diff --git a/libs/core/src/ecflow/core/Host.cpp b/libs/core/src/ecflow/core/Host.cpp index d8e8de078..ba2565c0d 100644 --- a/libs/core/src/ecflow/core/Host.cpp +++ b/libs/core/src/ecflow/core/Host.cpp @@ -27,7 +27,7 @@ Host::Host() { Host::Host(const std::string& host) : the_host_name_(host) { - if (the_host_name_ == Str::LOCALHOST()) { + if (the_host_name_ == ecf::string_constants::localhost) { get_host_name(); } } @@ -64,7 +64,7 @@ std::string Host::ecf_backup_checkpt_file(const std::string& port) const { } std::string Host::ecf_lists_file(const std::string& port) const { - return prefix_host_and_port(port, Str::WHITE_LIST_FILE()); + return prefix_host_and_port(port, ecf::string_constants::white_list_file); } std::string Host::ecf_passwd_file(const std::string& port) const { diff --git a/libs/core/src/ecflow/core/Host.hpp b/libs/core/src/ecflow/core/Host.hpp index 12316e485..4e66e8d9e 100644 --- a/libs/core/src/ecflow/core/Host.hpp +++ b/libs/core/src/ecflow/core/Host.hpp @@ -15,11 +15,27 @@ namespace ecf { +/// +/// @brief Represents a host and provides helpers to build ecFlow server file names +/// prefixed with the host and port (e.g. @c ..ecf.log). +/// class Host { public: - /// can throw std::runtime_error if the gethostname fails + /// + /// @brief Create a Host instance by resolving the current machine's hostname. + /// + /// @throws std::runtime_error if the hostname cannot be determined + /// Host(); + /// + /// @brief Create a Host instance with the given hostname. + /// + /// If @p host is @c "localhost", the actual machine hostname is resolved. + /// + /// @param host the hostname to use + /// @throws std::runtime_error if @p host is @c "localhost" and the hostname cannot be determined + /// explicit Host(const std::string& host); // Disable copy (and move) semantics @@ -30,28 +46,77 @@ class Host { ~Host() = default; - /// return the host name + /// + /// @brief Get the hostname. + /// + /// @return the hostname + /// std::string name() const; - /// returns the log file name + /// + /// @brief Get the log file name prefixed with the host and port. + /// + /// @param port the server port + /// @return the log file name of the form @c ..ecf.log + /// std::string ecf_log_file(const std::string& port) const; - /// return checkPoint file + /// + /// @brief Get the checkpoint file name prefixed with the host and port. + /// + /// @param port the server port + /// @return the checkpoint file name of the form @c ..check + /// std::string ecf_checkpt_file(const std::string& port) const; - /// return backup checkPoint file + /// + /// @brief Get the backup checkpoint file name prefixed with the host and port. + /// + /// @param port the server port + /// @return the backup checkpoint file name of the form @c ..check.b + /// std::string ecf_backup_checkpt_file(const std::string& port) const; - /// return ecf.list file. White list file used for authentication & authorisation + /// + /// @brief Get the white-list file name prefixed with the host and port. + /// + /// The white-list file is used for authentication and authorisation. + /// + /// @param port the server port + /// @return the white-list file name of the form @c ..ecf.lists + /// std::string ecf_lists_file(const std::string& port) const; - /// return ecf.passwd file. Used for authentication + /// + /// @brief Get the password file name prefixed with the host and port. + /// + /// The password file is used for authentication. + /// + /// @param port the server port + /// @return the password file name of the form @c ..ecf.passwd + /// std::string ecf_passwd_file(const std::string& port) const; - /// return ecf.custom_passwd file. Used for authentication + /// + /// @brief Get the custom password file name prefixed with the host and port. + /// + /// The custom password file is used for authentication. + /// + /// @param port the server port + /// @return the custom password file name of the form @c ..ecf.custom_passwd + /// std::string ecf_custom_passwd_file(const std::string& port) const; - /// Given a port and file name, will return ..file_name + /// + /// @brief Build a file name prefixed with the host and port. + /// + /// If @p file_name contains a path separator (@c /), it is returned unchanged. + /// Otherwise, returns a name of the form @c ... + /// + /// @param port the server port + /// @param file_name the base file name or absolute path + /// @return the prefixed file name, or @p file_name unchanged if it contains a path separator + /// std::string prefix_host_and_port(const std::string& port, std::string_view file_name) const; private: diff --git a/libs/core/src/ecflow/core/Identity.hpp b/libs/core/src/ecflow/core/Identity.hpp index 5c23b1fd6..861a0c4c4 100644 --- a/libs/core/src/ecflow/core/Identity.hpp +++ b/libs/core/src/ecflow/core/Identity.hpp @@ -81,7 +81,7 @@ class CustomUserX { [[nodiscard]] std::string username() const { return username_; } [[nodiscard]] std::string password() const { return password_; } - [[nodiscard]] std::string as_string() const { return "{UserX: " + username_ + ":" + password_ + "}"; } + [[nodiscard]] std::string as_string() const { return "{UserX: " + username_ + ":}"; } private: std::string username_; @@ -97,7 +97,7 @@ class SecureUserX { [[nodiscard]] std::string username() const { return username_; } [[nodiscard]] std::string password() const { return password_; } - [[nodiscard]] std::string as_string() const { return "{SecuredUserX: " + username_ + ":" + password_ + "}"; } + [[nodiscard]] std::string as_string() const { return "{SecuredUserX: " + username_ + ":}"; } private: std::string username_; diff --git a/libs/core/src/ecflow/core/Log.cpp b/libs/core/src/ecflow/core/Log.cpp index 0caa11060..5c7ed11e0 100644 --- a/libs/core/src/ecflow/core/Log.cpp +++ b/libs/core/src/ecflow/core/Log.cpp @@ -110,7 +110,7 @@ void Log::cache_time_stamp() { const std::string& Log::get_cached_time_stamp() const { std::scoped_lock lock(mx_); - return (logImpl_) ? logImpl_->get_cached_time_stamp() : Str::EMPTY(); + return (logImpl_) ? logImpl_->get_cached_time_stamp() : ecf::string_constants::empty; } void Log::flush() { @@ -392,7 +392,7 @@ bool LogImpl::do_log(Log::LogType lt, const std::string& message, bool newline) else { // If message has \n then split into multiple lines std::vector lines; - Str::split(message, lines, "\n"); + ecf::algorithm::split_at(lines, message, "\n"); size_t theSize = lines.size(); for (size_t i = 0; i < theSize; ++i) { file_ << log_type_and_time_stamp_ << lines[i] << '\n'; diff --git a/libs/core/src/ecflow/core/LogVerification.cpp b/libs/core/src/ecflow/core/LogVerification.cpp index b7effc0a3..712a330be 100644 --- a/libs/core/src/ecflow/core/LogVerification.cpp +++ b/libs/core/src/ecflow/core/LogVerification.cpp @@ -42,7 +42,7 @@ bool LogVerification::extractNodePathAndState(const std::string& logfile, } std::vector lineTokens; - Str::split(line, lineTokens); + ecf::algorithm::split_at(lineTokens, line); if (lineTokens.size() < 3) { continue; } @@ -52,7 +52,6 @@ bool LogVerification::extractNodePathAndState(const std::string& logfile, if (!NState::isValid(theState)) { continue; } - // cout << line_number << Str::COLON() << *i << "\n"; pathStateVec.emplace_back(lineTokens[3], theState); } @@ -94,10 +93,10 @@ bool LogVerification::compareNodeStates(const std::string& logfile, std::string theLine, theGoldenLine; if (i < lines.size()) { - theLine = lines[i].second + Str::COLON() + lines[i].first; + theLine = lines[i].second + ecf::string_constants::colon + lines[i].first; } if (i < goldenLines.size()) { - theGoldenLine = goldenLines[i].second + Str::COLON() + goldenLines[i].first; + theGoldenLine = goldenLines[i].second + ecf::string_constants::colon + goldenLines[i].first; } if (i < lines.size() && i < goldenLines.size()) { diff --git a/libs/core/src/ecflow/core/NodePath.cpp b/libs/core/src/ecflow/core/NodePath.cpp index 37d58869b..f0265f2ad 100644 --- a/libs/core/src/ecflow/core/NodePath.cpp +++ b/libs/core/src/ecflow/core/NodePath.cpp @@ -12,20 +12,22 @@ #include "ecflow/core/Str.hpp" -using namespace ecf; +namespace ecf { -void NodePath::split(const std::string& path, std::vector& thePath) { +namespace node { + +void split_path(const std::string& path, std::vector& thePath) { /// The path is of the form "/suite/family/task" - Str::split(path, thePath, Str::PATH_SEPARATOR()); + ecf::algorithm::split_at(thePath, path, ecf::string_constants::path_separator); } -bool NodePath::extractHostPort(const std::string& path, std::string& host, std::string& port) { +bool extract_host_and_port_from_path(const std::string& path, std::string& host, std::string& port) { if (path.empty()) { return false; } std::vector thePath; - NodePath::split(path, thePath); + split_path(path, thePath); if (thePath.empty()) { return false; @@ -53,7 +55,7 @@ bool NodePath::extractHostPort(const std::string& path, std::string& host, std:: return true; } -std::string NodePath::createPath(const std::vector& vec) { +std::string create_node_path(const std::vector& vec) { if (vec.empty()) { return std::string{}; } @@ -61,19 +63,24 @@ std::string NodePath::createPath(const std::vector& vec) { std::string ret; size_t size = vec.size(); for (size_t i = 0; i < size; i++) { - ret += Str::PATH_SEPARATOR(); + ret += ecf::string_constants::path_separator; ret += vec[i]; } return ret; } -std::string NodePath::removeHostPortFromPath(const std::string& path) { +std::string remove_host_and_port_from_path(const std::string& path) { std::vector pathVec; - NodePath::split(path, pathVec); + split_path(path, pathVec); + pathVec.erase(pathVec.begin()); - return NodePath::createPath(pathVec); + return create_node_path(pathVec); } -bool NodePath::isAbsolutePath(const std::string& path) { +bool is_absolute_path(const std::string& path) { return !path.empty() && path[0] == '/'; } + +} // namespace node + +} // namespace ecf diff --git a/libs/core/src/ecflow/core/NodePath.hpp b/libs/core/src/ecflow/core/NodePath.hpp index 307fc30bc..8f00d40a7 100644 --- a/libs/core/src/ecflow/core/NodePath.hpp +++ b/libs/core/src/ecflow/core/NodePath.hpp @@ -14,35 +14,67 @@ #include #include -class NodePath { -public: - // Disable default construction - NodePath() = delete; - - /// returns the path as a vector of strings, preserving the order - /// Note: multiple path separator '/' are treated as one separator. - /// Mimics unix path conventions. hence - /// '/suite//family///task' will be extracted as 'suite','family','task' - static void split(const std::string& path, std::vector&); - - /// If the path has form: - /// :/suite/family/task - /// extract the host and port. Return OK, if successful - static bool extractHostPort(const std::string& path, std::string& host, std::string& port); - - /// Given a vector of strings , create a path. "suite","family", returns /suite/family - static std::string createPath(const std::vector&); - - /// Given a path like: //localhost:3141/suite/family/task - /// returns /suite/family/task - static std::string removeHostPortFromPath(const std::string& path); - - /// - /// @brief Check if the given path is an absolute path. - /// - /// @return true if absolute path, false otherwise. - /// - static bool isAbsolutePath(const std::string& path); -}; +namespace ecf { + +namespace node { + +/// +/// @brief Split the given node path into its components, using '/' as separator. +/// +/// The container storing the path components will be cleared before storing any results. +/// +/// Multiple consecutive '/' separators are treated as one, mimicking unix path conventions: +/// the path '/suite//family///task' will be split into [ 'suite', 'family', 'task' ] +/// +/// @param path the input node path to split +/// @param components the vector to store the extracted path components +/// +void split_path(const std::string& path, std::vector& components); + +/// +/// @brief Extract host and port values from the given path. +/// +/// Expects the path to have the following form and extracts the @c and @c values: +/// @c :/suite/family/task +/// +/// @param path the input node path to extract host and port from +/// @param host the buffer to store the extracted host value +/// @param port the buffer to store the extracted port value +/// @return true if host and port were successfully extracted; false otherwise +/// +bool extract_host_and_port_from_path(const std::string& path, std::string& host, std::string& port); + +/// +/// @brief Create a node path string from the given vector of path components. +/// +/// For the components @c [ "suite", "family", "task" ], returns @c "/suite/family/task". +/// Returns an empty string if @p components is empty. +/// +/// @param components the path components to join +/// @return the node path string created from the components +/// +std::string create_node_path(const std::vector& components); + +/// +/// @brief Remove the host and port prefix from the given path. +/// +/// For the node path @c ":/suite/family/task", returns @c "/suite/family/task". +/// +/// @param path the input node path to remove the host and port prefix from +/// @return the node path with the host and port prefix removed +/// +std::string remove_host_and_port_from_path(const std::string& path); + +/// +/// @brief Check if the given path is an absolute path. +/// +/// @param path the path to check +/// @return true if @p path is an absolute path; false otherwise +/// +bool is_absolute_path(const std::string& path); + +} // namespace node + +} // namespace ecf #endif /* ecflow_core_NodePath_HPP */ diff --git a/libs/core/src/ecflow/core/PasswdFile.cpp b/libs/core/src/ecflow/core/PasswdFile.cpp index 47009620a..7f0c3c3e2 100644 --- a/libs/core/src/ecflow/core/PasswdFile.cpp +++ b/libs/core/src/ecflow/core/PasswdFile.cpp @@ -72,7 +72,7 @@ bool PasswdFile::load(const std::string& file, bool debug, std::string& errorMsg ecf::algorithm::trim(theLine); // remove leading and trailing spaces std::vector lineTokens; - Str::split(theLine, lineTokens); + ecf::algorithm::split_at(lineTokens, theLine); if (lineTokens.empty()) { continue; } @@ -131,7 +131,7 @@ bool PasswdFile::check_at_least_one_user_with_host_and_port(const std::string& h std::string PasswdFile::get_passwd(const std::string& user, const std::string& host, const std::string& port) { #ifdef DEBUG_ME - cout << "PasswdFile::get_passwd user(" << user << ") host(" << host << ") port(" << port << ")\n"; + std::cout << "PasswdFile::get_passwd user(" << user << ") host(" << host << ") port(" << port << ")\n"; #endif size_t vec_size = vec_.size(); for (size_t i = 0; i < vec_size; i++) { @@ -144,14 +144,14 @@ std::string PasswdFile::get_passwd(const std::string& user, const std::string& h bool PasswdFile::authenticate(const std::string& user, const std::string& passwd) const { #ifdef DEBUG_ME - cout << " PasswdFile::authenticate user:" << user << " passwd:" << passwd << " file:" << passwd_file_ - << " vec_.size(): " << vec_.size() << "\n"; - cout << dump() << "\n"; + std::cout << " PasswdFile::authenticate user:" << user << " passwd: file:" << passwd_file_ + << " vec_.size(): " << vec_.size() << "\n"; + std::cout << dump() << "\n"; #endif if (user.empty()) { #ifdef DEBUG_ME - cout << " PasswdFile::authenticate: user empty: FAIL\n"; + std::cout << " PasswdFile::authenticate: user empty: FAIL\n"; #endif return false; } @@ -159,7 +159,7 @@ bool PasswdFile::authenticate(const std::string& user, const std::string& passwd // no password specified, and password file is empty if (passwd.empty() && vec_.empty()) { #ifdef DEBUG_ME - cout << " PasswdFile::authenticate no password and password file empty: PASS\n"; + std::cout << " PasswdFile::authenticate no password and password file empty: PASS\n"; #endif return true; } @@ -170,13 +170,13 @@ bool PasswdFile::authenticate(const std::string& user, const std::string& passwd if (vec_[i].user() == user) { if (vec_[i].passwd() == passwd) { #ifdef DEBUG_ME - cout << " PasswdFile::authenticate user and password match: PASS\n"; + std::cout << " PasswdFile::authenticate user and password match: PASS\n"; #endif return true; } else { #ifdef DEBUG_ME - cout << " PasswdFile::authenticate: user found but passwd did not match: FAIL\n"; + std::cout << " PasswdFile::authenticate: user found but passwd did not match: FAIL\n"; #endif return false; } @@ -188,7 +188,7 @@ bool PasswdFile::authenticate(const std::string& user, const std::string& passwd // User not found, but if passwd is not empty, then fail. if (!passwd.empty()) { #ifdef DEBUG_ME - cout << " PasswdFile::authenticate: user NOT found, passwd is NOT EMPTY: FAIL\n"; + std::cout << " PasswdFile::authenticate: user NOT found, passwd is NOT EMPTY: FAIL\n"; #endif return false; } @@ -196,13 +196,14 @@ bool PasswdFile::authenticate(const std::string& user, const std::string& passwd // Server has a password file, but user not found and passwd is empty. if (vec_.empty()) { #ifdef DEBUG_ME - cout << " PasswdFile::authenticate: true, user NOT found, and passwd EMPTY, and password file EMPTY\n"; + std::cout + << " PasswdFile::authenticate: true, user NOT found, and passwd EMPTY, and password file EMPTY\n"; #endif return true; } #ifdef DEBUG_ME - cout << " PasswdFile::authenticate false, user NOT found, and passwd EMPTY\n"; + std::cout << " PasswdFile::authenticate false, user NOT found, and passwd EMPTY\n"; #endif return false; } @@ -210,11 +211,11 @@ bool PasswdFile::authenticate(const std::string& user, const std::string& passwd bool PasswdFile::validateVersionNumber(const std::string& line, std::string& errorMsg) const { // Expect 4.5.0 // If first character is NUMERIC and we have dots - bool firstCharIsNumeric = Str::NUMERIC().find(line[0], 0) != std::string::npos; + bool firstCharIsNumeric = ecf::string_constants::numeric_chars.find(line[0], 0) != std::string::npos; if (firstCharIsNumeric && line.find(".") != std::string::npos) { std::vector versionNumberTokens; - Str::split(line, versionNumberTokens, "."); + ecf::algorithm::split_at(versionNumberTokens, line, "."); if (versionNumberTokens.size() != 3) { errorMsg += "Expected version of the form .. i.e 4.4.0. but found invalid version number\n"; return false; @@ -309,7 +310,7 @@ bool PasswdFile::createWithAccess(const std::string& pathToFile, line.clear(); line += username; line += " "; - line += Str::LOCALHOST(); + line += ecf::string_constants::localhost; line += " "; line += port; line += " "; diff --git a/libs/core/src/ecflow/core/Str.cpp b/libs/core/src/ecflow/core/Str.cpp index c9427b6b6..9985db6f7 100644 --- a/libs/core/src/ecflow/core/Str.cpp +++ b/libs/core/src/ecflow/core/Str.cpp @@ -14,255 +14,90 @@ namespace ecf { -const char* VALID_NODE_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_."; +namespace algorithm { -const char* Str::CHILD_CMD() { - static const char* CHILD_CMD = "chd:"; - return CHILD_CMD; -} -const char* Str::USER_CMD() { - static const char* USER_CMD = "--"; - return USER_CMD; -} -const char* Str::SVR_CMD() { - static const char* SVR_CMD = "svr:"; - return SVR_CMD; -} // Only for automatic check_pt - -const std::string& Str::EMPTY() { - static std::string empty = std::string{}; - return empty; -} -const std::string& Str::ROOT_PATH() { - static std::string root_path = "/"; - return root_path; -} -const std::string& Str::PATH_SEPARATOR() { - static std::string path_sep = "/"; - return path_sep; -} -const std::string& Str::COLON() { - static std::string colon = ":"; - return colon; +inline bool is_valid_node_name_first_character(char c) { + return std::isalnum(c) || c == '_'; } -const std::string& Str::STATE_CHANGE() { - static std::string state_change = " state change "; - return state_change; -} - -const std::string& Str::TASK() { - static std::string task = "TASK"; - return task; -} -const std::string& Str::FAMILY() { - static std::string family = "FAMILY"; - return family; -} -const std::string& Str::SUITE() { - static std::string suite = "SUITE"; - return suite; -} -const std::string& Str::ALIAS() { - static std::string alias = "ALIAS"; - return alias; +inline bool is_valid_node_name_following_character(char c) { + return std::isalnum(c) || c == '_' || c == '.'; } -const std::string& Str::DEFAULT_PORT_NUMBER() { - static std::string port_number = "3141"; - return port_number; -} -const std::string& Str::LOCALHOST() { - static std::string localhost = "localhost"; - return localhost; -} - -const std::string& Str::WHITE_LIST_FILE() { - static std::string WHITE_LIST_FILE = "ecf.lists"; - return WHITE_LIST_FILE; -} - -const std::string& Str::ALPHANUMERIC_UNDERSCORE() { - static std::string ALPHANUMERIC_UNDERSCORE = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"; - return ALPHANUMERIC_UNDERSCORE; -} - -const std::string& Str::NUMERIC() { - static std::string NUMERIC = "0123456789"; - return NUMERIC; -} - -void Str::removeQuotes(std::string& s) { - if (!s.empty()) { - if (s[0] == '"' && s[s.size() - 1] == '"') { - s.erase(s.begin()); - s.erase(s.begin() + s.size() - 1); - } - } -} -// 047 is octal for ' -void Str::removeSingleQuotes(std::string& s) { - if (!s.empty()) { - if (s[0] == 047 && s[s.size() - 1] == 047) { - s.erase(s.begin()); - s.erase(s.begin() + s.size() - 1); - } - } -} +bool is_valid_name(const std::string& name, std::string& error) { -bool Str::replace(std::string& jobLine, const std::string& stringToFind, const std::string& stringToReplace) { - size_t pos = jobLine.find(stringToFind); - if (pos != std::string::npos) { - jobLine.replace(pos, stringToFind.length(), stringToReplace); - return true; + if (name.empty()) { + error = "Invalid name '': empty string"; + return false; } - return false; -} -bool Str::replace_all(std::string& subject, const std::string& stringToFind, const std::string& stringToReplace) { - bool replaced = false; - size_t pos = 0; - while ((pos = subject.find(stringToFind, pos)) != std::string::npos) { - subject.replace(pos, stringToFind.length(), stringToReplace); - pos += stringToReplace.length(); - replaced = true; + if (!is_valid_node_name_first_character(name.front())) { + error = "Invalid name '"; + error += name; + error += "': only alphanumeric characters or underscore are accepted as first character"; + return false; } - return replaced; -} -bool Str::extract_data_member_value(const std::string& str, - const std::string& data_member_name, - std::string& data_member_value) { - // 012345678901234567, - // str=cmd 1 user:mao - // data_member_name=user: - // data_member_value=ma0 - std::string::size_type start = str.find(data_member_name); - if (start != std::string::npos) { - start += data_member_name.size(); - data_member_value.clear(); - for (size_t i = start; i < str.size(); i++) { - if (str[i] == ' ') { - break; - } - data_member_value += str[i]; - } - return true; + if (!std::all_of(name.begin() + 1, name.end(), is_valid_node_name_following_character)) { + error = "Invalid name '"; + error += name; + error += "': only alphanumeric characters, underscore or dot are accepted"; + return false; } - return false; -} -void Str::replaceall(std::string& subject, const std::string& search, const std::string& replace) { - boost::replace_all(subject, search, replace); + return true; } -#define USE_STRINGSPLITTER 1 -void Str::split(const std::string& line, std::vector& tokens, const std::string& delimiters) { -#ifdef USE_STRINGSPLITTER - Str::split_using_string_view(line, tokens, delimiters); -#else - Str::split_orig(line, tokens, delimiters); -#endif - // This test will split a line 1000000 times: 'This is a long string that is going to be used to test the - // performance of splitting with different Implementations the fastest times wins ' Time for istreamstream 1000000 - // times = 2.131s wall, (2.140s user + 0.000s system = 2.140s) CPU (100.4%) Time for std::getline 1000000 times - // = 1.490s wall, (1.490s user + 0.000s system = 1.490s) CPU (100.0%) Time for boost::split 1000000 times = 0.930s - // wall, (0.920s user + 0.000s system = 0.920s) CPU (99.0%) Time for Str::split_orig 1000000 times - // = 1.065s wall, (1.070s user + 0.000s system = 1.070s) CPU (100.5%) Time for Str::split_orig1 1000000 times = - // 0.561s wall, (0.560s user + 0.000s system = 0.560s) CPU (99.9%) Time for Str::split_using_string_view2 1000000 - // times = 0.686s wall, (0.690s user + 0.000s system = 0.690s) CPU (100.6%) Time for Str::split_using_string_view - // 1000000 times = 0.482s wall, (0.480s user + 0.000s system = 0.480s) CPU (99.6%) Time for - // make_split_iterator::split 1000000 times = 3.611s wall, (3.610s user + 0.000s system = 3.610s) CPU (100.0%) - // Time for std::string_view 1000000 times = 0.769s wall, (0.770s user + 0.000s system = 0.770s) CPU - // (100.1%) Time for std::string_view(2) 1000000 times = 0.688s wall, (0.690s user + 0.000s system = - // 0.690s) CPU (100.3%) - // core :: test_str_split_perf_with_file - // This test will split each line in file ${ECF_TEST_DEFS_DIR}vsms2.31415.def - // Time for istreamstream 2001774 times = 1.567s wall, (1.570s user + 0.000s system = 1.570s) CPU - // (100.2%) Time for std::getline 2001774 times = 2.456s wall, (2.460s user + 0.000s system - // = 2.460s) CPU (100.2%) Time for boost::split 2001774 times = 1.698s wall, (1.690s user + - // 0.000s system = 1.690s) CPU (99.5%) Time for Str::split_orig 2001774 times = 0.822s wall, (0.830s - // user + 0.000s system = 0.830s) CPU (101.0%) Time for Str::split_orig1 2001774 times = 0.502s wall, - // (0.500s user + 0.000s system = 0.500s) CPU (99.7%) Time for Str::split_using_string_view 2001774 times = - // 0.489s wall, (0.490s user + 0.000s system = 0.490s) CPU (100.3%) Time for Str::split_using_string_view2 2001774 - // times = 0.639s wall, (0.630s user + 0.000s system = 0.630s) CPU (98.6%) Time for boost::make_split_iterator - // 2001774 times = 3.338s wall, (3.340s user + 0.000s system = 3.340s) CPU (100.1%) Time for std::string_view - // 2001774 times = 0.599s wall, (0.600s user + 0.000s system = 0.600s) CPU (100.1%) Time for - // std::string_view(2) 2001774 times = 0.696s wall, (0.700s user + 0.000s system = 0.700s) CPU (100.6%) +bool is_valid_name(const std::string& name) { + return !name.empty() && is_valid_node_name_first_character(name.front()) && + std::all_of(name.begin() + 1, name.end(), is_valid_node_name_following_character); } -void Str::split_orig(const std::string& line, std::vector& tokens, const std::string& delimiters) { - // Skip delimiters at beginning. - auto lastPos = line.find_first_not_of(delimiters, 0); - - // Find first "non-delimiter". - auto pos = line.find_first_of(delimiters, lastPos); - - while (std::string::npos != pos || std::string::npos != lastPos) { - tokens.push_back(line.substr(lastPos, pos - lastPos)); // Found a token, add it to the vector. - lastPos = line.find_first_not_of(delimiters, pos); // Skip delimiters. Note the "not_of" - pos = line.find_first_of(delimiters, lastPos); // Find next "non-delimiter" +bool tail(std::string& fileContents, size_t max_lines) { + if (fileContents.empty()) { + return false; } -} -void Str::split_orig1(const std::string& line, std::vector& tokens, const std::string& delims) { - auto first = std::cbegin(line); - auto end = std::cend(line); - - while (first != end) { - const auto second = std::find_first_of(first, end, std::cbegin(delims), std::cend(delims)); - - if (first != second) { - tokens.emplace_back(first, second); - } - - if (second == end) { - break; + size_t count = 0; + auto it = fileContents.end(); + while (it != fileContents.begin()) { + --it; + if (*it == '\n') { + ++count; + if (count == max_lines) { + fileContents.erase(fileContents.begin(), it + 1); + return true; + } } - - first = std::next(second); } + return false; } -void Str::split_using_string_view(std::string_view strv, std::vector& output, std::string_view delims) { - // Uses pointers - for (auto first = strv.data(), second = strv.data(), last = first + strv.size(); second != last && first != last; - first = second + 1) { - second = std::find_first_of(first, last, std::cbegin(delims), std::cend(delims)); - - if (first != second) { - output.emplace_back(first, second - first); - } +bool head(std::string& fileContents, size_t max_lines) { + if (fileContents.empty()) { + return false; } -} - -void Str::split_using_string_view2(std::string_view strv, std::vector& output, std::string_view delims) { - size_t first = 0; - - size_t strv_size = strv.size(); - while (first < strv_size) { - const auto second = strv.find_first_of(delims, first); - if (first != second) { - std::string_view ref = strv.substr(first, second - first); - output.emplace_back(ref.begin(), ref.end()); - } - - if (second == std::string_view::npos) { - break; + size_t count = 0; + for (size_t i = 0; i < fileContents.size(); ++i) { + if (fileContents[i] == '\n') { + ++count; + if (count == max_lines) { + fileContents.erase(i + 1); + return true; + } } - - first = second + 1; } + return false; } -std::vector Str::tokenize_quotation(const std::string& s, std::string_view quotes) { +std::vector split_within_quotes(const std::string& input, std::string_view quotes) { std::vector tokens; std::string levels; - const char* current = &s[0]; + const char* current = &input[0]; const char* start = current; while (*current != 0) { if (*current == ' ' && levels.empty()) { @@ -295,26 +130,21 @@ std::vector Str::tokenize_quotation(const std::string& s, std: return tokens; } -bool Str::get_token(std::string_view str, size_t pos, std::string& token, std::string_view delims) { - // Time for StringSplitter::get_token 250000 times = 1.457s wall, (1.460s user + 0.000s system = 1.460s) CPU - // (100.2%) Time for Str::get_token 250000 times = 0.566s wall, (0.560s user + 0.000s system = 0.560s) - // CPU (99.0%) Time for Str::get_token2 250000 times = 0.668s wall, (0.670s user + 0.000s system = - // 0.670s) CPU (100.3%) Time for Str::get_token3 250000 times = 0.620s wall, (0.620s user + 0.000s - // system = 0.620s) CPU (100.0%) +bool get_token(std::string_view input, size_t index, std::string& token, std::string_view delimiters) { - size_t current_pos = 0; - auto first = std::cbegin(str); - auto end = std::cend(str); + size_t current_index = 0; + auto first = std::cbegin(input); + auto end = std::cend(input); while (first != end) { - const auto second = std::find_first_of(first, end, std::cbegin(delims), std::cend(delims)); + const auto second = std::find_first_of(first, end, std::cbegin(delimiters), std::cend(delimiters)); if (first != second) { - if (current_pos == pos) { + if (current_index == index) { token = std::string(first, second); return true; } - current_pos++; + current_index++; } if (second == end) { @@ -326,207 +156,6 @@ bool Str::get_token(std::string_view str, size_t pos, std::string& token, std::s return false; } -bool Str::get_token2(std::string_view strv, size_t pos, std::string& token, std::string_view delims) { - size_t current_pos = 0; - size_t first = 0; - while (first < strv.size()) { - const auto second = strv.find_first_of(delims, first); - - if (first != second) { - if (current_pos == pos) { - std::string_view ref = strv.substr(first, second - first); - token = std::string(ref.begin(), ref.end()); - return true; - } - current_pos++; - } - - if (second == std::string_view::npos) { - break; - } - - first = second + 1; - } - return false; -} - -bool Str::get_token3(std::string_view str, size_t pos, std::string& token, std::string_view delims) { - size_t current_pos = 0; - for (auto first = str.data(), second = str.data(), last = first + str.size(); second != last && first != last; - first = second + 1) { - - second = std::find_first_of(first, last, std::cbegin(delims), std::cend(delims)); - if (first != second) { - if (current_pos == pos) { - token = std::string(first, second - first); - return true; - } - current_pos++; - } - } - return false; -} - -boost::split_iterator Str::make_split_iterator(const std::string& line, - const std::string& delimiters) { - return boost::make_split_iterator( - line, boost::algorithm::token_finder(boost::is_any_of(delimiters), boost::algorithm::token_compress_on)); -} - -static bool caseInsCharCompare(char a, char b) { - return (toupper(a) == toupper(b)); -} - -bool Str::caseInsCompare(const std::string& s1, const std::string& s2) { - return ((s1.size() == s2.size()) && equal(s1.begin(), s1.end(), s2.begin(), caseInsCharCompare)); -} - -bool Str::caseInsLess(const std::string& a, const std::string& b) { - return std::lexicographical_compare(a.begin(), a.end(), b.begin(), b.end(), [](char x, char y) -> bool { - if (toupper(x) == toupper(y)) { - return x > y; - } - return toupper(static_cast(x)) < toupper(static_cast(y)); - }); -} - -bool Str::caseInsGreater(const std::string& a, const std::string& b) { - return std::lexicographical_compare(a.begin(), a.end(), b.begin(), b.end(), [](char x, char y) -> bool { - if (toupper(x) == toupper(y)) { - return x < y; - } - return toupper(static_cast(x)) > toupper(static_cast(y)); - }); -} - -bool Str::valid_name(const std::string& name, std::string& msg) { - // valid names are alphabetic (alphanumeric | underscore | .) - // however we can't have a leading '.' as that can interfere with trigger expressions - - // verify that the string is not empty - if (name.empty()) { - msg = "Invalid name. Empty string."; - return false; - } - - // verify that the first character is alphanumeric or is an underscore - bool result = Str::ALPHANUMERIC_UNDERSCORE().find(name[0], 0) != std::string::npos; - if (!result) { - msg = "Valid names can only consist of alphanumeric characters, " - "underscores and dots (The first character cannot be a dot). " - "The first character is not valid (only alphanumeric or an underscore is allowed): "; - msg += name; - return false; - } - - // verify that any other characters are alphanumeric or underscore - if (name.size() > 1) { - result = name.find_first_not_of(VALID_NODE_CHARS, 1) == std::string::npos; - if (!result) { - msg = "Valid names can only consist of alphanumeric characters, " - "underscores and dots (The first character cannot be a dot). "; - if (name.find('\r') != std::string::npos) { - msg += "Windows line ending ? "; - } - msg += "'"; - msg += name; - msg += "'"; // use '' to show if PC format, i.e. carriage return - } - } - - return result; -} - -bool Str::valid_name(const std::string& name) { - // valid names are alphabetic (alphanumeric | underscore | .) - // however we can't have a leading '.' as that can interfere with trigger expressions - - // verify that the string is not empty - if (name.empty()) { - return false; - } - - // verify that the first character is alphabetic or has underscore - bool result = Str::ALPHANUMERIC_UNDERSCORE().find(name[0], 0) != std::string::npos; - if (!result) { - return false; - } - - // verify that any other characters are alphanumeric or underscore - if (name.size() > 1) { - result = name.find_first_not_of(VALID_NODE_CHARS, 1) == std::string::npos; - } - - return result; -} - -int Str::to_int(const std::string& the_str, int error_return) { - if (the_str.find_first_of(Str::NUMERIC(), 0) != std::string::npos) { - try { - return ecf::convert_to(the_str); - } - catch (const ecf::bad_conversion&) { - } - } - return error_return; -} - -bool Str::is_int(const std::string& s) { - try { - boost::lexical_cast(s); - return true; - } - catch (...) { - return false; - } -} - -bool Str::truncate_at_start(std::string& fileContents, size_t max_lines) { - if (fileContents.empty()) { - return false; - } - - /// Truncate from the front - size_t no_of_new_lines = 0; - for (size_t i = fileContents.size() - 1; i > 0; --i) { - if (fileContents[i] == '\n') { - no_of_new_lines++; - } - if (no_of_new_lines >= max_lines) { - fileContents.erase(fileContents.begin(), fileContents.begin() + i + 1); // skip new line at start of file - return true; - } - } - return false; -} - -bool Str::truncate_at_end(std::string& fileContents, size_t max_lines) { - if (fileContents.empty()) { - return false; - } - - /// Truncate from the back - size_t no_of_new_lines = 0; - size_t the_size = fileContents.size(); - for (size_t i = 0; i < the_size; ++i) { - if (fileContents[i] == '\n') { - no_of_new_lines++; - } - if (no_of_new_lines >= max_lines) { - fileContents.erase(fileContents.begin() + i + 1, fileContents.end()); // skip new line at end of file - return true; - } - } - return false; -} - -std::string Str::dump_string_vec(const std::vector& vec) { - std::string str; - for (const auto& s : vec) { - str += s; - str += "\n"; - } - return str; -} +} // namespace algorithm } // namespace ecf diff --git a/libs/core/src/ecflow/core/Str.hpp b/libs/core/src/ecflow/core/Str.hpp index c9460b4c0..d3bb69f58 100644 --- a/libs/core/src/ecflow/core/Str.hpp +++ b/libs/core/src/ecflow/core/Str.hpp @@ -11,221 +11,767 @@ #ifndef ecflow_core_Str_HPP #define ecflow_core_Str_HPP +#include +#include #include #include +#include +#include #include #include #include -#include +#include "ecflow/core/Converter.hpp" namespace ecf { +/// +/// @brief Common string constants used throughout ecFlow. +/// +/// This namespace provides a centralised set of well-known literal strings to avoid +/// ad-hoc string construction and to make intent explicit at call sites. +/// +namespace string_constants { + +/// Command prefixes used to identify the origin of a client request. +inline const std::string child_cmd = "chd:"; +inline const std::string user_cmd = "--"; +inline const std::string server_cmd = "svr:"; // Only for automatic check_pt + +/// An empty string, provided as a named constant for use as a default reference return. +inline const std::string empty = ""; + +/// Node path strings. +inline const std::string root_path = "/"; +inline const std::string path_separator = "/"; +inline const std::string colon = ":"; + +/// Node type name strings, as used in definition files and wire protocol messages. +inline const std::string task = "TASK"; +inline const std::string family = "FAMILY"; +inline const std::string family1 = "FAMILY1"; +inline const std::string suite = "SUITE"; +inline const std::string alias = "ALIAS"; + +/// Server connection defaults. +inline const std::string default_port_number = "3141"; +inline const std::string localhost = "localhost"; + +/// Name of the ecFlow access-control white-list file. +inline const std::string white_list_file = "ecf.lists"; + +/// Character sets used for name validation. +inline const std::string alphanumeric_underscore_chars = + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"; + +inline const std::string numeric_chars = "0123456789"; + +} // namespace string_constants + namespace algorithm { +/// +/// @brief Join a sequence of strings into a single string with a separator between them. The (optional) separator +/// defaults to ", " if not provided. +/// +/// @tparam Sequence1 A container type that supports iteration and whose elements can be converted to std::string_view. +/// @tparam Sequence2 A string-like type. +/// +/// @param strings The sequence of string-like objects to join. +/// @param separator The string-like object used as the separator between joined strings. +/// @return The joined string. +/// template -inline static auto join(const Sequence1& strings, const Sequence2& separator = std::string(", ")) { - return ::boost::algorithm::join(strings, separator); +inline std::string join(const Sequence1& strings, const Sequence2& separator = ", ") { + std::string buffer; + auto first = true; + for (const auto& s : strings) { + if (!first) { + buffer.append(std::data(separator), std::size(separator)); + } + buffer.append(s); + first = false; + } + return buffer; } +/// +/// @brief Replace the first occurrence of a search string in the input sequence with a replacement string. +/// +/// No replacement is performed if the search string is not found. +/// +/// @tparam Sequence A string-like type. +/// @tparam SearchSequence A string-like type. +/// @tparam ReplaceSequence A string-like type. +/// +/// @param input The input sequence to search and replace within. +/// @param search The string to search for. +/// @param replace The string to replace the search string with. +/// template -inline static auto replace_first(Sequence& input, const SearchSequence& search, const ReplaceSequence& replace) { - return ::boost::algorithm::replace_first(input, search, replace); +inline void replace_first(Sequence& input, const SearchSequence& search, const ReplaceSequence& replace) { + auto found = input.find(search); + if (found != std::string::npos) { + input.replace(found, std::string_view(search).size(), replace); + } +} + +/// +/// @brief Splits an input string into a sequence of substrings based on a set of separators. +/// +/// The resulting buffer is cleared of any contents, before being populated with the substrings. +/// Consecutive separators are treated as a single separator, meaning no empty substrings are produced. +/// +/// Splitting the input `"a,,b,c;d; ;e;;f;"` with the separator `",;"` would result +/// in a buffer containing `["a", "b", "c", "d", " ", "e", "f"]`. +/// It is important to notice that: +/// 1) the separators define a set of single characters, and thus duplications are treated as a single separator. +/// 2) consecutive separators are collapsed into one, so no empty substrings are produced. +/// 3) leading and trailing separators are ignored. +/// +/// If the input string is empty, or contains only separator characters, the result will be an empty sequence (i.e. +/// `[ ]`). +/// +/// @tparam ResultSequence A container type to hold the resulting substrings. +/// @tparam Sequence1 A string-like type representing the input string. +/// @tparam Sequence2 A string-like type representing single character separators. +/// +/// @param buffer A reference to the container where the substrings will be stored. +/// @param input The input string to be split. +/// @param separators The string containing characters to use as separators +/// @return A reference to the container holding the resulting substrings. +/// +template +inline ResultSequence& +split_at(ResultSequence& buffer, const Sequence1& input, const Sequence2& separators = std::string_view(" \t")) { + buffer.clear(); + std::string_view in(input); + std::string_view seps(separators); + size_t start = in.find_first_not_of(seps); + while (start != std::string_view::npos) { + auto pos = in.find_first_of(seps, start); + if (pos == std::string_view::npos) { + buffer.emplace_back(in.substr(start)); + break; + } + buffer.emplace_back(in.substr(start, pos - start)); + start = in.find_first_not_of(seps, pos + 1); + } + return buffer; } -template -inline static ResultSequence& split(ResultSequence& result, const Sequence1& input, const Sequence2& separators) { - return ::boost::algorithm::split(result, input, ::boost::is_any_of(separators)); +/// +/// @brief Splits an input string into a sequence of substrings based on a set of separators. +/// +/// The resulting buffer is cleared of any contents, before being populated with the substrings. +/// +/// A field separator is defined as any single character contained in the `separators` string. +/// Consecutive separators are used to split the input string into empty field. +/// An empty field is included in result, when a separator is found at the beginning and or at end of the input. +/// +/// Splitting the input `",,a,,b,c;d; ;e,;f;"` with the separators `",;"` would result in a buffer containing `["", "", +/// "a", "", "b", "c", "d", " ", "e", "", "f", ""]`. +/// +/// If the input string is empty, the result be one empty sequence (i.e. `[ ]`). +/// +/// @tparam ResultSequence A container type to hold the resulting substrings. +/// @tparam Sequence1 A string-like type representing the input string. +/// @tparam Sequence2 A string-like type representing single character separators. +/// +/// @param buffer A reference to the container where the substrings will be stored. +/// @param input The input string to be split. +/// @param separators The string containing characters to use as separators +/// @return A reference to the container holding the resulting substrings. +/// +template +static ResultSequence& split_fields_at(ResultSequence& buffer, const Sequence1& input, const Sequence2& pattern) { + buffer.clear(); + + if (input.empty()) { + return buffer; + } + + std::string::size_type start = 0; + std::string::size_type pos = std::string::npos; + while ((pos = input.find_first_of(pattern, start)) != std::string::npos) { + auto token = input.substr(start, pos - start); + buffer.push_back(token); + start = pos + 1; + } + + auto token = input.substr(start); + buffer.push_back(token); + + return buffer; } +/// +/// @brief Splits an input string into a sequence of substrings based on a given pattern. +/// +/// The resulting buffer is cleared of any contents, before being populated with the substrings. +/// Consecutive separator patterns are treated as a single separator, meaning no empty substrings are produced. +/// +/// Splitting the input `"a==b==c!=d==e == f"` with the pattern `"=="` would result +/// in a buffer containing `["a", "b", "c!=d", "e ", " f"]`. +/// +/// If the input string is empty, or contains only repetited separator patterns, the result will be one empty +/// sequence (i.e. `[ ]`). +/// +/// @tparam ResultSequence A container type to hold the resulting substrings. +/// @tparam Sequence1 A string-like type representing the input string. +/// @tparam Sequence2 A string-like type representing the pattern to split by. +/// +/// @param buffer A reference to the container where the resulting substrings will be stored. +/// @param input The input string to be split. +/// @param pattern The pattern to split the input string by. +/// @return A reference to the container holding the resulting substrings. +/// +template +inline ResultSequence& split_by(ResultSequence& buffer, const Sequence1& input, const Sequence2& pattern) { + buffer.clear(); + std::string::size_type start = 0; + std::string::size_type pos = 0; + while ((pos = input.find(pattern, start)) != std::string::npos) { + auto token = input.substr(start, pos - start); + if (!token.empty()) { + buffer.push_back(token); + } + if constexpr (std::is_same_v) { + start = pos + std::size(pattern); + } + else if constexpr (std::is_same_v) { + start = pos + std::size(pattern); + } + else { + start = pos + std::size(pattern) - 1; + } + } + auto token = input.substr(start); + if (!token.empty()) { + buffer.push_back(token); + } + return buffer; +} + +/// +/// @brief Tokenize the input string selecting the content within the specified quotation marks. +/// +/// The quotation marks are included in the resulting tokens! +/// +/// Some example: +/// - split_within_quotes("'a' x 'b' y 'c' zz ", "'") will return tokens ['a', x, 'b', y, 'c', zz]. +/// Notice that any non-whitespace characters _outside_ the quotation marks are also collected (without quotes). +/// +/// - split_within_quotes("'a \"b\" c' \"d 'e' f\"", "\"'") will return ['a \"b\" c', 'd \'e\' f'] +/// Every quotation mark provided is considered, but only one level/quoted region is considered. +/// This means that regions are detected by matching the first quote found. +/// +/// @param input The input string to tokenize. +/// @param quotes The set of characters to use as quotation marks. +/// @return A vector of string views representing the tokens extracted from the input string. +/// +std::vector split_within_quotes(const std::string& input, std::string_view quotes); + +/// +/// @brief Check if the input string starts with the given pattern. +/// +/// Notice that an empty pattern will match any string (empty or otherwise). +/// +/// @tparam Sequence1 A string-like type representing the input string. +/// @tparam Sequence2 A string-like type representing the pattern. +/// +/// @param input The input string to check. +/// @param pattern The pattern to check for at the start of the input. +/// @return true if the input starts with the pattern, false otherwise. +/// template inline static bool starts_with(const Sequence1& input, const Sequence2& pattern) { - return ::boost::algorithm::starts_with(input, pattern); + std::string_view in(input); + std::string_view p(pattern); + return in.size() >= p.size() && in.substr(0, p.size()) == p; } +/// +/// @brief Check if the input string ends with the given pattern. +/// +/// @tparam Sequence1 A string-like type representing the input string. +/// @tparam Sequence2 A string-like type representing the pattern. +/// +/// @param input The input string to check. +/// @param pattern The pattern to check for at the end of the input. +/// @return true if the input ends with the pattern, false otherwise. +template +inline static bool ends_with(const Sequence1& input, const Sequence2& pattern) { + std::string_view in(input); + std::string_view p(pattern); + return in.size() >= p.size() && in.substr(in.size() - p.size()) == p; +} + +/// +/// @brief Trim leading and trailing whitespace from the input string in-place. +/// +/// Whitespace is determined by `std::isspace` (i.e. considers ' ', '\f', '\r', '\n', '\t', '\v'). +/// +/// @tparam Sequence A string-like type representing the string to trim. +/// +/// @param input The string to trim. +/// template inline static void trim(Sequence& input) { - ::boost::algorithm::trim(input); + // remove leading whitespace + auto leading = std::find_if(std::begin(input), std::end(input), [](unsigned char ch) { return !std::isspace(ch); }); + input.erase(std::begin(input), leading); + + // remove trailing whitespace + auto trailing = + std::find_if(std::rbegin(input), std::rend(input), [](unsigned char ch) { return !std::isspace(ch); }); + input.erase(trailing.base(), std::end(input)); +} + +/// +/// @brief Trim leading whitespace from the input string in-place. +/// +/// Whitespace is determined by `std::isspace` (i.e. considers ' ', '\f', '\r', '\n', '\t', '\v'). +/// +/// @tparam Sequence A string-like type representing the string to trim. +/// +/// @param input The string to trim. +/// +template +inline static void trim_leading(Sequence& input) { + // remove leading whitespace + auto leading = std::find_if(input.begin(), input.end(), [](unsigned char ch) { return !std::isspace(ch); }); + input.erase(input.begin(), leading); } +/// +/// @brief Return a copy of the input string with leading whitespace removed. +/// +/// Whitespace is determined by `std::isspace` (i.e. considers ' ', '\f', '\r', '\n', '\t', '\v'). +/// +/// @tparam Sequence A string-like type representing the string to trim. +/// +/// @param input The string to trim. +/// @return A copy of the input with leading whitespace removed. +/// +template +inline static Sequence trim_leading_copy(const Sequence& input) { + Sequence buffer = input; + // remove leading whitespace + auto leading = std::find_if(buffer.begin(), buffer.end(), [](unsigned char ch) { return !std::isspace(ch); }); + buffer.erase(buffer.begin(), leading); + return buffer; +} + +/// +/// @brief Trim trailing whitespace from the input string in-place. +/// +/// Whitespace is determined by `std::isspace` (i.e. considers ' ', '\f', '\r', '\n', '\t', '\v'). +/// +/// @tparam Sequence A string-like type representing the string to trim. +/// +/// @param input The string to trim. +/// +template +inline static void trim_trailing(Sequence& input) { + // remove trailing whitespace + auto trailing = std::find_if(input.rbegin(), input.rend(), [](unsigned char ch) { return !std::isspace(ch); }); + input.erase(trailing.base(), input.end()); +} + +/// +/// @brief Return a copy of the input string with trailing whitespace removed. +/// +/// Whitespace is determined by `std::isspace` (i.e. considers ' ', '\f', '\r', '\n', '\t', '\v'). +/// +/// @tparam Sequence A string-like type representing the string to trim. +/// +/// @param input The string to trim. +/// @return A copy of the input with trailing whitespace removed. +/// +template +inline static Sequence trim_trailing_copy(const Sequence& input) { + Sequence buffer = input; + // remove trailing whitespace + auto trailing = std::find_if(buffer.rbegin(), buffer.rend(), [](unsigned char ch) { return !std::isspace(ch); }); + buffer.erase(trailing.base(), buffer.end()); + return buffer; +} + +/// +/// @brief Check if the input string contains the given pattern. +/// +/// Notice that an empty pattern is considered to be contained in any string. +/// +/// @tparam Sequence A string-like type representing the input string. +/// @tparam Pattern A string-like type representing the pattern to search for. +/// +/// @param input The input string to search within. +/// @param pattern The pattern to search for. +/// @return true if the input contains the pattern, false otherwise. +/// template inline static bool contains(const Sequence& input, const Pattern& pattern) { - return ::boost::algorithm::contains(input, pattern); + return std::string_view(input).find(std::string_view(pattern)) != std::string_view::npos; } +/// +/// @brief Remove all occurrences of the given pattern from the input string in-place. +/// +/// @tparam Sequence A string-like type representing the input string. +/// @tparam Pattern A string-like type representing the pattern to remove. +/// +/// @param input The input string to modify. +/// @param pattern The pattern to remove from the input. +/// template inline static void remove_all(Sequence& input, const Pattern& pattern) { - ::boost::algorithm::erase_all(input, pattern); + if (std::empty(std::string_view(pattern))) { + return; + } + + std::string_view pat(pattern); + size_t pos = 0; + while ((pos = input.find(pat.data(), pos, pat.size())) != std::string::npos) { + input.erase(pos, pat.size()); + } } -template -static std::vector transform_to_name_vector(const std::vector& i) { - std::vector o; - o.reserve(i.size()); - std::transform(std::begin(i), std::end(i), std::back_inserter(o), [](const auto& v) { return v->name(); }); - return o; +/// +/// @brief Transform a vector of elements into a new vector by applying a function to each element. +/// +/// @tparam Sequence A container type containing elements to be transformed. +/// @tparam Functor A callable type that takes a value of type T and returns the transformed value. +/// +/// @param input The sequence of elements to transform. +/// @param transformation The function to apply to each element. +/// @return A new vector containing the transformed elements. +/// +template +inline auto transform_to_vector(const Sequence& input, Functor transformation) { + std::vector()))> buffer; + buffer.reserve(input.size()); + std::transform(std::begin(input), std::end(input), std::back_inserter(buffer), [&transformation](const auto& v) { + return transformation(v); + }); + return buffer; } +/// +/// @brief Convert a string to lowercase. +/// +/// Returns a copy of the input string with all characters converted to their lowercase equivalents +/// using `std::tolower`. +/// +/// @param s The input string +/// @return A new string with all characters converted to lowercase. +/// inline std::string tolower(std::string s) { std::transform(std::begin(s), std::end(s), std::begin(s), [](unsigned char c) { return std::tolower(c); }); return s; } -} // namespace algorithm +/// +/// @brief Check if the \param name is valid according to the rules for node/attributes names in ecFlow. +/// +/// This is used to verify the validity of nodes and attributes (Variable, Label, Event, ...) names in ecFlow. +/// +/// If the name is valid, the error buffer will not be updated (e.g. cleared), otherwise the error buffer will contain a +/// description of the validation failure. +/// +/// @param name The name to be validated. +/// @param error A buffer to hold the error description, if validation fails (i.e. when function returns false). +/// @return true if the name is valid, false otherwise. +/// +bool is_valid_name(const std::string& name, std::string& error); + +/// +/// @brief Check if the \param name is valid according to the rules for node names in ecFlow. +/// +/// This overload returns only a boolean. Use the two-argument overload to also +/// retrieve a description of any validation failure. +/// +/// @param name The name to be validated. +/// @return true if the name is valid, false otherwise. +/// +bool is_valid_name(const std::string& name); + +/// +/// @brief Remove surrounding double quotes from a string in-place. +/// +/// Only if double quotes exist *at the beginning and at the end* of the input, will any change be performed. +/// +/// @param input The input string to modify. +/// +inline void remove_double_quotes(std::string& input) { + if (input.size() >= 2 && input.front() == '"' && input.back() == '"') { + input.erase(0, 1); + input.erase(input.size() - 1, 1); + } +} -class Str { -public: - // Disable default construction - Str() = delete; - - static int reserve_4() { return 4; } - static int reserve_8() { return 8; } - static int reserve_16() { return 17; } - static int reserve_32() { return 32; } - static int reserve_64() { return 64; } - - // remove any quotes on the string, else does nothing - // "fred" -> fred - // fred -> fred - static void removeQuotes(std::string&); - - // remove any single quotes on the string, else does nothing - // 'fred' -> fred - // fred -> fred - static void removeSingleQuotes(std::string&); - - /// Find 'stringToFind' in 'jobLine' and replace with string 'stringToReplace' - /// return true if replace ok else returns false; - static bool replace(std::string& subject, const std::string& stringToFind, const std::string& stringToReplace); - static bool replace_all(std::string& subject, const std::string& stringToFind, const std::string& stringToReplace); - static void replaceall(std::string& subject, const std::string& stringToFind, const std::string& stringToReplace); - - // extract data member value, ie given a string of the form: - // str=cmd a b fred:value - // data_member_name=fred: - // extract value - static bool extract_data_member_value(const std::string& str, - const std::string& data_member_name, - std::string& data_member_value); - - /// split string using default delimiters of space and tab as a separator; - /// The split is based on *ANY* of the characters in the delimiters. - /// **** Hence a delimiter of "==" will still split "a = complete" - /// **** sequential delimiter character are ignored **** - /// This function is used to choose the fastest implementation - static void split(const std::string& line, std::vector& tokens, const std::string& delimiters = " \t"); - - static std::vector tokenize_quotation(const std::string& s, std::string_view quotes); - - static void - split_orig(const std::string& line, std::vector& tokens, const std::string& delimiters = " \t"); - - static void - split_orig1(const std::string& line, std::vector& tokens, const std::string& delimiters = " \t"); - - static void split_using_string_view(std::string_view line, - std::vector& tokens, - std::string_view delimiters = " \t"); - - static void split_using_string_view2(std::string_view line, - std::vector& tokens, - std::string_view delimiters = " \t"); - - // Get token at a given pos. Two different implementations - static bool get_token(std::string_view line, size_t pos, std::string& token, std::string_view sep = " \t"); - static bool get_token2(std::string_view line, size_t pos, std::string& token, std::string_view sep = " \t"); - static bool get_token3(std::string_view line, size_t pos, std::string& token, std::string_view sep = " \t"); - - // Uses boost::make_split_iterator will remove - // consecutive delimiters in the middle of the string - // ** However preserves leading and trailing empty tokens *IF* delimiters at start/end - // - // Usage: - // boost::split_iterator tokens = Str::make_split_iterator(str); - // for(; !tokens.eof(); ++tokens ) { - // boost::iterator_range range = *tokens; - // std::string the_string(range.begin(), range.end()) ; - // } - // - // std::vector vec; - // using split_iter_t = boost::split_iterator; - // for(split_iter_t i = Str::split(s,delim); i != split_iter_t(); i++) { - // vec.push_back(boost::copy_range(*i)); - // } - static boost::split_iterator - make_split_iterator(const std::string& str, const std::string& delimiters = " \t"); - - /// case-insensitive string comparison - static bool caseInsCompare(const std::string&, const std::string&); - - /// case-insensitive less - static bool caseInsLess(const std::string&, const std::string&); - - /// case-insensitive Greater - static bool caseInsGreater(const std::string&, const std::string&); - - /// Used for checking node names - static bool valid_name(const std::string& name, std::string& msg); - static bool valid_name(const std::string& name); - - /** - * Convert a given string to an integer. - * - * This function checks if the string has any digits before attempting the conversion -- this approach is deemed - * faster than using ecf::convert_to directly (and thus always attempt to perform the conversion). - * - * Use this function when it is not possible to ensure the string is convertible to an integer (e.g. user input). - * - * @return upon successful conversion, the converted value; otherwise the given `error_return` value. - */ - static int to_int(const std::string&, int error_return = std::numeric_limits::max()); - - /** - * Check if the string can be converted to an integer. - * - * @param s the string to check - * @return true if the string can be converted to an integer, false otherwise. - */ - static bool is_int(const std::string& s); - - /// Truncate the input string at the start/end if exceeds max_lines_ newlines - /// returns true if truncated false otherwise - static bool truncate_at_start(std::string& fileContents, size_t max_lines); - static bool truncate_at_end(std::string& fileContents, size_t max_lines); - - static std::string dump_string_vec(const std::vector& vec); - - /// Only use strcmp if the first characters are the same - static int local_strcmp(const char* s, const char* t) { return (*s != *t ? *s - *t : strcmp(s, t)); } - - // returns a static string of alphanumerics and underscore - static const std::string& ALPHANUMERIC_UNDERSCORE(); - - // returns a static string of numerics chars - static const std::string& NUMERIC(); - - static const char* CHILD_CMD(); - static const char* USER_CMD(); - static const char* SVR_CMD(); // Only for automatic check_pt - - // Allows string to be returned by reference - static const std::string& EMPTY(); - static const std::string& ROOT_PATH(); // "/" - static const std::string& PATH_SEPARATOR(); // "/" - static const std::string& COLON(); // ":" - - static const std::string& STATE_CHANGE(); - - static const std::string& TASK(); - static const std::string& FAMILY(); - static const std::string& SUITE(); - static const std::string& ALIAS(); - - static const std::string& DEFAULT_PORT_NUMBER(); // "3141" - static const std::string& LOCALHOST(); - - static const std::string& WHITE_LIST_FILE(); -}; +/// +/// @brief Remove surrounding double quotes from a string. +/// +/// Only if double quotes exist *at the beginning and at the end* of the input, will any change be performed. +/// +/// @param input The input string. +/// @return A new string with surrounding double quotes removed. +/// +inline std::string remove_double_quotes_copy(std::string input) { + remove_double_quotes(input); + return input; +} + +/// +/// @brief Remove surrounding single quotes from a string in-place. +/// +/// Only if single quotes exist *at the beginning and at the end* of the input, will any change be performed. +/// +/// @param input The input string to modify. +/// +inline void remove_single_quotes(std::string& input) { + if (input.size() >= 2 && input.front() == '\'' && input.back() == '\'') { + input.erase(0, 1); + input.erase(input.size() - 1, 1); + } +} + +/// +/// @brief Remove surrounding single quotes from a string. +/// +/// Only if single quotes exist *at the beginning and at the end* of the input, will any change be performed. +/// +/// @param input The input string. +/// @return A new string with surrounding single quotes removed. +/// +inline std::string remove_single_quotes_copy(std::string input) { + remove_single_quotes(input); + return input; +} + +/// +/// @brief Compare two strings case-insensitively. +/// +/// @param s1 The first string to compare. +/// @param s2 The second string to compare. +/// @return True if the strings are equal when case is ignored, false otherwise. +/// +inline bool case_insensitive_compare(const std::string& s1, const std::string& s2) { + auto compare = [](char a, char b) { return (toupper(a) == toupper(b)); }; + return ((s1.size() == s2.size()) && equal(s1.begin(), s1.end(), s2.begin(), compare)); +} + +/// +/// @brief Compare two strings case-insensitively using a less-than ordering. +/// +/// When characters differ only in case, uppercase is considered greater (i.e. 'a' < 'A'). +/// Otherwise, characters are compared by their uppercase equivalents. +/// +/// @param a The first string to compare. +/// @param b The second string to compare. +/// @return True if \p a is less than \p b under case-insensitive ordering, false otherwise. +/// +inline bool case_insensitive_less(const std::string& a, const std::string& b) { + return std::lexicographical_compare(a.begin(), a.end(), b.begin(), b.end(), [](char x, char y) -> bool { + if (toupper(x) == toupper(y)) { + return x > y; + } + return toupper(static_cast(x)) < toupper(static_cast(y)); + }); +} + +/// +/// @brief Compare two strings case-insensitively using a greater-than ordering. +/// +/// When characters differ only in case, lowercase is considered greater (i.e. 'A' < 'a'). +/// Otherwise, characters are compared by their uppercase equivalents in reverse order. +/// +/// @param a The first string to compare. +/// @param b The second string to compare. +/// @return True if \p a is greater than \p b under case-insensitive ordering, false otherwise. +/// +inline bool case_insensitive_greater(const std::string& a, const std::string& b) { + return std::lexicographical_compare(a.begin(), a.end(), b.begin(), b.end(), [](char x, char y) -> bool { + if (toupper(x) == toupper(y)) { + return x < y; + } + return toupper(static_cast(x)) > toupper(static_cast(y)); + }); +} + +/// +/// @brief Check if the string can be converted to an integer. +/// +/// @param input the string to check. +/// @return true if the string can be converted to an integer, false otherwise. +/// +inline bool is_int(const std::string& input) { + if (input.empty()) { + return false; + } + size_t start = 0; + if (input[0] == '+' || input[0] == '-') { + start = 1; + } + if (start >= input.size()) { + return false; + } + return std::all_of(input.begin() + start, input.end(), [](unsigned char ch) { return std::isdigit(ch); }); +} + +/// +/// @brief Convert a given string to an integer. +/// +/// This function checks if the string has any digits before attempting the conversion -- this approach is deemed +/// faster than using ecf::convert_to directly (and thus always attempt to perform the conversion). +/// +/// Use this function when it is not possible to ensure the string is convertible to an integer (e.g. user input). +/// +/// @param input The string to convert. +/// @param error_return The value to return if the conversion fails (e.g. if the string contains non-numerical chars). +/// @return upon successful conversion, the converted value; otherwise the given `error_return` value. +/// +inline int to_int(const std::string& input, int error_return = std::numeric_limits::max()) { + if (input.find_first_of(ecf::string_constants::numeric_chars, 0) != std::string::npos) { + try { + return ecf::convert_to(input); + } + catch (const ecf::bad_conversion&) { + // falls through to returning error... + } + } + return error_return; +} + +/// +/// @brief Trim `fileContents` so that `max_lines` at the tail of the file are kept. +/// +/// Lines are counted by the number of '\n' characters found when scanning from the end of the string towards the +/// beginning. Once `max_lines` newlines have been encountered, all content before (and including) the newline +/// at the split point is erased. +/// +/// @param fileContents The string to truncate in-place. +/// @param max_lines The maximum number of lines to retain (counted from the end). +/// @return true if the string was truncated, false if it was empty or already within the limit. +/// +bool tail(std::string& fileContents, size_t max_lines); + +/// +/// @brief Trim `fileContents` so that `max_lines` at the head of the file are kept. +/// +/// Lines are counted by the number of '\n' characters found when scanning from the beginning of the string towards +/// the end. Once `max_lines` newlines have been encountered, all content after (and including) the +/// newline at the split point is erased. +/// +/// @param fileContents The string to truncate in-place. +/// @param max_lines The maximum number of lines to retain (counted from the beginning). +/// @return true if the string was truncated, false if it was empty or already within the limit. +/// +bool head(std::string& fileContents, size_t max_lines); + +/// +/// @brief Convert a boolean value to a string representation. +/// +/// @param value The boolean value to convert to a string. +/// @return 'true' if the value is true, 'false' otherwise. +/// +inline std::string as_string(bool value) { + return value ? "true" : "false"; +} + +/// +/// @brief Convert a string vector to a string representation. +/// +/// @param values The string vector to convert to a string. +/// @return A string representation of the vector, with elements separated by ", " and enclosed in square brackets. +/// +inline std::string as_string(const std::vector& values) { + return "[ " + ecf::algorithm::join(values) + " ]"; +} + +/// +/// @brief Convert a string_view vector to a string representation. +/// +/// @param values The string_view vector to convert to a string. +/// @return A string representation of the vector, with elements separated by ", " and enclosed in square brackets. +/// +inline std::string as_string(const std::vector& values) { + return "[ " + ecf::algorithm::join(values) + " ]"; +} + +/// +/// @brief Convert a vector of arithmetic values to a string representation. +/// +/// @tparam T The type of elements in the vector -- must be an arithmetic type (e.g. int, double, etc.). +/// @param values The vector of arithmetic values to convert to a string. +/// @return A string representation of the vector, with elements separated by ", " and enclosed in square brackets. +/// +template >> +inline std::string as_string(const std::vector& values) { + auto strings = ecf::algorithm::transform_to_vector(values, [](const auto& v) { return std::to_string(v); }); + return "[ " + ecf::algorithm::join(strings) + " ]"; +} + +/// +/// @brief Replace the first occurrence of 'find' with 'replace' in 'input'. +/// +/// When 'find' pattern is empty, no replacement is made and false is returned. +/// +/// @param input The input string in which to perform the replacement. +/// @param find The string to search for within the input. +/// @param replace The string to replace occurrences of 'find' with. +/// @return true if a replacement was made, false otherwise. +/// +inline bool replace(std::string& input, const std::string& find, const std::string& replace) { + if (find.empty()) { + return false; + } + + if (size_t pos = input.find(find); pos != std::string::npos) { + input.replace(pos, find.length(), replace); + return true; + } + return false; +} + +/// +/// @brief Replace all occurrences of 'find' with 'replace' in 'input'. +/// +/// When 'find' pattern is empty, no replacement is made and false is returned. +/// +/// @param input The input string in which to perform the replacement. +/// @param find The string to search for within the input. +/// @param replace The string to replace occurrences of 'find' with. +/// @return true if at least one replacement was made, false otherwise. +/// +inline bool replace_all(std::string& input, const std::string& find, const std::string& replace) { + if (find.empty()) { + return false; + } + + bool replaced = false; + size_t pos = 0; + while ((pos = input.find(find, pos)) != std::string::npos) { + input.replace(pos, find.length(), replace); + pos += replace.length(); + replaced = true; + } + return replaced; +} + +/// +/// @brief Extract the token at the specified index from the input string, using the given delimiters. +/// +/// @param input The input string to tokenise. +/// @param index The (0-based) index of the token to extract. +/// @param token The extracted token. +/// @param delimiters The set of characters to use as token separators. +/// @return true if a token was found at the specified index, false otherwise. +/// +bool get_token(std::string_view input, size_t index, std::string& token, std::string_view delimiters = " \t"); + +} // namespace algorithm } // namespace ecf diff --git a/libs/core/src/ecflow/core/TimeSeries.cpp b/libs/core/src/ecflow/core/TimeSeries.cpp index 6d38a8e59..aaedf586c 100644 --- a/libs/core/src/ecflow/core/TimeSeries.cpp +++ b/libs/core/src/ecflow/core/TimeSeries.cpp @@ -726,7 +726,7 @@ std::ostream& operator<<(std::ostream& os, const TimeSeries& d) { ecf::TimeSeries TimeSeries::create(const std::string& str) { std::vector lineTokens; - Str::split(str, lineTokens); + ecf::algorithm::split_at(lineTokens, str); size_t index = 0; return TimeSeries::create(index, lineTokens); } diff --git a/libs/core/src/ecflow/core/TimeSlot.cpp b/libs/core/src/ecflow/core/TimeSlot.cpp index a20c4510f..2568d07d6 100644 --- a/libs/core/src/ecflow/core/TimeSlot.cpp +++ b/libs/core/src/ecflow/core/TimeSlot.cpp @@ -71,7 +71,7 @@ void TimeSlot::write(std::string& ret) const { } ret += ecf::convert_to(h_); - ret += Str::COLON(); + ret += ecf::string_constants::colon; if (m_ < 10) { ret += "0"; } diff --git a/libs/core/src/ecflow/core/TimeStamp.cpp b/libs/core/src/ecflow/core/TimeStamp.cpp index 551b81ab8..cd3f937f3 100644 --- a/libs/core/src/ecflow/core/TimeStamp.cpp +++ b/libs/core/src/ecflow/core/TimeStamp.cpp @@ -18,18 +18,40 @@ namespace TimeStamp { namespace { +/// +/// @brief Format policy for the regular timestamp, including the year. +/// +/// Format: `"[HH:MM:SS DD.MM.YYYY] "` (strftime pattern: `"[%H:%M:%S %d.%m.%Y] "`). +/// struct regular { + /// strftime format string. static constexpr char const* format = "[%H:%M:%S %d.%m.%Y] "; - static constexpr size_t size = 23; + /// Buffer size in bytes, including the null terminator. + static constexpr size_t size = 23; }; +/// +/// @brief Format policy for the brief timestamp, omitting the year. +/// +/// Format: `"[HH:MM:SS DD.MM] "` (strftime pattern: `"[%H:%M:%S %d.%m] "`). +/// struct brief { + /// strftime format string. static constexpr char const* format = "[%H:%M:%S %d.%m] "; - static constexpr size_t size = 18; + /// Buffer size in bytes, including the null terminator. + static constexpr size_t size = 18; }; +/// +/// @brief Format the current local time as a timestamp string. +/// +/// @tparam FMT A format policy providing a strftime-compatible `format` string and the +/// corresponding `size` (in bytes, including the null terminator). Defaults to `regular`. +/// +/// @return The current local time formatted according to `FMT::format`. +/// template std::string format_now() { std::time_t now = std::time(nullptr); @@ -44,12 +66,12 @@ std::string now() { return format_now(); } -void now(std::string& time_stamp) { - time_stamp = format_now(); +void now(std::string& buffer) { + buffer = format_now(); } -void now_in_brief(std::string& time_stamp) { - time_stamp = format_now(); +void now_in_brief(std::string& buffer) { + buffer = format_now(); } } // namespace TimeStamp diff --git a/libs/core/src/ecflow/core/TimeStamp.hpp b/libs/core/src/ecflow/core/TimeStamp.hpp index 072e57fac..28291b108 100644 --- a/libs/core/src/ecflow/core/TimeStamp.hpp +++ b/libs/core/src/ecflow/core/TimeStamp.hpp @@ -14,31 +14,42 @@ #include namespace ecf { + +/// +/// @brief Utilities for generating formatted wall-clock timestamps. +/// namespace TimeStamp { -/// Generate a 'regular' time stamp. /// -/// Format specified as -/// - "[%02d:%02d:%02d %d.%d.%d] ", considering "[hour:min:sec day.month.year] -/// " +/// @brief Return a timestamp string representing the current time. /// -/// Results in the following examples -/// - "[05:26:20 29.10.2014] " -/// - "[05:26:20 17.1.2023] " +/// Format: `"[HH:MM:SS D.M.YYYY] "` (e.g. `"[05:26:20 29.10.2014] "` or `"[05:26:20 17.1.2023] "`). +/// Note that day and month have no leading zeros. +/// +/// @return A formatted timestamp string. /// std::string now(); -void now(std::string&); -/// Generate a 'brief' time stamp. /// -/// Format specified as -/// - "[%02d:%02d:%02d %d.%d] ", considering "[hour:min:sec day.month] " +/// @brief Append a timestamp of the current time to \p buffer. +/// +/// Format: `"[HH:MM:SS D.M.YYYY] "` (e.g. `"[05:26:20 29.10.2014] "` or `"[05:26:20 17.1.2023] "`). +/// Note that day and month have no leading zeros. +/// +/// @param buffer The string to which the formatted timestamp is appended. +/// +void now(std::string& buffer); + +/// +/// @brief Append a brief timestamp of the current time to \p buffer. +/// +/// The brief format omits the year component. +/// Format: `"[HH:MM:SS D.M] "` (e.g. `"[05:26:20 29.10] "` or `"[05:26:20 17.1] "`). +/// Note that day and month have no leading zeros. /// -/// Results in the following examples -/// - "[05:26:20 29.10] " -/// - "[05:26:20 17.1] " +/// @param buffer The string to which the formatted timestamp is appended. /// -void now_in_brief(std::string&); +void now_in_brief(std::string& buffer); } // namespace TimeStamp } // namespace ecf diff --git a/libs/core/src/ecflow/core/Timer.hpp b/libs/core/src/ecflow/core/Timer.hpp index 6a0ee3d14..f42f1b266 100644 --- a/libs/core/src/ecflow/core/Timer.hpp +++ b/libs/core/src/ecflow/core/Timer.hpp @@ -16,6 +16,15 @@ #include #include +/// +/// @brief A simple elapsed-time utility that records the instant of its construction. +/// +/// The elapsed time is measured from the moment the object is constructed. `Resolution` +/// controls the granularity of the reported duration. +/// +/// @tparam Resolution The chrono duration type used to express elapsed time +/// (defaults to `std::chrono::milliseconds`). +/// template class Timer { public: @@ -29,25 +38,48 @@ class Timer { public: Timer() = default; ~Timer() = default; + + /// + /// @brief Print the elapsed time since construction to standard output, prefixed by \p msg. + /// + /// @param msg The label printed before the elapsed-time value. + /// void elapsed(const char* msg) const { const auto end = Clock::now(); std::cout << msg << " " << std::chrono::duration_cast(end - start_).count() << std::endl; } + /// + /// @brief Return the elapsed time since construction. + /// + /// @return The elapsed duration cast to `Resolution`. + /// Resolution elapsed() const { return std::chrono::duration_cast(Clock::now() - start_); } }; namespace ecf { -/** - * @brief A class to measure the duration of a function invocation - * - * @tparam Time the unit of time used to measure the duration of a function invocation - * @tparam Clock the clock type used to measure the duration of a function invocation - */ +/// +/// @brief Measures the duration of a single function invocation. +/// +/// @tparam Time The chrono duration type used to express the measured duration +/// (defaults to `std::chrono::microseconds`). +/// @tparam Clock The clock type used to take timestamps +/// (defaults to `std::chrono::high_resolution_clock`). +/// template struct FunctionPerformanceTimer { + /// + /// @brief Invoke \p f with \p args and return the elapsed time. + /// + /// @tparam F A callable type. + /// @tparam Args Argument types forwarded to \p f. + /// + /// @param f The callable to invoke. + /// @param args Arguments to forward to \p f. + /// @return The elapsed duration cast to `Time`. + /// template static Time duration(F&& f, Args... args) { auto start = Clock::now(); @@ -59,9 +91,9 @@ struct FunctionPerformanceTimer } }; -/** - * @brief A timer capable of measuring and reporting the elapsed wall clock time in microseconds. - */ +/// +/// @brief A timer that measures elapsed wall-clock time with microsecond precision. +/// class DurationTimer { public: using clock_t = std::chrono::system_clock; @@ -73,40 +105,48 @@ class DurationTimer { ~DurationTimer() = default; - /** - * @return the elapsed duration, in whole seconds, since the start (i.e. creation of this DurationTimer) - */ + /// + /// @brief Return the elapsed time since construction, in whole seconds. + /// + /// @return The elapsed duration, in whole seconds, since the start (i.e. creation of this DurationTimer). + /// [[nodiscard]] int duration() const { return std::chrono::duration_cast(elapsed()).count(); } - /** - * @return the elapsed duration, since the start (i.e. creation of this DurationTimer) - */ + /// + /// @brief Return the elapsed time since construction. + /// + /// @return The elapsed duration since the start (i.e. creation of this DurationTimer). + /// [[nodiscard]] duration_t elapsed() const { return std::chrono::duration_cast(clock_t::now() - start_time_); } - /** - * @return the elapsed duration since the start, in decimal seconds with microsecond precision - */ + /// + /// @brief Return the elapsed time since construction, in decimal seconds. + /// + /// @return The elapsed duration since the start, in decimal seconds with microsecond precision. + /// [[nodiscard]] double elapsed_seconds() const { auto us = std::chrono::duration_cast(elapsed()); return static_cast(us.count()) / 1000000.0; } - /** - * @return the elapsed duration since the start, in decimal milliseconds with microsecond precision - */ + /// + /// @brief Return the elapsed time since construction, in decimal milliseconds. + /// + /// @return The elapsed duration since the start, in decimal milliseconds with microsecond precision. + /// [[nodiscard]] double elapsed_milliseconds() const { auto us = std::chrono::duration_cast(elapsed()); return static_cast(us.count()) / 1000.0; } - /** - * Formats the given duration as a string - * - * @param d the elapsed duration to format - * @return the formatted string in the form "HH:MM:SS.mmmuuu" - */ + /// + /// @brief Format a duration as a human-readable string. + /// + /// @param d The elapsed duration to format. + /// @return The formatted string in the form `"HH:MM:SS.mmmuuu"`. + /// static std::string to_simple_string(duration_t d) { using namespace std::chrono; auto h = duration_cast(d); @@ -135,13 +175,18 @@ class DurationTimer { instant_t start_time_; }; -/** - * @brief A timer capable of measuring and reporting the elapsed wall clock time in microseconds. - * - * The elapsed time is reported to standard out when this object goes out of scope (i.e. in the dtor). - */ +/// +/// @brief A timer that reports the elapsed wall-clock time to standard output when it goes out of scope. +/// +/// The elapsed time is printed to standard output when this object is destroyed (i.e. in the destructor). +/// class ScopedDurationTimer : public DurationTimer { public: + /// + /// @brief Construct a scoped timer with the given label. + /// + /// @param msg The label printed alongside the elapsed time when the timer is destroyed. + /// explicit ScopedDurationTimer(std::string msg) : DurationTimer(), msg_(std::move(msg)) {} @@ -152,21 +197,39 @@ class ScopedDurationTimer : public DurationTimer { std::string msg_; }; +/// +/// @brief A snapshot of elapsed wall-clock, user-CPU, and system-CPU time. +/// struct PerformanceMeasure { - std::chrono::nanoseconds wall; - std::chrono::nanoseconds user; - std::chrono::nanoseconds system; + std::chrono::nanoseconds wall; ///< Elapsed wall-clock time. + std::chrono::nanoseconds user; ///< Elapsed user-CPU time. + std::chrono::nanoseconds system; ///< Elapsed system-CPU time. + /// + /// @brief Reset all durations to zero. + /// void clear() { wall = std::chrono::nanoseconds{0}; user = std::chrono::nanoseconds{0}; system = std::chrono::nanoseconds{0}; } + /// + /// @brief Capture the current wall-clock, user-CPU, and system-CPU times. + /// + /// @return A `PerformanceMeasure` snapshot representing the current instant. + /// static PerformanceMeasure current(); }; +/// +/// @brief Compute the difference between two `PerformanceMeasure` snapshots. +/// +/// @param lhs The later (end) snapshot. +/// @param rhs The earlier (start) snapshot. +/// @return A `PerformanceMeasure` whose fields hold the per-dimension elapsed time. +/// inline PerformanceMeasure operator-(const PerformanceMeasure& lhs, const PerformanceMeasure& rhs) { auto wall = lhs.wall - rhs.wall; auto user = lhs.user - rhs.user; @@ -174,18 +237,45 @@ inline PerformanceMeasure operator-(const PerformanceMeasure& lhs, const Perform return PerformanceMeasure{wall, user, system}; } +/// +/// @brief A timer that measures wall-clock, user-CPU, and system-CPU time. +/// +/// Call `start()` to reset the timer, and `elapsed()` to obtain the elapsed +/// `PerformanceMeasure` since the last reset (or construction). +/// class PerformanceTimer { public: + /// + /// @brief Construct a performance timer, recording the start instant. + /// PerformanceTimer() : times_{PerformanceMeasure::current()} {} + /// + /// @brief Reset the timer to the current instant. + /// void start() noexcept { times_ = PerformanceMeasure::current(); } + + /// + /// @brief Return the elapsed time since the last reset (or construction). + /// + /// @return A `PerformanceMeasure` snapshot of the elapsed times. + /// PerformanceMeasure elapsed() const { return PerformanceMeasure::current() - times_; } private: PerformanceMeasure times_; }; +/// +/// @brief Write a human-readable performance summary of \p timer to \p os. +/// +/// The output format is: `s wall, (s user + s system = s) CPU (%)` +/// +/// @param os The output stream to write to. +/// @param timer The timer whose elapsed performance to format. +/// @return A reference to \p os. +/// inline std::ostream& operator<<(std::ostream& os, const PerformanceTimer& timer) { auto elapsed = timer.elapsed(); auto w = static_cast(elapsed.wall.count()) / 1000000000.0; @@ -197,6 +287,9 @@ inline std::ostream& operator<<(std::ostream& os, const PerformanceTimer& timer) return os; } +/// +/// @brief A performance timer that reports elapsed time to standard output when it goes out of scope. +/// class ScopedPerformanceTimer : public PerformanceTimer { public: ~ScopedPerformanceTimer() noexcept { std::cout << *this << std::endl; }; diff --git a/libs/core/src/ecflow/core/TypeTraits.hpp b/libs/core/src/ecflow/core/TypeTraits.hpp new file mode 100644 index 000000000..1770ed5bc --- /dev/null +++ b/libs/core/src/ecflow/core/TypeTraits.hpp @@ -0,0 +1,56 @@ +/* + * 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_core_TypeTraits_HPP +#define ecflow_core_TypeTraits_HPP + +#include + +namespace ecf { + +/// +/// @brief Check if a given reference type is one of the comparing types +/// +/// The value data member is true when the refernce type is one of the comparing types, otherwise false. +/// +/// @tparam T the reference type +/// @tparam Xs the comparing types +/// +template +struct is_one_of +{ + static constexpr bool value = false; +}; + +/// +/// @brief Specialisation of is_one_of to handle the comparison between the reference type and the comparing types +/// +/// @tparam T the reference type +/// @tparam X the first of the comparing types +/// @tparam Xs the remaining comparing types +/// +template +struct is_one_of +{ + static constexpr bool value = std::is_same_v || is_one_of::value; +}; + +/// +/// @brief A convenience variable template to get the value of is_one_of without needing to access the value data member +/// +/// @tparam T the reference type +/// @tparam Xs the comparing types +/// +template +inline constexpr bool is_one_of_v = is_one_of::value; + +} // namespace ecf + +#endif /* ecflow_core_TypeTraits_HPP */ diff --git a/libs/core/src/ecflow/core/User.hpp b/libs/core/src/ecflow/core/User.hpp index e21c8ff8a..c95ea040b 100644 --- a/libs/core/src/ecflow/core/User.hpp +++ b/libs/core/src/ecflow/core/User.hpp @@ -15,6 +15,11 @@ namespace ecf { +/// +/// @brief Return the login name of the current user. +/// +/// @return The login name of the current user as a string. +/// std::string get_login_name(); } // namespace ecf diff --git a/libs/core/src/ecflow/core/Version.hpp b/libs/core/src/ecflow/core/Version.hpp index f3611c294..249dcebb7 100644 --- a/libs/core/src/ecflow/core/Version.hpp +++ b/libs/core/src/ecflow/core/Version.hpp @@ -16,7 +16,7 @@ namespace ecf { /// -/// \brief Provides information regarding the version of ecFlow. +/// @brief Provides information regarding the version of ecFlow. /// /// The ecFlow version format is of the form `..[]`, /// following the semantic versioning scheme. @@ -28,41 +28,89 @@ class Version { // Disable default construction Version() = delete; + /// + /// @brief Return the major version component of ecFlow. + /// + /// @return The major version component as a string. + /// static std::string major(); + + /// + /// @brief Return the minor version component of ecFlow. + /// + /// @return The minor version component as a string. + /// static std::string minor(); + + /// + /// @brief Return the patch version component of ecFlow. + /// + /// @return The patch version component as a string. + /// static std::string patch(); + + /// + /// @brief Return the version suffix of ecFlow, if any. + /// + /// @return The version suffix as a string, or an empty string if no suffix is defined. + /// static std::string suffix(); /// - /// Creates a string with a descriptive version information, - /// including the version of ecFlow and relevant dependencies. + /// @brief Return a descriptive version string, including ecFlow and relevant dependency versions. /// - /// This provides user facing version information + /// This provides user-facing version information /// (shown by ecflow_client --help, and in the server info panel on ecflow_ui). /// + /// @return A formatted version description string. + /// static std::string description(); /// - /// Creates the ecFlow version, following the template: `..` + /// @brief Return the ecFlow version string, without any suffix. + /// + /// Format: `..` + /// + /// @return The base version string. /// static std::string base(); /// - /// Creates the ecFlow version, following the template: `..[]` + /// @brief Return the complete ecFlow version string, including any suffix. + /// + /// Format: `..[]` + /// + /// @return The full version string. /// static std::string full(); private: - /// Create a string containing the version of the Boost library + /// + /// @brief Return the version of the Boost library. + /// + /// @return The Boost library version as a string. + /// static std::string boost(); - /// Create a string containing the version of the Cereal library + /// + /// @brief Return the version of the Cereal library. + /// + /// @return The Cereal library version as a string. + /// static std::string cereal(); - /// Create a string containing the version of the Compiler used to build ecFlow + /// + /// @brief Return the version of the compiler used to build ecFlow. + /// + /// @return The compiler version as a string. + /// static std::string compiler(); - /// Create a string containing the version of the OpenSSL library + /// + /// @brief Return the version of the OpenSSL library. + /// + /// @return The OpenSSL library version as a string. + /// static std::string openssl(); }; diff --git a/libs/core/src/ecflow/core/WhiteListFile.cpp b/libs/core/src/ecflow/core/WhiteListFile.cpp index 66225bbe9..ae4126aa6 100644 --- a/libs/core/src/ecflow/core/WhiteListFile.cpp +++ b/libs/core/src/ecflow/core/WhiteListFile.cpp @@ -62,10 +62,10 @@ bool WhiteListFile::verify_write_access(const std::string& user) const { return true; } - if (verify_path_access(user, Str::EMPTY(), users_with_write_access_)) { + if (verify_path_access(user, ecf::string_constants::empty, users_with_write_access_)) { return true; } - if (verify_path_access("*", Str::EMPTY(), users_with_write_access_)) { + if (verify_path_access("*", ecf::string_constants::empty, users_with_write_access_)) { return true; } return false; @@ -342,7 +342,7 @@ bool WhiteListFile::load(const std::string& file, std::string& errorMsg) { ecf::algorithm::trim(theLine); // remove leading and trailing spaces std::vector lineTokens; - Str::split(theLine, lineTokens); + ecf::algorithm::split_at(lineTokens, theLine); if (lineTokens.empty()) { continue; } @@ -417,11 +417,11 @@ std::string WhiteListFile::dump_valid_users() const { bool WhiteListFile::validateVersionNumber(const std::string& line, std::string& errorMsg) const { // Expect 4.4.14, Current syntax in force after 4.4.5 // If first character is NUMERIC and we have dots - bool firstCharIsNumeric = Str::NUMERIC().find(line[0], 0) != std::string::npos; + bool firstCharIsNumeric = ecf::string_constants::numeric_chars.find(line[0], 0) != std::string::npos; if (firstCharIsNumeric && line.find(".") != std::string::npos) { std::vector versionNumberTokens; - Str::split(line, versionNumberTokens, "."); + ecf::algorithm::split_at(versionNumberTokens, line, "."); if (versionNumberTokens.size() != 3) { errorMsg += "Expected version of the form .. i.e 4.4.14. but found invalid version number\n"; return false; @@ -561,7 +561,7 @@ bool WhiteListFile::add_user(std::vector& tokens, std::string& erro else if (tok[0] == '/') { // path or set of paths std::vector local_paths; - Str::split(tok, local_paths, ","); + ecf::algorithm::split_at(local_paths, tok, ","); std::copy(local_paths.begin(), local_paths.end(), std::back_inserter(paths)); // root path '/' means apply to all suites, in which case the paths may as well be empty. diff --git a/libs/core/test/TestBase64.cpp b/libs/core/test/TestBase64.cpp index 12a5c2fed..97779154a 100644 --- a/libs/core/test/TestBase64.cpp +++ b/libs/core/test/TestBase64.cpp @@ -81,10 +81,66 @@ BOOST_AUTO_TEST_CASE(test_base64_is_able_to_decode_and_encode_long_string_with_n BOOST_AUTO_TEST_CASE(test_base64_is_able_to_validate) { ECF_NAME_THIS_TEST(); + BOOST_CHECK(ecf::validate_base64("")); + BOOST_CHECK(ecf::validate_base64("Zm9v")); + BOOST_CHECK(ecf::validate_base64("Zm8=")); BOOST_CHECK(ecf::validate_base64("SGVsbG8sIFdvcmxkIQ==")); BOOST_CHECK(ecf::validate_base64("U29tZSB0ZXh0IHdpdGggcGFkZGluZw==")); - BOOST_CHECK(!ecf::validate_base64("Invalid base64 string!")); - BOOST_CHECK(!ecf::validate_base64("SGVsbG8sIFdvcmxkIQ")); // missing padding + BOOST_CHECK(!ecf::validate_base64("Invalid base64 string!")); // invalid size + BOOST_CHECK(!ecf::validate_base64("Invalid base64 !")); // invalid characters + BOOST_CHECK(!ecf::validate_base64("SGVsbG8sIFdvcmxkIQ")); // missing padding + BOOST_CHECK(!ecf::validate_base64("Zm8=====")); // padding too long + BOOST_CHECK(!ecf::validate_base64("Zm9vYg=x")); // invalid padding characters +} + +BOOST_AUTO_TEST_CASE(test_base64_rfc_test_cases) { + ECF_NAME_THIS_TEST(); + + // The following test cases are based on RFC 4648, Section 10. + // The original tests can be found at https://datatracker.ietf.org/doc/html/rfc4648#section-10. + + // Encoding + { + BOOST_CHECK_EQUAL(ecf::encode_base64(""), ""); + BOOST_CHECK_EQUAL(ecf::encode_base64("f"), "Zg=="); + BOOST_CHECK_EQUAL(ecf::encode_base64("fo"), "Zm8="); + BOOST_CHECK_EQUAL(ecf::encode_base64("foo"), "Zm9v"); + BOOST_CHECK_EQUAL(ecf::encode_base64("foob"), "Zm9vYg=="); + BOOST_CHECK_EQUAL(ecf::encode_base64("fooba"), "Zm9vYmE="); + BOOST_CHECK_EQUAL(ecf::encode_base64("foobar"), "Zm9vYmFy"); + } + + // Decoding + { + BOOST_CHECK_EQUAL(ecf::decode_base64(""), ""); + BOOST_CHECK_EQUAL(ecf::decode_base64("Zg=="), "f"); + BOOST_CHECK_EQUAL(ecf::decode_base64("Zm8="), "fo"); + BOOST_CHECK_EQUAL(ecf::decode_base64("Zm9v"), "foo"); + BOOST_CHECK_EQUAL(ecf::decode_base64("Zm9vYg=="), "foob"); + BOOST_CHECK_EQUAL(ecf::decode_base64("Zm9vYmE="), "fooba"); + BOOST_CHECK_EQUAL(ecf::decode_base64("Zm9vYmFy"), "foobar"); + } +} + +BOOST_AUTO_TEST_CASE(test_base64_error_cases) { + ECF_NAME_THIS_TEST(); + + { + // invalid characters (' ' and '!') + BOOST_CHECK_THROW(ecf::decode_base64("Invalid base64 !"), std::invalid_argument); + + // invalid size (-2), missing padding + BOOST_CHECK_THROW(ecf::decode_base64("SGVsbG8sIFdvcmxkIQ"), std::invalid_argument); + + // invalid size (+1), newline after padding + BOOST_CHECK_THROW(ecf::decode_base64("SGVsbG8sIFdvcmxkIQ==\n"), std::invalid_argument); + + // invalid format, newline after padding + BOOST_CHECK_THROW(ecf::decode_base64("SGVsbG8sIFdvcmxkIQ=\n"), std::invalid_argument); + + // invalid format, valid base64 character but after padding + BOOST_CHECK_THROW(ecf::decode_base64("SGVsbG8sIFdvcmxkIQ=a"), std::invalid_argument); + } } BOOST_AUTO_TEST_SUITE_END() diff --git a/libs/core/test/TestCalendar.cpp b/libs/core/test/TestCalendar.cpp index 5a645d761..e19449b09 100644 --- a/libs/core/test/TestCalendar.cpp +++ b/libs/core/test/TestCalendar.cpp @@ -359,7 +359,7 @@ BOOST_AUTO_TEST_CASE(test_calendar_state_parsing) { // read the state, into a different calendar & compare std::vector lineTokens; - Str::split(calendar_state, lineTokens); + ecf::algorithm::split_at(lineTokens, calendar_state); Calendar calendar2; calendar2.read_state(calendar_state, lineTokens); BOOST_CHECK_MESSAGE(calendar == calendar2, @@ -376,7 +376,7 @@ BOOST_AUTO_TEST_CASE(test_calendar_state_parsing) { calendar_state.clear(); calendar.write_state(calendar_state); - Str::split(calendar_state, lineTokens); + ecf::algorithm::split_at(lineTokens, calendar_state); calendar2.read_state(calendar_state, lineTokens); BOOST_CHECK_MESSAGE(calendar == calendar2, "Calendar should be the same"); @@ -499,30 +499,30 @@ BOOST_AUTO_TEST_CASE(test_calendar_time_series_relative_complex) { if (intersects && boundaryOk) { BOOST_CHECK_MESSAGE(matches, "Calendar should match relative time series at " - << suiteTm.tm_hour << Str::COLON() << suiteTm.tm_min + << suiteTm.tm_hour << ecf::string_constants::colon << suiteTm.tm_min << " suite time = " << to_simple_string(calendar.suiteTime())); if (!matches) { - ECF_TEST_ERR(<< "suiteTm.tm_hour =" << suiteTm.tm_hour << " suiteTm.tm_min = " << suiteTm.tm_min - << " timeSeries.start().hour() " << timeSeries.start().hour() - << " timeSeries.start().minute() " << timeSeries.start().minute() - << " timeSeries.finish().hour() " << timeSeries.finish().hour() - << " timeSeries.finish().minute() " << timeSeries.finish().minute() - << " suiteTm.tm_min % 15 = " << suiteTm.tm_min % 15); + ECF_TEST_ERR("suiteTm.tm_hour =" << suiteTm.tm_hour << " suiteTm.tm_min = " << suiteTm.tm_min + << " timeSeries.start().hour() " << timeSeries.start().hour() + << " timeSeries.start().minute() " << timeSeries.start().minute() + << " timeSeries.finish().hour() " << timeSeries.finish().hour() + << " timeSeries.finish().minute() " << timeSeries.finish().minute() + << " suiteTm.tm_min % 15 = " << suiteTm.tm_min % 15); } } else { BOOST_CHECK_MESSAGE(!matches, "Calendar should NOT match relative time series at " - << suiteTm.tm_hour << Str::COLON() << suiteTm.tm_min + << suiteTm.tm_hour << ecf::string_constants::colon << suiteTm.tm_min << " suite time = " << to_simple_string(calendar.suiteTime())); if (matches) { - ECF_TEST_ERR(<< "suiteTm.tm_hour =" << suiteTm.tm_hour << " suiteTm.tm_min = " << suiteTm.tm_min - << " timeSeries.start().hour() " << timeSeries.start().hour() - << " timeSeries.start().minute() " << timeSeries.start().minute() - << " timeSeries.finish().hour() " << timeSeries.finish().hour() - << " timeSeries.finish().minute() " << timeSeries.finish().minute() - << " suiteTm.tm_min % 15 = " << suiteTm.tm_min % 15); + ECF_TEST_ERR("suiteTm.tm_hour =" << suiteTm.tm_hour << " suiteTm.tm_min = " << suiteTm.tm_min + << " timeSeries.start().hour() " << timeSeries.start().hour() + << " timeSeries.start().minute() " << timeSeries.start().minute() + << " timeSeries.finish().hour() " << timeSeries.finish().hour() + << " timeSeries.finish().minute() " << timeSeries.finish().minute() + << " suiteTm.tm_min % 15 = " << suiteTm.tm_min % 15); } } } @@ -601,12 +601,12 @@ BOOST_AUTO_TEST_CASE(test_calendar_time_series_real_complex) { << suiteTm.tm_hour << ":" << suiteTm.tm_min << " suite time = " << to_simple_string(calendar.suiteTime())); if (!matches) { - ECF_TEST_ERR(<< "suiteTm.tm_hour =" << suiteTm.tm_hour << " suiteTm.tm_min = " << suiteTm.tm_min - << " timeSeries.start().hour() " << timeSeries.start().hour() - << " timeSeries.start().minute() " << timeSeries.start().minute() - << " timeSeries.finish().hour() " << timeSeries.finish().hour() - << " timeSeries.finish().minute() " << timeSeries.finish().minute() - << " suiteTm.tm_min % 15 = " << suiteTm.tm_min % 15); + ECF_TEST_ERR("suiteTm.tm_hour =" << suiteTm.tm_hour << " suiteTm.tm_min = " << suiteTm.tm_min + << " timeSeries.start().hour() " << timeSeries.start().hour() + << " timeSeries.start().minute() " << timeSeries.start().minute() + << " timeSeries.finish().hour() " << timeSeries.finish().hour() + << " timeSeries.finish().minute() " << timeSeries.finish().minute() + << " suiteTm.tm_min % 15 = " << suiteTm.tm_min % 15); } } else { @@ -616,12 +616,12 @@ BOOST_AUTO_TEST_CASE(test_calendar_time_series_real_complex) { << " suite time = " << to_simple_string(calendar.suiteTime())); if (matches) { - ECF_TEST_ERR(<< "suiteTm.tm_hour =" << suiteTm.tm_hour << " suiteTm.tm_min = " << suiteTm.tm_min - << " timeSeries.start().hour() " << timeSeries.start().hour() - << " timeSeries.start().minute() " << timeSeries.start().minute() - << " timeSeries.finish().hour() " << timeSeries.finish().hour() - << " timeSeries.finish().minute() " << timeSeries.finish().minute() - << " suiteTm.tm_min % 15 = " << suiteTm.tm_min % 15); + ECF_TEST_ERR("suiteTm.tm_hour =" << suiteTm.tm_hour << " suiteTm.tm_min = " << suiteTm.tm_min + << " timeSeries.start().hour() " << timeSeries.start().hour() + << " timeSeries.start().minute() " << timeSeries.start().minute() + << " timeSeries.finish().hour() " << timeSeries.finish().hour() + << " timeSeries.finish().minute() " << timeSeries.finish().minute() + << " suiteTm.tm_min % 15 = " << suiteTm.tm_min % 15); } } } diff --git a/libs/core/test/TestClassDataMemberInit.cpp b/libs/core/test/TestClassDataMemberInit.cpp index 984b3644a..b5e6004ea 100644 --- a/libs/core/test/TestClassDataMemberInit.cpp +++ b/libs/core/test/TestClassDataMemberInit.cpp @@ -35,7 +35,7 @@ class MyType { MyType(const MyType& other) : mName(other.mName) { #ifdef DEBUG_ME - ECF_TEST_DBG(<< "MyType::MyType(const MyType&) " << mName << " my_int:" << my_int_); + ECF_TEST_DBG("MyType::MyType(const MyType&) " << mName << " my_int:" << my_int_); #endif } diff --git a/libs/core/test/TestFile.cpp b/libs/core/test/TestFile.cpp index 56a128a94..ee4ba04cc 100644 --- a/libs/core/test/TestFile.cpp +++ b/libs/core/test/TestFile.cpp @@ -157,8 +157,8 @@ BOOST_AUTO_TEST_CASE(test_file_tokenizer) { BOOST_CHECK_MESSAGE(File::splitFileIntoLines(path, lines), " Failed to open file " << path << " (" << strerror(errno) << ")"); } - ECF_TEST_DBG(<< "Time for opening file " << openFileNTimes - << " times = " << timer.format(3, Str::cpu_timer_format())); + ECF_TEST_DBG("Time for opening file " << openFileNTimes + << " times = " << timer.format(3, Str::cpu_timer_format())); } #endif @@ -189,11 +189,11 @@ BOOST_AUTO_TEST_CASE(test_file_backwardSearch) { std::vector fileContents; fileContents.emplace_back("something"); std::vector nodePathTokens; - NodePath::split(nodePath, nodePathTokens); + ecf::node::split_path(nodePath, nodePathTokens); while (nodePathTokens.size() > 0) { // Reconstitute the path - std::string path = NodePath::createPath(nodePathTokens); + std::string path = ecf::node::create_node_path(nodePathTokens); std::string combinedPath = rootPath + path; BOOST_REQUIRE_MESSAGE(File::createDirectories(combinedPath), "Failed to create dirs " << combinedPath); @@ -263,10 +263,10 @@ BOOST_AUTO_TEST_CASE(test_file_forwardSearch) { std::vector fileContents; fileContents.emplace_back("something"); std::vector nodePathTokens; - NodePath::split(nodePath, nodePathTokens); + ecf::node::split_path(nodePath, nodePathTokens); while (nodePathTokens.size() > 0) { - std::string path = NodePath::createPath(nodePathTokens); + std::string path = ecf::node::create_node_path(nodePathTokens); std::string combinedPath = rootPath + path + File::ECF_EXTN(); // .ecf, .man , etc @@ -316,7 +316,7 @@ BOOST_AUTO_TEST_CASE(test_create_missing_directories) { // This test FAIL's randomly on the cray in BATCH mode, but passes in interactive mode. if (ecf::environment::has("ECFLOW_CRAY_BATCH")) { - ECF_TEST_DBG(<< "Test skipped until HPC team can fix File::createMissingDirectories.(like mkdir -p)"); + ECF_TEST_DBG("Test skipped until HPC team can fix File::createMissingDirectories.(like mkdir -p)"); return; } diff --git a/libs/core/test/TestLog.cpp b/libs/core/test/TestLog.cpp index ceea8070a..7d88a1c89 100644 --- a/libs/core/test/TestLog.cpp +++ b/libs/core/test/TestLog.cpp @@ -22,15 +22,15 @@ using namespace ecf; void dump_path(const fs::path& path) { - ECF_TEST_DBG(<< "path = " << path); - ECF_TEST_DBG(<< "path.root_path(): " << path.root_path()); - ECF_TEST_DBG(<< "path.root_name() : " << path.root_name()); - ECF_TEST_DBG(<< "path.root_directory() : " << path.root_directory()); - ECF_TEST_DBG(<< "path.relative_path() : " << path.relative_path()); - ECF_TEST_DBG(<< "path.parent_path() : " << path.parent_path()); - ECF_TEST_DBG(<< "path.filename() : " << path.filename()); - ECF_TEST_DBG(<< "path.stem() : " << path.stem()); - ECF_TEST_DBG(<< "path.extension() : " << path.extension()); + ECF_TEST_DBG("path = " << path); + ECF_TEST_DBG("path.root_path(): " << path.root_path()); + ECF_TEST_DBG("path.root_name() : " << path.root_name()); + ECF_TEST_DBG("path.root_directory() : " << path.root_directory()); + ECF_TEST_DBG("path.relative_path() : " << path.relative_path()); + ECF_TEST_DBG("path.parent_path() : " << path.parent_path()); + ECF_TEST_DBG("path.filename() : " << path.filename()); + ECF_TEST_DBG("path.stem() : " << path.stem()); + ECF_TEST_DBG("path.extension() : " << path.extension()); } BOOST_AUTO_TEST_SUITE(U_Core) diff --git a/libs/core/test/TestNodeNameValidity.cpp b/libs/core/test/TestNodeNameValidity.cpp new file mode 100644 index 000000000..9ef9f7444 --- /dev/null +++ b/libs/core/test/TestNodeNameValidity.cpp @@ -0,0 +1,332 @@ +/* + * Copyright 2009- ECMWF. + * + * This software is licensed under the terms of the Apache Licence version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation + * nor does it submit to any jurisdiction. + */ + +#include // std::ostream_iterator +#include +#include + +#include + +#include "ecflow/core/NodePath.hpp" +#include "ecflow/core/Stl.hpp" +#include "ecflow/core/Str.hpp" +#include "ecflow/core/Timer.hpp" +#include "ecflow/test/scaffold/Naming.hpp" + +// +// Important: +// +// The following implementation was the one used by ecFlow to check if a node name is valid. +// It is now replaced by the implementation in Str.hpp, but it was kept in the scope of this test +// to check that the new implementation behaves as expected. +// + +namespace ecf { +namespace prototype { + +const char* VALID_NODE_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_."; + +bool Str_valid_name(const std::string& name, std::string& msg) { + // valid names are alphabetic (alphanumeric | underscore | .) + // however we can't have a leading '.' as that can interfere with trigger expressions + + // verify that the string is not empty + if (name.empty()) { + msg = "Invalid name. Empty string."; + return false; + } + + // verify that the first character is alphanumeric or is an underscore + bool result = ecf::string_constants::alphanumeric_underscore_chars.find(name[0], 0) != std::string::npos; + if (!result) { + msg = "Valid names can only consist of alphanumeric characters, " + "underscores and dots (The first character cannot be a dot). " + "The first character is not valid (only alphanumeric or an underscore is allowed): "; + msg += name; + return false; + } + + // verify that any other characters are alphanumeric or underscore + if (name.size() > 1) { + result = name.find_first_not_of(VALID_NODE_CHARS, 1) == std::string::npos; + if (!result) { + msg = "Valid names can only consist of alphanumeric characters, " + "underscores and dots (The first character cannot be a dot). "; + if (name.find('\r') != std::string::npos) { + msg += "Windows line ending ? "; + } + msg += "'"; + msg += name; + msg += "'"; // use '' to show if PC format, i.e. carriage return + } + } + + return result; +} + +bool Str_valid_name(const std::string& name) { + // valid names are alphabetic (alphanumeric | underscore | .) + // however we can't have a leading '.' as that can interfere with trigger expressions + + // verify that the string is not empty + if (name.empty()) { + return false; + } + + // verify that the first character is alphabetic or has underscore + bool result = ecf::string_constants::alphanumeric_underscore_chars.find(name[0], 0) != std::string::npos; + if (!result) { + return false; + } + + // verify that any other characters are alphanumeric or underscore + if (name.size() > 1) { + result = name.find_first_not_of(VALID_NODE_CHARS, 1) == std::string::npos; + } + + return result; +} + +} // namespace prototype +} // namespace ecf + +BOOST_AUTO_TEST_SUITE(U_Core) + +BOOST_AUTO_TEST_SUITE(T_NodeNameValidity) + +BOOST_AUTO_TEST_CASE(test_node_name_validity) { + ECF_NAME_THIS_TEST(); + + struct tc + { + std::string name; + bool expected; + }; + + std::vector test_cases = { + {"", false}, + {".", false}, + {".a.", false}, + {".A", false}, + {".1", false}, + {"_", true}, + {"_a.", true}, + {"_A", true}, + {"_1", true}, + {"a.", true}, + {"A", true}, + {"1", true}, + {"aa.", true}, + {"AA", true}, + {"11", true}, + {"a.", true}, + {"ab.", true}, + {"a.b", true}, + {"a.b.", true}, + {"a._", true}, + {"ab._", true}, + {"a.b_", true}, + {"a.b._", true}, + {"a1", true}, + {"1a", true}, + {"a1.", true}, + {"1a.", true}, + {"a1_", true}, + {"1a_", true}, + {"a", true}, + {"a122345", true}, + {"_a122345", true}, + {"0", true}, + {"1", true}, + {"2", true}, + {"3", true}, + {"4", true}, + {"5", true}, + {"6", true}, + {"7", true}, + {"8", true}, + {"9", true}, + {"11", true}, + {"111", true}, + {"abcdefghABCDEFGH", true}, + {"1234567890abcdefghABCDEFGH", true}, + {"abcdefgh1234567890ABCDEFGH", true}, + {"abcdefghABCDEFGH1234567890", true}, + {"a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z", true}, + {"a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z.", true}, + {"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_.", true}, + {"_abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_.", true}, + {".abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_.", false}, + {"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_.\r", false}, + {"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_.\n", false}, + {"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_.\t", false}, + {"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_. ", false}, + {"\t", false}, + {"\n", false}, + {"\r\n", false}, + {"?", false}, + {"!", false}, + {"\"", false}, + {"$", false}, + {"%", false}, + {"^", false}, + {"*", false}, + {"(", false}, + {")", false}, + {"-", false}, + {"+", false}, + {":", false}, + {";", false}, + {"@", false}, + {"~", false}, + {"<", false}, + {">", false}, + {"!", false}, + {" invalid", false}, + {"\tinvalid", false}, + {"\ninvalid", false}, + {"invalid space", false}, + {"invalid ", false}, + {"inva lid", false}, + {"inva\tlid", false}, + {"inva\nlid", false}, + {"inva\rlid", false}, + }; + + for (const auto& tc : test_cases) { + + { + bool actual = ecf::algorithm::is_valid_name(tc.name); + bool original = ecf::prototype::Str_valid_name(tc.name); + + BOOST_CHECK_MESSAGE(actual == tc.expected, + "For name '" << tc.name << "' expected " << tc.expected << ", found " << actual); + BOOST_CHECK_MESSAGE(actual == original, + "For name '" << tc.name << "' both original and new algorithms provide same result"); + } + { + std::string actual_error; + bool actual = ecf::algorithm::is_valid_name(tc.name, actual_error); + + std::string original_error; + bool original = ecf::prototype::Str_valid_name(tc.name, original_error); + + BOOST_CHECK_MESSAGE(actual == tc.expected, + "For name '" << tc.name << "' expected " << tc.expected << ", found " << actual); + BOOST_CHECK_MESSAGE(actual == original, + "For name '" << tc.name << "' both original and new algorithms provide same result"); + + if (!tc.expected) { + auto expected_prefix = "Invalid name '" + tc.name + "': "; + auto actual_prefix = actual_error.substr(0, expected_prefix.size()); + + BOOST_CHECK_MESSAGE(actual_prefix == expected_prefix, + "The error message for '" << tc.name << "' expected prefix <" << expected_prefix + << ">, found <" << actual_prefix << ">"); + } + } + } +} + +BOOST_AUTO_TEST_CASE(test_node_name_validity_random) { + ECF_NAME_THIS_TEST(); + + struct timer + { + timer() = default; + ~timer() = default; + + void begin() { start = std::chrono::steady_clock::now(); } + void end() { finish = std::chrono::steady_clock::now(); } + + std::chrono::nanoseconds duration() const { + return std::chrono::duration_cast(finish - start); + } + + std::chrono::time_point start; + std::chrono::time_point finish; + std::string_view name; + std::string_view type; + }; + + auto generate_random_valid_name = []() { + std::string_view valid = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_."; + + static std::random_device rd; + static std::mt19937_64 gen(rd()); + std::uniform_int_distribution length_dist(1, 20); + + std::uniform_int_distribution first_chars_dist(0, 62); // abc...ABC...0123456789_ (63 chars, no '.'!) + std::uniform_int_distribution following_chars_dist(0, 63); // abc...ABC...0123456789_. (64 chars) + + int length = length_dist(gen); + std::string name; + name.reserve(length); + for (int i = 0; i < length; ++i) { + if (i == 0) { + name.push_back(valid[first_chars_dist(gen)]); + } + else { + name.push_back(valid[following_chars_dist(gen)]); + } + } + return name; + }; + + double n_time = 0.; + double o_time = 0.; + + int iterations = 1000; + + for (int i = 0; i < iterations; ++i) { + + auto name = generate_random_valid_name(); + + bool original; + { + timer t; + + t.begin(); + original = ecf::prototype::Str_valid_name(name); + t.end(); + + auto ns = t.duration().count(); + o_time += ns; + ECF_TEST_DBG("Original approach, name: '" << name << "', duration: " << ns << " ns"); + } + + bool actual; + { + timer t; + + t.begin(); + actual = ecf::algorithm::is_valid_name(name); + t.end(); + + auto ns = t.duration().count(); + n_time += ns; + ECF_TEST_DBG(" New approach, name: " << name << ", duration: " << ns << " ns"); + } + + BOOST_CHECK_MESSAGE(actual == original, + "For name '" << name << "' both original and new approaches provide same result"); + } + + double old_time_per_iteration = o_time / iterations; + ECF_TEST_DBG("Original approach time: " << old_time_per_iteration << " ns"); + double new_time_per_iteration = n_time / iterations; + ECF_TEST_DBG(" New approach time: " << new_time_per_iteration << " ns"); + + BOOST_WARN_MESSAGE(new_time_per_iteration < old_time_per_iteration, "New algorithm expected faster than original"); +} + +BOOST_AUTO_TEST_SUITE_END() + +BOOST_AUTO_TEST_SUITE_END() diff --git a/libs/core/test/TestNodePath.cpp b/libs/core/test/TestNodePath.cpp index 90c9659a9..a59d93c27 100644 --- a/libs/core/test/TestNodePath.cpp +++ b/libs/core/test/TestNodePath.cpp @@ -14,6 +14,7 @@ #include #include "ecflow/core/NodePath.hpp" +#include "ecflow/core/Str.hpp" #include "ecflow/core/Timer.hpp" #include "ecflow/test/scaffold/Naming.hpp" @@ -21,104 +22,72 @@ BOOST_AUTO_TEST_SUITE(U_Core) BOOST_AUTO_TEST_SUITE(T_NodePath) -static void checkPath(const std::vector& expectedPath, const std::string& path) { - std::vector thePath; - NodePath::split(path, thePath); - if (thePath != expectedPath) { - BOOST_CHECK_MESSAGE(false, "Failed for " << path); - std::ostringstream ss; - ss << "Expected '"; - std::copy(expectedPath.begin(), expectedPath.end(), std::ostream_iterator(ss, " ")); - ss << "'\nbut found '"; - std::copy(thePath.begin(), thePath.end(), std::ostream_iterator(ss, " ")); - ECF_TEST_DBG(<< ss.str()); - } -} - -BOOST_AUTO_TEST_CASE(test_path_extractor_constructor) { - ECF_NAME_THIS_TEST(); - - BOOST_CHECK(true); // stop boost test from complaining about no checks - - std::vector theExpectedPath; - checkPath(theExpectedPath, ""); - - theExpectedPath.emplace_back("suite"); - checkPath(theExpectedPath, "/suite"); - checkPath(theExpectedPath, "suite"); -} - -BOOST_AUTO_TEST_CASE(test_path_extractor) { - ECF_NAME_THIS_TEST(); - - BOOST_CHECK(true); // stop boost test from complaining about no checks - - std::vector theExpectedPath; - theExpectedPath.emplace_back("suite"); - theExpectedPath.emplace_back("family"); - theExpectedPath.emplace_back("task"); - - checkPath(theExpectedPath, "/suite/family/task"); - checkPath(theExpectedPath, "/suite/family/task/"); -} - BOOST_AUTO_TEST_CASE(test_unix_path_extractor) { ECF_NAME_THIS_TEST(); - BOOST_CHECK(true); // stop boost test from complaining about no checks - - // On Unix multiple '/' are treated as one. - std::vector theExpectedPath; - theExpectedPath.emplace_back("suite"); - theExpectedPath.emplace_back("family"); - theExpectedPath.emplace_back("task"); - - checkPath(theExpectedPath, "/suite///family////task"); - checkPath(theExpectedPath, "/suite///family////task//"); - checkPath(theExpectedPath, "//suite///family////task//"); - checkPath(theExpectedPath, "///suite///family////task//"); - checkPath(theExpectedPath, "///suite///family////task///"); + struct tc + { + std::string path; + std::vector expected; + }; + + std::vector test_cases = {{"suite", {"suite"}}, + {"/suite", {"suite"}}, + {"/suite/family/task", {"suite", "family", "task"}}, + {"/suite/family/task/", {"suite", "family", "task"}}, + {"/suite///family////task", {"suite", "family", "task"}}, + {"/suite///family////task//", {"suite", "family", "task"}}, + {"//suite///family////task//", {"suite", "family", "task"}}, + {"///suite///family////task//", {"suite", "family", "task"}}, + {"///suite///family////task///", {"suite", "family", "task"}}}; + + for (const auto& tc : test_cases) { + + std::vector actual; + ecf::node::split_path(tc.path, actual); + BOOST_CHECK_MESSAGE(actual == tc.expected, + "Splitting " << tc.path << ", expected " << ecf::algorithm::as_string(tc.expected) + << ", found " << ecf::algorithm::as_string(actual)); + } } BOOST_AUTO_TEST_CASE(test_extractHostPort) { ECF_NAME_THIS_TEST(); - std::string path; - std::string host; - std::string port; - BOOST_CHECK_MESSAGE(!NodePath::extractHostPort(path, host, port), "expected failure"); - - path = "Apath"; - BOOST_CHECK_MESSAGE(!NodePath::extractHostPort(path, host, port), "expected failure"); - - path = " : "; - BOOST_CHECK_MESSAGE(!NodePath::extractHostPort(path, host, port), "expected failure"); - - path = "host:"; - BOOST_CHECK_MESSAGE(!NodePath::extractHostPort(path, host, port), "expected failure"); - - path = ":port"; - BOOST_CHECK_MESSAGE(!NodePath::extractHostPort(path, host, port), "expected failure"); - - path = "host:port"; - BOOST_CHECK_MESSAGE(NodePath::extractHostPort(path, host, port), "expected success " << host << ":" << port); - BOOST_CHECK_MESSAGE(host == "host" && port == "port", "expected 'host:port' found " << host << ":" << port); - - path = "//host:port"; - BOOST_CHECK_MESSAGE(NodePath::extractHostPort(path, host, port), "expected success " << host << ":" << port); - BOOST_CHECK_MESSAGE(host == "host" && port == "port", "expected 'host:port' found " << host << ":" << port); - - path = "//host:port/"; - BOOST_CHECK_MESSAGE(NodePath::extractHostPort(path, host, port), "expected success " << host << ":" << port); - BOOST_CHECK_MESSAGE(host == "host" && port == "port", "expected 'host:port' found " << host << ":" << port); - - path = "//host:port/suite"; - BOOST_CHECK_MESSAGE(NodePath::extractHostPort(path, host, port), "expected success " << host << ":" << port); - BOOST_CHECK_MESSAGE(host == "host" && port == "port", "expected 'host:port' found " << host << ":" << port); - - path = "//host:port/suite/family/task"; - BOOST_CHECK_MESSAGE(NodePath::extractHostPort(path, host, port), "expected success " << host << ":" << port); - BOOST_CHECK_MESSAGE(host == "host" && port == "port", "expected 'host:port' found " << host << ":" << port); + struct tc + { + std::string path; + bool expected; + std::string expected_host; + std::string expected_port; + }; + + std::vector test_cases = {{"", false, "", ""}, + {"Apath", false, "", ""}, + {" : ", false, "", ""}, + {"host:", false, "host", ""}, + {":port", false, "", "port"}, + {"host:port", true, "host", "port"}, + {"//host:port", true, "host", "port"}, + {"//host:port/", true, "host", "port"}, + {"//host:port/suite", true, "host", "port"}, + {"//host:port/suite/family/task", true, "host", "port"}}; + + for (const auto& tc : test_cases) { + std::string actual_host; + std::string actual_port; + auto actual = ecf::node::extract_host_and_port_from_path(tc.path, actual_host, actual_port); + + BOOST_REQUIRE_MESSAGE(actual == tc.expected, + "For path " << tc.path << ", expected " << tc.expected << " found " << actual); + + BOOST_REQUIRE_MESSAGE(actual_host == tc.expected_host, + "For path '" << tc.path << "', expected host '" << tc.expected_host << "' found '" + << actual_host << "'"); + BOOST_REQUIRE_MESSAGE(actual_port == tc.expected_port, + "For path '" << tc.path << "', expected port '" << tc.expected_port << "' found '" + << actual_port << "'"); + } } BOOST_AUTO_TEST_CASE(test_NodePath_perf, *boost::unit_test::disabled()) { @@ -136,9 +105,10 @@ BOOST_AUTO_TEST_CASE(test_NodePath_perf, *boost::unit_test::disabled()) { thePath.reserve(20); for (int i = 0; i < n; i++) { thePath.clear(); - NodePath::split("/this/is/a/test/string/that/will/be/used/to/check/perf/of/node/path/extraction", thePath); + ecf::node::split_path("/this/is/a/test/string/that/will/be/used/to/check/perf/of/node/path/extraction", + thePath); } - ECF_TEST_DBG(<< "Timing for " << n << " NodePath is " << timer); + ECF_TEST_DBG("Timing for " << n << " NodePath is " << timer); } BOOST_AUTO_TEST_SUITE_END() diff --git a/libs/core/test/TestPasswdFile.cpp b/libs/core/test/TestPasswdFile.cpp index e1faf3edc..10a820b7a 100644 --- a/libs/core/test/TestPasswdFile.cpp +++ b/libs/core/test/TestPasswdFile.cpp @@ -32,7 +32,7 @@ void test_passwd_files(const std::string& directory, bool pass) { BOOST_CHECK(fs::is_directory(full_path)); #if DEBUG_ME - ECF_TEST_DBG(<< "...In directory: " << full_path.relative_path()); + ECF_TEST_DBG("...In directory: " << full_path.relative_path()); #endif fs::directory_iterator end_iter; @@ -46,7 +46,7 @@ void test_passwd_files(const std::string& directory, bool pass) { continue; } #if DEBUG_ME - ECF_TEST_DBG(<< "......Parsing file " << relPath.string()); + ECF_TEST_DBG("......Parsing file " << relPath.string()); #endif PasswdFile theFile; std::string errorMsg; @@ -65,12 +65,12 @@ void test_passwd_files(const std::string& directory, bool pass) { << errorMsg << "\n" << theFile.dump()); #if DEBUG_ME - ECF_TEST_DBG(<< errorMsg); + ECF_TEST_DBG(errorMsg); #endif } } catch (const std::exception& ex) { - ECF_TEST_DBG(<< dir_itr->path().filename() << " " << ex.what()); + ECF_TEST_DBG(dir_itr->path().filename() << " " << ex.what()); } } } diff --git a/libs/core/test/TestPerfTimer.cpp b/libs/core/test/TestPerfTimer.cpp index 7f617b73d..563350d81 100644 --- a/libs/core/test/TestPerfTimer.cpp +++ b/libs/core/test/TestPerfTimer.cpp @@ -38,10 +38,10 @@ BOOST_AUTO_TEST_CASE(test_perf_with_chrono) { [[maybe_unused]] auto t3 = ecf::FunctionPerformanceTimer::duration(func, 100000); #if PRINT_TIMING_RESULTS - ECF_TEST_DBG(<< " " << std::chrono::duration(t).count() << " micro"); - ECF_TEST_DBG(<< " " << std::chrono::duration(t).count() << " milli"); - ECF_TEST_DBG(<< " " << std::chrono::duration(t).count() << " nano"); - ECF_TEST_DBG(<< " " << std::chrono::duration(t1 + t2 + t3).count() << " milli"); + ECF_TEST_DBG(" " << std::chrono::duration(t).count() << " micro"); + ECF_TEST_DBG(" " << std::chrono::duration(t).count() << " milli"); + ECF_TEST_DBG(" " << std::chrono::duration(t).count() << " nano"); + ECF_TEST_DBG(" " << std::chrono::duration(t1 + t2 + t3).count() << " milli"); #endif BOOST_CHECK_MESSAGE(true, "dummy to keep unit test happy"); @@ -57,9 +57,9 @@ BOOST_AUTO_TEST_CASE(test_chrono_timer) { #if PRINT_TIMING_RESULTS using namespace std::chrono; - ECF_TEST_DBG(<< " " << duration(timer.elapsed()).count() << " micro"); - ECF_TEST_DBG(<< " " << duration(timer.elapsed()).count() << " milli"); - ECF_TEST_DBG(<< " " << duration(timer.elapsed()).count() << " nano"); + ECF_TEST_DBG(" " << duration(timer.elapsed()).count() << " micro"); + ECF_TEST_DBG(" " << duration(timer.elapsed()).count() << " milli"); + ECF_TEST_DBG(" " << duration(timer.elapsed()).count() << " nano"); #endif } { diff --git a/libs/core/test/TestRealCalendar.cpp b/libs/core/test/TestRealCalendar.cpp index 49230d203..cf0532a3c 100644 --- a/libs/core/test/TestRealCalendar.cpp +++ b/libs/core/test/TestRealCalendar.cpp @@ -111,12 +111,12 @@ BOOST_AUTO_TEST_CASE(test_REAL_calendar_time_series_relative_complex) { << suiteTm.tm_hour << ":" << suiteTm.tm_min << " suite time = " << to_simple_string(calendar.suiteTime())); if (!matches) { - ECF_TEST_DBG(<< "suiteTm.tm_hour =" << suiteTm.tm_hour << " suiteTm.tm_min = " << suiteTm.tm_min - << " timeSeries.start().hour() " << timeSeries.start().hour() - << " timeSeries.start().minute() " << timeSeries.start().minute() - << " timeSeries.finish().hour() " << timeSeries.finish().hour() - << " timeSeries.finish().minute() " << timeSeries.finish().minute() - << " suiteTm.tm_min % 15 = " << suiteTm.tm_min % 15); + ECF_TEST_DBG("suiteTm.tm_hour =" << suiteTm.tm_hour << " suiteTm.tm_min = " << suiteTm.tm_min + << " timeSeries.start().hour() " << timeSeries.start().hour() + << " timeSeries.start().minute() " << timeSeries.start().minute() + << " timeSeries.finish().hour() " << timeSeries.finish().hour() + << " timeSeries.finish().minute() " << timeSeries.finish().minute() + << " suiteTm.tm_min % 15 = " << suiteTm.tm_min % 15); } } else { @@ -126,12 +126,12 @@ BOOST_AUTO_TEST_CASE(test_REAL_calendar_time_series_relative_complex) { << " suite time = " << to_simple_string(calendar.suiteTime())); if (matches) { - ECF_TEST_DBG(<< "suiteTm.tm_hour =" << suiteTm.tm_hour << " suiteTm.tm_min = " << suiteTm.tm_min - << " timeSeries.start().hour() " << timeSeries.start().hour() - << " timeSeries.start().minute() " << timeSeries.start().minute() - << " timeSeries.finish().hour() " << timeSeries.finish().hour() - << " timeSeries.finish().minute() " << timeSeries.finish().minute() - << " suiteTm.tm_min % 15 = " << suiteTm.tm_min % 15); + ECF_TEST_DBG("suiteTm.tm_hour =" << suiteTm.tm_hour << " suiteTm.tm_min = " << suiteTm.tm_min + << " timeSeries.start().hour() " << timeSeries.start().hour() + << " timeSeries.start().minute() " << timeSeries.start().minute() + << " timeSeries.finish().hour() " << timeSeries.finish().hour() + << " timeSeries.finish().minute() " << timeSeries.finish().minute() + << " suiteTm.tm_min % 15 = " << suiteTm.tm_min % 15); } } } @@ -214,12 +214,12 @@ BOOST_AUTO_TEST_CASE(test_REAL_calendar_time_series_complex) { << suiteTm.tm_hour << ":" << suiteTm.tm_min << " suite time = " << to_simple_string(calendar.suiteTime())); if (!matches) { - ECF_TEST_DBG(<< "suiteTm.tm_hour =" << suiteTm.tm_hour << " suiteTm.tm_min = " << suiteTm.tm_min - << " timeSeries.start().hour() " << timeSeries.start().hour() - << " timeSeries.start().minute() " << timeSeries.start().minute() - << " timeSeries.finish().hour() " << timeSeries.finish().hour() - << " timeSeries.finish().minute() " << timeSeries.finish().minute() - << " suiteTm.tm_min % 15 = " << suiteTm.tm_min % 15); + ECF_TEST_DBG("suiteTm.tm_hour =" << suiteTm.tm_hour << " suiteTm.tm_min = " << suiteTm.tm_min + << " timeSeries.start().hour() " << timeSeries.start().hour() + << " timeSeries.start().minute() " << timeSeries.start().minute() + << " timeSeries.finish().hour() " << timeSeries.finish().hour() + << " timeSeries.finish().minute() " << timeSeries.finish().minute() + << " suiteTm.tm_min % 15 = " << suiteTm.tm_min % 15); } } else { @@ -229,12 +229,12 @@ BOOST_AUTO_TEST_CASE(test_REAL_calendar_time_series_complex) { << " suite time = " << to_simple_string(calendar.suiteTime())); if (matches) { - ECF_TEST_DBG(<< "suiteTm.tm_hour =" << suiteTm.tm_hour << " suiteTm.tm_min = " << suiteTm.tm_min - << " timeSeries.start().hour() " << timeSeries.start().hour() - << " timeSeries.start().minute() " << timeSeries.start().minute() - << " timeSeries.finish().hour() " << timeSeries.finish().hour() - << " timeSeries.finish().minute() " << timeSeries.finish().minute() - << " suiteTm.tm_min % 15 = " << suiteTm.tm_min % 15); + ECF_TEST_DBG("suiteTm.tm_hour =" << suiteTm.tm_hour << " suiteTm.tm_min = " << suiteTm.tm_min + << " timeSeries.start().hour() " << timeSeries.start().hour() + << " timeSeries.start().minute() " << timeSeries.start().minute() + << " timeSeries.finish().hour() " << timeSeries.finish().hour() + << " timeSeries.finish().minute() " << timeSeries.finish().minute() + << " suiteTm.tm_min % 15 = " << suiteTm.tm_min % 15); } } } diff --git a/libs/core/test/TestStr.cpp b/libs/core/test/TestStr.cpp index f9e101fa6..3ccf12f72 100644 --- a/libs/core/test/TestStr.cpp +++ b/libs/core/test/TestStr.cpp @@ -20,660 +20,1117 @@ #include "ecflow/core/Timer.hpp" #include "ecflow/test/scaffold/Naming.hpp" -using namespace ecf; - BOOST_AUTO_TEST_SUITE(U_Core) BOOST_AUTO_TEST_SUITE(T_Str) -BOOST_AUTO_TEST_CASE(test_str) { +BOOST_AUTO_TEST_CASE(test_algorithm_join) { + ECF_NAME_THIS_TEST(); + + struct tc + { + std::vector input; + std::string delimiter; + std::string expected; + }; + + std::vector test_cases = { + {{}, ",", ""}, + {{"a", "b", "c"}, "", "abc"}, + {{"a"}, ",", "a"}, + {{"a", "b"}, ",", "a,b"}, + {{"a", "b", "c"}, ",", "a,b,c"}, + {{"a", "b", "c"}, ";;", "a;;b;;c"}, + }; + + for (const auto& tc : test_cases) { + auto actual = ecf::algorithm::join(tc.input, tc.delimiter); + BOOST_CHECK_MESSAGE(actual == tc.expected, "Expected '" << tc.expected << "', found '" << actual << "'"); + } +} + +BOOST_AUTO_TEST_CASE(test_algorithm_replace_first) { ECF_NAME_THIS_TEST(); + struct tc { - std::string str; + std::string input; + std::string search; + std::string replace; std::string expected; - Str::removeQuotes(str); - BOOST_CHECK_MESSAGE(str == expected, " Expected " << expected << " but found " << str); + }; + + std::vector test_cases = { + {"abc def abc def abc", " ", "", "abcdef abc def abc"}, + {"abc def abc def abc", "a", "x", "xbc def abc def abc"}, + {"abc def abc def abc", "b", "x", "axc def abc def abc"}, + {"abc def abc def abc", "c", "x", "abx def abc def abc"}, + {"abc def abc def abc", "d", "x", "abc xef abc def abc"}, + {"abc def abc def abc", "abc", "xxx", "xxx def abc def abc"}, + }; + + for (const auto& tc : test_cases) { + auto actual = tc.input; + ecf::algorithm::replace_first(actual, tc.search, tc.replace); + BOOST_CHECK_MESSAGE(actual == tc.expected, "Expected '" << tc.expected << "', found '" << actual << "'"); + } +} + +BOOST_AUTO_TEST_CASE(test_algorithm_split_at) { + ECF_NAME_THIS_TEST(); + struct tc + { + std::string input; + std::vector expected; + }; + + std::vector test_cases = { + {"", {}}, + {" ", {}}, + {"a", {"a"}}, + {" a", {"a"}}, + {"a ", {"a"}}, + {" a ", {"a"}}, + {"\ta", {"a"}}, + {"a\t", {"a"}}, + {"\ta\t", {"a"}}, + {"\n", {"\n"}}, + {" \t a\t \t", {"a"}}, + {"\t\t\ta\t\t\t", {"a"}}, + {"\n", {"\n"}}, + {" a b c d ", {"a", "b", "c", "d"}}, + {" - ! $ % ^ & * ( ) - + ?", {"-", "!", "$", "%", "^", "&", "*", "(", ")", "-", "+", "?"}}, + {"This is a string", {"This", "is", "a", "string"}}, + {" verify complete:8 # 4 sundays in october hence expect 8 task completions", + {"verify", + "complete:8", + "#", + "4", + "sundays", + "in", + "october", + "hence", + "expect", + "8", + "task", + "completions"}}}; + + for (const auto& tc : test_cases) { + std::vector splits; + + auto& actual = ecf::algorithm::split_at(splits, tc.input); + + BOOST_CHECK_MESSAGE(&actual == &splits, + "Expected the returned value and the input vector to be the same object"); + BOOST_CHECK_MESSAGE(actual == splits, "Expected the returned value and the input vector to have same content"); + BOOST_CHECK_MESSAGE(splits == tc.expected, + "Expected:\n" + << ecf::algorithm::as_string(tc.expected) << "\nbut found:\n" + << ecf::algorithm::as_string(splits)); + } +} + +BOOST_AUTO_TEST_CASE(test_algorithm_split_at_with_separators) { + ECF_NAME_THIS_TEST(); + + struct tc + { + std::string input; + std::string separator; + std::vector expected; + }; + + std::vector test_cases = {{"", ",", {}}, + {"abc,def,ghi", ",", {"abc", "def", "ghi"}}, + {"abc,,def,ghi", ",", {"abc", "def", "ghi"}}, + {",abc,def,ghi", ",", {"abc", "def", "ghi"}}, + {"abc,def,ghi,", ",", {"abc", "def", "ghi"}}, + {"xxx;,;yyy;,;zzz", ";,", {"xxx", "yyy", "zzz"}}, + {"xxx;a,b;yyy;c,d;zzz", ";,", {"xxx", "a", "b", "yyy", "c", "d", "zzz"}}, + {"aeiou,12345 12345,aeiou", " ", {"aeiou,12345", "12345,aeiou"}}, + {"a,,b,c;d; ;e;;;f;", ",,;", {"a", "b", "c", "d", " ", "e", "f"}}}; + + for (const auto& tc : test_cases) { + std::vector splits{"this", "is", "garbage"}; + + const auto& actual = ecf::algorithm::split_at(splits, tc.input, tc.separator); + + BOOST_CHECK_MESSAGE(&actual == &splits, + "Expected the returned value and the input vector to be the same object"); + BOOST_CHECK_MESSAGE(actual == splits, "Expected the returned value and the input vector to have same content"); + BOOST_CHECK_MESSAGE(actual == tc.expected, + "Splitting >>>" << tc.input << "<<<, Expected " << ecf::algorithm::as_string(tc.expected) + << ", found " << ecf::algorithm::as_string(actual)); + } +} + +BOOST_AUTO_TEST_CASE(test_algorithm_split_by_with_separators) { + + struct TestCase + { + std::string input; + std::string pattern; + std::vector expected; + }; + + std::vector testCases = { + {"This is a string", "...", {"This is a string"}}, + {"This...is...a...string", "...", {"This", "is", "a", "string"}}, + {"This...is...a...string...", "...", {"This", "is", "a", "string"}}, + {"...This...is...a...string", "...", {"This", "is", "a", "string"}}, + {"...This...is...a...string...", "...", {"This", "is", "a", "string"}}, + {"......This......is......a......string......", "...", {"This", "is", "a", "string"}}, + {"..This.....is.....a.....string.....", "...", {"..This", "..is", "..a", "..string", ".."}}, + {"expression 1==expression 2", "==", {"expression 1", "expression 2"}}, + {"expression 1==expression 2==expression 3", "==", {"expression 1", "expression 2", "expression 3"}}, + {"expression 1 == expression 2", " == ", {"expression 1", "expression 2"}}, + {"expression 1 == expression 2 == expression 3", " == ", {"expression 1", "expression 2", "expression 3"}}, + {"expression 1 eq expression 2", " eq ", {"expression 1", "expression 2"}}, + {"expression 1eqexpression 2", " eq ", {"expression 1eqexpression 2"}}}; + + for (const auto& testCase : testCases) { + std::vector splits; + auto& actual = ecf::algorithm::split_by(splits, testCase.input, testCase.pattern); + + BOOST_CHECK_MESSAGE(&actual == &splits, + "Expected the returned value and the input vector to be the same object"); + BOOST_CHECK_MESSAGE(actual == splits, "Expected the returned value and the input vector to have same content"); + BOOST_CHECK_MESSAGE(splits == testCase.expected, + "Failed for input: '" << testCase.input << "' pattern: '" << testCase.pattern + << "'. Expected: " << ecf::algorithm::as_string(testCase.expected) + << " but found: " << ecf::algorithm::as_string(splits)); + } +} + +BOOST_AUTO_TEST_CASE(test_algorithm_split_fields_at_with_separators) { + ECF_NAME_THIS_TEST(); + + struct tc + { + std::string input; + std::string separator; + std::vector expected; + }; + + std::vector test_cases = { + {"", ",", {}}, + {"abc,def,ghi", ",", {"abc", "def", "ghi"}}, + {"abc,def,ghi,", ",", {"abc", "def", "ghi", ""}}, + {",abc,def,ghi", ",", {"", "abc", "def", "ghi"}}, + {",abc,def,ghi,", ",", {"", "abc", "def", "ghi", ""}}, + {",abc,,def,,ghi,", ",", {"", "abc", "", "def", "", "ghi", ""}}, + {"abc,,def,ghi", ",", {"abc", "", "def", "ghi"}}, + {"abc,def,,ghi", ",", {"abc", "def", "", "ghi"}}, + {"abc,,def,,ghi,", ",", {"abc", "", "def", "", "ghi", ""}}, + {"xxx;,;yyy;,;zzz", ";,", {"xxx", "", "", "yyy", "", "", "zzz"}}, + {"xxx;a,b;yyy;c,d;zzz", ";,", {"xxx", "a", "b", "yyy", "c", "d", "zzz"}}, + {"aeiou,12345 12345,aeiou", " ", {"aeiou,12345", "12345,aeiou"}}, + {"a;,b,c;d; ;e;,; f;", ",,;", {"a", "", "b", "c", "d", " ", "e", "", "", " f", ""}}, + {",,a,,b,c;d; ;e,;f;", ",,;", {"", "", "a", "", "b", "c", "d", " ", "e", "", "f", ""}}}; + + for (const auto& tc : test_cases) { + std::vector splits{"this", "is", "garbage"}; + + const auto& actual = ecf::algorithm::split_fields_at(splits, tc.input, tc.separator); + + BOOST_CHECK_MESSAGE(&actual == &splits, + "Expected the returned value and the input vector to be the same object"); + BOOST_CHECK_MESSAGE(actual == splits, "Expected the returned value and the input vector to have same content"); + BOOST_CHECK_MESSAGE(actual == tc.expected, + "Splitting >>>" << tc.input << "<<<, Expected " << ecf::algorithm::as_string(tc.expected) + << ", found " << ecf::algorithm::as_string(actual)); + } +} + +BOOST_AUTO_TEST_CASE(test_algorithm_split_within_quotes) { + ECF_NAME_THIS_TEST(); + + struct tc + { + std::string input; + std::string quotes; + std::vector expected; + }; + + std::vector test_cases = {{"", "'", {}}, + {"''", "'", {"''"}}, + {"'' ' '", "'", {"''", "' '"}}, + {"'a' 'b' ''", "'", {"'a'", "'b'", "''"}}, + {"'abc' 'def' 'ghi'", "'", {"'abc'", "'def'", "'ghi'"}}, + {"'a' 'b' '", "'", {"'a'", "'b'", "'"}}, + {"'a' b' '", "'", {"'a'", "' '"}}, + {"'a' x 'b' y 'c' zz ", "'", {"'a'", "x", "'b'", "y", "'c'", "zz"}}, + {"\"a\" x \"b\" x \"c\"", "\"", {"\"a\"", "x", "\"b\"", "x", "\"c\""}}, + {"'a \" b \" c' \"d ' e ' f\"", "\"'", {"'a \" b \" c'", "\"d ' e ' f\""}}}; + + for (const auto& tc : test_cases) { + const auto& actual = ecf::algorithm::split_within_quotes(tc.input, tc.quotes); + + BOOST_CHECK_MESSAGE(actual == tc.expected, + "Splitting >>>" << tc.input << "<<<, Expected " << ecf::algorithm::as_string(tc.expected) + << ", found " << ecf::algorithm::as_string(actual)); + } +} + +BOOST_AUTO_TEST_CASE(test_algorithm_starts_with) { + ECF_NAME_THIS_TEST(); + + struct tc + { + std::string input; + std::string prefix; + bool expected; + }; + + std::vector test_cases = {{"", "", true}, + {"abcdef", "", true}, + {"abcdef", "a", true}, + {"abcdef", "ab", true}, + {"abcdef", "abc", true}, + {"abcdef", "abcx", false}, + {"abcdef", "abcdefx", false}, + {"zabcdef", "abcdef", false}}; + + for (const auto& tc : test_cases) { + auto actual = ecf::algorithm::starts_with(tc.input, tc.prefix); + BOOST_CHECK_MESSAGE(actual == tc.expected, + "Expected " << ecf::algorithm::as_string(tc.expected) << ", found " + << ecf::algorithm::as_string(actual)); + } +} + +BOOST_AUTO_TEST_CASE(test_algorithm_ends_with) { + ECF_NAME_THIS_TEST(); + + struct tc + { + std::string input; + std::string prefix; + bool expected; + }; + + std::vector test_cases = {{"", "", true}, + {"abcdef", "", true}, + {"abcdef", "f", true}, + {"abcdef", "ef", true}, + {"abcdef", "def", true}, + {"abcdef", "xdef", false}, + {"abcdef", "xabcdef", false}, + {"abcdefz", "abcdef", false}}; + + for (const auto& tc : test_cases) { + auto actual = ecf::algorithm::ends_with(tc.input, tc.prefix); + BOOST_CHECK_MESSAGE(actual == tc.expected, + "Expected " << ecf::algorithm::as_string(tc.expected) << ", found " + << ecf::algorithm::as_string(actual)); + } +} + +BOOST_AUTO_TEST_CASE(test_algorithm_trim) { + ECF_NAME_THIS_TEST(); + + struct tc + { + std::string input; + std::string expected_trim_both; + std::string expected_trim_leading; + std::string expected_trim_trailing; + }; + + std::vector test_cases = { + {"", "", "", ""}, + {" ", "", "", ""}, + {" ", "", "", ""}, + {"abc", "abc", "abc", "abc"}, + {" abc", "abc", "abc", " abc"}, + {"abc ", "abc", "abc ", "abc"}, + {" abc ", "abc", "abc ", " abc"}, + {"\tabc\t", "abc", "abc\t", "\tabc"}, + {"\nabc\n", "abc", "abc\n", "\nabc"}, + {" \t\nabc \t\n", "abc", "abc \t\n", " \t\nabc"}, + {" \t\n abc \t\n ", "abc", "abc \t\n ", " \t\n abc"}, + }; + + for (const auto& tc : test_cases) { + auto actual = tc.input; + ecf::algorithm::trim(actual); + auto expected = tc.expected_trim_both; + BOOST_CHECK_MESSAGE(actual == expected, "Expected '" << expected << "', found '" << actual << "'"); + } - str = "\"\""; - expected = ""; - Str::removeQuotes(str); - BOOST_CHECK_MESSAGE(str == expected, " Expected " << expected << " but found " << str); + for (const auto& tc : test_cases) { + auto actual = tc.input; + ecf::algorithm::trim_leading(actual); + auto expected = tc.expected_trim_leading; + BOOST_CHECK_MESSAGE(actual == expected, "Expected '" << expected << "', found '" << actual << "'"); + } - str = "fred"; - expected = "fred"; - Str::removeQuotes(str); - BOOST_CHECK_MESSAGE(str == expected, " Expected " << expected << " but found " << str); + for (const auto& tc : test_cases) { + auto actual = ecf::algorithm::trim_leading_copy(tc.input); + auto expected = tc.expected_trim_leading; + BOOST_CHECK_MESSAGE(actual == expected, "Expected '" << expected << "', found '" << actual << "'"); + } - str = "\"fred\""; - expected = "fred"; - Str::removeQuotes(str); - BOOST_CHECK_MESSAGE(str == expected, " Expected " << expected << " but found " << str); + for (const auto& tc : test_cases) { + auto actual = tc.input; + ecf::algorithm::trim_trailing(actual); + auto expected = tc.expected_trim_trailing; + BOOST_CHECK_MESSAGE(actual == expected, "Expected '" << expected << "', found '" << actual << "'"); } + + for (const auto& tc : test_cases) { + auto actual = ecf::algorithm::trim_trailing_copy(tc.input); + auto expected = tc.expected_trim_trailing; + BOOST_CHECK_MESSAGE(actual == expected, "Expected '" << expected << "', found '" << actual << "'"); + } +} + +BOOST_AUTO_TEST_CASE(test_algorithm_contains) { + ECF_NAME_THIS_TEST(); + + struct tc { - std::string str; + std::string input; + std::string pattern; + bool expected; + }; + + std::vector test_cases = {{"", "", true}, + {"abcdef", "", true}, + {"abcdef", "a", true}, + {"abcdef", "ab", true}, + {"abcdef", "abc", true}, + {"abcdef", "abcx", false}, + {"abcdef", "abcdefx", false}, + {"zabcdef", "abcdef", true}}; + + for (const auto& tc : test_cases) { + auto actual = ecf::algorithm::contains(tc.input, tc.pattern); + BOOST_CHECK_MESSAGE(actual == tc.expected, + "Expected " << ecf::algorithm::as_string(tc.expected) << ", found " + << ecf::algorithm::as_string(actual)); + } +} + +BOOST_AUTO_TEST_CASE(test_algorithm_remove_all) { + ECF_NAME_THIS_TEST(); + + struct tc + { + std::string input; + std::string pattern; std::string expected; - Str::removeSingleQuotes(str); - BOOST_CHECK_MESSAGE(str == expected, " Expected " << expected << " but found " << str); + }; + + std::vector test_cases = {{"", "", ""}, + {"abcdef", "", "abcdef"}, + {"abcdef", "a", "bcdef"}, + {"abcdef", "ab", "cdef"}, + {"abcdef", "abc", "def"}, + {"abcdef", "abcd", "ef"}, + {"abcdef", "abcx", "abcdef"}, + {"abcdef", "abcdefx", "abcdef"}, + {"zabcdef", "abcdef", "z"}, + {"abcxyzdef", "xyz", "abcdef"}, + {"xyzabcdef", "xyz", "abcdef"}, + {"abcxyzdef", "xyz", "abcdef"}, + {"xaxbxcxdxexfx", "x", "abcdef"}, + {"xyzaxyzbxyzcxyzdxyzexyzfxyz", "xyz", "abcdef"}}; + + for (const auto& tc : test_cases) { + auto actual = tc.input; + ecf::algorithm::remove_all(actual, tc.pattern); + BOOST_CHECK_MESSAGE(actual == tc.expected, "Expected " << tc.expected << ", found " << actual); + } +} - str = "''"; - expected = ""; - Str::removeSingleQuotes(str); - BOOST_CHECK_MESSAGE(str == expected, " Expected " << expected << " but found " << str); +BOOST_AUTO_TEST_CASE(test_algorithm_transform_to_vector) { + ECF_NAME_THIS_TEST(); + + using namespace ecf::algorithm; - str = "fred"; - expected = "fred"; - Str::removeSingleQuotes(str); - BOOST_CHECK_MESSAGE(str == expected, " Expected " << expected << " but found " << str); + { + std::vector input = {1, 2, 3, 4, 5, 6, 7, 8, 9, 0}; + std::vector expected = {1, 4, 9, 16, 25, 36, 49, 64, 81, 0}; - str = "'fred'"; - expected = "fred"; - Str::removeSingleQuotes(str); - BOOST_CHECK_MESSAGE(str == expected, " Expected " << expected << " but found " << str); + auto actual = ecf::algorithm::transform_to_vector(input, [](int v) { return v * v; }); + BOOST_CHECK_MESSAGE(actual == expected, + "Expected '" << ecf::algorithm::as_string(expected) << "', found '" + << ecf::algorithm::as_string(actual) << "'"); } + { - std::string test; - BOOST_CHECK_MESSAGE(!Str::truncate_at_start(test, 7), "Empty sring should return false"); + std::vector input = {1, 2, 3, 4, 5, 6, 7, 8, 9, 0}; + std::vector expected = {"1", "2", "3", "4", "5", "6", "7", "8", "9", "0"}; - test = "this\nis\na\nstring\nwith\nlots\nof\nnew\nline"; - std::string expected = "line"; - BOOST_CHECK_MESSAGE(Str::truncate_at_start(test, 1) && test == expected, - "Expected:\n" - << expected << "\nbut found:\n" - << test); + auto actual = ecf::algorithm::transform_to_vector(input, [](int v) { return std::to_string(v); }); + BOOST_CHECK_MESSAGE(actual == expected, + "Expected '" << ecf::algorithm::as_string(expected) << "', found '" + << ecf::algorithm::as_string(actual) << "'"); + } - test = "this\nis\na\nstring\nwith\nlots\nof\nnew\nline"; - expected = "a\nstring\nwith\nlots\nof\nnew\nline"; - BOOST_CHECK_MESSAGE(Str::truncate_at_start(test, 7) && test == expected, - "Expected:\n" - << expected << "\nbut found:\n" - << test); + { + std::vector input = {"1", "2", "3", "4", "5", "6", "7", "8", "9", "0"}; + std::vector expected = {1, 2, 3, 4, 5, 6, 7, 8, 9, 0}; - test = "this\nis\na\nstring\nwith\nlots\nof\nnew\nline"; - expected = test; - BOOST_CHECK_MESSAGE(!Str::truncate_at_start(test, 9) && test == expected, - "Expected:\n" - << expected << "\nbut found:\n" - << test); + auto actual = ecf::algorithm::transform_to_vector(input, [](const std::string& v) { return std::stoi(v); }); + BOOST_CHECK_MESSAGE(actual == expected, + "Expected '" << ecf::algorithm::as_string(expected) << "', found '" + << ecf::algorithm::as_string(actual) << "'"); } +} + +BOOST_AUTO_TEST_CASE(test_algorithm_tolower) { + ECF_NAME_THIS_TEST(); + + struct tc { - std::string test; - BOOST_CHECK_MESSAGE(!Str::truncate_at_end(test, 7), "Empty string should return false"); + std::string input; + std::string expected; + }; + + std::vector test_cases = {{"", ""}, + {"a", "a"}, + {"A", "a"}, + {"a1", "a1"}, + {"A1", "a1"}, + {"abc", "abc"}, + {"ABC", "abc"}, + {"AbC", "abc"}, + {"aBc", "abc"}, + {"aBC", "abc"}, + {"Abc", "abc"}, + {"abC", "abc"}, + {"123", "123"}, + {"!@#$%^&*()_+", "!@#$%^&*()_+"}, + {"a1B2c3D4e5F6g7H8i9J0", "a1b2c3d4e5f6g7h8i9j0"}}; + + for (const auto& tc : test_cases) { + auto actual = ecf::algorithm::tolower(tc.input); + BOOST_CHECK_MESSAGE(actual == tc.expected, "Expected '" << tc.expected << "', found '" << actual << "'"); + } +} - test = "this\nis\na\nstring\nwith\nlots\nof\nnew\nline"; - std::string expected = "this\n"; - BOOST_CHECK_MESSAGE(Str::truncate_at_end(test, 1) && test == expected, - "Expected:\n" - << expected << "\nbut found:\n" - << test); +BOOST_AUTO_TEST_CASE(test_algorithm_is_valid_name) { + ECF_NAME_THIS_TEST(); - test = "this\nis\na\nstring\nwith\nlots\nof\nnew\nline"; - expected = "this\nis\n"; - BOOST_CHECK_MESSAGE(Str::truncate_at_end(test, 2) && test == expected, - "Expected:\n" - << expected << "\nbut found:\n" - << test); + struct tc + { + std::string input; + bool expected; + }; + + std::vector test_cases = { + {"", false}, + {"a", true}, + {"abc", true}, + {"1abc", true}, + {"abc1", true}, + {"a1b2c3", true}, + {"a1c", true}, + {"_abc", true}, // '_' is an allowed character as the first character of a name + {"a_b_c", true}, // '_' is an allowed character as the first character of a name + {"-abc", false}, // '-' is not an allowed character + {"a-b-c", false}, // '-' is not an allowed character + {".abc", false}, // '.' is an allowed character but not as the first character of a name' + {"a.b.c", true}, // '.' is an allowed character but not as the first character of a name' + {"a_b-c.d", false}, // '-' is not an allowed character + }; + + for (const auto& tc : test_cases) { + auto actual = ecf::algorithm::is_valid_name(tc.input); + BOOST_CHECK_MESSAGE(actual == tc.expected, + "Validating '" << tc.input << "', Expected '" << tc.expected << "', found '" << actual + << "'"); + } - test = "this\nis\na\nstring\nwith\nlots\nof\nnew\nline"; - expected = "this\nis\na\nstring\nwith\nlots\nof\n"; - BOOST_CHECK_MESSAGE(Str::truncate_at_end(test, 7) && test == expected, - "Expected:\n" - << expected << "\nbut found:\n" - << test); + for (const auto& tc : test_cases) { + std::string error; + auto actual = ecf::algorithm::is_valid_name(tc.input, error); + BOOST_CHECK_MESSAGE(actual == tc.expected, "Expected '" << tc.expected << "', found '" << actual << "'"); + if (tc.expected) { + BOOST_CHECK_MESSAGE(error.empty(), "Expected empty error, found '" << error << "'"); + } + else { + BOOST_CHECK_MESSAGE(!error.empty(), "Expected non-empty error, found '" << error << "'"); + } + } +} - test = "this\nis\na\nstring\nwith\nlots\nof\nnew\nline"; - expected = test; - BOOST_CHECK_MESSAGE(!Str::truncate_at_end(test, 9) && test == expected, - "Expected:\n" - << expected << "\nbut found:\n" - << test); +BOOST_AUTO_TEST_CASE(test_algorithm_remove_double_quotes) { + ECF_NAME_THIS_TEST(); + + struct tc + { + std::string input; + std::string expected; + }; + + const std::vector test_cases = { + {"", ""}, + {"\"\"", ""}, + {"\"", "\""}, + {"''", "''"}, + {"'", "'"}, + {"abc", "abc"}, + {"\"abc\"", "abc"}, + {"\"abc", "\"abc"}, + {"abc\"", "abc\""}, + {"'abc", "'abc"}, + {"abc'", "abc'"}, + {"'abc'", "'abc'"}, + }; + + for (const auto& tc : test_cases) { + { + std::string actual = tc.input; + ecf::algorithm::remove_double_quotes(actual); + BOOST_CHECK_MESSAGE(actual == tc.expected, "Expected '" << tc.expected << "', found '" << actual << "'"); + } + { + std::string actual = ecf::algorithm::remove_double_quotes_copy(tc.input); + BOOST_CHECK_MESSAGE(actual == tc.expected, "Expected '" << tc.expected << "', found '" << actual << "'"); + } } } -static void -check(const std::string& line, const std::vector& result, const std::vector& expected) { - BOOST_CHECK_MESSAGE(result.size() == expected.size(), - "expected size " << expected.size() << " but found " << result.size() << " for '" << line - << "'"); - BOOST_CHECK_MESSAGE(result == expected, "failed for '" << line << "'"); - if (result != expected) { - ECF_TEST_DBG(<< "Line :'" << line); - ECF_TEST_DBG(<< "Actual :"); - for (const std::string& t : result) { - ECF_TEST_DBG(<< " '" << t << "'"); +BOOST_AUTO_TEST_CASE(test_algorithm_remove_single_quotes) { + ECF_NAME_THIS_TEST(); + + struct tc + { + std::string input; + std::string expected; + }; + + std::vector test_cases = { + {"", ""}, + {"\"\"", "\"\""}, + {"\"", "\""}, + {"''", ""}, + {"'", "'"}, + {"abc", "abc"}, + {"\"abc\"", "\"abc\""}, + {"\"abc", "\"abc"}, + {"abc\"", "abc\""}, + {"'abc", "'abc"}, + {"abc'", "abc'"}, + {"'abc'", "abc"}, + }; + + for (const auto& tc : test_cases) { + { + std::string actual = tc.input; + ecf::algorithm::remove_single_quotes(actual); + BOOST_CHECK_MESSAGE(actual == tc.expected, "Expected '" << tc.expected << "', found '" << actual << "'"); } - ECF_TEST_DBG(<< "Expected:"); - for (const std::string& t : expected) { - ECF_TEST_DBG(<< "'" << t << "'"); + { + std::string actual = ecf::algorithm::remove_single_quotes_copy(tc.input); + BOOST_CHECK_MESSAGE(actual == tc.expected, "Expected '" << tc.expected << "', found '" << actual << "'"); } } } -static void -check(const std::string& line, const std::vector& result2, const std::vector& expected) { - std::vector result; - for (auto ref : result2) { - result.emplace_back(ref.begin(), ref.end()); +BOOST_AUTO_TEST_CASE(test_algorithm_case_insensitive_compare) { + ECF_NAME_THIS_TEST(); + + struct tc + { + std::string lhs; + std::string rhs; + bool expected; + }; + + std::vector test_cases = {{"", "", true}, + {"a", "a", true}, + {"a", "A", true}, + {"A", "a", true}, + {"abc", "abc", true}, + {"abc", "ABC", true}, + {"Abc", "aBC", true}, + {"Str", "Str", true}, + {"Str", "Str1", false}, + {"", "Str1", false}, + {"Str", "STR", true}, + {"Case", "CaSE", true}}; + + for (const auto& tc : test_cases) { + auto actual = ecf::algorithm::case_insensitive_compare(tc.lhs, tc.rhs); + BOOST_CHECK_MESSAGE(actual == tc.expected, "Expected '" << tc.expected << "', found '" << actual << "'"); } - check(line, result, expected); } -static void check(const std::string& line, - boost::split_iterator res, - const std::vector& expected) { - std::vector result; - using split_iter_t = boost::split_iterator; - for (; res != split_iter_t(); res++) { - result.push_back(boost::copy_range(*res)); - } - check(line, result, expected); -} - -static void check_splitters(const std::string& line, const std::vector& expected) { - std::vector result, result1, result2, result3; - std::vector result4; - - Str::split_orig(line, result); - check(line, result, expected); - Str::split_orig1(line, result1); - check(line, result1, expected); - Str::split_using_string_view(line, result2); - check(line, result2, expected); - Str::split_using_string_view2(line, result3); - check(line, result3, expected); - StringSplitter::split(line, result4); - check(line, result4, expected); - - // While were at it also check get_token - for (size_t i = 0; i < result1.size(); i++) { - std::string token; - BOOST_CHECK_MESSAGE(Str::get_token(line, i, token) && token == result1[i], - "Str::get_token failed for pos " << i << " line:'" << line << "' expected '" << result1[i] - << "' but found '" << token << "'"); - - token.clear(); - BOOST_CHECK_MESSAGE(Str::get_token2(line, i, token) && token == result1[i], - "Str::get_token2 failed for pos " << i << " line:'" << line << "' expected '" << result1[i] - << "' but found '" << token << "'"); - - token.clear(); - BOOST_CHECK_MESSAGE(Str::get_token3(line, i, token) && token == result1[i], - "Str::get_token3 failed for pos " << i << " line:'" << line << "' expected '" << result1[i] - << "' but found '" << token << "'"); - - token.clear(); - BOOST_CHECK_MESSAGE(StringSplitter::get_token(line, i, token) && token == result1[i], - "StringSplitter::get_token failed for pos " << i << " line:'" << line << "' expected '" - << result1[i] << "' but found '" << token - << "'"); - } -} - -BOOST_AUTO_TEST_CASE(test_str_split) { - ECF_NAME_THIS_TEST(); - - std::vector expected; - - std::string line = "This is a string"; - expected.emplace_back("This"); - expected.emplace_back("is"); - expected.emplace_back("a"); - expected.emplace_back("string"); - check_splitters(line, expected); - - expected.clear(); - line = " "; - check_splitters(line, expected); - - line = "a"; - expected.clear(); - expected.emplace_back("a"); - check_splitters(line, expected); - - // Some implementation fail this test - line = "\n"; - expected.clear(); - expected.emplace_back("\n"); - check_splitters(line, expected); - - line = "a "; - expected.clear(); - expected.emplace_back("a"); - check_splitters(line, expected); - - line = " a"; - expected.clear(); - expected.emplace_back("a"); - check_splitters(line, expected); - - line = " a"; // check tabs - expected.clear(); - expected.emplace_back("a"); - check_splitters(line, expected); - - line = " a "; // check sequential tabs - expected.clear(); - expected.emplace_back("a"); - check_splitters(line, expected); - - line = " a "; - expected.clear(); - expected.emplace_back("a"); - check_splitters(line, expected); - - line = " a b c d "; - expected.clear(); - expected.emplace_back("a"); - expected.emplace_back("b"); - expected.emplace_back("c"); - expected.emplace_back("d"); - check_splitters(line, expected); - - line = " - ! $ % ^ & * ( ) - + ?"; - expected.clear(); - expected.emplace_back("-"); - expected.emplace_back("!"); - expected.emplace_back("$"); - expected.emplace_back("%"); - expected.emplace_back("^"); - expected.emplace_back("&"); - expected.emplace_back("*"); - expected.emplace_back("("); - expected.emplace_back(")"); - expected.emplace_back("-"); - expected.emplace_back("+"); - expected.emplace_back("?"); - check_splitters(line, expected); - - // Check tabs - line = " verify complete:8 # 4 sundays in october hence expect 8 task " - "completions"; - expected.clear(); - expected.emplace_back("verify"); - expected.emplace_back("complete:8"); - expected.emplace_back("#"); - expected.emplace_back("4"); - expected.emplace_back("sundays"); - expected.emplace_back("in"); - expected.emplace_back("october"); - expected.emplace_back("hence"); - expected.emplace_back("expect"); - expected.emplace_back("8"); - expected.emplace_back("task"); - expected.emplace_back("completions"); - check_splitters(line, expected); -} - -BOOST_AUTO_TEST_CASE(test_str_split_make_split_iterator) { - ECF_NAME_THIS_TEST(); - - std::string line = "This is a string"; - std::vector expected; - expected.emplace_back("This"); - expected.emplace_back("is"); - expected.emplace_back("a"); - expected.emplace_back("string"); - check(line, Str::make_split_iterator(line), expected); - - expected.clear(); - expected.emplace_back(""); - line = ""; - check(line, Str::make_split_iterator(line), expected); - - expected.clear(); - expected.emplace_back(""); - expected.emplace_back(""); - line = " "; // If start/end is delimeter, then preserved as empty token - check(line, Str::make_split_iterator(line), expected); - - expected.clear(); - line = "a"; - expected.emplace_back("a"); - check(line, Str::make_split_iterator(line), expected); - - // Some implementation fail this test - expected.clear(); - line = "\n"; - expected.emplace_back("\n"); - check(line, Str::make_split_iterator(line), expected); - - expected.clear(); - line = "a "; - expected.emplace_back("a"); - expected.emplace_back(""); // delimeter at start/end preserved, as empty token - check(line, Str::make_split_iterator(line), expected); - - expected.clear(); - line = " a"; - expected.emplace_back(""); - expected.emplace_back("a"); // delimeter at start/end preserved, as empty token - check(line, Str::make_split_iterator(line), expected); - - expected.clear(); - line = " a"; // check tabs - expected.emplace_back(""); - expected.emplace_back("a"); // delimeter at start/end preserved, as empty token - check(line, Str::make_split_iterator(line), expected); - - expected.clear(); - line = " a "; // check sequential tabs - expected.emplace_back(""); - expected.emplace_back("a"); // delimeter at start/end preserved, as empty token - expected.emplace_back(""); - check(line, Str::make_split_iterator(line), expected); - - expected.clear(); - line = " a "; - expected.emplace_back(""); - expected.emplace_back("a"); // delimeter at start/end preserved, as empty token - expected.emplace_back(""); - check(line, Str::make_split_iterator(line), expected); - - expected.clear(); - line = " a b c d "; - expected.emplace_back(""); // delimeter at start/end preserved, as empty token - expected.emplace_back("a"); - expected.emplace_back("b"); - expected.emplace_back("c"); - expected.emplace_back("d"); - expected.emplace_back(""); - check(line, Str::make_split_iterator(line), expected); - - expected.clear(); - line = " - ! $ % ^ & * ( ) - + ?"; - expected.emplace_back(""); // delimeter at start/end preserved, as empty token - expected.emplace_back("-"); - expected.emplace_back("!"); - expected.emplace_back("$"); - expected.emplace_back("%"); - expected.emplace_back("^"); - expected.emplace_back("&"); - expected.emplace_back("*"); - expected.emplace_back("("); - expected.emplace_back(")"); - expected.emplace_back("-"); - expected.emplace_back("+"); - expected.emplace_back("?"); - check(line, Str::make_split_iterator(line), expected); - - // Check tabs - expected.clear(); - line = " verify complete:8 # 4 sundays in october hence expect 8 task completions"; - expected.emplace_back(""); // delimeter at start/end preserved, as empty token - expected.emplace_back("verify"); - expected.emplace_back("complete:8"); - expected.emplace_back("#"); - expected.emplace_back("4"); - expected.emplace_back("sundays"); - expected.emplace_back("in"); - expected.emplace_back("october"); - expected.emplace_back("hence"); - expected.emplace_back("expect"); - expected.emplace_back("8"); - expected.emplace_back("task"); - expected.emplace_back("completions"); - check(line, Str::make_split_iterator(line), expected); -} - -static void -test_replace(std::string& testStr, const std::string& find, const std::string& replace, const std::string& expected) { - BOOST_CHECK_MESSAGE(Str::replace(testStr, find, replace), - "Replace failed for " << testStr << " find(" << find << ") replace(" << replace << ")"); - BOOST_CHECK_MESSAGE(testStr == expected, "Expected '" << expected << "' but found '" << testStr << "'"); -} - -static void test_replace_all(std::string& testStr, - const std::string& find, - const std::string& replace, - const std::string& expected) { - std::string testStrCopy = testStr; - - BOOST_CHECK_MESSAGE(Str::replace_all(testStr, find, replace), - "Replace failed for " << testStr << " find(" << find << ") replace(" << replace << ")"); - BOOST_CHECK_MESSAGE(testStr == expected, "Expected '" << expected << "' but found '" << testStr << "'"); - - Str::replaceall(testStrCopy, find, replace); - BOOST_CHECK_MESSAGE(testStr == testStrCopy, "Expected '" << testStrCopy << "' but found '" << testStr << "'"); -} - -BOOST_AUTO_TEST_CASE(test_str_replace) { - ECF_NAME_THIS_TEST(); - - std::string testStr = "This is a string"; - test_replace(testStr, "This", "That", "That is a string"); - - testStr = "This is a string"; - test_replace(testStr, "This is a string", "", ""); - - testStr = "This is a string"; - test_replace(testStr, "is a", "was a", "This was a string"); - - testStr = "This\n is a string"; - test_replace(testStr, "\n", "\\n", "This\\n is a string"); - - testStr = "This\n is\n a\n string\n"; - test_replace_all(testStr, "\n", "\\n", R"(This\n is\n a\n string\n)"); +BOOST_AUTO_TEST_CASE(test_algorithm_case_insensitive_less) { + ECF_NAME_THIS_TEST(); - // Test case insenstive string comparison - BOOST_CHECK_MESSAGE(Str::caseInsCompare("", ""), " bug1"); - BOOST_CHECK_MESSAGE(!Str::caseInsCompare("Str", "Str1"), " bug1"); - BOOST_CHECK_MESSAGE(!Str::caseInsCompare("", "Str1"), " bug1"); - BOOST_CHECK_MESSAGE(Str::caseInsCompare("Str", "STR"), " bug1"); - BOOST_CHECK_MESSAGE(Str::caseInsCompare("Case", "CaSE"), " bug1"); + struct tc + { + std::string lhs; + std::string rhs; + bool expected; + }; + + std::vector test_cases = {{"", "", false}, + {"a", "a", false}, + {"a", "A", true}, + {"A", "a", false}, + {"abc", "abc", false}, + {"abc", "ABC", true}, + {"Abc", "aBC", false}, + {"Str", "Str", false}, + {"Str", "Str1", true}, + {"", "Str1", true}, + {"Str", "STR", true}, + {"Case", "CaSE", true}, + {"abc", "abd", true}, + {"abd", "abc", false}}; + + for (const auto& tc : test_cases) { + auto actual = ecf::algorithm::case_insensitive_less(tc.lhs, tc.rhs); + BOOST_CHECK_MESSAGE(actual == tc.expected, + "Comparing '" << tc.lhs << "' < '" << tc.rhs << "' Expected '" << tc.expected + << "', found '" << actual << "'"); + } } -BOOST_AUTO_TEST_CASE(test_str_replace_all) { +BOOST_AUTO_TEST_CASE(test_algorithm_case_insensitive_greater) { ECF_NAME_THIS_TEST(); - std::string testStr = "This is a string"; - test_replace_all(testStr, "This", "That", "That is a string"); + struct tc + { + std::string lhs; + std::string rhs; + bool expected; + }; + + std::vector test_cases = {{"", "", false}, + {"a", "a", false}, + {"a", "A", false}, + {"A", "a", true}, + {"abc", "abc", false}, + {"abc", "ABC", false}, + {"Abc", "aBC", true}, + {"Str", "Str", false}, + {"Str", "Str1", true}, + {"", "Str1", true}, + {"Str", "STR", false}, + {"Case", "CaSE", false}, + {"abc", "abd", false}, + {"abd", "abc", true}}; + + for (const auto& tc : test_cases) { + auto actual = ecf::algorithm::case_insensitive_greater(tc.lhs, tc.rhs); + BOOST_CHECK_MESSAGE(actual == tc.expected, + "Comparing '" << tc.lhs << "' > '" << tc.rhs << "' Expected '" << tc.expected + << "', found '" << actual << "'"); + } +} - testStr = "This is a string"; - test_replace_all(testStr, "This is a string", "", ""); +BOOST_AUTO_TEST_CASE(test_algorithm_is_int) { + ECF_NAME_THIS_TEST(); + + struct test_case + { + std::string input; + bool expected; + }; + + std::vector test_cases = {{"0", true}, + {"1", true}, + {"-0", true}, + {"-1", true}, + {"", false}, + {"-", false}, + {" ", false}, + {"q", false}, + {"q22", false}, + {"99 99", false}, + {"99\t99", false}}; + + for (const auto& tc : test_cases) { + auto actual = ecf::algorithm::is_int(tc.input); + BOOST_CHECK_MESSAGE(actual == tc.expected, + "Checking '" << tc.input << "', Expected " << tc.expected << ", found " << actual); + } +} + +BOOST_AUTO_TEST_CASE(test_algorithm_to_int) { + ECF_NAME_THIS_TEST(); + + struct test_case + { + std::string input; + int expected; + }; + + std::vector test_cases = {{"0", 0}, + {"1", 1}, + {"-0", 0}, + {"-1", -1}, + {"", std::numeric_limits::max()}, + {"-", std::numeric_limits::max()}, + {" ", std::numeric_limits::max()}, + {"q", std::numeric_limits::max()}, + {"q22", std::numeric_limits::max()}, + {"99 99", std::numeric_limits::max()}}; + + for (const auto& tc : test_cases) { + auto actual = ecf::algorithm::to_int(tc.input); + BOOST_CHECK_MESSAGE(actual == tc.expected, + "Converting '" << tc.input << "' to int, Expected " << tc.expected << ", found " << actual); + } +} + +BOOST_AUTO_TEST_CASE(test_algorithm_tail) { + ECF_NAME_THIS_TEST(); + std::string test; + BOOST_CHECK_MESSAGE(!ecf::algorithm::tail(test, 7), "Empty sring should return false"); + + test = "this\nis\na\nstring\nwith\nlots\nof\nnew\nline"; + std::string expected = "line"; + BOOST_CHECK_MESSAGE(ecf::algorithm::tail(test, 1) && test == expected, + "Expected:\n" + << expected << "\nbut found:\n" + << test); + + test = "this\nis\na\nstring\nwith\nlots\nof\nnew\nline"; + expected = "a\nstring\nwith\nlots\nof\nnew\nline"; + BOOST_CHECK_MESSAGE(ecf::algorithm::tail(test, 7) && test == expected, + "Expected:\n" + << expected << "\nbut found:\n" + << test); + + test = "this\nis\na\nstring\nwith\nlots\nof\nnew\nline"; + expected = test; + BOOST_CHECK_MESSAGE(!ecf::algorithm::tail(test, 9) && test == expected, + "Expected:\n" + << expected << "\nbut found:\n" + << test); +} + +BOOST_AUTO_TEST_CASE(test_algorithm_head) { + ECF_NAME_THIS_TEST(); + std::string test; + BOOST_CHECK_MESSAGE(!ecf::algorithm::head(test, 7), "Empty string should return false"); + + test = "this\nis\na\nstring\nwith\nlots\nof\nnew\nline"; + std::string expected = "this\n"; + BOOST_CHECK_MESSAGE(ecf::algorithm::head(test, 1) && test == expected, + "Expected:\n" + << expected << "\nbut found:\n" + << test); + + test = "this\nis\na\nstring\nwith\nlots\nof\nnew\nline"; + expected = "this\nis\n"; + BOOST_CHECK_MESSAGE(ecf::algorithm::head(test, 2) && test == expected, + "Expected:\n" + << expected << "\nbut found:\n" + << test); + + test = "this\nis\na\nstring\nwith\nlots\nof\nnew\nline"; + expected = "this\nis\na\nstring\nwith\nlots\nof\n"; + BOOST_CHECK_MESSAGE(ecf::algorithm::head(test, 7) && test == expected, + "Expected:\n" + << expected << "\nbut found:\n" + << test); + + test = "this\nis\na\nstring\nwith\nlots\nof\nnew\nline"; + expected = test; + BOOST_CHECK_MESSAGE(!ecf::algorithm::head(test, 9) && test == expected, + "Expected:\n" + << expected << "\nbut found:\n" + << test); +} - testStr = "This is a string"; - test_replace_all(testStr, "is a", "was a", "This was a string"); +BOOST_AUTO_TEST_CASE(test_algorithm_as_string_for_bool) { + ECF_NAME_THIS_TEST(); - testStr = "This\n is a string"; - test_replace_all(testStr, "\n", "\\n", "This\\n is a string"); + struct tc + { + bool input; + std::string expected; + }; - testStr = "This\n is\n a\n string\n"; - test_replace_all(testStr, "\n", "\\n", R"(This\n is\n a\n string\n)"); + std::vector test_cases = {{true, "true"}, {false, "false"}}; - testStr = "This\n is\n a\n string\n"; - test_replace_all(testStr, "\n", "", "This is a string"); + for (const auto& tc : test_cases) { + auto actual = ecf::algorithm::as_string(tc.input); + BOOST_CHECK_MESSAGE(actual == tc.expected, "Expected '" << tc.expected << "', found '" << actual << "'"); + }; } -BOOST_AUTO_TEST_CASE(test_str_to_int) { +BOOST_AUTO_TEST_CASE(test_algorithm_as_string_for_vector_of_arithmetic) { ECF_NAME_THIS_TEST(); - BOOST_CHECK_MESSAGE(Str::to_int("0") == 0, "Expected 0"); - BOOST_CHECK_MESSAGE(Str::to_int("1") == 1, "Expected 1"); - BOOST_CHECK_MESSAGE(Str::to_int("-0") == 0, "Expected 0"); - BOOST_CHECK_MESSAGE(Str::to_int("-1") == -1, "Expected -1"); - BOOST_CHECK_MESSAGE(Str::to_int("") == std::numeric_limits::max(), "Expected max int"); - BOOST_CHECK_MESSAGE(Str::to_int("-") == std::numeric_limits::max(), "Expected max int"); - BOOST_CHECK_MESSAGE(Str::to_int(" ") == std::numeric_limits::max(), "Expected max int"); - BOOST_CHECK_MESSAGE(Str::to_int("q") == std::numeric_limits::max(), "Expected max int"); - BOOST_CHECK_MESSAGE(Str::to_int("q22") == std::numeric_limits::max(), "Expected max int"); - BOOST_CHECK_MESSAGE(Str::to_int("q22", -1) == -1, "Expected -1 on failure"); - BOOST_CHECK_MESSAGE(Str::to_int("99 99") == std::numeric_limits::max(), "Expected max int"); - BOOST_CHECK_MESSAGE(Str::to_int("99 99", 0) == 0, "Expected 0 for failure"); + struct tc + { + std::vector input; + std::string expected; + }; + + std::vector test_cases = { + {{}, "[ ]"}, {{1}, "[ 1 ]"}, {{1, 2}, "[ 1, 2 ]"}, {{123, 456, 789}, "[ 123, 456, 789 ]"}}; + + for (const auto& tc : test_cases) { + auto actual = ecf::algorithm::as_string(tc.input); + BOOST_CHECK_MESSAGE(actual == tc.expected, "Expected '" << tc.expected << "', found '" << actual << "'"); + }; } -BOOST_AUTO_TEST_CASE(test_str_is_int) { +BOOST_AUTO_TEST_CASE(test_algorithm_as_string_for_vector_of_strings) { ECF_NAME_THIS_TEST(); - BOOST_CHECK_EQUAL(Str::is_int("0"), true); - BOOST_CHECK_EQUAL(Str::is_int("1"), true); - BOOST_CHECK_EQUAL(Str::is_int("-0"), true); - BOOST_CHECK_EQUAL(Str::is_int("-1"), true); - BOOST_CHECK_EQUAL(Str::is_int(""), false); - BOOST_CHECK_EQUAL(Str::is_int("-"), false); - BOOST_CHECK_EQUAL(Str::is_int(" "), false); - BOOST_CHECK_EQUAL(Str::is_int("q"), false); - BOOST_CHECK_EQUAL(Str::is_int("q22"), false); - BOOST_CHECK_EQUAL(Str::is_int("99 99"), false); - BOOST_CHECK_EQUAL(Str::is_int("99 99"), false); + struct tc + { + std::vector input; + std::string expected; + }; + + std::vector test_cases = { + {{}, "[ ]"}, {{"a"}, "[ a ]"}, {{"a", "b"}, "[ a, b ]"}, {{"abc", "def", "ghi"}, "[ abc, def, ghi ]"}}; + + for (const auto& tc : test_cases) { + auto actual = ecf::algorithm::as_string(tc.input); + BOOST_CHECK_MESSAGE(actual == tc.expected, "Expected '" << tc.expected << "', found '" << actual << "'"); + }; } -BOOST_AUTO_TEST_CASE(test_extract_data_member_value) { +BOOST_AUTO_TEST_CASE(test_algorithm_as_string_for_vector_of_string_views) { ECF_NAME_THIS_TEST(); - std::string expected = "value"; - std::string actual; - std::string str = "aa bb c fred:value"; - BOOST_CHECK_MESSAGE(Str::extract_data_member_value(str, "fred:", actual), " failed"); - BOOST_CHECK_MESSAGE(expected == actual, "expected '" << expected << "' but found '" << actual << "'"); + struct tc + { + std::vector input; + std::string expected; + }; - str = "fred:x bill:zzz jake:12345 1234:99 6677"; - expected = "x"; - BOOST_CHECK_MESSAGE(Str::extract_data_member_value(str, "fred:", actual), " failed"); - BOOST_CHECK_MESSAGE(expected == actual, "expected '" << expected << "' but found '" << actual << "'"); + std::vector test_cases = { + {{}, "[ ]"}, {{"a"}, "[ a ]"}, {{"a", "b"}, "[ a, b ]"}, {{"abc", "def", "ghi"}, "[ abc, def, ghi ]"}}; - expected = "zzz"; - BOOST_CHECK_MESSAGE(Str::extract_data_member_value(str, "bill:", actual), " failed"); - BOOST_CHECK_MESSAGE(expected == actual, "expected '" << expected << "' but found '" << actual << "'"); + for (const auto& tc : test_cases) { + auto actual = ecf::algorithm::as_string(tc.input); + BOOST_CHECK_MESSAGE(actual == tc.expected, "Expected '" << tc.expected << "', found '" << actual << "'"); + }; +} - expected = "12345"; - BOOST_CHECK_MESSAGE(Str::extract_data_member_value(str, "jake:", actual), " failed"); - BOOST_CHECK_MESSAGE(expected == actual, "expected '" << expected << "' but found '" << actual << "'"); +BOOST_AUTO_TEST_CASE(test_algortihm_replace) { + ECF_NAME_THIS_TEST(); + + struct tc + { + std::string input; + std::string find; + std::string replace; + std::string expected; + bool result; + }; - expected = "99"; - BOOST_CHECK_MESSAGE(Str::extract_data_member_value(str, "1234:", actual), " failed"); - BOOST_CHECK_MESSAGE(expected == actual, "expected '" << expected << "' but found '" << actual << "'"); + std::vector test_cases = {{"This is a string", "", "Replacement", "This is a string", false}, + {"This is a string", "Pattern", "Replacment", "This is a string", false}, + {"This is a string", "This", "That", "That is a string", true}, + {"This is a string", "This is a string", "", "", true}, + {"This is a string", "is a", "was a", "This was a string", true}, + {"This\n is a string", "\n", "\\n", "This\\n is a string", true}}; - expected = "77"; - BOOST_CHECK_MESSAGE(Str::extract_data_member_value(str, "66", actual), " failed"); - BOOST_CHECK_MESSAGE(expected == actual, "expected '" << expected << "' but found '" << actual << "'"); + for (const auto& tc : test_cases) { + std::string actual = tc.input; + + auto result = ecf::algorithm::replace(actual, tc.find, tc.replace); + + BOOST_CHECK_MESSAGE(result == tc.result, + "Replace failed for " << actual << " find(" << tc.find << ") replace(" << tc.replace + << ")"); + BOOST_CHECK_MESSAGE(actual == tc.expected, "Expected '" << tc.expected << "' but found '" << actual << "'"); + } } -std::string toString(const std::vector& c) { - std::ostringstream ss; - std::copy(c.begin(), c.end(), std::ostream_iterator(ss, ", ")); - return ss.str(); +BOOST_AUTO_TEST_CASE(test_algorithm_replace_all) { + ECF_NAME_THIS_TEST(); + + struct tc + { + std::string input; + std::string find; + std::string replace; + std::string expected; + bool result; + }; + + std::vector test_cases = { + {"This is a string", "", "Replacement", "This is a string", false}, + {"This is a string", "Pattern", "Replacement", "This is a string", false}, + {"This is a string", "This", "That", "That is a string", true}, + {"This is a string", "This is a string", "", "", true}, + {"This is a string", "is a", "was a", "This was a string", true}, + {"This\n is a string", "\n", "\\n", "This\\n is a string", true}, + {"This\n is\n a\n string\n", "\n", "\\n", R"(This\n is\n a\n string\n)", true}, + {"This\n is\n a\n string\n", "\n", "", "This is a string", true}, + }; + + for (const auto& tc : test_cases) { + auto actual = tc.input; + + { // Check that replacement happens as expected + bool result = ecf::algorithm::replace_all(actual, tc.find, tc.replace); + + BOOST_CHECK_MESSAGE(result == tc.result, + "Replace successful for " << actual << " find(" << tc.find << ") replace(" << tc.replace + << ")"); + BOOST_CHECK_MESSAGE(actual == tc.expected, "Expected '" << tc.expected << "' but found '" << actual << "'"); + } + + auto copy = actual; + + { // Ensure that attempting replacement again returns false and does not change the input string + bool result = ecf::algorithm::replace_all(copy, tc.find, tc.replace); + + BOOST_CHECK_MESSAGE(!result, + "Replace unsuccessful for " << copy << " find(" << tc.find << ") replace(" << tc.replace + << "), since no occurrences were left"); + BOOST_CHECK_MESSAGE(actual == copy, "Expected '" << actual << "' but found '" << copy << "'"); + } + } } -BOOST_AUTO_TEST_CASE(test_str_less_greater) { +BOOST_AUTO_TEST_CASE(test_algorithm_sort_using_case_insensitive_order) { ECF_NAME_THIS_TEST(); - std::vector expected; - expected.emplace_back("a1"); - expected.emplace_back("A2"); - expected.emplace_back("b1"); - expected.emplace_back("B2"); - expected.emplace_back("c"); - - std::vector expectedGreater; - expectedGreater.emplace_back("c"); - expectedGreater.emplace_back("B2"); - expectedGreater.emplace_back("b1"); - expectedGreater.emplace_back("A2"); - expectedGreater.emplace_back("a1"); - - std::vector vec; - vec.emplace_back("c"); - vec.emplace_back("A2"); - vec.emplace_back("a1"); - vec.emplace_back("b1"); - vec.emplace_back("B2"); - - std::sort(vec.begin(), vec.end(), Str::caseInsLess); - BOOST_REQUIRE_MESSAGE(vec == expected, "expected " << toString(expected) << " but found " << toString(vec)); - - std::sort(vec.begin(), vec.end(), Str::caseInsGreater); - BOOST_REQUIRE_MESSAGE(vec == expectedGreater, - "expected " << toString(expectedGreater) << " but found " << toString(vec)); - - // -------------------------------------------------------------------- - - expected.clear(); - expected.emplace_back("a"); - expected.emplace_back("A"); - expected.emplace_back("b"); - expected.emplace_back("B"); - expected.emplace_back("c"); - - expectedGreater.clear(); - expectedGreater.emplace_back("c"); - expectedGreater.emplace_back("B"); - expectedGreater.emplace_back("b"); - expectedGreater.emplace_back("A"); - expectedGreater.emplace_back("a"); - - vec.clear(); - vec.emplace_back("c"); - vec.emplace_back("B"); - vec.emplace_back("A"); - vec.emplace_back("b"); - vec.emplace_back("a"); - - std::sort(vec.begin(), vec.end(), Str::caseInsLess); - BOOST_REQUIRE_MESSAGE(vec == expected, "expected " << toString(expected) << " but found " << toString(vec)); - - std::sort(vec.begin(), vec.end(), Str::caseInsGreater); - BOOST_REQUIRE_MESSAGE(vec == expectedGreater, - "expected " << toString(expectedGreater) << " but found " << toString(vec)); - - // -------------------------------------------------------------------- - - expected.clear(); - expected.emplace_back("1234"); - expected.emplace_back("baSE"); - expected.emplace_back("Base"); - expected.emplace_back("case"); - expected.emplace_back("CaSe"); - expected.emplace_back("suite"); - expected.emplace_back("SUITE"); - - expectedGreater.clear(); - expectedGreater.emplace_back("SUITE"); - expectedGreater.emplace_back("suite"); - expectedGreater.emplace_back("CaSe"); - expectedGreater.emplace_back("case"); - expectedGreater.emplace_back("Base"); - expectedGreater.emplace_back("baSE"); - expectedGreater.emplace_back("1234"); - - vec.clear(); - vec.emplace_back("suite"); - vec.emplace_back("SUITE"); - vec.emplace_back("baSE"); - vec.emplace_back("Base"); - vec.emplace_back("case"); - vec.emplace_back("CaSe"); - vec.emplace_back("1234"); - - std::sort(vec.begin(), vec.end(), Str::caseInsLess); - BOOST_REQUIRE_MESSAGE(vec == expected, "expected " << toString(expected) << " but found " << toString(vec)); - - std::sort(vec.begin(), vec.end(), Str::caseInsGreater); - BOOST_REQUIRE_MESSAGE(vec == expectedGreater, - "expected " << toString(expectedGreater) << " but found " << toString(vec)); -} - -//// ============================================================== -//// Timing to find the fastest looping -//// ============================================================== -class Fred { -public: - explicit Fred(int i = 0) - : i_(i) { - // Do nothing... - } - Fred(const Fred& rhs) = default; - Fred& operator=(const Fred& rhs) = default; - ~Fred() = default; - - void inc() { i_++; } - -private: - int i_; -}; - -BOOST_AUTO_TEST_CASE(test_loop, *boost::unit_test::disabled()) { + struct tc + { + std::vector input; + std::vector expected_less; + std::vector expected_greater; + }; + + std::vector test_cases = { + // clang-format off + { + {"c", "A2", "a1", "b1", "B2"}, + {"a1", "A2", "b1", "B2", "c"}, + {"c", "B2", "b1", "A2", "a1"} + }, + { + {"c", "B", "A", "b", "a"}, + {"a", "A", "b", "B", "c"}, + {"c", "B", "b", "A", "a"} + }, + { + {"suite", "SUITE", "baSE", "Base", "case", "CaSe", "1234"}, + {"1234", "baSE", "Base", "case", "CaSe", "suite", "SUITE"}, + {"SUITE", "suite", "CaSe", "case", "Base", "baSE", "1234"} + } + // clang-format on + }; + + for (const auto& tc : test_cases) { + + { + auto actual = tc.input; + std::sort(actual.begin(), actual.end(), ecf::algorithm::case_insensitive_less); + BOOST_REQUIRE_MESSAGE(actual == tc.expected_less, + "expected " << ecf::algorithm::as_string(tc.expected_less) << " but found " + << ecf::algorithm::as_string(actual)); + } + + { + auto actual = tc.input; + std::sort(actual.begin(), actual.end(), ecf::algorithm::case_insensitive_greater); + BOOST_REQUIRE_MESSAGE(actual == tc.expected_greater, + "expected " << ecf::algorithm::as_string(tc.expected_greater) << " but found " + << ecf::algorithm::as_string(actual)); + } + } +} + +BOOST_AUTO_TEST_CASE(test_algorithm_get_token) { + + struct TestCase + { + std::string input; + size_t index; + std::string delimiters; + bool expected_outcome; + std::string expected_token; + }; + + std::vector testCases = { + // all tokens can be accessed + {"0,1,2,3,4,5,6,7,8,9,10", 0, ",", true, "0"}, + {"0,1,2,3,4,5,6,7,8,9,10", 1, ",", true, "1"}, + {"0,1,2,3,4,5,6,7,8,9,10", 2, ",", true, "2"}, + {"0,1,2,3,4,5,6,7,8,9,10", 3, ",", true, "3"}, + {"0,1,2,3,4,5,6,7,8,9,10", 4, ",", true, "4"}, + {"0,1,2,3,4,5,6,7,8,9,10", 5, ",", true, "5"}, + {"0,1,2,3,4,5,6,7,8,9,10", 6, ",", true, "6"}, + {"0,1,2,3,4,5,6,7,8,9,10", 7, ",", true, "7"}, + {"0,1,2,3,4,5,6,7,8,9,10", 8, ",", true, "8"}, + {"0,1,2,3,4,5,6,7,8,9,10", 9, ",", true, "9"}, + {"0,1,2,3,4,5,6,7,8,9,10", 10, ",", true, "10"}, + + // out-of-range tokens are correctly handled + {"0,1,2,3,4,5,6,7,8,9,10", 11, ",", false, ""}, + {"0,1,2,3,4,5,6,7,8,9,10", 12, ",", false, ""}, + + // now using another delimiter + {"0 1 2 3 4 5 6 7 8 9 10", 0, " ", true, "0"}, + {"0 1 2 3 4 5 6 7 8 9 10", 5, " ", true, "5"}, + {"0 1 2 3 4 5 6 7 8 9 10", 10, " ", true, "10"}, + {"0 1 2 3 4 5 6 7 8 9 10", 42, " ", false, ""}, + + // now using multiple delimiters + {"0 1\t2 3\t4 5\t6 7\t8 9\t10", 0, " \t", true, "0"}, + {"0 1\t2 3\t4 5\t6 7\t8 9\t10", 5, " \t", true, "5"}, + {"0 1\t2 3\t4 5\t6 7\t8 9\t10", 10, " \t", true, "10"}, + {"0 1\t2 3\t4 5\t6 7\t8 9\t10", 42, " \t", false, ""}, + }; + + for (const auto& testCase : testCases) { + std::string actual_token; + auto actual_outcome = + ecf::algorithm::get_token(testCase.input, testCase.index, actual_token, testCase.delimiters); + + BOOST_REQUIRE_MESSAGE(actual_outcome == testCase.expected_outcome, + "Correct output getting token from '" << testCase.input << "' at index " << testCase.index + << " with delimiters '" << testCase.delimiters + << "'"); + + if (testCase.expected_outcome) { + BOOST_CHECK_MESSAGE(actual_token == testCase.expected_token, + "Found correct token with input: '" + << testCase.input << "' at index " << testCase.index << " with delimiters '" + << testCase.delimiters << "'. Expected '" << testCase.expected_token << "' found '" + << actual_token << "'"); + } + } +} + +BOOST_AUTO_TEST_CASE(test_performance_loop, *boost::unit_test::disabled()) { ECF_NAME_THIS_TEST(); + // + // The goal of this test is to investigate the fastest looping mechanism over an std::vector, by comparing: + // - raw for loop + // - std::for_each + // - std::vector::iterator-based iteration + // - index-based iteration + // + + class Fred { + public: + explicit Fred(int i = 0) + : i_(i) { + // Do nothing... + } + Fred(const Fred& rhs) = default; + Fred& operator=(const Fred& rhs) = default; + ~Fred() = default; + + void inc() { i_++; } + + private: + int i_; + }; + const size_t size = 200000000; std::vector vec; vec.reserve(size); @@ -682,72 +1139,68 @@ BOOST_AUTO_TEST_CASE(test_loop, *boost::unit_test::disabled()) { } { - PerformanceTimer timer; + ecf::PerformanceTimer timer; for (auto& fred : vec) { fred.inc(); } - ECF_TEST_DBG(<< "Time: for(auto &fred : vec) { fred.inc(); } " + ECF_TEST_DBG("Time: for(auto &fred : vec) { fred.inc(); } " << timer); } { - PerformanceTimer timer; + ecf::PerformanceTimer timer; std::for_each(vec.begin(), vec.end(), [](Fred& fred) { fred.inc(); }); - ECF_TEST_DBG(<< "Time: std::for_each(vec.begin(),vec.end(),[](Fred& fred) { fred.inc();} ); " + ECF_TEST_DBG("Time: std::for_each(vec.begin(),vec.end(),[](Fred& fred) { fred.inc();} ); " << timer); } { - PerformanceTimer timer; + ecf::PerformanceTimer timer; auto theEnd = vec.end(); for (auto i = vec.begin(); i < theEnd; i++) { (*i).inc(); } - ECF_TEST_DBG(<< "Time: for (std::vector::iterator i = vec.begin(); i < theEnd ; i++) { (*i).inc(); } " + ECF_TEST_DBG("Time: for (std::vector::iterator i = vec.begin(); i < theEnd ; i++) { (*i).inc(); } " << timer); } { - PerformanceTimer timer; + ecf::PerformanceTimer timer; size_t theSize = vec.size(); for (size_t i = 0; i < theSize; i++) { vec[i].inc(); } - ECF_TEST_DBG(<< "Time: for (size_t i = 0; i < theSize ; i++) { vec[i].inc(); } " + ECF_TEST_DBG("Time: for (size_t i = 0; i < theSize ; i++) { vec[i].inc(); } " << timer); } } -/// ============================================================== -/// Timing to find the fastest conversion from string to int -/// ============================================================== -static void methodX(const std::string& str, std::vector& stringRes, std::vector& numberRes) { - // 0.81 - // for bad conversion istringstream seems to return 0, hence add guard - if (str.find_first_of(Str::NUMERIC(), 0) == 0) { - int number = 0; - std::istringstream(str) >> number; - numberRes.push_back(number); - } - else { - stringRes.push_back(str); - } -} +BOOST_AUTO_TEST_CASE(test_performance_convert_string_to_int, *boost::unit_test::disabled()) { + ECF_NAME_THIS_TEST(); -static void method1(const std::string& str, std::vector& stringRes, std::vector& numberRes) { - // 12.2 - try { - int number = ecf::convert_to(str); - numberRes.push_back(number); - } - catch (const ecf::bad_conversion&) { - stringRes.push_back(str); - } -} + // + // The goal of this test is to investigate the fastest mechanism to convert from string to int, by comparing: + // - methodX, using istringstream marshalling + // - method1, using ecf::convert_to with try/catch + // - method2, using ecf::convert_to with try/catch but only if the first character of the string is a numeric char + // - method3, using atoi (which we known to be fast but does not handle errors) + // + + auto methodX = [](const std::string& str, std::vector& stringRes, std::vector& numberRes) { + // 0.81 + // for bad conversion istringstream seems to return 0, hence add guard + if (str.find_first_of(ecf::string_constants::numeric_chars, 0) == 0) { + int number = 0; + std::istringstream(str) >> number; + numberRes.push_back(number); + } + else { + stringRes.push_back(str); + } + }; -static void method2(const std::string& str, std::vector& stringRes, std::vector& numberRes) { - // 0.6 - if (str.find_first_of(Str::NUMERIC(), 0) == 0) { + auto method1 = [](const std::string& str, std::vector& stringRes, std::vector& numberRes) { + // 12.2 try { int number = ecf::convert_to(str); numberRes.push_back(number); @@ -755,26 +1208,35 @@ static void method2(const std::string& str, std::vector& stringRes, catch (const ecf::bad_conversion&) { stringRes.push_back(str); } - } - else { - stringRes.push_back(str); - } -} - -static void method3(const std::string& str, std::vector& stringRes, std::vector& numberRes) { - // 0.14 - // atoi return 0 for errors, - int number = atoi(str.c_str()); // does not handle errors - if (number == 0 && str.size() != 1) { - stringRes.push_back(str); - } - else { - numberRes.push_back(number); - } -} + }; + + auto method2 = [](const std::string& str, std::vector& stringRes, std::vector& numberRes) { + // 0.6 + if (str.find_first_of(ecf::string_constants::numeric_chars, 0) == 0) { + try { + int number = ecf::convert_to(str); + numberRes.push_back(number); + } + catch (const ecf::bad_conversion&) { + stringRes.push_back(str); + } + } + else { + stringRes.push_back(str); + } + }; -BOOST_AUTO_TEST_CASE(test_lexical_cast_perf, *boost::unit_test::disabled()) { - ECF_NAME_THIS_TEST(); + auto method3 = [](const std::string& str, std::vector& stringRes, std::vector& numberRes) { + // 0.14 + // atoi return 0 for errors, + int number = atoi(str.c_str()); // does not handle errors + if (number == 0 && str.size() != 1) { + stringRes.push_back(str); + } + else { + numberRes.push_back(number); + } + }; size_t the_size = 1000000; std::vector stringTokens; @@ -794,14 +1256,14 @@ BOOST_AUTO_TEST_CASE(test_lexical_cast_perf, *boost::unit_test::disabled()) { numberRes.reserve(expectedNumberRes.size()); { - PerformanceTimer timer; + ecf::PerformanceTimer timer; for (size_t i = 0; i < stringTokens.size(); i++) { method1(stringTokens[i], stringRes, numberRes); } for (size_t i = 0; i < numberTokens.size(); i++) { method1(numberTokens[i], stringRes, numberRes); } - ECF_TEST_DBG(<< "Time for method1 elapsed time = " << timer); + ECF_TEST_DBG("Time for method1 elapsed time = " << timer); BOOST_CHECK_MESSAGE(numberRes == expectedNumberRes, " method 1 wrong"); BOOST_CHECK_MESSAGE(stringTokens == stringRes, "method 1 wrong"); numberRes.clear(); @@ -809,14 +1271,14 @@ BOOST_AUTO_TEST_CASE(test_lexical_cast_perf, *boost::unit_test::disabled()) { } { - PerformanceTimer timer; + ecf::PerformanceTimer timer; for (size_t i = 0; i < stringTokens.size(); i++) { methodX(stringTokens[i], stringRes, numberRes); } for (size_t i = 0; i < numberTokens.size(); i++) { methodX(numberTokens[i], stringRes, numberRes); } - ECF_TEST_DBG(<< "Time for methodX elapsed time = " << timer); + ECF_TEST_DBG("Time for methodX elapsed time = " << timer); BOOST_CHECK_MESSAGE(numberRes == expectedNumberRes, " method X wrong"); BOOST_CHECK_MESSAGE(stringTokens == stringRes, "method X wrong"); numberRes.clear(); @@ -824,7 +1286,7 @@ BOOST_AUTO_TEST_CASE(test_lexical_cast_perf, *boost::unit_test::disabled()) { } { - PerformanceTimer timer; + ecf::PerformanceTimer timer; for (size_t i = 0; i < stringTokens.size(); i++) { method2(stringTokens[i], stringRes, numberRes); } @@ -839,14 +1301,14 @@ BOOST_AUTO_TEST_CASE(test_lexical_cast_perf, *boost::unit_test::disabled()) { } { - PerformanceTimer timer; + ecf::PerformanceTimer timer; for (size_t i = 0; i < stringTokens.size(); i++) { method3(stringTokens[i], stringRes, numberRes); } for (size_t i = 0; i < numberTokens.size(); i++) { method3(numberTokens[i], stringRes, numberRes); } - ECF_TEST_DBG(<< "Time for method3 elapsed time = " << timer); + ECF_TEST_DBG("Time for method3 elapsed time = " << timer); BOOST_CHECK_MESSAGE(numberRes == expectedNumberRes, " method3 wrong numberRes.size()=" << numberRes.size() << " expected size = " << expectedNumberRes.size()); @@ -858,16 +1320,19 @@ BOOST_AUTO_TEST_CASE(test_lexical_cast_perf, *boost::unit_test::disabled()) { } } -BOOST_AUTO_TEST_CASE(test_int_to_str_perf, *boost::unit_test::disabled()) { +BOOST_AUTO_TEST_CASE(test_performance_convert_int_to_string, *boost::unit_test::disabled()) { ECF_NAME_THIS_TEST(); - // Lexical_cast is approx twice as fast as using streams - // time for ostream = 0.97 - // time for lexical_cast = 0.45 + // + // The goal of this test is to investigate the fastest mechanism to convert from string to int, by comparing: + // - ostream = 0.97 + // - lexical_cast = 0.45 + // const int the_size = 1000000; + { - PerformanceTimer timer; + ecf::PerformanceTimer timer; for (size_t i = 0; i < the_size; i++) { std::ostringstream st; st << i; @@ -877,7 +1342,7 @@ BOOST_AUTO_TEST_CASE(test_int_to_str_perf, *boost::unit_test::disabled()) { } { - PerformanceTimer timer; + ecf::PerformanceTimer timer; for (size_t i = 0; i < the_size; i++) { [[maybe_unused]] std::string s = ecf::convert_to(i); } @@ -885,64 +1350,6 @@ BOOST_AUTO_TEST_CASE(test_int_to_str_perf, *boost::unit_test::disabled()) { } } -BOOST_AUTO_TEST_CASE(test_str_valid_name) { - ECF_NAME_THIS_TEST(); - - std::vector valid; - valid.emplace_back("a"); - valid.emplace_back("a122345"); - valid.emplace_back("_a122345"); - valid.emplace_back("_"); - valid.emplace_back("0"); - valid.emplace_back("1"); - valid.emplace_back("2"); - valid.emplace_back("3"); - valid.emplace_back("4"); - valid.emplace_back("5"); - valid.emplace_back("6"); - valid.emplace_back("7"); - valid.emplace_back("8"); - valid.emplace_back("9"); - valid.emplace_back("11"); - valid.emplace_back("111"); - for (const auto& i : valid) { - std::string msg; - BOOST_CHECK_MESSAGE(Str::valid_name(i, msg), "Expected " << i << " to be valid"); - BOOST_CHECK_MESSAGE(Str::valid_name(i), "Expected " << i << " to be valid"); - } - - BOOST_CHECK_MESSAGE(!Str::valid_name(""), "Expected empty string to be in-valid"); - BOOST_CHECK_MESSAGE(!Str::valid_name("."), "Expected '.' string to be in-valid"); - std::vector invalid; - invalid.emplace_back("?"); - invalid.emplace_back("!"); - invalid.emplace_back("\""); - invalid.emplace_back("$"); - invalid.emplace_back("%"); - invalid.emplace_back("^"); - invalid.emplace_back("*"); - invalid.emplace_back("("); - invalid.emplace_back(")"); - invalid.emplace_back("-"); - invalid.emplace_back("+"); - invalid.emplace_back(":"); - invalid.emplace_back(";"); - invalid.emplace_back("@"); - invalid.emplace_back("~"); - invalid.emplace_back("<"); - invalid.emplace_back(">"); - invalid.emplace_back("!"); - for (const auto& i : invalid) { - std::string msg; - BOOST_CHECK_MESSAGE(!Str::valid_name(i, msg), "Expected " << i << " to be in-valid"); - BOOST_CHECK_MESSAGE(!Str::valid_name(i), "Expected " << i << " to be in-valid"); - - std::string s = "a" + i; - BOOST_CHECK_MESSAGE(!Str::valid_name(s, msg), "Expected " << s << " to be in-valid"); - BOOST_CHECK_MESSAGE(!Str::valid_name(s), "Expected " << s << " to be in-valid"); - } -} - BOOST_AUTO_TEST_SUITE_END() BOOST_AUTO_TEST_SUITE_END() diff --git a/libs/core/test/TestStringSplitPerf.cpp b/libs/core/test/TestStringSplitPerf.cpp index 51313ccaf..47df0f0b3 100644 --- a/libs/core/test/TestStringSplitPerf.cpp +++ b/libs/core/test/TestStringSplitPerf.cpp @@ -59,7 +59,7 @@ BOOST_AUTO_TEST_CASE(test_str_split_perf) { std::string line = "This is a long string that is going to be used to test the performance of splitting with " "different Implementations the fastest times wins "; size_t times = 1000000; - ECF_TEST_DBG(<< " This test will split a line " << times << " times: '" << line); + ECF_TEST_DBG(" This test will split a line " << times << " times: '" << line); std::string reconstructed; reconstructed.reserve(line.size()); @@ -76,8 +76,8 @@ BOOST_AUTO_TEST_CASE(test_str_split_perf) { reconstructed += " "; } } - ECF_TEST_DBG(<< " Time for istreamstream " << times - << " times = " << timer.format(3, Str::cpu_timer_format())); + ECF_TEST_DBG(" Time for istreamstream " + << times << " times = " << timer.format(3, Str::cpu_timer_format())); BOOST_CHECK_MESSAGE(line == reconstructed, "\n'" << line << "'\n'" << reconstructed << "'"); } @@ -91,8 +91,8 @@ BOOST_AUTO_TEST_CASE(test_str_split_perf) { reconstructed += " "; } } - ECF_TEST_DBG(<< " Time for std::getline " << times - << " times = " << timer.format(3, Str::cpu_timer_format())); + ECF_TEST_DBG(" Time for std::getline " + << times << " times = " << timer.format(3, Str::cpu_timer_format())); BOOST_CHECK_MESSAGE(line == reconstructed, "\n'" << line << "'\n'" << reconstructed << "'"); } @@ -110,8 +110,8 @@ BOOST_AUTO_TEST_CASE(test_str_split_perf) { reconstructed += " "; } } - ECF_TEST_DBG(<< " Time for boost::split " << times - << " times = " << timer.format(3, Str::cpu_timer_format())); + ECF_TEST_DBG(" Time for boost::split " + << times << " times = " << timer.format(3, Str::cpu_timer_format())); // BOOST_CHECK_MESSAGE(line==reconstructed,"\n'" << line << "'\n'" << reconstructed << "'"); // add extra space } @@ -128,8 +128,8 @@ BOOST_AUTO_TEST_CASE(test_str_split_perf) { reconstructed += " "; } } - ECF_TEST_DBG(<< " Time for Str::split_orig " << times - << " times = " << timer.format(3, Str::cpu_timer_format())); + ECF_TEST_DBG(" Time for Str::split_orig " + << times << " times = " << timer.format(3, Str::cpu_timer_format())); BOOST_CHECK_MESSAGE(line == reconstructed, " error"); } { // Str::split_orig1 @@ -145,8 +145,8 @@ BOOST_AUTO_TEST_CASE(test_str_split_perf) { reconstructed += " "; } } - ECF_TEST_DBG(<< " Time for Str::split_orig1 " << times - << " times = " << timer.format(3, Str::cpu_timer_format())); + ECF_TEST_DBG(" Time for Str::split_orig1 " + << times << " times = " << timer.format(3, Str::cpu_timer_format())); BOOST_CHECK_MESSAGE(line == reconstructed, " error"); } { // Str::split_using_string_view2 @@ -162,8 +162,8 @@ BOOST_AUTO_TEST_CASE(test_str_split_perf) { reconstructed += " "; } } - ECF_TEST_DBG(<< " Time for Str::split_using_string_view2 " << times - << " times = " << timer.format(3, Str::cpu_timer_format())); + ECF_TEST_DBG(" Time for Str::split_using_string_view2 " + << times << " times = " << timer.format(3, Str::cpu_timer_format())); BOOST_CHECK_MESSAGE(line == reconstructed, " error"); } { // Str::split_using_string_view @@ -179,26 +179,25 @@ BOOST_AUTO_TEST_CASE(test_str_split_perf) { reconstructed += " "; } } - ECF_TEST_DBG(<< " Time for Str::split_using_string_view " << times - << " times = " << timer.format(3, Str::cpu_timer_format())); + ECF_TEST_DBG(" Time for Str::split_using_string_view " + << times << " times = " << timer.format(3, Str::cpu_timer_format())); BOOST_CHECK_MESSAGE(line == reconstructed, " error"); } - { // boost::make_split_iterator + { // make_split_iterator (std::vector based) boost::timer::cpu_timer timer; for (size_t i = 0; i < times; i++) { auto tokens = Str::make_split_iterator(line); std::ostringstream ss; - for (; !tokens.eof(); ++tokens) { - boost::iterator_range range = *tokens; - ss << range << " "; + for (const auto& token : tokens) { + ss << token << " "; } reconstructed = ss.str(); } - ECF_TEST_DBG(<< " Time for make_split_iterator::split " << times - << " times = " << timer.format(3, Str::cpu_timer_format())); + ECF_TEST_DBG(" Time for make_split_iterator::split " + << times << " times = " << timer.format(3, Str::cpu_timer_format())); } { // std::string_view @@ -213,8 +212,8 @@ BOOST_AUTO_TEST_CASE(test_str_split_perf) { reconstructed += " "; } } - ECF_TEST_DBG(<< " Time for std::string_view " << times - << " times = " << timer.format(3, Str::cpu_timer_format())); + ECF_TEST_DBG(" Time for std::string_view " + << times << " times = " << timer.format(3, Str::cpu_timer_format())); BOOST_CHECK_MESSAGE(line == reconstructed, "\n'" << line << "'\n'" << reconstructed << "'"); } @@ -232,8 +231,8 @@ BOOST_AUTO_TEST_CASE(test_str_split_perf) { reconstructed += " "; } } - ECF_TEST_DBG(<< " Time for std::string_view(2) " << times - << " times = " << timer.format(3, Str::cpu_timer_format())); + ECF_TEST_DBG(" Time for std::string_view(2) " + << times << " times = " << timer.format(3, Str::cpu_timer_format())); BOOST_CHECK_MESSAGE(line == reconstructed, "\n'" << line << "'\n'" << reconstructed << "'"); } } @@ -252,7 +251,7 @@ BOOST_AUTO_TEST_CASE(test_str_split_perf_with_file) { // Now test performance of splitting with a big DEFS file auto ecf_test_defs_dir = ecf::environment::fetch("ECF_TEST_DEFS_DIR"); if (!ecf_test_defs_dir) { - ECF_TEST_DBG(<< "Igoring test, since directory defined by environment variable(ECF_TEST_DEFS_DIR) is missing"); + ECF_TEST_DBG("Igoring test, since directory defined by environment variable(ECF_TEST_DEFS_DIR) is missing"); return; } std::string path = ecf_test_defs_dir.value() + "/vsms2.31415.def"; @@ -265,7 +264,7 @@ BOOST_AUTO_TEST_CASE(test_str_split_perf_with_file) { std::vector file_contents; if (File::splitFileIntoLines(path, file_contents, true /* ignore empty lines*/)) { - ECF_TEST_DBG(<< " This test will split each line in file " << path); + ECF_TEST_DBG(" This test will split each line in file " << path); std::vector result; result.reserve(300); @@ -286,16 +285,16 @@ BOOST_AUTO_TEST_CASE(test_str_split_perf_with_file) { std::istream_iterator()); reconstruct_line(result1); } - ECF_TEST_DBG(<< " Time for istreamstream " << file_contents.size() - << " times = " << timer.format(3, Str::cpu_timer_format())); + ECF_TEST_DBG(" Time for istreamstream " << file_contents.size() + << " times = " << timer.format(3, Str::cpu_timer_format())); } { // std::getline boost::timer::cpu_timer timer; for (size_t i = 0; i < file_contents.size(); i++) { reconstruct_line(split_using_getline(file_contents[i], ' ')); } - ECF_TEST_DBG(<< " Time for std::getline " << file_contents.size() - << " times = " << timer.format(3, Str::cpu_timer_format())); + ECF_TEST_DBG(" Time for std::getline " << file_contents.size() + << " times = " << timer.format(3, Str::cpu_timer_format())); } { boost::timer::cpu_timer timer; @@ -307,8 +306,8 @@ BOOST_AUTO_TEST_CASE(test_str_split_perf_with_file) { reconstruct_line(result); } - ECF_TEST_DBG(<< " Time for boost::split " << file_contents.size() - << " times = " << timer.format(3, Str::cpu_timer_format())); + ECF_TEST_DBG(" Time for boost::split " << file_contents.size() + << " times = " << timer.format(3, Str::cpu_timer_format())); } { boost::timer::cpu_timer timer; @@ -318,8 +317,8 @@ BOOST_AUTO_TEST_CASE(test_str_split_perf_with_file) { reconstruct_line(result); } - ECF_TEST_DBG(<< " Time for Str::split_orig " << file_contents.size() - << " times = " << timer.format(3, Str::cpu_timer_format())); + ECF_TEST_DBG(" Time for Str::split_orig " << file_contents.size() + << " times = " << timer.format(3, Str::cpu_timer_format())); } { boost::timer::cpu_timer timer; @@ -329,8 +328,8 @@ BOOST_AUTO_TEST_CASE(test_str_split_perf_with_file) { reconstruct_line(result); } - ECF_TEST_DBG(<< " Time for Str::split_orig1 " << file_contents.size() - << " times = " << timer.format(3, Str::cpu_timer_format())); + ECF_TEST_DBG(" Time for Str::split_orig1 " << file_contents.size() + << " times = " << timer.format(3, Str::cpu_timer_format())); } { boost::timer::cpu_timer timer; @@ -340,8 +339,8 @@ BOOST_AUTO_TEST_CASE(test_str_split_perf_with_file) { reconstruct_line(result); } - ECF_TEST_DBG(<< " Time for Str::split_using_string_view " << file_contents.size() - << " times = " << timer.format(3, Str::cpu_timer_format())); + ECF_TEST_DBG(" Time for Str::split_using_string_view " + << file_contents.size() << " times = " << timer.format(3, Str::cpu_timer_format())); } { boost::timer::cpu_timer timer; @@ -351,8 +350,8 @@ BOOST_AUTO_TEST_CASE(test_str_split_perf_with_file) { reconstruct_line(result); } - ECF_TEST_DBG(<< " Time for Str::split_using_string_view2 " << file_contents.size() - << " times = " << timer.format(3, Str::cpu_timer_format())); + ECF_TEST_DBG(" Time for Str::split_using_string_view2 " + << file_contents.size() << " times = " << timer.format(3, Str::cpu_timer_format())); } { boost::timer::cpu_timer timer; @@ -361,14 +360,13 @@ BOOST_AUTO_TEST_CASE(test_str_split_perf_with_file) { std::ostringstream ss; auto tokens = Str::make_split_iterator(file_contents[i]); - for (; !tokens.eof(); ++tokens) { - auto range = *tokens; - ss << range << " "; + for (const auto& token : tokens) { + ss << token << " "; } std::string reconstructed = ss.str(); } - ECF_TEST_DBG(<< " Time for boost::make_split_iterator " << file_contents.size() - << " times = " << timer.format(3, Str::cpu_timer_format())); + ECF_TEST_DBG(" Time for make_split_iterator " << file_contents.size() + << " times = " << timer.format(3, Str::cpu_timer_format())); } { @@ -384,8 +382,8 @@ BOOST_AUTO_TEST_CASE(test_str_split_perf_with_file) { reconstructed += " "; } } - ECF_TEST_DBG(<< " Time for std::string_view " << file_contents.size() - << " times = " << timer.format(3, Str::cpu_timer_format())); + ECF_TEST_DBG(" Time for std::string_view " << file_contents.size() + << " times = " << timer.format(3, Str::cpu_timer_format())); } { @@ -402,8 +400,8 @@ BOOST_AUTO_TEST_CASE(test_str_split_perf_with_file) { } result1.clear(); } - ECF_TEST_DBG(<< " Time std::string_view(2) " << file_contents.size() - << " times = " << timer.format(3, Str::cpu_timer_format())); + ECF_TEST_DBG(" Time std::string_view(2) " << file_contents.size() + << " times = " << timer.format(3, Str::cpu_timer_format())); } } } @@ -415,7 +413,7 @@ BOOST_AUTO_TEST_CASE(test_str_get_token_perf) { std::string line = "This is a long string that is going to be used to test the performance of splitting with " "different Implementations the fastest times wins "; size_t times = 250000; - ECF_TEST_DBG(<< " This test will split a line " << times << " times: '" << line); + ECF_TEST_DBG(" This test will split a line " << times << " times: '" << line); Str::split_using_string_view(line, result); size_t result_size = result.size(); @@ -429,8 +427,8 @@ BOOST_AUTO_TEST_CASE(test_str_get_token_perf) { (void)StringSplitter::get_token(line, r, token); } } - ECF_TEST_DBG(<< " Time for StringSplitter::get_token " << times - << " times = " << timer.format(3, Str::cpu_timer_format())); + ECF_TEST_DBG(" Time for StringSplitter::get_token " << times + << " times = " << timer.format(3, Str::cpu_timer_format())); BOOST_CHECK_MESSAGE(true, "Keep compiler happy"); } { @@ -441,8 +439,8 @@ BOOST_AUTO_TEST_CASE(test_str_get_token_perf) { (void)Str::get_token(line, r, token); } } - ECF_TEST_DBG(<< " Time for Str::get_token " << times - << " times = " << timer.format(3, Str::cpu_timer_format())); + ECF_TEST_DBG(" Time for Str::get_token " << times + << " times = " << timer.format(3, Str::cpu_timer_format())); BOOST_CHECK_MESSAGE(true, "Keep compiler happy"); } { @@ -453,8 +451,8 @@ BOOST_AUTO_TEST_CASE(test_str_get_token_perf) { (void)Str::get_token2(line, r, token); } } - ECF_TEST_DBG(<< " Time for Str::get_token2 " << times - << " times = " << timer.format(3, Str::cpu_timer_format())); + ECF_TEST_DBG(" Time for Str::get_token2 " << times + << " times = " << timer.format(3, Str::cpu_timer_format())); BOOST_CHECK_MESSAGE(true, "Keep compiler happy"); } { @@ -465,8 +463,8 @@ BOOST_AUTO_TEST_CASE(test_str_get_token_perf) { (void)Str::get_token3(line, r, token); } } - ECF_TEST_DBG(<< " Time for Str::get_token3 " << times - << " times = " << timer.format(3, Str::cpu_timer_format())); + ECF_TEST_DBG(" Time for Str::get_token3 " << times + << " times = " << timer.format(3, Str::cpu_timer_format())); BOOST_CHECK_MESSAGE(true, "Keep compiler happy"); } } diff --git a/libs/core/test/TestStringSplitter.cpp b/libs/core/test/TestStringSplitter.cpp index 6a5fab5d1..8326330c5 100644 --- a/libs/core/test/TestStringSplitter.cpp +++ b/libs/core/test/TestStringSplitter.cpp @@ -36,14 +36,14 @@ check(const std::string& line, const StringSplitter& string_splitter, const std: << "'"); BOOST_CHECK_MESSAGE(result == expected, "failed for '" << line << "'"); if (result != expected) { - ECF_TEST_DBG(<< "Line :'" << line); - ECF_TEST_DBG(<< "Actual :"); + ECF_TEST_DBG("Line :'" << line); + ECF_TEST_DBG("Actual :"); for (const std::string& t : result) { - ECF_TEST_DBG(<< " '" << t << "'"); + ECF_TEST_DBG(" '" << t << "'"); } - ECF_TEST_DBG(<< "Expected:"); + ECF_TEST_DBG("Expected:"); for (const std::string& t : expected) { - ECF_TEST_DBG(<< "'" << t << "'"); + ECF_TEST_DBG("'" << t << "'"); } } } @@ -61,14 +61,14 @@ static void check(const std::string& line, const std::vector& expec << "'"); BOOST_CHECK_MESSAGE(result == expected, "failed for '" << line << "'"); if (result != expected) { - ECF_TEST_DBG(<< "Line :'" << line); - ECF_TEST_DBG(<< "Actual :"); + ECF_TEST_DBG("Line :'" << line); + ECF_TEST_DBG("Actual :"); for (const std::string& t : result) { - ECF_TEST_DBG(<< " '" << t << "'"); + ECF_TEST_DBG(" '" << t << "'"); } - ECF_TEST_DBG(<< "Expected:"); + ECF_TEST_DBG("Expected:"); for (const std::string& t : expected) { - ECF_TEST_DBG(<< "'" << t << "'"); + ECF_TEST_DBG("'" << t << "'"); } } } @@ -274,7 +274,7 @@ BOOST_AUTO_TEST_CASE(test_str_split_StringSplitter) { static void test_get_token(const std::string& line, const char* delims = " \t") { std::vector tokens; - Str::split_orig(line, tokens, delims); + ecf::algorithm::split_at(tokens, line, delims); for (size_t i = 0; i < tokens.size(); i++) { std::string token; BOOST_CHECK_MESSAGE(StringSplitter::get_token(line, i, token, delims) && token == tokens[i], diff --git a/libs/core/test/TestTimeSeries.cpp b/libs/core/test/TestTimeSeries.cpp index 22c93684e..a38773148 100644 --- a/libs/core/test/TestTimeSeries.cpp +++ b/libs/core/test/TestTimeSeries.cpp @@ -689,7 +689,7 @@ BOOST_AUTO_TEST_CASE(test_time_series_state_parsing) { size_t index = 0; std::string the_time = "+00:00 20:00 00:10"; std::vector lineTokens; - Str::split(the_time, lineTokens); + ecf::algorithm::split_at(lineTokens, the_time); BOOST_CHECK_MESSAGE(TimeSeries::create(index, lineTokens) == ecf::TimeSeries(TimeSlot(0, 0), TimeSlot(20, 0), TimeSlot(0, 10), true), "Error"); @@ -698,7 +698,7 @@ BOOST_AUTO_TEST_CASE(test_time_series_state_parsing) { size_t index = 0; std::string the_time = "+10:10"; std::vector lineTokens; - Str::split(the_time, lineTokens); + ecf::algorithm::split_at(lineTokens, the_time); BOOST_CHECK_MESSAGE(TimeSeries::create(index, lineTokens) == ecf::TimeSeries(TimeSlot(10, 10), true), "Error"); } { @@ -708,7 +708,7 @@ BOOST_AUTO_TEST_CASE(test_time_series_state_parsing) { expected.set_isValid(false); std::vector lineTokens; - Str::split(the_time, lineTokens); + ecf::algorithm::split_at(lineTokens, the_time); ecf::TimeSeries parsed_ts = TimeSeries::create(index, lineTokens, true); BOOST_CHECK_MESSAGE(parsed_ts == expected, "Expected \n'" << expected.toString() << expected.state_to_string(false) << "'" @@ -722,7 +722,7 @@ BOOST_AUTO_TEST_CASE(test_time_series_state_parsing) { expected.set_isValid(false); std::vector lineTokens; - Str::split(the_time, lineTokens); + ecf::algorithm::split_at(lineTokens, the_time); ecf::TimeSeries parsed_ts = TimeSeries::create(index, lineTokens, true); BOOST_CHECK_MESSAGE(parsed_ts == expected, "Expected \n'" << expected.toString() << expected.state_to_string(false) << "'" @@ -743,7 +743,7 @@ BOOST_AUTO_TEST_CASE(test_time_series_state_parsing) { expected.calendarChanged(calendar); std::vector lineTokens; - Str::split(the_time, lineTokens); + ecf::algorithm::split_at(lineTokens, the_time); ecf::TimeSeries parsed_ts = TimeSeries::create(index, lineTokens, true); BOOST_CHECK_MESSAGE(parsed_ts == expected, "Expected \n'" << expected.toString() << expected.state_to_string(false) << "'" @@ -764,7 +764,7 @@ BOOST_AUTO_TEST_CASE(test_time_series_state_parsing) { expected.set_isValid(false); std::vector lineTokens; - Str::split(the_time, lineTokens); + ecf::algorithm::split_at(lineTokens, the_time); size_t index = 0; ecf::TimeSeries parsed_ts = TimeSeries::create(index, lineTokens, true); diff --git a/libs/core/test/TestVersion.cpp b/libs/core/test/TestVersion.cpp index b8e81104e..84a677d6d 100644 --- a/libs/core/test/TestVersion.cpp +++ b/libs/core/test/TestVersion.cpp @@ -34,7 +34,7 @@ std::string find_cmake_version(const std::vector& cmake_content) { for (auto& line : cmake_content) { if (contains(line, "project", "ecflow", "LANGUAGES", "CXX", "VERSION")) { std::vector tokens; - Str::split(line, tokens); + ecf::algorithm::split_at(tokens, line); auto version_arg = std::find(tokens.begin(), tokens.end(), "VERSION"); auto version_val = version_arg + 1; diff --git a/libs/core/test/scaffold/ecflow/test/scaffold/Naming.hpp b/libs/core/test/scaffold/ecflow/test/scaffold/Naming.hpp index 048bfdd10..6f7874825 100644 --- a/libs/core/test/scaffold/ecflow/test/scaffold/Naming.hpp +++ b/libs/core/test/scaffold/ecflow/test/scaffold/Naming.hpp @@ -18,7 +18,7 @@ #include -namespace ecf::test { +namespace ecf::test::scaffold { inline std::string name_this_test() { std::string fullname = boost::unit_test::framework::current_test_case().p_name; @@ -33,22 +33,22 @@ inline std::string name_this_test() { return fullname; } -} // namespace ecf::test +} // namespace ecf::test::scaffold // NOLINTBEGIN(bugprone-macro-parentheses) -#define ECF_NAME_THIS_TEST(ARGS) \ - do { \ - std::cout << " * " << ecf::test::name_this_test() ARGS << std::endl; \ +#define ECF_NAME_THIS_TEST(ARGS) \ + do { \ + std::cout << " * " << ecf::test::scaffold::name_this_test() ARGS << std::endl; \ } while (0) -#define ECF_TEST_DBG(ARGS) \ - do { \ - std::cout << " +++ " ARGS << std::endl; \ +#define ECF_TEST_DBG(ARGS) \ + do { \ + std::cout << " +++ " << ARGS << std::endl; \ } while (0) -#define ECF_TEST_ERR(ARGS) \ - do { \ - std::cerr << " +++ " ARGS << std::endl; \ +#define ECF_TEST_ERR(ARGS) \ + do { \ + std::cerr << " +++ " << ARGS << std::endl; \ } while (0) // NOLINTEND(bugprone-macro-parentheses) diff --git a/libs/test/foolproof/scaffold/Process.cpp b/libs/core/test/scaffold/ecflow/test/scaffold/Process.cpp similarity index 92% rename from libs/test/foolproof/scaffold/Process.cpp rename to libs/core/test/scaffold/ecflow/test/scaffold/Process.cpp index 316da2d0a..a3c398726 100644 --- a/libs/test/foolproof/scaffold/Process.cpp +++ b/libs/core/test/scaffold/ecflow/test/scaffold/Process.cpp @@ -16,7 +16,6 @@ #include #include "Naming.hpp" -#include "ecflow/core/Environment.hpp" #if BOOST_VERSION < 106600 @@ -44,7 +43,7 @@ namespace bp = boost::process; #endif -namespace foolproof::scaffold { +namespace ecf::test::scaffold { #if BOOST_PROCESS_VERSION == 1 @@ -55,13 +54,11 @@ struct Process::Impl err_{}, handle_{} { // Determine the invocation command - auto invoke_command = std::string(executable); - for (const auto& arg : args) { - invoke_command += " " + arg; - } + auto exe = std::string{executable}; + auto argv = std::vector{args.begin(), args.end()}; // Start the process - handle_ = bp::child{invoke_command, bp::std_out > out_, bp::std_err > err_, bp::start_dir(cwd.string())}; + handle_ = bp::child{exe, bp::args(argv), bp::std_out > out_, bp::std_err > err_, bp::start_dir(cwd.string())}; } ~Impl() = default; @@ -204,4 +201,4 @@ std::string Process::read_stderr() const { return impl_->read_stderr(); } -} // namespace foolproof::scaffold +} // namespace ecf::test::scaffold diff --git a/libs/test/foolproof/scaffold/Process.hpp b/libs/core/test/scaffold/ecflow/test/scaffold/Process.hpp similarity index 90% rename from libs/test/foolproof/scaffold/Process.hpp rename to libs/core/test/scaffold/ecflow/test/scaffold/Process.hpp index 8a6ebd212..994160bc2 100644 --- a/libs/test/foolproof/scaffold/Process.hpp +++ b/libs/core/test/scaffold/ecflow/test/scaffold/Process.hpp @@ -11,13 +11,14 @@ #ifndef ecflow_test_foolproof_scaffold_Process_HPP #define ecflow_test_foolproof_scaffold_Process_HPP +#include #include #include #include -#include "ecflow/core/Filesystem.hpp" +namespace fs = std::filesystem; -namespace foolproof::scaffold { +namespace ecf::test::scaffold { class Process { public: @@ -47,6 +48,6 @@ class Process { std::unique_ptr impl_; }; -} // namespace foolproof::scaffold +} // namespace ecf::test::scaffold #endif /* ecflow_test_foolproof_scaffold_Process_HPP */ diff --git a/libs/core/test/scaffold/ecflow/test/scaffold/Provisioning.hpp b/libs/core/test/scaffold/ecflow/test/scaffold/Provisioning.hpp index 0ec83443f..cbd6d26ab 100644 --- a/libs/core/test/scaffold/ecflow/test/scaffold/Provisioning.hpp +++ b/libs/core/test/scaffold/ecflow/test/scaffold/Provisioning.hpp @@ -8,17 +8,32 @@ * nor does it submit to any jurisdiction. */ -#ifndef ecflow_test_scaffold_Provisioning_HPP -#define ecflow_test_scaffold_Provisioning_HPP +#ifndef ecflow_test_foolproof_scaffold_Provisioning_HPP +#define ecflow_test_foolproof_scaffold_Provisioning_HPP #include +#include #include +#include #include +#include +#include #include +#include +#include #include +#include "ecflow/client/ClientInvoker.hpp" +#include "ecflow/core/EcfPortLock.hpp" #include "ecflow/core/Filesystem.hpp" +#include "ecflow/core/PasswordEncryption.hpp" +#include "ecflow/core/ecflow_source_build_dir.h" +#include "ecflow/core/ecflow_version.h" +#include "ecflow/test/scaffold/Naming.hpp" +#include "ecflow/test/scaffold/Process.hpp" + +namespace ecf::test::scaffold { /** * The following classes help to provision the test environment, by handling @@ -130,4 +145,1244 @@ class WithTestFile { std::string content_; }; -#endif /* ecflow_test_scaffold_Provisioning_HPP */ +inline std::string pretty_print_path(const fs::path& path) { + auto canonical = fs::canonical(path); + std::string in = canonical.string(); + std::string fst = std::regex_replace(in, std::regex(CMAKE_ECFLOW_BUILD_DIR()), "/~~~build~~~"); + std::string snd = std::regex_replace(fst, std::regex(CMAKE_ECFLOW_SOURCE_DIR()), "/~~~source~~~"); + return snd; +} + +class Directory { +public: + explicit Directory(fs::path location) + : location_{std::move(location)} { + ECF_TEST_DBG("Created working directory at: " + location_.string()); + } + + Directory(const Directory&) = delete; + Directory& operator=(const Directory&) = delete; + Directory(Directory&&) = delete; + Directory& operator=(Directory&&) = delete; + + ~Directory() { + fs::remove_all(location_); + ECF_TEST_DBG("Removed working directory at: " + location_.string()); + } + + [[nodiscard]] const fs::path& path() const { return location_; } + +private: + fs::path location_; +}; + +class MakeDirectory { +public: + struct Information + { + private: + static std::string get_compiler() { +#if defined(_AIX) + return "aix"; +#elif defined(HPUX) + return "hpux"; +#elif defined(__clang__) + return "clang"; +#elif defined(__INTEL_COMPILER) + return "intel"; +#elif defined(_CRAYC) + return "cray"; +#elif defined(__GNUC__) + return "gnu"; +#else + return "unknown"; +#endif + }; + + static std::string get_architecture() { +#if defined(__x86_64__) || defined(_M_X64) + return "x86_64"; +#elif defined(__aarch64__) || defined(_M_ARM64) + return "aarch64"; +#else + return "unknown"; +#endif + } + + static std::string get_build_type() { + auto type = CMAKE_ECFLOW_BUILD_TYPE(); + std::transform(type.begin(), type.end(), type.begin(), [](unsigned char c) { return std::tolower(c); }); + return type; + } + + static std::string get_test_name() { + return std::string{boost::unit_test::framework::current_test_case().p_name}; + } + + static std::string get_pid() { + auto pid = getpid(); + return std::to_string(pid); + } + + public: + static std::string generate_relative_path() { + const std::string compiler = get_compiler(); + const std::string architecture = get_architecture(); + const std::string build_type = get_build_type(); + const std::string test_name = get_test_name(); + const std::string pid = get_pid(); + + return "data/ECF_HOME__b_" + build_type + "__c_" + compiler + "__a_" + architecture + "__t_" + test_name + + "__p_" + pid + "__"; + } + }; + + MakeDirectory() + : location_{make_working_directory()} {} + + Directory create() const { + // ensure the directory exists + fs::create_directories(location_); + assert(fs::exists(location_)); + assert(fs::is_directory(location_)); + return Directory{location_}; + } + + MakeDirectory(const MakeDirectory&) = delete; + MakeDirectory& operator=(const MakeDirectory&) = delete; + MakeDirectory(MakeDirectory&&) = delete; + MakeDirectory& operator=(MakeDirectory&&) = delete; + + ~MakeDirectory() = default; + +private: + static fs::path make_working_directory() { + auto base_path = fs::current_path(); + return base_path / Information::generate_relative_path(); + } + + fs::path location_; +}; + +class EnvironmentVariable { +public: + EnvironmentVariable(std::string name, std::string value) + : name_{std::move(name)}, + value_{std::move(value)} { + ECF_TEST_DBG("Setting environment variable: " << name_ << "=" << value_); + setenv(name_.c_str(), value_.c_str(), true); + } + + EnvironmentVariable(const EnvironmentVariable&) = delete; + EnvironmentVariable& operator=(const EnvironmentVariable&) = delete; + EnvironmentVariable(EnvironmentVariable&&) noexcept = delete; + EnvironmentVariable& operator=(EnvironmentVariable&&) noexcept = delete; + + [[nodiscard]] std::string name() const { return name_; } + [[nodiscard]] std::string value() const { return value_; } + + ~EnvironmentVariable() { + ECF_TEST_DBG("Unsetting environment variable: " << name_); + unsetenv(name_.c_str()); + } + + static std::optional get_environment_variable(const std::string& name) { + ECF_TEST_DBG("Getting environment variable: " << name); + char* value = getenv(name.c_str()); + if (value == nullptr) { + return std::nullopt; + } + return std::string{value}; + } + +private: + std::string name_; + std::string value_; +}; + +class MakeEnvironmentVariable { +public: + struct InvalidVariableName : public std::runtime_error + { + explicit InvalidVariableName(std::string message) + : std::runtime_error(message) {} + }; + + MakeEnvironmentVariable() + : name_{}, + value_{} {} + + MakeEnvironmentVariable(const MakeEnvironmentVariable&) = delete; + MakeEnvironmentVariable& operator=(const MakeEnvironmentVariable&) = delete; + MakeEnvironmentVariable(MakeEnvironmentVariable&&) noexcept = delete; + MakeEnvironmentVariable& operator=(MakeEnvironmentVariable&&) noexcept = delete; + + ~MakeEnvironmentVariable() = default; + + MakeEnvironmentVariable& with(const std::string& name, const std::string& value) { + name_ = name; + value_ = value; + return *this; + } + + [[nodiscard]] EnvironmentVariable create() const { + if (name_.empty()) { + throw InvalidVariableName("Environment variable name cannot be empty"); + } + return EnvironmentVariable{name_, value_}; + } + +private: + std::string name_; + std::string value_; +}; + +class SpecificFileLocation { +public: + explicit SpecificFileLocation(const std::string& name, const Directory& cwd) + : SpecificFileLocation(name, cwd.path()) {} + + explicit SpecificFileLocation(const std::string& name, const fs::path& location) + : path_{location / name} {} + + constexpr const fs::path& path() const { return path_; } + +private: + fs::path path_; +}; + +class AutomaticFileLocation { +public: + explicit AutomaticFileLocation(const fs::path& location = fs::current_path()) + : path_{location / randomized_name_n(MAX_CHARS)} {} + explicit AutomaticFileLocation(const std::string& name, const fs::path& location = fs::current_path()) + : path_{location / suffixed_name_n(name, MAX_CHARS)} {} + + constexpr const fs::path& path() const { return path_; } + +private: + fs::path path_; + + static constexpr size_t MIN_CHARS = 8; + static constexpr size_t MAX_CHARS = 24; + + static std::string randomized_name_n(size_t n) { + assert(n >= MIN_CHARS); + return random_n(n); + } + + static std::string suffixed_name_n(const std::string& name, size_t n) { + assert(name.size() > 0); + assert(n >= MIN_CHARS); + return name + "." + random_n(n); + } + + static std::string random_n(size_t n) { + std::random_device device; + std::mt19937 generator(device()); + std::uniform_int_distribution distribution(0, charset.size() - 1); + + auto random = [&]() -> char { return charset[distribution(generator) % charset.size()]; }; + + fs::path::string_type str(n, 0); + std::generate_n(str.begin(), n, random); + return str; + } + + static constexpr std::array charset = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', + 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', + 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'}; +}; + +class File { +public: + explicit File(fs::path location) + : location_{std::move(location)} { + assert(fs::exists(location_)); + assert(fs::is_regular_file(location_)); + ECF_TEST_DBG("Created file: " << location_); + } + + File(const File&) = delete; + File& operator=(const File&) = delete; + File(File&&) noexcept = default; + File& operator=(File&&) noexcept = default; + + ~File() { + fs::remove(location_); + ECF_TEST_DBG("Removed file: " << location_.string()); + } + + [[nodiscard]] const fs::path& path() const { return location_; } + [[nodiscard]] fs::path filename() const { return location_.filename(); } + +private: + fs::path location_; +}; + +class MakeTestFile { +public: + explicit MakeTestFile() + : location_{}, + content_{} {} + + MakeTestFile(const MakeTestFile&) = delete; + MakeTestFile& operator=(const MakeTestFile&) = delete; + MakeTestFile(MakeTestFile&&) noexcept = delete; + MakeTestFile& operator=(MakeTestFile&&) noexcept = delete; + + ~MakeTestFile() = default; + + MakeTestFile& with(const SpecificFileLocation& location) { + location_ = location.path(); + return *this; + } + + MakeTestFile& with(const AutomaticFileLocation& location) { + location_ = location.path(); + return *this; + } + + MakeTestFile& with(const char* content) { + content_ = content; + return *this; + } + + MakeTestFile& with(const std::string& content) { + content_ = content; + return *this; + } + + template + MakeTestFile& with(const Content& content) { + content_ = content.data(); + return *this; + } + + [[nodiscard]] File create() const { + { // Caution: We assume that existing test files can be overwritten + std::ofstream os(location_.string(), std::ios::out | std::ios::trunc); + os << content_; + } + + // Now that the file actually exists, we update the location to the canonical path + auto actual_location = fs::canonical(location_); + + return File{actual_location}; + } + + const fs::path& path() const { return location_; } + +private: + fs::path location_; + std::string content_; +}; + +class LockFile { +public: + static std::optional make_lock(const fs::path& lock_file) { + if (create_file(lock_file)) { + return LockFile{lock_file}; + } + return std::nullopt; + }; + +private: + LockFile(const fs::path& lock_file) + : lock_file_(lock_file) { + assert(!lock_file_.empty()); + assert(fs::exists(lock_file_)); + assert(fs::is_regular_file(lock_file_)); + } + +public: + LockFile(const LockFile&) = delete; + LockFile& operator=(const LockFile&) = delete; + LockFile(LockFile&&) noexcept = default; + LockFile& operator=(LockFile&&) noexcept = default; + + ~LockFile() { + if (!lock_file_.empty()) { + fs::remove(lock_file_); + } + } + + [[nodiscard]] const fs::path& path() const { return lock_file_; } + +private: + static bool create_file(const fs::path& file) { + if (auto lock = fopen(file.c_str(), "wx")) { + auto content = std::string("This is a lock file!"); // This is dummy content! + fwrite(content.c_str(), 1, content.size(), lock); + fclose(lock); + return true; + } + return false; + } + + fs::path lock_file_{}; +}; + +struct User +{ + std::string username; + std::string password; + std::string permission; + + bool can_read() const { return permission.find("r") != std::string::npos; } + bool can_write() const { return permission.find("w") != std::string::npos; } +}; + +struct Host +{ + using host_t = std::string; + + Host() = default; + Host(host_t host) + : host_{host} { + if (host_.empty()) { + throw std::runtime_error("host name cannot be empty\n"); + } + + if (host_ == default_host) { + host_ = resolve_hostname(); + } + } + + Host(const Host&) = default; + Host& operator=(const Host&) = default; + Host(Host&&) noexcept = default; + Host& operator=(Host&&) noexcept = default; + + ~Host() = default; + + [[nodiscard]] bool is_valid() const { return !host_.empty(); } + + [[nodiscard]] const host_t& value() const { return host_; } + + static inline const host_t default_host = "localhost"; + + friend bool operator==(const Host& lhs, const Host& rhs) { return lhs.host_ == rhs.host_; } + friend bool operator!=(const Host& lhs, const Host& rhs) { return !(lhs == rhs); } + + friend std::ostream& operator<<(std::ostream& os, const Host& host) { return os << host.host_; } + + static std::string resolve_hostname() { + std::array buffer; + if (gethostname(buffer.data(), buffer.size()) != -1) { + return buffer.data(); + } + else { + throw std::runtime_error("could not get host name\n"); + } + } + +private: + host_t host_{default_host}; +}; + +struct Port +{ + using port_t = uint32_t; + using location_t = fs::path; + + Port(port_t port, LockFile&& lock) + : port_{port}, + lock_{std::move(lock)} {} + + Port(const Port&) = delete; + Port& operator=(const Port&) = delete; + Port(Port&&) noexcept = delete; + Port& operator=(Port&&) noexcept = delete; + + ~Port() { ECF_TEST_DBG("Port " << port_ << " is unlocked, lock file removed from " << lock_.path()); } + + [[nodiscard]] port_t value() const { return port_; } + + [[nodiscard]] location_t lock_location() const { return lock_.path(); } + + static constexpr port_t default_port = 3141; + static constexpr port_t maximum_port = 65535; + + friend bool operator==(const Port& lhs, const Port& rhs) { return lhs.port_ == rhs.port_; } + friend bool operator!=(const Port& lhs, const Port& rhs) { return !(lhs == rhs); } + + friend std::ostream& operator<<(std::ostream& os, const Port& port) { return os << port.port_; } + +private: + port_t port_; + LockFile lock_; +}; + +struct PasswordsFile +{ + template + PasswordsFile(const Host& host, const Port& port, const Entry&... entries) + : content_{} { + content_ += ECFLOW_VERSION; + content_ += "\n"; + auto print = [this, host, &port](auto& user) { + content_ += user.username; + content_ += " "; + content_ += host.value(); + content_ += " "; + content_ += std::to_string(port.value()); + content_ += " "; + content_ += user.password; + content_ += "\n"; + }; + (print(entries), ...); + } + + const std::string& data() const { return content_; } + +private: + std::string content_; +}; + +struct WhitelistFile +{ + template + WhitelistFile(Entry... entries) + : content_{} { + content_ += ECFLOW_VERSION; + content_ += "\n"; + auto print = [this](auto& user) { + content_ += user.can_write() ? "" : "-"; + content_ += user.username; + content_ += "\n"; + }; + (print(entries), ...); + } + + const std::string& data() const { return content_; } + +private: + std::string content_; +}; + +struct ServerEnvironmentFile +{ + template + ServerEnvironmentFile(Entry... entries) + : content_{} { + content_ += "\n"; + auto print = [this](auto& entry) { + auto [variable, value] = entry; + content_ += variable; + content_ += "="; + content_ += value; + content_ += "\n"; + }; + (print(entries), ...); + } + + const std::string& data() const { return content_; } + +private: + std::string content_; +}; + +class MakeHost { +public: + MakeHost() = default; + + MakeHost(const MakeHost&) = delete; + MakeHost& operator=(const MakeHost&) = delete; + MakeHost(MakeHost&&) noexcept = delete; + MakeHost& operator=(MakeHost&&) noexcept = delete; + + ~MakeHost() = default; + + MakeHost& with(const Host::host_t name) { + host_ = name; + return *this; + } + + [[nodiscard]] Host create() const { return Host{host_}; } + +private: + Host::host_t host_{Host::default_host}; +}; + +struct SpecificPortValue +{ + using port_t = Port::port_t; + + explicit SpecificPortValue(port_t port = Port::default_port) + : base_port{port} {} + + port_t base_port; + + static std::optional> attempt_to_lock_port(const fs::path lock_dir, port_t port) { + // define name of 'lock' file + auto lock_name = std::to_string(port) + ".lock"; + fs::path lock_file = lock_dir / lock_name; + + // attempt to create 'lock' file + if (auto lock = LockFile::make_lock(lock_file); lock.has_value()) { + return std::make_pair(port, std::move(lock.value())); + } + + return std::nullopt; + } +}; + +struct AutomaticPortValue +{ + using port_t = Port::port_t; + + explicit AutomaticPortValue(port_t port = Port::default_port) + : base_port{port} {} + + port_t base_port; + + static std::optional> attempt_to_lock_port(const fs::path lock_dir, port_t port) { + + for (port_t current = port; current <= Port::maximum_port; ++current) { + if (auto found = SpecificPortValue::attempt_to_lock_port(lock_dir, current); found.has_value()) { + return found; + } + } + + return std::nullopt; + } +}; + +class MakePort { +public: + class UnableToLockPort : public std::runtime_error { + public: + explicit UnableToLockPort(std::string msg) + : std::runtime_error(std::move(msg)) {} + }; + + class NoLockCurrentlyAvailable : public std::runtime_error { + public: + explicit NoLockCurrentlyAvailable(std::string msg) + : std::runtime_error(std::move(msg)) {} + }; + + using location_t = fs::path; + + MakePort() + : strategy_{SpecificPortValue{}} {} + + MakePort(const MakePort&) = delete; + MakePort& operator=(const MakePort&) = delete; + MakePort(MakePort&&) noexcept = delete; + MakePort& operator=(MakePort&&) noexcept = delete; + + ~MakePort() = default; + + template + MakePort& with(Strategy strategy) { + strategy_ = strategy; + return *this; + } + + [[nodiscard]] Port create() const { + // define location to store 'lock' files + // 1) by default, use project build directory + // 2) overriden by ECF_PORT_LOCK_DIR environment variable + fs::path lock_dir = CMAKE_ECFLOW_SOURCE_DIR(); + if (const char* env = std::getenv("ECF_PORT_LOCK_DIR")) { + lock_dir = env; + }; + + return std::visit( + [&lock_dir](auto&& strategy) { + // attemp to lock port (i.e. create the lock file) + using Strategy = std::decay_t; + if (auto found = Strategy::attempt_to_lock_port(lock_dir, strategy.base_port); found.has_value()) { + auto port_ = found.value().first; + auto lock_ = std::move(found.value().second); + ECF_TEST_DBG("Port " << port_ << " is locked, lock file created at " << lock_.path()); + return Port{port_, std::move(lock_)}; + } + else { + throw UnableToLockPort("Failed to find an available port"); + }; + }, + strategy_); + } + + std::variant strategy_; +}; + +template +class Outcome { + + struct Error + { + E reason_; + }; + +public: + using value_t = V; + using error_t = E; + + Outcome() = delete; + + Outcome(const Outcome&) = default; + Outcome& operator=(const Outcome&) = default; + Outcome(Outcome&&) = default; + Outcome& operator=(Outcome&&) = default; + +private: + explicit Outcome(const V& value) + : success_{true}, + data_{value} {} + explicit Outcome(V&& value) + : success_{true}, + data_{std::move(value)} {} + explicit Outcome(const Error& error) + : success_{false}, + data_{error} {} + +public: + ~Outcome() = default; + + static Outcome success(const V& value) { return Outcome(value); } + static Outcome success(V&& value) { return Outcome(std::move(value)); } + static Outcome failure(const E& error) { return Outcome{Error{error}}; } + + [[nodiscard]] bool ok() const { return success_; } + [[nodiscard]] value_t get() { return std::move(std::get(data_)); } + [[nodiscard]] const value_t& value() const { return std::get(data_); } + [[nodiscard]] const error_t& reason() const { return std::get(data_).reason_; } + +private: + bool success_; + std::variant data_; +}; + +struct Client +{ + int exit_code; + std::string stdout_buffer; + std::string stderr_buffer; + + bool stdout_contains(const std::string& expected) const { + return stdout_buffer.find(expected) != std::string::npos; + } + bool stdout_contains(const std::regex& expected) const { + std::smatch match; + std::regex_search(stdout_buffer, match, expected); + return !match.empty(); + } +}; + +class RunClient { +public: + using host_t = Host; + using port_t = Port; + + struct CommandHelp + { + static constexpr bool contacts_server = false; + + std::vector options() const { return {"--help"}; } + }; + + struct CommandVersion + { + static constexpr bool contacts_server = false; + + std::vector options() const { return {"--version"}; } + }; + + struct CommandPing + { + static constexpr bool contacts_server = true; + + std::vector options() const { return {"--ping", "-d"}; } + }; + + struct CommandGet + { + static constexpr bool contacts_server = true; + + std::vector options() const { return {"--get"}; } + }; + + struct CommandGetState + { + static constexpr bool contacts_server = true; + + std::vector options() const { return {"--get_state"}; } + }; + + struct CommandLoad + { + static constexpr bool contacts_server = true; + + explicit CommandLoad(fs::path defs) + : defs_{defs} {} + + std::vector options() const { return {"--load", defs_.string(), "-d"}; } + + fs::path defs_; + }; + + struct CommandReplace + { + static constexpr bool contacts_server = true; + + explicit CommandReplace(fs::path defs, std::string node) + : defs_{defs}, + node_{std::move(node)} {} + + std::vector options() const { return {"--replace", node_, defs_.string()}; } + + fs::path defs_; + std::string node_; + }; + + struct CommandDelete + { + static constexpr bool contacts_server = true; + + explicit CommandDelete(std::string path) + : path_{path} {} + + std::vector options() const { return {"--delete", "force", "yes", path_}; } + + std::string path_; + }; + + struct CommandAlterAddInlimit + { + static constexpr bool contacts_server = true; + + explicit CommandAlterAddInlimit(std::string limit_path, std::string inlimit_value, std::string node_path) + : limit_path_{std::move(limit_path)}, + inlimit_value_{std::move(inlimit_value)}, + node_path_{std::move(node_path)} {} + + std::vector options() const { + return {"--alter", "add", "inlimit", limit_path_, inlimit_value_, node_path_}; + } + + std::string limit_path_; + std::string inlimit_value_; + std::string node_path_; + }; + + struct CommandAlterDeleteInlimit + { + static constexpr bool contacts_server = true; + + explicit CommandAlterDeleteInlimit(std::string node_path) + : node_path_{std::move(node_path)}, + inlimit_name_{} {} + explicit CommandAlterDeleteInlimit(std::string inlimit_name, std::string node_path) + : node_path_{std::move(node_path)}, + inlimit_name_{inlimit_name} {} + + std::vector options() const { + if (inlimit_name_) { + return {"--alter", "delete", "inlimit", inlimit_name_.value(), node_path_}; + } + else { + return {"--alter", "delete", "inlimit", node_path_}; + } + } + + std::string node_path_; + std::optional inlimit_name_; + }; + + struct CommandAlterUpdateLabel + { + static constexpr bool contacts_server = true; + + explicit CommandAlterUpdateLabel(std::string path, std::string label, std::string value) + : path_{path}, + label_{label}, + value_{value} {} + + std::vector options() const { return {"--alter", "change", "label", label_, value_, path_}; } + + std::string path_; + std::string label_; + std::string value_; + }; + + struct CommandReloadWhitelist + { + static constexpr bool contacts_server = true; + + explicit CommandReloadWhitelist() {} + + std::vector options() const { return {"--reloadwsfile"}; } + }; + + explicit RunClient() + : host_{nullptr}, + port_{nullptr}, + user_{nullptr}, + cwd_{nullptr} {}; + + RunClient(const RunClient&) = delete; + RunClient& operator=(const RunClient&) = delete; + RunClient(RunClient&&) = delete; + RunClient& operator=(RunClient&&) = delete; + + ~RunClient() = default; + + RunClient& with(const Host& host) { + host_ = &host; + return *this; + } + + RunClient& with(const Port& port) { + port_ = &port; + return *this; + } + + RunClient& with(const User& user) { + user_ = &user; + return *this; + } + + RunClient& with(const Directory& cwd) { + cwd_ = &cwd; + return *this; + } + + template + Outcome execute(const Command& command) { + return launch_ecflow_client(host_, port_, user_, cwd_, command); + } + +private: + static fs::path find_ecflow_client_path() { return fs::path{CMAKE_ECFLOW_BUILD_DIR()} / "bin" / "ecflow_client"; } + + template + static Outcome launch_ecflow_client(const Host* host, + const Port* port, + const User* user, + const Directory* cwd, + const Command& command) { + if constexpr (Command::contacts_server) { + BOOST_REQUIRE_MESSAGE(host != nullptr, "The server host is non-null"); + BOOST_REQUIRE_MESSAGE(port != nullptr, "The server port is non-null"); + BOOST_REQUIRE_MESSAGE(cwd != nullptr, "The working directory is non-null"); + } + + auto client_path = find_ecflow_client_path(); + + BOOST_REQUIRE_MESSAGE(!client_path.empty(), "The ecflow client path is non-empty"); + BOOST_REQUIRE_MESSAGE(fs::exists(client_path), "The ecflow client executable exist at " << client_path); + + auto options = std::vector{}; + if (host != nullptr) { + options.push_back("--host"); + options.push_back(host->value()); + } + if (port != nullptr) { + options.push_back("--port"); + options.push_back(std::to_string(port->value())); + } + if (user != nullptr) { + options.push_back("--user"); + options.push_back(user->username); + options.push_back("--password"); + options.push_back(user->password); + } + for (auto option : command.options()) { + options.push_back(option); + } + + auto wd = cwd != nullptr ? cwd->path() : fs::current_path(); + + auto ecflow_client = Process(client_path, options, wd); + + auto print = [](const auto& executable, const auto& options) { + std::string buffer = "[ " + pretty_print_path(executable) + (options.empty() ? "" : ", "); + for (size_t i = 0; i < options.size(); ++i) { + buffer += options[i]; + if (i != options.size() - 1) { + buffer += ", "; + } + } + return buffer + " ]"; + }; + + if (auto r = ecflow_client.wait(); r == 0) { + ECF_TEST_DBG("Executed " << print(client_path, options)); + ECF_TEST_DBG(" result: [OK]"); + ECF_TEST_DBG(" pid: " << ecflow_client.pid()); + ECF_TEST_DBG(" cwd: " << wd.string()); + auto [stdout_buffer, stderr_buffer] = dump_client_execution_report(wd, ecflow_client); + return Outcome::success(Client{r, stdout_buffer, stderr_buffer}); + } + else { + ECF_TEST_DBG("Executed " << print(client_path, options)); + ECF_TEST_DBG(" result: [FAIL]"); + ECF_TEST_DBG(" pid: " << ecflow_client.pid()); + ECF_TEST_DBG(" cwd: " << wd.string()); + auto [stdout_buffer, stderr_buffer] = dump_client_execution_report(wd, ecflow_client); + return Outcome::failure("ecflow_client failed, return code: " + std::to_string(r) + + ", stdout: " + stdout_buffer + ", stderr: " + stderr_buffer); + } + } + + static std::tuple dump_client_execution_report(const fs::path& wd, + const Process& server) { + auto out = server.read_stdout(); + auto err = server.read_stderr(); + + std::string report_stdout_file = + std::string{"ecflow_client__execution_report."} + std::to_string(server.pid()) + ".stdout.txt"; + std::string report_stderr_file = + std::string{"ecflow_client__execution_report."} + std::to_string(server.pid()) + ".stderr.txt"; + fs::path report_stdout_path = wd / report_stdout_file; + fs::path report_stderr_path = wd / report_stderr_file; + + { + std::ofstream ofs(report_stdout_path.c_str()); + ofs << out; + } + { + std::ofstream ofs(report_stderr_path.c_str()); + ofs << err; + } + + ECF_TEST_DBG(" stdout: " << pretty_print_path(report_stdout_path)); + ECF_TEST_DBG(" stderr: " << pretty_print_path(report_stderr_path)); + + return std::make_tuple(out, err); + } + + const Host* host_; + const Port* port_; + const User* user_; + const Directory* cwd_; +}; + +class Server { +public: + using streams_t = std::tuple; + + struct UnableToShutdownServer : public std::runtime_error + { + explicit UnableToShutdownServer(std::string msg) + : std::runtime_error(std::move(msg)) {} + }; + + explicit Server(const Host& host, const Port& port, const Directory& cwd, Process&& process) + : host_{host}, + port_{port}, + cwd_{cwd}, + process_{std::move(process)} {} + + Server(const Server&) = delete; + Server& operator=(const Server&) = delete; + Server(Server&& other) noexcept + : host_{other.host_}, + port_{other.port_}, + cwd_{other.cwd_}, + process_{std::move(other.process_)} {} + Server& operator=(Server&& other) = delete; + + ~Server() { + if (process_.is_running()) { + shutdown_ecflow_server(); + } + } + + streams_t shutdown() { + if (process_.is_running()) { + auto [out, err] = shutdown_ecflow_server(); + return {out, err}; + } + return {"", ""}; + } + + pid_t pid() const { return process_.pid(); } + const Host& host() const { return host_; } + const Port& port() const { return port_; } + const Directory& cwd() const { return cwd_; } + +private: + streams_t shutdown_ecflow_server() { + if (auto o = process_.terminate(); o == 0) { + ECF_TEST_DBG("Shutdown server: [OK]"); + return dump_server_execution_report(cwd_, process_); + } + else { + ECF_TEST_DBG("Shutdown server: [FAIL]"); + return dump_server_execution_report(cwd_, process_); + } + + if (auto o = ensure_ecflow_server_is_shutdown(host_, port_, cwd_); !o.ok()) { + throw UnableToShutdownServer(o.reason()); + } + } + + static Outcome + ensure_ecflow_server_is_shutdown(const Host& host, + const Port& port, + const Directory& cwd, + std::chrono::seconds timeout = std::chrono::seconds(10)) { + auto start = std::chrono::system_clock::now(); + for (;;) { + auto r = RunClient{}.with(host).with(port).with(cwd).execute(RunClient::CommandPing{}); + if (!r.ok()) { + // Important: this checks that the client finished with FAILURE! + // As this means the ping failed, and thus the server is no longer responding! + break; + } + + auto now = std::chrono::system_clock::now(); + if (now - start > timeout) { + ECF_TEST_DBG("ecflow server is ***NOT*** shutdown"); + return Outcome::failure("Timed out waiting for ecflow server to shutdown"); + } + + std::this_thread::sleep_for(std::chrono::milliseconds(250)); + } + return Outcome::success("ecflow server is shutdown"); + } + + static streams_t dump_server_execution_report(const Directory& cwd, const Process& server) { + std::string report_file = + std::string{"ecflow_server__execution_report."} + std::to_string(server.pid()) + ".txt"; + fs::path report_path = cwd.path() / report_file; + std::ofstream ofs(report_path.c_str()); + auto out = server.read_stdout(); + ofs << "Report STDOUT \n <<< STDOUT BEGIN >>>\n" << out << " <<< STDOUT END >>>\n"; + auto err = server.read_stderr(); + ofs << "Report STDERR \n <<< STDERR BEGIN >>>\n" << err << " <<< STDERR END >>>\n"; + + ECF_TEST_DBG("Server execution report written to " << pretty_print_path(report_path)); + + return std::make_tuple(out, err); + } + + const Host& host_; + const Port& port_; + const Directory& cwd_; + Process process_; +}; + +class MakeServer { +public: + explicit MakeServer() + : host_{nullptr}, + port_{nullptr}, + cwd_{nullptr} {}; + + MakeServer(const MakeServer&) = delete; + MakeServer& operator=(const MakeServer&) = delete; + MakeServer(MakeServer&&) = delete; + MakeServer& operator=(MakeServer&&) = delete; + + MakeServer& with(const Host& host) { + host_ = &host; + return *this; + } + + MakeServer& with(const Port& port) { + port_ = &port; + return *this; + } + + MakeServer& with(const Directory& cwd) { + cwd_ = &cwd; + return *this; + } + + Outcome launch() { + BOOST_REQUIRE_MESSAGE(host_ != nullptr, "The server host is non-null"); + BOOST_REQUIRE_MESSAGE(port_ != nullptr, "The server port is non-null"); + BOOST_REQUIRE_MESSAGE(cwd_ != nullptr, "The server working directory is non-null"); + + if (auto o = launch_ecflow_server(*host_, *port_, *cwd_); o.ok()) { + return Outcome::success(Server{*host_, *port_, *cwd_, o.get()}); + } + else { + return Outcome::failure(o.reason()); + } + } + + ~MakeServer() = default; + +private: + static fs::path find_ecflow_server_path() { return fs::path{CMAKE_ECFLOW_BUILD_DIR()} / "bin" / "ecflow_server"; } + + static Outcome launch_ecflow_server(const Host& host, const Port& port, const Directory& cwd) { + auto server_path = find_ecflow_server_path(); + + BOOST_REQUIRE_MESSAGE(!server_path.empty(), "The ecflow server path could not be found"); + BOOST_REQUIRE_MESSAGE(fs::exists(server_path), + "The ecflow server executable does not exist at " << server_path); + + auto options = std::vector{"--port", std::to_string(port.value()), "-d"}; + + auto ecflow_server = Process(server_path, options, cwd.path()); + + auto print = [](const auto& executable, const auto& options) { + std::string buffer = "[ " + pretty_print_path(executable) + (options.empty() ? "" : ", "); + for (size_t i = 0; i < options.size(); ++i) { + buffer += options[i]; + if (i != options.size() - 1) { + buffer += ", "; + } + } + return buffer + " ]"; + }; + + ECF_TEST_DBG("Launched " << print(server_path, options) << "; now waiting for it to be ready..."); + + if (auto found = ensure_ecflow_server_is_running(host, port, cwd); found.ok()) { + ECF_TEST_DBG("Executed " << print(server_path, options)); + ECF_TEST_DBG(" result: [OK]"); + ECF_TEST_DBG(" pid: " << ecflow_server.pid()); + ECF_TEST_DBG(" cwd: " << cwd.path().string()); + return Outcome::success(std::move(ecflow_server)); + } + else { + ECF_TEST_DBG("Executed " << print(server_path, options)); + ECF_TEST_DBG(" result: [FAIL], due to: " << found.reason()); + ECF_TEST_DBG(" pid: " << ecflow_server.pid()); + ECF_TEST_DBG(" cwd: " << cwd.path().string()); + return Outcome::failure(found.reason()); + } + } + + static Outcome + ensure_ecflow_server_is_running(const Host& host, + const Port& port, + const Directory& cwd, + std::chrono::seconds timeout = std::chrono::seconds(10)) { + auto start = std::chrono::system_clock::now(); + for (;;) { + auto r = RunClient{}.with(host).with(port).with(cwd).execute(RunClient::CommandPing{}); + if (r.ok()) { + // Important: this checks that the client finished with SUCCESS! + // As this means the ping succeeded, and thus the server is now responding! + break; + } + + auto now = std::chrono::system_clock::now(); + if (now - start > timeout) { + return Outcome::failure("Timed out waiting for ecflow server to start"); + } + + std::this_thread::sleep_for(std::chrono::milliseconds(250)); + } + return Outcome::success("ecflow server is running"); + } + +private: + const Host* host_; + const Port* port_; + const Directory* cwd_; +}; + +} // namespace ecf::test::scaffold + +#endif /* ecflow_test_foolproof_scaffold_Provisioning_HPP */ diff --git a/libs/node/src/ecflow/node/Alias.cpp b/libs/node/src/ecflow/node/Alias.cpp index c9ef022d6..632318a5f 100644 --- a/libs/node/src/ecflow/node/Alias.cpp +++ b/libs/node/src/ecflow/node/Alias.cpp @@ -70,7 +70,7 @@ void Alias::requeue(Requeue_args& args) { } const std::string& Alias::debugType() const { - return ecf::Str::ALIAS(); + return ecf::string_constants::alias; } node_ptr Alias::removeChild(Node*) { diff --git a/libs/node/src/ecflow/node/AutoRestoreAttr.cpp b/libs/node/src/ecflow/node/AutoRestoreAttr.cpp index bbf797b8f..4b979ff8f 100644 --- a/libs/node/src/ecflow/node/AutoRestoreAttr.cpp +++ b/libs/node/src/ecflow/node/AutoRestoreAttr.cpp @@ -98,7 +98,7 @@ void AutoRestoreAttr::check(std::string& errorMsg) const { // OK a little bit of duplication, since findReferencedNode, will also look for externs // See if the Path:name is defined as an extern, in which case *DONT* error: // This is client side specific, since server does not have externs. - if (node_->defs()->find_extern(i, Str::EMPTY())) { + if (node_->defs()->find_extern(i, ecf::string_constants::empty)) { continue; } diff --git a/libs/node/src/ecflow/node/AvisoAttr.cpp b/libs/node/src/ecflow/node/AvisoAttr.cpp index 9cadff605..00acb673c 100644 --- a/libs/node/src/ecflow/node/AvisoAttr.cpp +++ b/libs/node/src/ecflow/node/AvisoAttr.cpp @@ -36,7 +36,7 @@ std::string ensure_single_quotes(const AvisoAttr::listener_t listener) { } // namespace implementation bool AvisoAttr::is_valid_name(const std::string& name) { - return ecf::Str::valid_name(name); + return ecf::algorithm::is_valid_name(name); } AvisoAttr::AvisoAttr(Node* parent, @@ -59,7 +59,7 @@ AvisoAttr::AvisoAttr(Node* parent, reason_{implementation::ensure_single_quotes(reason)}, revision_{revision}, controller_{nullptr} { - if (!ecf::Str::valid_name(name_)) { + if (!ecf::algorithm::is_valid_name(name_)) { THROW_EXCEPTION(ecf::InvalidArgument, "Invalid AvisoAttr name :" << name_); } } diff --git a/libs/node/src/ecflow/node/ClientSuites.cpp b/libs/node/src/ecflow/node/ClientSuites.cpp index 0eeb6f0e6..55843cad8 100644 --- a/libs/node/src/ecflow/node/ClientSuites.cpp +++ b/libs/node/src/ecflow/node/ClientSuites.cpp @@ -143,14 +143,14 @@ defs_ptr ClientSuites::create_defs(defs_ptr server_defs) const { // If the user has registered *ALL* the suites just return the server defs auto suites_end = suites_.end(); - if (suites_.size() == server_defs->suiteVec().size()) { + if (suites_.size() == server_defs->suites().size()) { size_t real_suite_count = 0; for (auto i = suites_.begin(); i != suites_end; ++i) { if (suite_ptr suite = (*i).weak_suite_ptr_.lock(); suite.get()) { real_suite_count++; } } - if (real_suite_count == server_defs->suiteVec().size()) { + if (real_suite_count == server_defs->suites().size()) { server_defs->set_state_change_no(Ecf::state_change_no()); server_defs->set_modify_change_no(Ecf::modify_change_no()); @@ -277,13 +277,13 @@ std::string ClientSuites::dump() const { } void ClientSuites::update_suite_order() { - const std::vector& server_suite_vec = defs_->suiteVec(); - size_t server_suite_vec_size = server_suite_vec.size(); + const auto& suites = defs_->suites(); + size_t size = suites.size(); auto suites_end = suites_.end(); for (auto i = suites_.begin(); i != suites_end; ++i) { - for (size_t s = 0; s < server_suite_vec_size; s++) { - if ((*i).name_ == server_suite_vec[s]->name()) { + for (size_t s = 0; s < size; s++) { + if ((*i).name_ == suites[s]->name()) { (*i).index_ = static_cast(s); break; } diff --git a/libs/node/src/ecflow/node/Defs.cpp b/libs/node/src/ecflow/node/Defs.cpp index 48b0ea6a9..bcdf747bf 100644 --- a/libs/node/src/ecflow/node/Defs.cpp +++ b/libs/node/src/ecflow/node/Defs.cpp @@ -133,7 +133,7 @@ void Defs::handle_migration() { auto it = edit_history_.begin(); while (it != edit_history_.end()) { - if ((*it).first == Str::ROOT_PATH()) { + if ((*it).first == ecf::string_constants::root_path) { it++; continue; // root path is defs, which is not a node, hence ignore } @@ -354,12 +354,12 @@ void Defs::absorb(Defs* input_defs, bool force) { updateCalendarCount_ = 0; // We must make a copy, otherwise we are iterating over a vector that is being deleted - std::vector suiteVecCopy = input_defs->suiteVec(); - size_t theSize = suiteVecCopy.size(); + auto suitesCopy = input_defs->suites(); + size_t theSize = suitesCopy.size(); for (size_t s = 0; s < theSize; s++) { /// regardless remove the suite from the input defs - suite_ptr the_input_suite = input_defs->removeSuite(suiteVecCopy[s]); + suite_ptr the_input_suite = input_defs->removeSuite(suitesCopy[s]); if (force) { /// The suite of the same name exists. remove it from *existing* defs @@ -373,7 +373,7 @@ void Defs::absorb(Defs* input_defs, bool force) { /// This stops accidental overwrite addSuite(the_input_suite); } - LOG_ASSERT(input_defs->suiteVec().empty(), "Defs::absorb"); + LOG_ASSERT(input_defs->suites().empty(), "Defs::absorb"); // Copy over server user variables server_state().add_or_update_user_variables(input_defs->server_state().user_variables()); @@ -692,7 +692,7 @@ std::string Defs::dump_edit_history() const { } else { std::string h = c; - Str::replaceall(h, "\n", "\\n"); + ecf::algorithm::replace_all(h, "\n", "\\n"); ss << " "; ss << h; } @@ -791,7 +791,7 @@ void Defs::read_history(const std::string& line, const std::vector& size_t close_p = parsed_message.find("]"); std::string date = parsed_message.substr(space_pos + 1, close_p - space_pos - 1); vec.clear(); - Str::split(date, vec, "."); + ecf::algorithm::split_at(vec, date, "."); if (vec.size() == 3) { try { int day = ecf::convert_to(vec[0]); @@ -907,7 +907,7 @@ node_ptr Defs::findAbsNode(const std::string& pathToNode) const { // This is 14% quicker than the previous algorithm, that split 'pathToNode' into a vector of strings first. node_ptr ret; bool first = false; - StringSplitter string_splitter(pathToNode, Str::PATH_SEPARATOR()); + StringSplitter string_splitter(pathToNode, ecf::string_constants::path_separator); while (!string_splitter.finished()) { std::string_view path_token = string_splitter.next(); if (!first) { @@ -941,7 +941,7 @@ node_ptr Defs::findAbsNode(const std::string& pathToNode) const { node_ptr Defs::find_closest_matching_node(const std::string& pathToNode) const { std::vector theNodeNames; - NodePath::split(pathToNode, theNodeNames); + ecf::node::split_path(pathToNode, theNodeNames); if (theNodeNames.empty()) { return node_ptr(); } @@ -971,7 +971,7 @@ bool Defs::find_extern(const std::string& pathToNode, const std::string& node_at } std::string extern_path = pathToNode; - extern_path += Str::COLON(); + extern_path += ecf::string_constants::colon; extern_path += node_attr_name; if (externs_.find(extern_path) != externs_.end()) { @@ -1000,27 +1000,31 @@ std::string Defs::find_node_path(const std::string& type, const std::string& nam } node_ptr Defs::find_node(const std::string& type, const std::string& pathToNode) const { - // std::cout << "Defs::find_node type:" << type << " path: " << pathToNode << "\n"; node_ptr node_p = findAbsNode(pathToNode); if (!node_p) { - // std::cout << " node not found\n"; return node_p; } - if (Str::caseInsCompare(type, "task")) { + if (ecf::algorithm::case_insensitive_compare(type, "task")) { if (node_p->isTask()) { return node_p; } return node_ptr(); } - if (Str::caseInsCompare(type, "family")) { + if (ecf::algorithm::case_insensitive_compare(type, "family")) { if (node_p->isFamily()) { return node_p; } return node_ptr(); } - if (Str::caseInsCompare(type, "suite")) { - if (node_p->suite()) { + if (ecf::algorithm::case_insensitive_compare(type, "suite")) { + if (node_p->isSuite()) { + return node_p; + } + return node_ptr(); + } + if (ecf::algorithm::case_insensitive_compare(type, "alias")) { + if (node_p->isAlias()) { return node_p; } return node_ptr(); @@ -1495,7 +1499,7 @@ void Defs::order(Node* immediateChild, NOrder::Order ord) { catch (const ecf::bad_conversion&) { } - return Str::caseInsLess(a->name(), b->name()); + return ecf::algorithm::case_insensitive_less(a->name(), b->name()); }); order_state_change_no_ = Ecf::incr_state_change_no(); client_suite_mgr_.update_suite_order(); @@ -1503,7 +1507,7 @@ void Defs::order(Node* immediateChild, NOrder::Order ord) { } case NOrder::ORDER: { std::sort(suiteVec_.begin(), suiteVec_.end(), [](const suite_ptr& a, const suite_ptr& b) { - return Str::caseInsGreater(a->name(), b->name()); + return ecf::algorithm::case_insensitive_greater(a->name(), b->name()); }); order_state_change_no_ = Ecf::incr_state_change_no(); client_suite_mgr_.update_suite_order(); @@ -1640,7 +1644,7 @@ void Defs::collate_defs_changes_only(DefsDelta& incremental_changes) const { // Create StateMemento to signal a change in state change of the Defs if (state_.state_change_no() > incremental_changes.client_state_change_no()) { if (!comp.get()) { - comp = std::make_shared(Str::ROOT_PATH()); + comp = std::make_shared(ecf::string_constants::root_path); } comp->add(std::make_shared(state_.state())); } @@ -1648,7 +1652,7 @@ void Defs::collate_defs_changes_only(DefsDelta& incremental_changes) const { // Create OrderMemento to signal a change in Suite order if (order_state_change_no_ > incremental_changes.client_state_change_no()) { if (!comp.get()) { - comp = std::make_shared(Str::ROOT_PATH()); + comp = std::make_shared(ecf::string_constants::root_path); } std::vector order; order.reserve(suiteVec_.size()); @@ -1661,7 +1665,7 @@ void Defs::collate_defs_changes_only(DefsDelta& incremental_changes) const { // Create FlagMemento to signal a change in the flag value if (flag_.state_change_no() > incremental_changes.client_state_change_no()) { if (!comp.get()) { - comp = std::make_shared(Str::ROOT_PATH()); + comp = std::make_shared(ecf::string_constants::root_path); } comp->add(std::make_shared(flag_)); } @@ -1670,7 +1674,7 @@ void Defs::collate_defs_changes_only(DefsDelta& incremental_changes) const { // Currently only watching server state i.e., HALTED, SHUTDOWN, RUNNING. if (server_.state_change_no() > incremental_changes.client_state_change_no()) { if (!comp.get()) { - comp = std::make_shared(Str::ROOT_PATH()); + comp = std::make_shared(ecf::string_constants::root_path); } comp->add(std::make_shared(server_.get_state())); } @@ -1678,7 +1682,7 @@ void Defs::collate_defs_changes_only(DefsDelta& incremental_changes) const { // Create a ServerVariableMemento to signal a change in the list of server variables if (server_.variable_state_change_no() > incremental_changes.client_state_change_no()) { if (!comp.get()) { - comp = std::make_shared(Str::ROOT_PATH()); + comp = std::make_shared(ecf::string_constants::root_path); } comp->add(std::make_shared(server_.user_variables())); } @@ -1901,7 +1905,7 @@ void DefsHistoryParser::parse(const std::string& line) { if (pos != std::string::npos) { // keep compatibility with the current way of writing history std::string requests = line.substr(pos); - Str::split(requests, parsed_messages_, "\b"); + ecf::algorithm::split_at(parsed_messages_, requests, "\b"); return; } diff --git a/libs/node/src/ecflow/node/Defs.hpp b/libs/node/src/ecflow/node/Defs.hpp index 393be8530..3fcbb3359 100644 --- a/libs/node/src/ecflow/node/Defs.hpp +++ b/libs/node/src/ecflow/node/Defs.hpp @@ -227,6 +227,16 @@ class Defs { suite_ptr findSuite(const std::string& name) const; std::string find_node_path(const std::string& type, const std::string& name) const; + + /// + /// @brief Find a node of the speficied type at the given path + /// + /// @param type the type of node to find, either "suite", "family", "task", or "alias" -- case insensitive. + /// @param pathToNode the path to the requested node + /// @return the requested node, if avaible; nullptr, otherwise + /// @throws std::runtime_error when the node at the given path is not of the specified type + /// (n.b. this is guaranteed to be thrown if the type is not one "suite", "family", "task", or "alias") + /// node_ptr find_node(const std::string& type, const std::string& pathToNode) const; [[deprecated]] const std::vector& suiteVec() const { return suiteVec_; } @@ -432,18 +442,6 @@ class Defs { bool placeChild(const node_ptr&, size_t position = std::numeric_limits::max()); friend class Node; - /// For use by python interface, - std::vector::const_iterator suite_begin() const { return suiteVec_.begin(); } - std::vector::const_iterator suite_end() const { return suiteVec_.end(); } - std::set::const_iterator extern_begin() const { return externs_.begin(); } - std::set::const_iterator extern_end() const { return externs_.end(); } - std::vector::const_iterator user_variables_begin() const { return server_.user_variables().begin(); } - std::vector::const_iterator user_variables_end() const { return server_.user_variables().end(); } - std::vector::const_iterator server_variables_begin() const { return server_.server_variables().begin(); } - std::vector::const_iterator server_variables_end() const { return server_.server_variables().end(); } - - friend void export_Defs(); - private: /// Note: restoring from a check point file will reset, defs state and modify numbers mutable size_t print_cache_{0}; // NOT persisted diff --git a/libs/node/src/ecflow/node/DefsTreeVisitor.hpp b/libs/node/src/ecflow/node/DefsTreeVisitor.hpp index b29ef9eee..4982221e8 100644 --- a/libs/node/src/ecflow/node/DefsTreeVisitor.hpp +++ b/libs/node/src/ecflow/node/DefsTreeVisitor.hpp @@ -58,7 +58,7 @@ struct DefsTreeVisitor nodes_t nodes_at(const path_t& path) const { nodes_t nodes; if (path == "/") { - std::vector suites = defs_.suiteVec(); + auto suites = defs_.suites(); std::transform(std::begin(suites), std::end(suites), std::back_inserter(nodes), [](const suite_ptr& ptr) { return static_cast(ptr.get()); }); @@ -83,8 +83,8 @@ struct DefsTreeVisitor // Visit suite itself v_.begin_visit(*found); // Visit suite children - for (auto&& entry : found->nodeVec()) { - visit(*entry.get()); + for (auto&& child : found->children()) { + visit(*child.get()); } v_.end_visit(*found); return; @@ -94,8 +94,8 @@ struct DefsTreeVisitor // Visit family itself v_.begin_visit(*found); // Visit family children - for (auto&& entry : found->nodeVec()) { - visit(*entry.get()); + for (auto&& child : found->children()) { + visit(*child.get()); } v_.end_visit(*found); return; diff --git a/libs/node/src/ecflow/node/EcfFile.cpp b/libs/node/src/ecflow/node/EcfFile.cpp index 39227327f..1926d55c1 100644 --- a/libs/node/src/ecflow/node/EcfFile.cpp +++ b/libs/node/src/ecflow/node/EcfFile.cpp @@ -465,7 +465,7 @@ std::vector EcfFile::get_ecf_include_paths(const EcfFile& ecf) { // if ECF_INCLUDE is a set a paths, search in order. i.e., like $PATH if (ecf_include.find(':') != std::string::npos) { - Str::split(ecf_include, paths, ":"); + ecf::algorithm::split_at(paths, ecf_include, ":"); } else { paths = {ecf_include}; @@ -719,12 +719,12 @@ static void replace(std::string::size_type commentPos, if (commentPos == std::string::npos) { std::string replace1 = clientPath; replace1 += ecfEquiv; - Str::replace(jobLine, smsChildCmd, replace1); + ecf::algorithm::replace(jobLine, smsChildCmd, replace1); } else if (childPos < commentPos) { std::string replace2 = clientPath; replace2 += ecfEquiv; - Str::replace(jobLine, smsChildCmd, replace2); + ecf::algorithm::replace(jobLine, smsChildCmd, replace2); } } } @@ -754,7 +754,7 @@ bool EcfFile::replaceSmsChildCmdsWithEcf(const std::string& clientPath, std::str } bool EcfFile::extract_ecfmicro(const std::string& line, std::string& ecfmicro, std::string& error_msg) const { - if (!Str::get_token(line, 1, ecfmicro)) { + if (!ecf::algorithm::get_token(line, 1, ecfmicro)) { error_msg += MESSAGE("ecfmicro does not have a replacement character, in " << script_path_or_cmd_); return false; } @@ -911,16 +911,16 @@ void EcfFile::get_used_variables(std::string& used_variables) const { } // We must use exact match, to avoid user variables like ESUITE,EFAMILY,ETASK - if (item.first == Str::TASK()) { + if (item.first == ecf::string_constants::task) { continue; } - if (item.first == Str::FAMILY()) { + if (item.first == ecf::string_constants::family) { continue; } - if (item.first == "FAMILY1") { + if (item.first == ecf::string_constants::family1) { continue; } - if (item.first == Str::SUITE()) { + if (item.first == ecf::string_constants::suite) { continue; } used_variables += item.first; @@ -1582,7 +1582,7 @@ void PreProcessor::preProcess_line() { // allow : %FRED:val% // disallow: %FRED std::string the_include_token; - if (!Str::get_token(script_line, 1, the_include_token)) { + if (!ecf::algorithm::get_token(script_line, 1, the_include_token)) { int ecfMicroCount = EcfFile::countEcfMicro(script_line, ecf_micro_); if (ecfMicroCount % 2 != 0) { throw std::runtime_error( @@ -1624,7 +1624,7 @@ void PreProcessor::preProcess_includes() { } std::string the_include_token; - if (!Str::get_token(script_line, 1, the_include_token)) { + if (!ecf::algorithm::get_token(script_line, 1, the_include_token)) { throw std::runtime_error( MESSAGE(error_context() << "Could not extract include token at : " << script_line)); } @@ -1768,7 +1768,8 @@ std::string PreProcessor::getIncludedFilePath(const std::string& includedFile1, if (includedFile.find("./") == 1 || includedFile.find("../") == 1) { // remove the leading and trailing '"' std::string the_included_file = includedFile; - Str::removeQuotes(the_included_file); + + ecf::algorithm::remove_double_quotes(the_included_file); // Get the root path, i.e. script_or_job_path() is of the form: // "/user/home/ma/mao/course/t1.ecf || /user/home/ma/mao/course/t1.job" diff --git a/libs/node/src/ecflow/node/ExprAst.cpp b/libs/node/src/ecflow/node/ExprAst.cpp index f59150692..cfbe07f53 100644 --- a/libs/node/src/ecflow/node/ExprAst.cpp +++ b/libs/node/src/ecflow/node/ExprAst.cpp @@ -1557,11 +1557,11 @@ int AstVariable::plus(Ast* right) const { } void AstVariable::print_flat(std::ostream& os, bool /*add_bracket*/) const { - os << nodePath_ << Str::COLON() << name_; + os << nodePath_ << ecf::string_constants::colon << name_; } std::string AstVariable::expression() const { - return nodePath_ + Str::COLON() + name_; + return nodePath_ + ecf::string_constants::colon + name_; } std::string AstVariable::why_expression(bool html) const { @@ -1601,7 +1601,7 @@ std::string AstVariable::why_expression(bool html) const { if (!ref_node) { ret += "(?)"; } - ret += Str::COLON(); + ret += ecf::string_constants::colon; ret += name_; ret += "("; std::ostringstream ss; @@ -1671,7 +1671,7 @@ int AstParentVariable::minus(Ast* right) const { if (ref_node) { return ref_node->findExprVariableValueAndMinus(name_, right->value()); } - return right->value(); + return -right->value(); } int AstParentVariable::plus(Ast* right) const { @@ -1683,11 +1683,11 @@ int AstParentVariable::plus(Ast* right) const { } void AstParentVariable::print_flat(std::ostream& os, bool /*add_bracket*/) const { - os << Str::COLON() << name_; + os << ecf::string_constants::colon << name_; } std::string AstParentVariable::expression() const { - return Str::COLON() + name_; + return ecf::string_constants::colon + name_; } std::string AstParentVariable::why_expression(bool html) const { @@ -1727,7 +1727,7 @@ std::string AstParentVariable::why_expression(bool html) const { if (!ref_node) { ret += "(?)"; } - ret += Str::COLON(); + ret += ecf::string_constants::colon; ret += name_; ret += "("; std::ostringstream ss; @@ -1790,7 +1790,7 @@ VariableHelper::VariableHelper(const AstVariable* astVariable, std::string& erro } errorMsg += MESSAGE("From expression Variable " - << astVariable_->nodePath() << Str::COLON() << astVariable_->name() + << astVariable_->nodePath() << ecf::string_constants::colon << astVariable_->name() << " the referenced node is " << theReferenceNode_->debugNodePath() << "\n" << "Could not find event, meter, variable, repeat, generated variable, limit or queue of name('" << astVariable_->name() << "') on node " << theReferenceNode_->debugNodePath() << "\n"); diff --git a/libs/node/src/ecflow/node/ExprAst.hpp b/libs/node/src/ecflow/node/ExprAst.hpp index 0fb5d2018..8592d3639 100644 --- a/libs/node/src/ecflow/node/ExprAst.hpp +++ b/libs/node/src/ecflow/node/ExprAst.hpp @@ -87,6 +87,10 @@ class AstTop : public Ast { AstTop* isTop() const override { return const_cast(this); } bool evaluate() const override; bool check(std::string& error_msg) const override; + int value() const override { + assert(root_); + return root_ ? root_->value() : 0; + } bool empty() const override { return (root_) ? false : true; } void print_flat(std::ostream&, bool add_brackets = false) const override; diff --git a/libs/node/src/ecflow/node/ExprParser.cpp b/libs/node/src/ecflow/node/ExprParser.cpp index f4973af12..e015a5093 100644 --- a/libs/node/src/ecflow/node/ExprParser.cpp +++ b/libs/node/src/ecflow/node/ExprParser.cpp @@ -1196,66 +1196,95 @@ bool has_complex_expressions(const std::string& expr) { } bool SimpleExprParser::doParse() { - // 3199.def 57Mg - // a == b, || a eq b i.e simple 76940 - // complex 39240 + + // If expression has complex operators, return false if (has_complex_expressions(expr_)) { return false; } - // look for path == || path eq state - // This gets round issue with Str::split: - // "a = complete" will be split - // since will split on *ANY* of character, and hence this would not be reported as an error - std::vector tokens; - if (expr_.find("==") != std::string::npos) { - Str::split(expr_, tokens, "=="); + // Start by trimming the original expression + auto expression = expr_; + ecf::algorithm::trim(expression); + + // If expression is empty, return false + if (expression.empty()) { + return false; + } + + // If expression begins/ends with '==' or 'eq', return false + // n.b. for 'eq' the comparison is done considering the necessary whitespace after/before the operator + if (ecf::algorithm::starts_with(expression, "==") || ecf::algorithm::starts_with(expression, "eq ") || + ecf::algorithm::ends_with(expression, "==") || ecf::algorithm::ends_with(expression, " eq")) { + return false; + } + + // Split the expression into tokens, considering '==' or 'eq' operators. + // n.b. for 'eq' the comparison is done considering the necessary whitespace before and after the operator + std::vector operands; + if (expression.find("==") != std::string::npos) { + // Expecting expressions such as: + // `/path/to/node==` + // `/path/to/node == ` + // `==` + // ` == ` + ecf::algorithm::split_by(operands, expression, "=="); } - else if (expr_.find(" eq ") != std::string::npos) { - Str::split(expr_, tokens, " eq "); + else if (expression.find(" eq ") != std::string::npos) { + // Or, expecting expressions such as: + // `/path/to/node eq ` + // ` eq ` + ecf::algorithm::split_by(operands, expression, " eq "); } else { + // If the expression does not contain simple operators ('==' or 'eq'), return false return false; } - if (tokens.size() == 2) { + // If the expression does not have exactly two operands, return false + if (operands.size() != 2) { + return false; + } - ecf::algorithm::trim(tokens[0]); - ecf::algorithm::trim(tokens[1]); + // Trim the operands + ecf::algorithm::trim(operands.front()); + ecf::algorithm::trim(operands.back()); - if (tokens[0].find(' ') != std::string::npos) { - // cout << "Found space " << expr_ << "\n"; - return false; - } + const auto& left_operand = operands.front(); + const auto& right_operand = operands.back(); - if (DState::isValid(tokens[1])) { + // If the left operand (i.e. either a `/path/to/node` or ``) contain spaces, return false + if (left_operand.find(' ') != std::string::npos) { + return false; + } - ast_ = std::make_unique(); - Ast* someRoot = new AstEqual(); - someRoot->addChild(new AstNode(tokens[0])); - someRoot->addChild(new AstNodeState(DState::toState(tokens[1]))); - ast_->addChild(someRoot); - // cout << "simple expr : " << expr_ << " `" << tokens[0] << "' = '" << tokens[1] << "'\n"; - return true; - } - else { - try { - auto left = ecf::convert_to(tokens[0]); - auto right = ecf::convert_to(tokens[1]); - ast_ = std::make_unique(); - Ast* someRoot = new AstEqual(); - someRoot->addChild(new AstInteger(left)); - someRoot->addChild(new AstInteger(right)); - ast_->addChild(someRoot); - // cout << "simple INT expr : " << expr_ << " `" << tokens[0] << "' = '" << tokens[1] << - // "'\n"; - return true; - } - catch (const ecf::bad_conversion&) { - // cout << "simple INT FAILED expr : " << expr_ << " `" << tokens[0] << "' = '" - // << tokens[1] << "'\n"; - } - } + // If the right operand (i.e. either a `` or ``) contain spaces, return false + if (right_operand.find(' ') != std::string::npos) { + return false; + } + + // If the right operand is a valid ``), parse the expression and return true + if (DState::isValid(right_operand)) { + ast_ = std::make_unique(); + Ast* someRoot = new AstEqual(); + someRoot->addChild(new AstNode(left_operand)); + someRoot->addChild(new AstNodeState(DState::toState(right_operand))); + ast_->addChild(someRoot); + return true; + } + + // Otherwise, the expression must be a comparison of two numerical values. + // Parse the operands as integers, and return accordingly. + try { + auto left = ecf::convert_to(left_operand); + auto right = ecf::convert_to(right_operand); + ast_ = std::make_unique(); + Ast* someRoot = new AstEqual(); + someRoot->addChild(new AstInteger(left)); + someRoot->addChild(new AstInteger(right)); + ast_->addChild(someRoot); + return true; + } + catch (const ecf::bad_conversion&) { + return false; } - return false; } diff --git a/libs/node/src/ecflow/node/ExprParser.hpp b/libs/node/src/ecflow/node/ExprParser.hpp index c7d9a41b6..729a5b965 100644 --- a/libs/node/src/ecflow/node/ExprParser.hpp +++ b/libs/node/src/ecflow/node/ExprParser.hpp @@ -45,9 +45,23 @@ class ExprParser { std::string expr_; }; -// This class was added to mitigate the slowness of the boost classic spirit parser -// we will recognise very simple expression, and bypass spirit. Very limited -// But the simple expression do form a very large subset +/// +/// @brief This class enables quick 'simple' expression parsing without the overhead of the Boost spirit parser. +/// +/// This is used as a first pass to quickly parse simple expressions. A 'simple' expression is of the form: +/// - `/path/to/node==` +/// - `/path/to/node == ` +/// - `/path/to/node eq ` +/// - `==` +/// - ` == ` +/// - ` eq ` +/// +/// This optimisation significantly improves performance when a significant number of expressions are 'simple', +/// which is typical based on empirical observations. +/// +/// If the provided expression is not 'simple', the parsing fails and the overall parser falls back to the full boost +/// spirit parser. +/// class SimpleExprParser { public: explicit SimpleExprParser(const std::string& expression) diff --git a/libs/node/src/ecflow/node/Expression.hpp b/libs/node/src/ecflow/node/Expression.hpp index da5985bb2..5a25d2035 100644 --- a/libs/node/src/ecflow/node/Expression.hpp +++ b/libs/node/src/ecflow/node/Expression.hpp @@ -129,13 +129,6 @@ class Expression { // 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_; } -private: /// For use by python interface, - friend void export_NodeAttr(); - friend class Trigger; - friend class Complete; - std::vector::const_iterator part_begin() const { return vec_.begin(); } - std::vector::const_iterator part_end() const { return vec_.end(); } - private: // They are created on demand. reasons: // 1/ Help with AIX serialisation diff --git a/libs/node/src/ecflow/node/Family.cpp b/libs/node/src/ecflow/node/Family.cpp index ed051c98a..9bd64ee6a 100644 --- a/libs/node/src/ecflow/node/Family.cpp +++ b/libs/node/src/ecflow/node/Family.cpp @@ -102,7 +102,7 @@ void Family::read_state(const std::string& line, const std::vector& } const std::string& Family::debugType() const { - return ecf::Str::FAMILY(); + return ecf::string_constants::family; } void Family::collateChanges(DefsDelta& changes) const { @@ -154,7 +154,7 @@ void Family::gen_variables(std::vector& vec) const { } std::string Family::find_node_path(const std::string& type, const std::string& node_name) const { - if (Str::caseInsCompare(type, "family")) { + if (ecf::algorithm::case_insensitive_compare(type, "family")) { if (node_name == name()) { return absNodePath(); } diff --git a/libs/node/src/ecflow/node/Flag.cpp b/libs/node/src/ecflow/node/Flag.cpp index 17984f18f..d03f900d4 100644 --- a/libs/node/src/ecflow/node/Flag.cpp +++ b/libs/node/src/ecflow/node/Flag.cpp @@ -10,6 +10,7 @@ #include "ecflow/node/Flag.hpp" +#include #include #include "ecflow/core/Ecf.hpp" @@ -332,7 +333,7 @@ void Flag::write(std::string& ret) const { void Flag::set_flag(const std::string& flags) { std::vector the_flags_vec; - Str::split(flags, the_flags_vec, ","); + ecf::algorithm::split_at(the_flags_vec, flags, ","); for (const auto& i : the_flags_vec) { if (i == "migrated") { diff --git a/libs/node/src/ecflow/node/InLimit.cpp b/libs/node/src/ecflow/node/InLimit.cpp index d68449173..bd58a608f 100644 --- a/libs/node/src/ecflow/node/InLimit.cpp +++ b/libs/node/src/ecflow/node/InLimit.cpp @@ -13,6 +13,8 @@ #include #include "ecflow/core/Converter.hpp" +#include "ecflow/core/Extract.hpp" +#include "ecflow/core/Message.hpp" #include "ecflow/core/Serialization.hpp" #include "ecflow/core/Str.hpp" #include "ecflow/node/Limit.hpp" @@ -22,7 +24,54 @@ using namespace ecf; -///////////////////////////////////////////////////////////////////////////////////////////// +namespace ecf { + +InlimitOptions parse_inlimit_value(std::string value) { + if (value.empty()) { + return InlimitOptions{/* tokens = */ 1, /* limited_submission = */ false, /* limited_node = */ false}; + } + + bool limited_submission = false; + constexpr const char* submission_flag = "-s"; + if (ecf::algorithm::contains(value, submission_flag)) { + limited_submission = true; + ecf::algorithm::remove_all(value, submission_flag); + } + + bool limited_node = false; + constexpr const char* node_flag = "-n"; + if (ecf::algorithm::contains(value, node_flag)) { + limited_node = true; + ecf::algorithm::remove_all(value, node_flag); + } + + if (limited_submission && limited_node) { + throw std::runtime_error("AlterCmd: an inlimit cannot be limited for both submission and node"); + } + + ecf::algorithm::trim(value); + + if (value.empty()) { + return InlimitOptions{/* tokens = */ 1, limited_submission, limited_node}; + } + + try { + int tokens = ecf::convert_to(value); + + if (tokens <= 0) { + throw std::runtime_error( + MESSAGE("AlterCmd: the inlimit value must be > 0, but value was: '" << tokens << "'")); + } + + return InlimitOptions{tokens, limited_submission, limited_node}; + } + catch (const ecf::bad_conversion&) { + throw std::runtime_error( + MESSAGE("AlterCmd: the inlimit value, '" << value << "', cannot be converted to an integer")); + } +} + +} // namespace ecf InLimit::InLimit(const std::string& name, const std::string& pathToNode, @@ -35,7 +84,7 @@ InLimit::InLimit(const std::string& name, tokens_(tokens), limit_this_node_only_(limit_this_node_only), limit_submission_(limit_submission) { - if (check && !Str::valid_name(name)) { + if (check && !ecf::algorithm::is_valid_name(name)) { throw std::runtime_error("InLimit::InLimit: Invalid InLimit name: " + name); } if (limit_this_node_only_ && limit_submission_) { @@ -44,6 +93,21 @@ InLimit::InLimit(const std::string& name, } } +InLimit InLimit::make_from_name_and_value(const std::string& name, const std::string& value) { + // Parse the inlimit 'name', separating it into path and actual name + std::string l_path; // This can be empty + std::string l_name; + if (!Extract::pathAndName(name, l_path, l_name)) { + throw std::runtime_error("Invalid inlimit reference to '[': " + name); + } + + // Parse the inlimit 'value', separating tokens from the flags for submission and node only + auto [l_tokens, l_submission, l_node] = parse_inlimit_value(value); + + // Create the actual limit, performing additional validation of the parameters (throwing if not valid) + return InLimit(l_name, l_path, l_tokens, l_node, l_submission); +} + bool InLimit::operator==(const InLimit& rhs) const { if (path_ != rhs.path_) { #ifdef DEBUG @@ -121,7 +185,7 @@ void InLimit::write(std::string& ret) const { } else { ret += path_; - ret += Str::COLON(); + ret += ecf::string_constants::colon; ret += n_; } if (tokens_ != 1) { diff --git a/libs/node/src/ecflow/node/InLimit.hpp b/libs/node/src/ecflow/node/InLimit.hpp index 520613052..821bd8fc7 100644 --- a/libs/node/src/ecflow/node/InLimit.hpp +++ b/libs/node/src/ecflow/node/InLimit.hpp @@ -41,6 +41,24 @@ class InLimit { ); InLimit() = default; + /// + /// @brief Create a new InLimit based on the provided name and value. + /// + /// The name contains the optional path to the node holding the limit and the limit name, separated by a colon ':'. + /// - "" + /// - "[:]" + /// + /// The value contains the inlimit options, including token value and flags for limited submission and node. + /// - "[tokens] [-s] [-n]" + /// + /// @param name the name of the inlimit + /// @param value the value of the inlimit + /// @return the inlimit created based on the provided name and value + /// @throws std::runtime_error if the name is invalid (e.g. missing limit name) or if the value is invalid (e.g. + /// cannot be parsed, or contains incompatible options) + /// + static InLimit make_from_name_and_value(const std::string& name, const std::string& value); + bool operator==(const InLimit& rhs) const; bool operator<(const InLimit& rhs) const { return n_ < rhs.name(); } @@ -83,4 +101,41 @@ class InLimit { void serialize(Archive& ar); }; +namespace ecf { + +struct InlimitOptions +{ + /// + /// @brief the number of tokens considered for the inlimit, the minimum/default is 1 + /// + int tokens = 1; + + /// + /// @brief flag indicating whether the inlimit is limited for submission + /// + bool limited_submission = false; + + /// + /// @brief flag indicating whether the inlimit is limited for node + /// + bool limited_node = false; +}; + +/// +/// @brief Parses the inlimit options from a given value and returns the corresponding InlimitOptions struct. +/// +/// @param value the value to parse, which can be in the following formats: +/// - "" (tokens = 1, limited_submission = false, limited_node = false) +/// - "10" (tokens = 10, limited_submission = false, limited_node = false) +/// - "-s 10" (tokens = 10, limited_submission = true, limited_node = false) +/// - "-n 10" (tokens = 10, limited_submission = false, limited_node = true) +/// @return the InlimitOptions containing the tokens, limited_submission, and limited_node values +/// @throws std::runtime_error for the following cases: +/// - the value, after stripped of '-n' and '-s' options, and 'spaces' trimmed, is not a valid positive integer +/// - both '-n' and '-s' options are specified at the same time +/// +InlimitOptions parse_inlimit_value(std::string value); + +} // namespace ecf + #endif /* ecflow_node_InLimit_HPP */ diff --git a/libs/node/src/ecflow/node/InLimitMgr.cpp b/libs/node/src/ecflow/node/InLimitMgr.cpp index 045db9365..4166ee7a4 100644 --- a/libs/node/src/ecflow/node/InLimitMgr.cpp +++ b/libs/node/src/ecflow/node/InLimitMgr.cpp @@ -68,7 +68,7 @@ bool InLimitMgr::operator==(const InLimitMgr& rhs) const { void InLimitMgr::addInLimit(const InLimit& l, bool check) { if (check && findInLimitByNameAndPath(l)) { - throw std::runtime_error("Add InLimit failed: Duplicate InLimit see node " + node_->debugNodePath()); + throw std::runtime_error("Add InLimit failed: already exists at " + node_->debugNodePath()); } vec_.push_back(l); } @@ -342,11 +342,12 @@ bool InLimitMgr::why(std::vector& vec, bool html) const { } else { if (html) { - auto ref = MESSAGE("[limit]" << i.pathToNode() << Str::COLON() << limit->name()); + auto ref = + MESSAGE("[limit]" << i.pathToNode() << ecf::string_constants::colon << limit->name()); ss << Node::path_href_attribute(ref) << " is full"; } else { - ss << "limit " << i.pathToNode() << Str::COLON() << limit->name() << " is full"; + ss << "limit " << i.pathToNode() << ecf::string_constants::colon << limit->name() << " is full"; } } @@ -422,7 +423,7 @@ limit_ptr InLimitMgr::find_limit(const InLimit& inLimit, // See if the name is defined, as an extern, in which case *DONT* warn: // This is client side specific, since server does not have externs. - if (node_->defs()->find_extern(inLimit.name(), Str::EMPTY())) { + if (node_->defs()->find_extern(inLimit.name(), ecf::string_constants::empty)) { return referencedLimit; // this is empty/NULL } diff --git a/libs/node/src/ecflow/node/JobCreationCtrl.cpp b/libs/node/src/ecflow/node/JobCreationCtrl.cpp index 2ffb5b446..1424b84ff 100644 --- a/libs/node/src/ecflow/node/JobCreationCtrl.cpp +++ b/libs/node/src/ecflow/node/JobCreationCtrl.cpp @@ -16,16 +16,20 @@ #include "ecflow/core/Environment.hpp" void JobCreationCtrl::generate_temp_dir() { - if (auto tmpdir = ecf::environment::fetch("TMPDIR"); tmpdir) { - tempDirForJobGeneration_ = tmpdir.value(); - tempDirForJobGeneration_ += "/ecf_check_job_creation"; - if (fs::exists(tempDirForJobGeneration_)) { - fs::remove_all(tempDirForJobGeneration_); - } - std::cout << "JobCreationCtrl::generate_temp_dir() " << tempDirForJobGeneration_ << "\n"; + if (auto tmpdir_env = ecf::environment::fetch("TMPDIR"); tmpdir_env) { + tempDirForJobGeneration_ = tmpdir_env.value(); + } + else if (auto tmpdir_default = fs::path{"/tmp"}; fs::exists(tmpdir_default)) { + tempDirForJobGeneration_ = tmpdir_default.string(); } else { - throw std::runtime_error( - "JobCreationCtrl::generate_temp_dir(), The environment variable TMPDIR is not defined"); + throw std::runtime_error("JobCreationCtrl::generate_temp_dir(), The environment variable TMPDIR is not " + "defined, and default alternative `/tmp` does not exist"); + } + + tempDirForJobGeneration_ += "/ecf_check_job_creation"; + if (fs::exists(tempDirForJobGeneration_)) { + fs::remove_all(tempDirForJobGeneration_); } + std::cout << "JobCreationCtrl::generate_temp_dir() " << tempDirForJobGeneration_ << "\n"; } diff --git a/libs/node/src/ecflow/node/Jobs.cpp b/libs/node/src/ecflow/node/Jobs.cpp index 3bb8cd3f7..d5bb713e4 100644 --- a/libs/node/src/ecflow/node/Jobs.cpp +++ b/libs/node/src/ecflow/node/Jobs.cpp @@ -55,7 +55,7 @@ bool Jobs::generate(JobsParam& jobsParam) const { if (defs_) { if (defs_->server_state().get_state() == SState::RUNNING) { - const std::vector& suites = defs_->suiteVec(); + const auto& suites = defs_->suites(); for (const suite_ptr& suite : suites) { // SuiteChanged moved into Suite::resolveDependencies. // This ensures the fast path and when suite are not begun we save a ctor/dtor call diff --git a/libs/node/src/ecflow/node/Limit.cpp b/libs/node/src/ecflow/node/Limit.cpp index 918aba1c2..6a22cb671 100644 --- a/libs/node/src/ecflow/node/Limit.cpp +++ b/libs/node/src/ecflow/node/Limit.cpp @@ -21,7 +21,7 @@ Limit::Limit(const std::string& name, int limit) : n_(name), lim_(limit) { - if (!ecf::Str::valid_name(name)) { + if (!ecf::algorithm::is_valid_name(name)) { throw std::runtime_error("Limit::Limit: Invalid Limit name: " + name); } } @@ -31,7 +31,7 @@ Limit::Limit(const std::string& name, int limit, int value, const std::setfindAbsNode(absNodePath_); if (!node.get()) { - if (absNodePath_ != Str::ROOT_PATH()) { + if (absNodePath_ != ecf::string_constants::root_path) { // add more context, about what's suites are in the client defs. std::string error_msg = "CompoundMemento::incremental_sync: could not find path "; error_msg += absNodePath_; error_msg += "\nClient has the following suites: "; - for (const auto& suite : client_def->suiteVec()) { + for (const auto& suite : client_def->suites()) { error_msg += suite->name(); error_msg += ","; } diff --git a/libs/node/src/ecflow/node/MirrorAttr.cpp b/libs/node/src/ecflow/node/MirrorAttr.cpp index f77f4a2aa..c0eff298a 100644 --- a/libs/node/src/ecflow/node/MirrorAttr.cpp +++ b/libs/node/src/ecflow/node/MirrorAttr.cpp @@ -22,7 +22,7 @@ namespace ecf { bool MirrorAttr::is_valid_name(const std::string& name) { - return ecf::Str::valid_name(name); + return ecf::algorithm::is_valid_name(name); } MirrorAttr::MirrorAttr(Node* parent, diff --git a/libs/node/src/ecflow/node/Node.cpp b/libs/node/src/ecflow/node/Node.cpp index 67bc6c1ec..2f44de292 100644 --- a/libs/node/src/ecflow/node/Node.cpp +++ b/libs/node/src/ecflow/node/Node.cpp @@ -69,7 +69,7 @@ Node::Node(const std::string& n, bool check) : n_(n) { if (check) { std::string msg; - if (!Str::valid_name(n, msg)) { + if (!ecf::algorithm::is_valid_name(n, msg)) { throw std::runtime_error("Invalid node name : " + msg); } } @@ -279,7 +279,7 @@ void Node::begin() { if (!mirrors_.empty()) { // In case mirror attributes are available, the node state becomes UNKNOWN - setStateOnly(NState::State::UNKNOWN, true /*force*/, Str::EMPTY() /* additional info to log */, false); + setStateOnly(NState::State::UNKNOWN, true, ecf::string_constants::empty, false); } clearTrigger(); @@ -352,7 +352,7 @@ void Node::requeue(Requeue_args& args) { if (!mirrors_.empty()) { // In case mirror attributes are available, the node state becomes UNKNOWN - setStateOnly(NState::State::UNKNOWN, true /*force*/, Str::EMPTY() /* additional info to log */, false); + setStateOnly(NState::State::UNKNOWN, true, ecf::string_constants::empty, false); } // Set the state without causing any side effects @@ -458,7 +458,7 @@ void Node::reset() { if (!mirrors_.empty()) { // In case of mirror attributes, the node state becomes UNKNOWN - setStateOnly(NState::State::UNKNOWN, true /*force*/, Str::EMPTY() /* additional info to log */, false); + setStateOnly(NState::State::UNKNOWN, true, ecf::string_constants::empty, false); } clearTrigger(); @@ -614,7 +614,7 @@ void Node::initState(int clear_suspended_in_child_nodes, bool log_state_changes) /// Note: DState::SUSPENDED is not a real state, its really a user interaction /// Replace with suspend, and set underlying state as queued suspend(); - setStateOnly(NState::QUEUED, false /*force*/, Str::EMPTY() /* additional info to log */, log_state_changes); + setStateOnly(NState::QUEUED, false, ecf::string_constants::empty, log_state_changes); } else { @@ -624,10 +624,7 @@ void Node::initState(int clear_suspended_in_child_nodes, bool log_state_changes) // convert DState --> NState. // NOTE:: NState does *NOT* have SUSPENDED - setStateOnly(DState::convert(d_st_.state()), - false /*force*/, - Str::EMPTY() /* additional info to log */, - log_state_changes); + setStateOnly(DState::convert(d_st_.state()), false, ecf::string_constants::empty, log_state_changes); } } @@ -985,7 +982,7 @@ bool Node::evaluateTrigger() const { } const std::string& Node::abortedReason() const { - return Str::EMPTY(); + return ecf::string_constants::empty; } void Node::set_state(NState::State newState, bool force, const std::string& additional_info_to_log) { @@ -1571,7 +1568,7 @@ bool Node::variable_dollar_substitution(std::string& cmd) const { break; } - size_t secondPos = cmd.find_first_not_of(Str::ALPHANUMERIC_UNDERSCORE(), firstPos + 1); + size_t secondPos = cmd.find_first_not_of(ecf::string_constants::alphanumeric_underscore_chars, firstPos + 1); if (secondPos == std::string::npos) { secondPos = cmd.size(); } @@ -2435,8 +2432,11 @@ bool Node::checkInvariants(std::string& errorMsg) const { } std::string Node::absNodePath() const { + const auto NR_OF_PATH_ELEMENTS = 16; + const auto BASE_PATH_LENGTH = 64; + std::vector vec; - vec.reserve(Str::reserve_16()); + vec.reserve(NR_OF_PATH_ELEMENTS); vec.push_back(name()); Node* theParent = parent(); while (theParent) { @@ -2444,30 +2444,19 @@ std::string Node::absNodePath() const { theParent = theParent->parent(); } std::string ret; - ret.reserve(Str::reserve_64()); + ret.reserve(BASE_PATH_LENGTH); auto r_end = vec.rend(); for (auto r = vec.rbegin(); r != r_end; ++r) { ret += '/'; ret += *r; } - // // Another algorithm broadly similar results - // std::string ret; ret.reserve(Str::reserve_64()); - // ret += '/'; - // ret += name(); - // Node* theParent = parent(); - // while (theParent) { - // ret.insert(0,"/"); - // ret.insert(1,theParent->name()); - // theParent = theParent->parent(); - // } - return ret; } std::string Node::debugNodePath() const { std::string ret = debugType(); - ret += Str::COLON(); + ret += ecf::string_constants::colon; ret += absNodePath(); return ret; } @@ -2704,13 +2693,15 @@ void Node::sort_attributes(ecf::Attr::Type attr, bool recursive, const std::vect } } } - auto caseInsen = [](const auto& a, const auto& b) { return Str::caseInsLess(a.name(), b.name()); }; + auto caseInsen = [](const auto& a, const auto& b) { + return ecf::algorithm::case_insensitive_less(a.name(), b.name()); + }; state_change_no_ = Ecf::incr_state_change_no(); switch (attr) { case Attr::EVENT: sort(events_.begin(), events_.end(), [](const Event& a, const Event& b) { - return Str::caseInsLess(a.name_or_number(), b.name_or_number()); + return ecf::algorithm::case_insensitive_less(a.name_or_number(), b.name_or_number()); }); break; case Attr::METER: @@ -2721,7 +2712,7 @@ void Node::sort_attributes(ecf::Attr::Type attr, bool recursive, const std::vect break; case Attr::LIMIT: sort(limits_.begin(), limits_.end(), [](const limit_ptr& a, const limit_ptr& b) { - return Str::caseInsLess(a->name(), b->name()); + return ecf::algorithm::case_insensitive_less(a->name(), b->name()); }); break; case Attr::VARIABLE: @@ -2730,12 +2721,12 @@ void Node::sort_attributes(ecf::Attr::Type attr, bool recursive, const std::vect case Attr::ALL: sort(vars_.begin(), vars_.end(), caseInsen); sort(events_.begin(), events_.end(), [](const Event& a, const Event& b) { - return Str::caseInsLess(a.name_or_number(), b.name_or_number()); + return ecf::algorithm::case_insensitive_less(a.name_or_number(), b.name_or_number()); }); sort(meters_.begin(), meters_.end(), caseInsen); sort(labels_.begin(), labels_.end(), caseInsen); sort(limits_.begin(), limits_.end(), [](const limit_ptr& a, const limit_ptr& b) { - return Str::caseInsLess(a->name(), b->name()); + return ecf::algorithm::case_insensitive_less(a->name(), b->name()); }); break; case Attr::UNKNOWN: @@ -2859,56 +2850,8 @@ const std::vector& Node::generics() const { return generics_; } -std::vector::const_iterator Node::zombie_begin() const { - if (misc_attrs_) { - return misc_attrs_->zombie_begin(); - } - return zombies_.begin(); -} -std::vector::const_iterator Node::zombie_end() const { - if (misc_attrs_) { - return misc_attrs_->zombie_end(); - } - return zombies_.end(); -} -std::vector::const_iterator Node::verify_begin() const { - if (misc_attrs_) { - return misc_attrs_->verify_begin(); - } - return verifys_.begin(); -} -std::vector::const_iterator Node::verify_end() const { - if (misc_attrs_) { - return misc_attrs_->verify_end(); - } - return verifys_.end(); -} -std::vector::const_iterator Node::queue_begin() const { - if (misc_attrs_) { - return misc_attrs_->queue_begin(); - } - return queues_.begin(); -} -std::vector::const_iterator Node::queue_end() const { - if (misc_attrs_) { - return misc_attrs_->queue_end(); - } - return queues_.end(); -} -std::vector::const_iterator Node::generic_begin() const { - if (misc_attrs_) { - return misc_attrs_->generic_begin(); - } - return generics_.begin(); -} -std::vector::const_iterator Node::generic_end() const { - if (misc_attrs_) { - return misc_attrs_->generic_end(); - } - return generics_.end(); -} - namespace ecf { + std::vector inherited_variables(const Node& node) { std::vector all; const Node* current = &node; diff --git a/libs/node/src/ecflow/node/Node.hpp b/libs/node/src/ecflow/node/Node.hpp index 511e006a1..dd55721d7 100644 --- a/libs/node/src/ecflow/node/Node.hpp +++ b/libs/node/src/ecflow/node/Node.hpp @@ -440,8 +440,36 @@ class Node : public std::enable_shared_from_this { const MiscAttrs* get_misc_attrs() const { return misc_attrs_.get(); } - [[deprecated]] virtual void gen_variables(std::vector&) const; + /// + /// @brief Retrieves the generated variables associated with this node. + /// + /// @note generated variables are automatically created from repeat attributes and provide + /// additional context about the current repeat iteration. + /// @note this virtual function allows derived classes (e.g., Submittable) to add additional + /// generated variables beyond those from the repeat attribute. + /// + /// @param vars Reference to a vector that will be populated with the generated variables. + /// The function appends to the vector rather than replacing its contents. + /// + /// @see repeat attribute documentation for details on the specific generated variables created + /// by each repeat type. + /// + virtual void gen_variables(std::vector& vars) const; + + /// + /// @brief Retrieves the generated variables associated with this node. + /// + /// This is a convenience wrapper around gen_variables(std::vector&) that + /// creates and returns a new vector containing the generated variables. + /// + /// @note For performance-critical code where a vector already exists, prefer using + /// gen_variables(std::vector&) to avoid unnecessary allocations. + /// + /// @return A vector containing all generated variables for this node's repeat attribute. + /// Returns an empty vector if the node has no repeat attribute. + /// std::vector gen_variables() const; + bool getLabelValue(const std::string& name, std::string& value) const; bool getLabelNewValue(const std::string& name, std::string& value) const; @@ -904,45 +932,6 @@ class Node : public std::enable_shared_from_this { void clear(); /// Clear *ALL* internal attributes void delete_attributes(); -private: /// For use by python interface, - friend void export_Node(); - friend void export_Task(); - friend void export_SuiteAndFamily(); - std::vector::const_iterator meter_begin() const { return meters_.begin(); } - std::vector::const_iterator meter_end() const { return meters_.end(); } - std::vector::const_iterator event_begin() const { return events_.begin(); } - std::vector::const_iterator event_end() const { return events_.end(); } - std::vector