diff --git a/Viewer/ecflowUI/src/CommandDesignerWidget.cpp b/Viewer/ecflowUI/src/CommandDesignerWidget.cpp index 05a7cd350..10ff4f522 100644 --- a/Viewer/ecflowUI/src/CommandDesignerWidget.cpp +++ b/Viewer/ecflowUI/src/CommandDesignerWidget.cpp @@ -15,6 +15,7 @@ #include "CustomCommandHandler.hpp" #include "NodeExpression.hpp" #include "NodeQueryResult.hpp" +#include "ecflow/base/HelpCatalog.hpp" #include "ecflow/core/Child.hpp" #include "ecflow/core/Str.hpp" @@ -228,16 +229,14 @@ void CommandDesignerWidget::showCommandHelp(QListWidgetItem* item, bool showFull ); if (od) { - // get the description, but only take the first line - std::vector lines; - ecf::algorithm::split_at(lines, od->description(), "\n"); - if (!lines.empty()) { - QString text = qCommand + QString(": "); - commandHelpLabel_->setText(text + QString::fromStdString(lines[0])); - } + std::string summary = ecf::HelpCatalog::summary_for(od->long_name()).value_or(ecf::HelpCatalog::not_provided); + QString text = qCommand + QString(": "); + commandHelpLabel_->setText(text + QString::fromStdString(summary)); if (showFullHelp) { - commandManPage_->setText(qCommand + "\n\n" + QString::fromStdString(od->description())); + std::string description = + ecf::HelpCatalog::description_for(od->long_name()).value_or(ecf::HelpCatalog::not_provided); + commandManPage_->setText(qCommand + "\n\n" + QString::fromStdString(description)); } } else { diff --git a/cmake/CompilerOptions.cmake b/cmake/CompilerOptions.cmake index a7ec218d2..8d890e8db 100644 --- a/cmake/CompilerOptions.cmake +++ b/cmake/CompilerOptions.cmake @@ -83,6 +83,7 @@ if (HAVE_WARNINGS) $<$,$,$,9.0.0>>:-Wno-deprecated-copy> # silence warnings in Qt5 related headers $<$,$>:-Wno-unused-result> $<$,$>:-Wno-unused-parameter> + $<$,$>:-Wno-overlength-strings> # silence warning for the embedded CLI help manifest string literal (generated_client_help.hpp) ## Clang (MacOS Homebrew, AMD Clang-base) $<$,$>:-Wno-deprecated-copy-with-user-provided-copy> # silence warnings in Qt5 related headers $<$,$>:-Wno-missing-field-initializers> # silence warning in Boost.Python related headers @@ -90,6 +91,7 @@ if (HAVE_WARNINGS) $<$,$,$,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__) + $<$,$>:-Wno-overlength-strings> # silence warning for the embedded CLI help manifest string literal (generated_client_help.hpp) ## Clang (MacOS AppleClang) $<$,$>:-Wno-deprecated-copy-with-user-provided-copy> # silence warnings in Qt5 related headers $<$,$>:-Wno-missing-field-initializers> # silence warning in Boost.Python related headers @@ -97,10 +99,12 @@ if (HAVE_WARNINGS) $<$,$,$,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__) + $<$,$>:-Wno-overlength-strings> # silence warning for the embedded CLI help manifest string literal (generated_client_help.hpp) ## Clang (Intel Clang-based) $<$,$>:-Wno-deprecated-copy-with-user-provided-copy> # silence warnings in Qt5 related headers $<$,$>:-Wno-missing-field-initializers> # silence warning in Boost.Python related headers $<$,$>:-Wno-unused-parameter> + $<$,$>:-Wno-overlength-strings> # silence warning for the embedded CLI help manifest string literal (generated_client_help.hpp) ) endif () diff --git a/cmake/GenerateClientHelp.cmake b/cmake/GenerateClientHelp.cmake new file mode 100644 index 000000000..2ff131bbc --- /dev/null +++ b/cmake/GenerateClientHelp.cmake @@ -0,0 +1,75 @@ +# 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. +# + +# +# Generates a C++ header embedding docs/client_api/help.json as a raw string +# literal, so ecflow_client can carry its own CLI help data without reading an +# external file at runtime. +# +# Invoked as a build-time custom command (not configure_file), so the header +# regenerates whenever the manifest changes, without requiring a full CMake +# reconfigure. +# +# Expected variables (passed via -D on the command line): +# MANIFEST path to docs/client_api/help.json +# OUTPUT path to the generated header to write +# + +if(NOT DEFINED MANIFEST) + message(FATAL_ERROR "GenerateClientHelp.cmake: MANIFEST is not set") +endif() +if(NOT DEFINED OUTPUT) + message(FATAL_ERROR "GenerateClientHelp.cmake: OUTPUT is not set") +endif() + +file(READ "${MANIFEST}" manifest_content) + +if(manifest_content MATCHES "\\)ecflow_help\"") + message(FATAL_ERROR + "GenerateClientHelp.cmake: ${MANIFEST} contains the raw-string delimiter " + ")ecflow_help\", which would break the generated header. Pick a different " + "delimiter here and in generated_client_help.hpp's consumers.") +endif() + +file(WRITE "${OUTPUT}" "/* + * 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. + */ + +/* + * Generated from docs/client_api/help.json -- see cmake/GenerateClientHelp.cmake. + * + * DO NOT EDIT DIRECTLY. + */ + +#ifndef ecflow_base_generated_client_help_HPP +#define ecflow_base_generated_client_help_HPP + +#include + +/* + * Workaround for EDG-based compilers (e.g. NVidia) that complain about + * constexpr value exceeding the maximum allowed size. + */ +#if defined(__EDG__) +#define ECFLOW_HELP_CONSTEXPR const +#else +#define ECFLOW_HELP_CONSTEXPR constexpr +#endif + +inline ECFLOW_HELP_CONSTEXPR std::string_view client_help_json = R\"ecflow_help( +${manifest_content})ecflow_help\"; + +#endif /* ecflow_base_generated_client_help_HPP */ +") diff --git a/docs/CMakeLists.txt b/docs/CMakeLists.txt index b7bd83af7..d274da861 100644 --- a/docs/CMakeLists.txt +++ b/docs/CMakeLists.txt @@ -22,6 +22,28 @@ else() ecbuild_fatal_error("Sphinx executable not present at ${SPHINX_EXECUTABLE}") endif() +# +# Validate the ecFlow CLI help manifest +# +# This is the single source of ecflow_client CLI help metadata, eventually consumed +# both by the C++ client (embedded at build time) and by this documentation build. +# Validation fails fast on an invalid manifest, before Sphinx or the (future) +# manifest-driven build.py consume it. +# + +add_custom_command( + OUTPUT validate_ecflow_client_help_manifest + COMMAND + ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/client_api/validate_help_manifest.py + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/client_api + USES_TERMINAL + DEPENDS + ${CMAKE_CURRENT_SOURCE_DIR}/client_api/help.json + ${CMAKE_CURRENT_SOURCE_DIR}/client_api/help.schema.json + ${CMAKE_CURRENT_SOURCE_DIR}/client_api/validate_help_manifest.py +) +add_custom_target(ecflow_client_help_validate DEPENDS validate_ecflow_client_help_manifest) + # # (Re-)generate ecFlow CLI documentation # @@ -32,8 +54,9 @@ add_custom_command( COMMAND ${CMAKE_COMMAND} -E rm -rf ${CMAKE_CURRENT_SOURCE_DIR}/client_api/api/* COMMAND - ${CMAKE_COMMAND} -E env "PATH=${CMAKE_BINARY_DIR}/bin:$ENV{PATH}" - ${CMAKE_CURRENT_SOURCE_DIR}/client_api/build.py + ${CMAKE_CURRENT_SOURCE_DIR}/client_api/build.py + COMMAND + ${CMAKE_CURRENT_SOURCE_DIR}/client_api/update_internal.py COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_BINARY_DIR}/client_api/api @@ -52,9 +75,15 @@ add_custom_command( ${CMAKE_CURRENT_SOURCE_DIR}/client_api WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/client_api USES_TERMINAL - DEPENDS ecflow_client + DEPENDS + ${CMAKE_CURRENT_SOURCE_DIR}/client_api/build.py + ${CMAKE_CURRENT_SOURCE_DIR}/client_api/update_internal.py + ${CMAKE_CURRENT_SOURCE_DIR}/client_api/command_internals.rst + ${CMAKE_CURRENT_SOURCE_DIR}/client_api/help.json + ${CMAKE_CURRENT_SOURCE_DIR}/client_api/help.schema.json + ${CMAKE_CURRENT_SOURCE_DIR}/client_api/validate_help_manifest.py ) -add_custom_target(ecflow_client_docs DEPENDS generate_ecflow_client_docs) +add_custom_target(ecflow_client_docs DEPENDS generate_ecflow_client_docs ecflow_client_help_validate) # diff --git a/docs/client_api/api/abort.rst b/docs/client_api/api/abort.rst index cb13cec43..9bc5ac74e 100644 --- a/docs/client_api/api/abort.rst +++ b/docs/client_api/api/abort.rst @@ -23,17 +23,21 @@ The following help text is generated by :code:`ecflow_client --help=abort` Mark task as aborted. For use in the '.ecf' script file *only* Hence the context is supplied via environment variables - arg1 = (optional) string(reason) - Optionally provide a reason why the abort was raised - If this child command is a zombie, then the default action will be to *block*. + Argument(s): + + reason: (optional) string + # Optionally provide a reason why the abort was raised + + If this task command is a zombie, then the default action will be to *block*. The default can be overridden by using zombie attributes. Otherwise the blocking period is defined by ECF_TIMEOUT. Usage: - ecflow_client --abort=reasonX - The client considers, for both user and child commands, the following environment variables: + --abort= + + The client considers, for both user and task commands, the following environment variables: ECF_HOST [mandatory*] The main server hostname; default value is 'localhost' @@ -50,20 +54,20 @@ The following help text is generated by :code:`ecflow_client --help=abort` with the server, either by setting the environment variables or by specifying the command line options. - The following environment variables are used specifically by child commands: + The following environment variables are used specifically by task commands: ECF_NAME [mandatory] Full path name to the task ECF_PASS [mandatory] The job password (defined by the server, and used to authenticate client requests) ECF_TRYNO [mandatory] - The run number of the job (defined by the server, and used in job/output file name generation. - ECF_RID [mandatory] + The run number of the job (defined by the server, and used in job/output file name generation). + ECF_RID [mandatory*] The process identifier. Supports identifying zombies and automated killing of running jobs ECF_TIMEOUT [optional] Maximum time in *seconds* for client to deliver message to main server; default is 24 hours ECF_DENIED [optional] - Allows task to exit with an error, upon connection failure, thus avoids ECF_TIMEOUTs wait. + Allows task to exit with an error, upon connection failure, thus avoids ECF_TIMEOUT's wait. NO_ECF [optional] If set, ecflow_client exits immediately with success; useful to test the scripts without a server diff --git a/docs/client_api/api/alter.rst b/docs/client_api/api/alter.rst index 22ed98368..277c283db 100644 --- a/docs/client_api/api/alter.rst +++ b/docs/client_api/api/alter.rst @@ -114,48 +114,47 @@ The following help text is generated by :code:`ecflow_client --help=alter` Usage: - ecflow_client --alter=add variable "value" / # add server variable - ecflow_client --alter=add variable "value" /path/to/node # add node variable + --alter=add variable "value" / # add server variable + --alter=add variable "value" /path/to/node # add node variable - ecflow_client --alter=add time "+00:20" /path/to/node + --alter=add time "+00:20" /path/to/node - ecflow_client --alter=add date "01.*.*" /path/to/node + --alter=add date "01.*.*" /path/to/node - ecflow_client --alter=add day "sunday" /path/to/node + --alter=add day "sunday" /path/to/node - ecflow_client --alter=add label "label_value" /path/to/node + --alter=add label "label_value" /path/to/node - ecflow_client --alter=add event "set"|"clear" /path/to/node + --alter=add event "set"|"clear" /path/to/node - ecflow_client --alter=add meter ",,value" /path/to/node + --alter=add meter ",,value" /path/to/node - ecflow_client --alter=add late "-s 00:01 -a 14:30 -c +00:01" /path/to/node + --alter=add late "-s 00:01 -a 14:30 -c +00:01" /path/to/node - ecflow_client --alter=add limit mars "100" /path/to/node + --alter=add limit mars "100" /path/to/node - ecflow_client --alter=add inlimit /path/to/node/withlimit:limit_name "10" /path/to/node + --alter=add inlimit /path/to/node/withlimit:limit_name "10" /path/to/node - ecflow_client --alter=add inlimit /path/to/node/withlimit:limit_name "-s 10" /path/to/node + --alter=add inlimit /path/to/node/withlimit:limit_name "-s 10" /path/to/node - ecflow_client --alter=add inlimit /path/to/node/withlimit:limit_name "-n 10" /path/to/node + --alter=add inlimit /path/to/node/withlimit:limit_name "-n 10" /path/to/node - # zombie attributes have the following structure: - `zombie_type`:(`client_side_action` | `server_side_action`):`child`:`zombie_life_time` - zombie_type = "user" | "ecf" | "path" | "ecf_pid" | "ecf_passwd" | "ecf_pid_passwd" - client_side_action = "fob" | "fail" | "block" - server_side_action = "adopt" | "delete" | "kill" - child = "init" | "event" | "meter" | "label" | "wait" | "abort" | "complete" | "queue" - zombie_life_time = unsigned integer default: user(300), ecf(3600), path(900) minimum is 60 + # zombie attributes have the following structure: + `zombie_type`:(`client_side_action` | `server_side_action`):`child`:`zombie_life_time` + zombie_type = "user" | "ecf" | "path" | "ecf_pid" | "ecf_passwd" | "ecf_pid_passwd" + client_side_action = "fob" | "fail" | "block" + server_side_action = "adopt" | "delete" | "kill" + child = "init" | "event" | "meter" | "label" | "wait" | "abort" | "complete" | "queue" + zombie_life_time = unsigned integer default: user(300), ecf(3600), path(900) minimum is 60 - ecflow_client --alter=add zombie "ecf:fail::" /path/to/node # ask system zombies to fail - ecflow_client --alter=add zombie "user:fail::" /path/to/node # ask user generated zombies to fail - ecflow_client --alter=add zombie "path:fail::" /path/to/node # ask path zombies to fail + --alter=add zombie "ecf:fail::" /path/to/node # ask system zombies to fail + --alter=add zombie "user:fail::" /path/to/node # ask user generated zombies to fail + --alter=add zombie "path:fail::" /path/to/node # ask path zombies to fail - ecflow_client --alter=delete variable FRED /path/to/node # delete variable FRED - ecflow_client --alter=delete variable /path/to/node # delete *ALL* variables on the given snode + --alter=delete variable FRED /path/to/node # delete variable FRED + --alter=delete variable /path/to/node # delete *ALL* variables on the given snode - - The client considers, for both user and child commands, the following environment variables: + The client considers, for both user and task commands, the following environment variables: ECF_HOST [mandatory*] The main server hostname; default value is 'localhost' diff --git a/docs/client_api/api/archive.rst b/docs/client_api/api/archive.rst index 0c5b8dc8c..618a83f4e 100644 --- a/docs/client_api/api/archive.rst +++ b/docs/client_api/api/archive.rst @@ -53,12 +53,13 @@ The following help text is generated by :code:`ecflow_client --help=archive` endfamily endsuite - Usage:: - --archive=/s1 # archive suite s1 - --archive=/s1/f1 /s2 # archive family /s1/f1 and suite /s2 - --archive=force /s1 /s2 # archive suites /s1,/s2 even if they have active tasks + Usage: - The client considers, for both user and child commands, the following environment variables: + --archive=/s1 # archive suite s1 + --archive=/s1/f1 /s2 # archive family /s1/f1 and suite /s2 + --archive=force /s1 /s2 # archive suites /s1,/s2 even if they have active tasks + + The client considers, for both user and task commands, the following environment variables: ECF_HOST [mandatory*] The main server hostname; default value is 'localhost' diff --git a/docs/client_api/api/begin.rst b/docs/client_api/api/begin.rst index 9f714abff..68f6b7ee5 100644 --- a/docs/client_api/api/begin.rst +++ b/docs/client_api/api/begin.rst @@ -23,18 +23,22 @@ The following help text is generated by :code:`ecflow_client --help=begin` Begin playing the definition in the server. Expects zero or a single quoted string. - arg1 = suite-name | Nothing | force - play the chosen suite, if no arg specified, play all suites, in the definition - force means reset the begin status on the suites and bypass checks. - This is only required if suite-name is provide as the first argument - Using force can cause the creation of zombies + + Argument(s): + + suite-name: (optional) + # The name of the selected suite; if not specified, means all suites. + # Including `--force` in the value means reset the begin status and bypass checks. + # Important: using `--force` might cause the appearance of zombies. + Usage: - --begin # will begin all suites - --begin="--force" # reset and then begin all suites, bypassing any checks. Note: string must be quoted - --begin="mySuite" # begin playing suite of name 'mySuite' - --begin="mySuite --force" # reset and begin playing suite 'mySuite', bypass check - The client considers, for both user and child commands, the following environment variables: + --begin # will begin all suites + --begin="--force" # reset and then begin all suites, bypassing any checks. Note: string must be quoted + --begin="mySuite" # begin playing suite of name 'mySuite' + --begin="mySuite --force" # reset and begin playing suite 'mySuite', bypass check + + The client considers, for both user and task commands, the following environment variables: ECF_HOST [mandatory*] The main server hostname; default value is 'localhost' diff --git a/docs/client_api/api/ch_add.rst b/docs/client_api/api/ch_add.rst index dbd1dbc60..2eaf25851 100644 --- a/docs/client_api/api/ch_add.rst +++ b/docs/client_api/api/ch_add.rst @@ -22,15 +22,25 @@ The following help text is generated by :code:`ecflow_client --help=ch_add` ------ Add a set of suites, to an existing handle. - arg1 = handle(integer) # The handle must be an integer that is > 0 - arg2 = names # should be a list of suite names, names not in the definition are ignored + + Argument(s): + + handle: integer + # The handle value (must be an integer > 0) + + names + # A space separated list of suite names. + # Note: names not in the definition are ignored. + Usage: - --ch_add=10 s2 s3 s4 # add suites s2 s3,s4 to handle 10 + + --ch_add=10 s2 s3 s4 # add suites s2 s3,s4 to handle 10 + An error is returned if the handle had not previously been registered The handle is created with --ch_register command To list all suites and handles use --ch_suites - The client considers, for both user and child commands, the following environment variables: + The client considers, for both user and task commands, the following environment variables: ECF_HOST [mandatory*] The main server hostname; default value is 'localhost' diff --git a/docs/client_api/api/ch_auto_add.rst b/docs/client_api/api/ch_auto_add.rst index 8c7d680d3..cbf4c51f9 100644 --- a/docs/client_api/api/ch_auto_add.rst +++ b/docs/client_api/api/ch_auto_add.rst @@ -22,15 +22,24 @@ The following help text is generated by :code:`ecflow_client --help=ch_auto_add` ----------- Change an existing handle so that new suites can be added automatically. - arg1 = handle(integer) # The handle must be an integer that is > 0 - arg2 = true | false # true means add new suites to my list, when they are created + + Argument(s): + + handle: integer + # The handle value (must be an integer > 0) + + auto-add: true | false + # `true` means add new suites to my list, when they are created + Usage: - --ch_auto_add=10 true # modify handle 10 so that new suites, get added automatically to it - --ch_auto_add=10 false # modify handle 10 so that no new suites are added + + --ch_auto_add=10 true # modify handle 10 so that new suites, get added automatically to it + --ch_auto_add=10 false # modify handle 10 so that no new suites are added + The handle is created with --ch_register command To list all suites and handles use --ch_suites - The client considers, for both user and child commands, the following environment variables: + The client considers, for both user and task commands, the following environment variables: ECF_HOST [mandatory*] The main server hostname; default value is 'localhost' diff --git a/docs/client_api/api/ch_drop.rst b/docs/client_api/api/ch_drop.rst index 896f338b2..2463d0804 100644 --- a/docs/client_api/api/ch_drop.rst +++ b/docs/client_api/api/ch_drop.rst @@ -23,14 +23,21 @@ The following help text is generated by :code:`ecflow_client --help=ch_drop` Drop/de-register the client handle. Un-used handle should be dropped otherwise they will stay, in the server. - arg1 = handle(integer) # The handle must be an integer that is > 0 + + Argument(s): + + handle: integer + # The handle value (must be an integer > 0) + Usage: - --ch_drop=10 # drop the client handle 10 - An error is returned if the handle had not previously been registered + + --ch_drop=10 # drop the client handle 10 + + An error is returned if the handle was not previously been registered. The handle stored on the local client is set to zero To list all suites and handles use --ch_suites - The client considers, for both user and child commands, the following environment variables: + The client considers, for both user and task commands, the following environment variables: ECF_HOST [mandatory*] The main server hostname; default value is 'localhost' diff --git a/docs/client_api/api/ch_drop_user.rst b/docs/client_api/api/ch_drop_user.rst index 390130bb2..faae5dbef 100644 --- a/docs/client_api/api/ch_drop_user.rst +++ b/docs/client_api/api/ch_drop_user.rst @@ -24,14 +24,21 @@ The following help text is generated by :code:`ecflow_client --help=ch_drop_user Drop/de-register all handles associated with the given user. If no user provided will drop for current user. Client must ensure un-used handle are dropped otherwise they will stay, in the server. - arg1 = user # The user to be drooped, if left empty drop current user + + Argument(s): + + user + # The user to be dropped; if empty, means drop current user + Usage: - --ch_drop_user=ma0 # drop all handles associated with user ma0 - --ch_drop_user # drop all handles associated with current user + + --ch_drop_user=ma0 # drop all handles associated with user ma0 + --ch_drop_user # drop all handles associated with current user + An error is returned if no registered handles To list all suites and handles use --ch_suites - The client considers, for both user and child commands, the following environment variables: + The client considers, for both user and task commands, the following environment variables: ECF_HOST [mandatory*] The main server hostname; default value is 'localhost' diff --git a/docs/client_api/api/ch_register.rst b/docs/client_api/api/ch_register.rst index d837ed276..4bd1c68ba 100644 --- a/docs/client_api/api/ch_register.rst +++ b/docs/client_api/api/ch_register.rst @@ -29,17 +29,28 @@ The following help text is generated by :code:`ecflow_client --help=ch_register` Deleted suites will stay registered, and must be explicitly removed/dropped. Note: Suites can be registered before they are loaded into the server This command affects news() and sync() commands - arg1 = true | false # true means add new suites to my list, when they are created - arg2 = names # should be a list of suite names, names not in the definition are ignored + + Argument(s): + + auto-add: true | false + # `true` means add any new suites to user list, when they are created + + names + # A space separated list of suite names. + # Note: names not in the definition are ignored. + Usage: - --ch_register=true s1 s2 s3 # register interest in suites s1,s2,s3 and any new suites - --ch_register=false s1 s2 s3 # register interest in suites s1,s2,s3 only - --ch_register=false # register handle, suites will be added later on - --ch_register=1 true s1 s2 s3 # drop handle 1 then register interest in suites s1,s2,s3 and any new suites - # The client handle as the first argument is typically used by GUI/python # When the client handle is no zero, then it is dropped first + + --ch_register=true s1 s2 s3 # register interest in suites s1,s2,s3 and any new suites + --ch_register=false s1 s2 s3 # register interest in suites s1,s2,s3 only + --ch_register=false # register handle, suites will be added later on + --ch_register=1 true s1 s2 s3 # drop handle 1 then register interest in suites s1,s2,s3 and any new suites + # The client handle as the first argument is typically used by GUI/python + # When the client handle is no zero, then it is dropped first + To list all suites and handles use --ch_suites - The client considers, for both user and child commands, the following environment variables: + The client considers, for both user and task commands, the following environment variables: ECF_HOST [mandatory*] The main server hostname; default value is 'localhost' diff --git a/docs/client_api/api/ch_rem.rst b/docs/client_api/api/ch_rem.rst index 42bf73e78..49ccbc874 100644 --- a/docs/client_api/api/ch_rem.rst +++ b/docs/client_api/api/ch_rem.rst @@ -22,14 +22,25 @@ The following help text is generated by :code:`ecflow_client --help=ch_rem` ------ Remove a set of suites, from an existing handle. - arg1 = handle(integer) # The handle must be an integer that is > 0 - arg2 = names # should be a list of suite names, names not in the definition are ignored + + Argument(s): + + handle: integer + # The handle value (must be an integer > 0) + + names + # A space separated list of suite names. + # Note: names not in the definition are ignored. + Usage: - --ch_rem=10 s2 s3 s4 # remove suites s2 s3,s4 from handle 10 + + --ch_rem=10 s2 s3 s4 # remove suites s2 s3,s4 from handle 10 + The handle is created with --ch_register command + To list all suites and handles use --ch_suites - The client considers, for both user and child commands, the following environment variables: + The client considers, for both user and task commands, the following environment variables: ECF_HOST [mandatory*] The main server hostname; default value is 'localhost' diff --git a/docs/client_api/api/ch_suites.rst b/docs/client_api/api/ch_suites.rst index 7d902e632..dbfc1e9f6 100644 --- a/docs/client_api/api/ch_suites.rst +++ b/docs/client_api/api/ch_suites.rst @@ -23,7 +23,7 @@ The following help text is generated by :code:`ecflow_client --help=ch_suites` Shows all the client handles, and the suites they reference - The client considers, for both user and child commands, the following environment variables: + The client considers, for both user and task commands, the following environment variables: ECF_HOST [mandatory*] The main server hostname; default value is 'localhost' diff --git a/docs/client_api/api/check.rst b/docs/client_api/api/check.rst index 8663e5c74..6dec4ada1 100644 --- a/docs/client_api/api/check.rst +++ b/docs/client_api/api/check.rst @@ -26,12 +26,14 @@ The following help text is generated by :code:`ecflow_client --help=check` (Note: On the client side unresolved paths in trigger expressions must have an associated 'extern' specified) arg = [ _all_ | / | list of node paths ] + Usage: - --check=_all_ # Checks all the suites - --check=/ # Checks all the suites - --check=/s1 /s2/f1/t1 # Check suite /s1 and task t1 - The client considers, for both user and child commands, the following environment variables: + --check=_all_ # Checks all the suites + --check=/ # Checks all the suites + --check=/s1 /s2/f1/t1 # Check suite /s1 and task t1 + + The client considers, for both user and task commands, the following environment variables: ECF_HOST [mandatory*] The main server hostname; default value is 'localhost' diff --git a/docs/client_api/api/checkJobGenOnly.rst b/docs/client_api/api/checkJobGenOnly.rst index 82c387d7c..d73f3d98e 100644 --- a/docs/client_api/api/checkJobGenOnly.rst +++ b/docs/client_api/api/checkJobGenOnly.rst @@ -27,7 +27,7 @@ The following help text is generated by :code:`ecflow_client --help=checkJobGenO arg = node path | arg = NULL If no node path specified generates for all Tasks in the definition. For Test only - The client considers, for both user and child commands, the following environment variables: + The client considers, for both user and task commands, the following environment variables: ECF_HOST [mandatory*] The main server hostname; default value is 'localhost' diff --git a/docs/client_api/api/check_pt.rst b/docs/client_api/api/check_pt.rst index 0e3154e11..a2d42d616 100644 --- a/docs/client_api/api/check_pt.rst +++ b/docs/client_api/api/check_pt.rst @@ -30,31 +30,37 @@ The following help text is generated by :code:`ecflow_client --help=check_pt` or by using --alter functionality Note excessive save times can interfere with job scheduling. The alarm threshold can be changed. See below. - arg1 = (optional) mode [ never | on_time | on_time: | always | ] - never : Never check point the definition in the server - on_time : Turn on automatic check pointing at interval stored on server - on_time : Turn on automatic check point, with the specified interval in seconds - alarm : Modify the alarm notification time for check pt saving to disk - always : Check point at any change in node tree, *NOT* recommended for large definitions - : This specifies the interval in seconds when server should automatically check pt. - This will only take effect of mode is on_time/CHECK_ON_TIME - Should ideally be a value greater than 60 seconds, default is 120 seconds + + Argument(s): + + mode: (optional) [ never | on_time | on_time: | always | ] + # never : Never check point the definition in the server + # on_time : Turn on automatic check pointing at interval stored on server + # on_time : Turn on automatic check point, with the specified interval in seconds + # alarm : Modify the alarm notification time for check pt saving to disk + # always : Check point at any change in node tree, *NOT* recommended for large definitions + # : This specifies the interval in seconds when server should automatically check pt. + # + # This only takes effect if mode is on_time/CHECK_ON_TIME + # The default value is 120 seconds, and should be greater than 60 seconds + Usage: - --check_pt - Immediately check point the definition held in the server - --check_pt=never - Switch off check pointing - --check_pt=on_time - Start automatic check pointing at the interval stored in the server - --check_pt=180 - Change the check pt interval to 180 seconds - --check_pt=on_time:90 - Change mode and interval, to automatic check pointing every 90 seconds - --check_pt=alarm:35 - Change the alarm time for check pt saves. i.e if saving the check pt takes longer than 35 seconds - set the late flag on the server. - The client considers, for both user and child commands, the following environment variables: + --check_pt + Immediately check point the definition held in the server + --check_pt=never + Switch off check pointing + --check_pt=on_time + Start automatic check pointing at the interval stored in the server + --check_pt=180 + Change the check pt interval to 180 seconds + --check_pt=on_time:90 + Change mode and interval, to automatic check pointing every 90 seconds + --check_pt=alarm:35 + Change the alarm time for check pt saves. i.e if saving the check pt takes longer than 35 seconds + set the late flag on the server. + + The client considers, for both user and task commands, the following environment variables: ECF_HOST [mandatory*] The main server hostname; default value is 'localhost' diff --git a/docs/client_api/api/complete.rst b/docs/client_api/api/complete.rst index a1adbd958..64ed6d72a 100644 --- a/docs/client_api/api/complete.rst +++ b/docs/client_api/api/complete.rst @@ -24,18 +24,23 @@ The following help text is generated by :code:`ecflow_client --help=complete` Mark task as complete. For use in the '.ecf' script file *only* Hence the context is supplied via environment variables - If this child command is a zombie, then the default action will be to *block*. + If this task command is a zombie, then the default action will be to *block*. The default can be overridden by using zombie attributes. Otherwise the blocking period is defined by ECF_TIMEOUT. The init command allows variables to be added, and complete command allows for them to be removed. - arg1(--remove)(optional) = a list of variables to removed from this task + + Argument(s): + + --remove: (optional) + # A list of space separated variables to removed from this task Usage: - ecflow_client --complete - ecflow_client --complete --remove name1 name2 # delete variables name1 and name2 on the task - The client considers, for both user and child commands, the following environment variables: + --complete + --complete --remove name1 name2 # delete variables name1 and name2 on the task + + The client considers, for both user and task commands, the following environment variables: ECF_HOST [mandatory*] The main server hostname; default value is 'localhost' @@ -52,20 +57,20 @@ The following help text is generated by :code:`ecflow_client --help=complete` with the server, either by setting the environment variables or by specifying the command line options. - The following environment variables are used specifically by child commands: + The following environment variables are used specifically by task commands: ECF_NAME [mandatory] Full path name to the task ECF_PASS [mandatory] The job password (defined by the server, and used to authenticate client requests) ECF_TRYNO [mandatory] - The run number of the job (defined by the server, and used in job/output file name generation. - ECF_RID [mandatory] + The run number of the job (defined by the server, and used in job/output file name generation). + ECF_RID [mandatory*] The process identifier. Supports identifying zombies and automated killing of running jobs ECF_TIMEOUT [optional] Maximum time in *seconds* for client to deliver message to main server; default is 24 hours ECF_DENIED [optional] - Allows task to exit with an error, upon connection failure, thus avoids ECF_TIMEOUTs wait. + Allows task to exit with an error, upon connection failure, thus avoids ECF_TIMEOUT's wait. NO_ECF [optional] If set, ecflow_client exits immediately with success; useful to test the scripts without a server diff --git a/docs/client_api/api/debug_server_off.rst b/docs/client_api/api/debug_server_off.rst index 7a32da9e1..67d6fb6c7 100644 --- a/docs/client_api/api/debug_server_off.rst +++ b/docs/client_api/api/debug_server_off.rst @@ -23,7 +23,7 @@ The following help text is generated by :code:`ecflow_client --help=debug_server Disables debug output from the server - The client considers, for both user and child commands, the following environment variables: + The client considers, for both user and task commands, the following environment variables: ECF_HOST [mandatory*] The main server hostname; default value is 'localhost' diff --git a/docs/client_api/api/debug_server_on.rst b/docs/client_api/api/debug_server_on.rst index ce2b0c48e..3f00b2872 100644 --- a/docs/client_api/api/debug_server_on.rst +++ b/docs/client_api/api/debug_server_on.rst @@ -23,7 +23,7 @@ The following help text is generated by :code:`ecflow_client --help=debug_server Enables debug output from the server - The client considers, for both user and child commands, the following environment variables: + The client considers, for both user and task commands, the following environment variables: ECF_HOST [mandatory*] The main server hostname; default value is 'localhost' diff --git a/docs/client_api/api/delete.rst b/docs/client_api/api/delete.rst index 51595ce9e..3c02d0bdd 100644 --- a/docs/client_api/api/delete.rst +++ b/docs/client_api/api/delete.rst @@ -22,17 +22,27 @@ The following help text is generated by :code:`ecflow_client --help=delete` ------ Deletes the specified node(s) or _ALL_ existing definitions( i.e delete all suites) in the server. - arg1 = [ force | yes ](optional) # Use this parameter to bypass checks, i.e. for active or submitted tasks - arg2 = yes(optional) # Use 'yes' to bypass the confirmation prompt - arg3 = node paths | _all_ # _all_ means delete all suites - # node paths must start with a leading '/' + + Argument(s): + + force: (optional) [ force | yes ] + # Use this parameter to bypass checks, i.e. for active or submitted tasks + + confirm: (optional) + # value must be "yes"; bypasses the confirmation prompt + + target: node paths | _all_ + # The value `_all_` means delete all suites. + # The node paths must start with a leading '/'. + Usage: - --delete=_all_ # Delete all suites in server. Use with care. - --delete=/suite/f1/t1 # Delete node at /suite/f1/t1. This will prompt - --delete=force /suite/f1/t1 # Delete node at /suite/f1/t1 even if active or submitted - --delete=force yes /s1 /s2 # Delete suites s1,s2 even if active or submitted, bypassing prompt - The client considers, for both user and child commands, the following environment variables: + --delete=_all_ # Delete all suites in server. Use with care. + --delete=/suite/f1/t1 # Delete node at /suite/f1/t1. This will prompt + --delete=force /suite/f1/t1 # Delete node at /suite/f1/t1 even if active or submitted + --delete=force yes /s1 /s2 # Delete suites s1,s2 even if active or submitted, bypassing prompt + + The client considers, for both user and task commands, the following environment variables: ECF_HOST [mandatory*] The main server hostname; default value is 'localhost' diff --git a/docs/client_api/api/edit_history.rst b/docs/client_api/api/edit_history.rst index 4fc8619de..138c90f05 100644 --- a/docs/client_api/api/edit_history.rst +++ b/docs/client_api/api/edit_history.rst @@ -23,12 +23,13 @@ The following help text is generated by :code:`ecflow_client --help=edit_history Returns the edit history associated with a Node. Can also be used to clear the edit history. - Usage:: - --edit_history=/s1/f1/t1 # return history of changes for the given node - --edit_history=clear # clear/purge *ALL* edit history from all nodes. + Usage: - The client considers, for both user and child commands, the following environment variables: + --edit_history=/s1/f1/t1 # return history of changes for the given node + --edit_history=clear # clear/purge *ALL* edit history from all nodes. + + The client considers, for both user and task commands, the following environment variables: ECF_HOST [mandatory*] The main server hostname; default value is 'localhost' diff --git a/docs/client_api/api/edit_script.rst b/docs/client_api/api/edit_script.rst index ab86c99c2..7a2b3663d 100644 --- a/docs/client_api/api/edit_script.rst +++ b/docs/client_api/api/edit_script.rst @@ -23,55 +23,74 @@ The following help text is generated by :code:`ecflow_client --help=edit_script` Allows user to edit, pre-process and submit the script. Will allow pre-processing of arbitrary file with 'pre_process_file' option - arg1 = path to task # The path to the task/alias - arg2 = [ edit | pre_process | submit | pre_process_file | submit_file ] - edit : will return the script file to standard out. The script will - include used variables enclosed between %comment/%end at the - start of the file - pre_process: Will return the script file to standard out.The script will - include used variables enclosed between %comment/%end at the - start of the file and with all %include expanded - submit: Will extract the used variables from the supplied file, i.e - between the %comment/%end and use these them to generate the - job using the ecf file accessible from the server - pre_process_file: Will pre process the user supplied file. - Will expand includes,variable substitution, - remove manual & comment sections. - submit_file: Like submit, but the supplied file, is submitted by the server - The last 2 options allow complete freedom to debug the script file - arg3 = [ path_to_script_file ] - needed for option [ pre_process_file | submit_file ] - arg4 = create_alias (optional) default value is false, for use with 'submit_file' option - arg5 = no_run (optional) default value is false, i.e immediately run the alias - is no_run is specified the alias in only created + + Argument(s): + + path to task + # The path to the task/alias + + action: [ edit | pre_process | submit | pre_process_file | submit_file ] + # edit : Returns the script file to standard out. The script will + # include used variables enclosed between %comment/%end at the + # start of the file + # + # pre_process: Returns the script file to standard out. The script will + # include used variables enclosed between %comment/%end at the + # start of the file and with all %include expanded + # + # submit: Extracts the used variables from the supplied file, i.e + # between the %comment/%end and use these them to generate the + # job using the ecf file accessible from the server + # + # pre_process_file: Pre-process the user supplied file, + # expanding includes, performing variable substitution, + # removing manual & comment sections. + # + # submit_file: Like submit, but the supplied file is submitted by the server. + # + # Options `pre_process_file` and `submit_file` facilitate debugging the script file + + path_to_script_file: (optional) + # The path to the script file + # Required for options `pre_process_file` and `submit_file` + + create_alias: (optional) + # If specified, will create an alias for the task and submit the job using this alias + # Default value is false, to be used in combination with `submit_file` option + + no_run: (optional) + # If specified, will create an alias for the task but will not submit the job + # Default value is false, to be used in combination with `submit_file` option + Usage: - --edit_script=/path/to/task edit > script_file - server returns script with the used variables to standard out - The user can choose to edit this file - --edit_script=/path/to/task pre_process > pre_processed_script_file - server will pre process the ecf file accessible from the server - (i.e expand all %includes) and return the file to standard out + --edit_script=/path/to/task edit > script_file + server returns script with the used variables to standard out + The user can choose to edit this file + + --edit_script=/path/to/task pre_process > pre_processed_script_file + server will pre process the ecf file accessible from the server + (i.e expand all %includes) and return the file to standard out - --edit_script=/path/to/task submit script_file - Will extract the used variables in the 'script_file' and will uses these - variables during variable substitution of the ecf file accessible by the - server. This is then submitted as a job + --edit_script=/path/to/task submit script_file + Will extract the used variables in the 'script_file' and will uses these + variables during variable substitution of the ecf file accessible by the + server. This is then submitted as a job - --edit_script=/path/to/task pre_process_file file_to_pre_process - The server will pre-process the user supplied file and return the contents - to standard out. This pre-processing is the same as job file processing, - but on a arbitrary file + --edit_script=/path/to/task pre_process_file file_to_pre_process + The server will pre-process the user supplied file and return the contents + to standard out. This pre-processing is the same as job file processing, + but on a arbitrary file - --edit_script=/path/to/task submit_file file_to_submit - Will extract the used variables in the 'file_to_submit' and will uses these - variables during variable substitution, the file is then submitted for job - generation by the server + --edit_script=/path/to/task submit_file file_to_submit + Will extract the used variables in the 'file_to_submit' and will uses these + variables during variable substitution, the file is then submitted for job + generation by the server - --edit_script=/path/to/task submit_file file_to_submit create_alias - Like the the previous example but will create and run as an alias + --edit_script=/path/to/task submit_file file_to_submit create_alias + Like the the previous example but will create and run as an alias - The client considers, for both user and child commands, the following environment variables: + The client considers, for both user and task commands, the following environment variables: ECF_HOST [mandatory*] The main server hostname; default value is 'localhost' diff --git a/docs/client_api/api/event.rst b/docs/client_api/api/event.rst index 737160b67..b728672cb 100644 --- a/docs/client_api/api/event.rst +++ b/docs/client_api/api/event.rst @@ -23,21 +23,26 @@ The following help text is generated by :code:`ecflow_client --help=event` Change event. For use in the '.ecf' script file *only* Hence the context is supplied via environment variables - arg1(string | int) = event-name - arg2(string)(optional) = [ set | clear] default value is set + Argument(s): - If this child command is a zombie, then the default action will be to *fob*, + event-name: string | int + # The name of the event to be set or cleared. + + state: (optional) string + # [ set | clear], default value is set + + If this task command is a zombie, then the default action will be to *fob*, i.e allow the ecflow client command to complete without an error The default can be overridden by using zombie attributes. Usage: - ecflow_client --event=ev # set the event, default since event initial value is clear - ecflow_client --event=ev set # set the event, explicit - ecflow_client --event=ev clear # clear the event, use when event initial value is set + --event=ev # set the event, default since event initial value is clear + --event=ev set # set the event, explicit + --event=ev clear # clear the event, use when event initial value is set - The client considers, for both user and child commands, the following environment variables: + The client considers, for both user and task commands, the following environment variables: ECF_HOST [mandatory*] The main server hostname; default value is 'localhost' @@ -54,20 +59,20 @@ The following help text is generated by :code:`ecflow_client --help=event` with the server, either by setting the environment variables or by specifying the command line options. - The following environment variables are used specifically by child commands: + The following environment variables are used specifically by task commands: ECF_NAME [mandatory] Full path name to the task ECF_PASS [mandatory] The job password (defined by the server, and used to authenticate client requests) ECF_TRYNO [mandatory] - The run number of the job (defined by the server, and used in job/output file name generation. - ECF_RID [mandatory] + The run number of the job (defined by the server, and used in job/output file name generation). + ECF_RID [mandatory*] The process identifier. Supports identifying zombies and automated killing of running jobs ECF_TIMEOUT [optional] Maximum time in *seconds* for client to deliver message to main server; default is 24 hours ECF_DENIED [optional] - Allows task to exit with an error, upon connection failure, thus avoids ECF_TIMEOUTs wait. + Allows task to exit with an error, upon connection failure, thus avoids ECF_TIMEOUT's wait. NO_ECF [optional] If set, ecflow_client exits immediately with success; useful to test the scripts without a server diff --git a/docs/client_api/api/file.rst b/docs/client_api/api/file.rst index 8fa1d549f..3ec44d430 100644 --- a/docs/client_api/api/file.rst +++ b/docs/client_api/api/file.rst @@ -23,13 +23,24 @@ The following help text is generated by :code:`ecflow_client --help=file` Return the chosen file. Select from [ script | job | jobout | manual | kill | stat ] By default will return the script. - arg1 = path to node - arg2 = (optional) [ script | job | jobout | manual | kill | stat ] - kill will attempt to return output of ECF_KILL_CMD, i.e the file %ECF_JOB%.kill - stat will attempt to return output of ECF_STATUS_CMD, i.e the file %ECF_JOB%.stat - arg3 = (optional) max_lines = 10000 - The client considers, for both user and child commands, the following environment variables: + Argument(s): + + path: + # The path to the node + + kind: (optional) [ script | job | jobout | manual | kill | stat ] + # `script`: returns the script file, e.g., %ECF_JOB%.ecf + # `job`: returns the pre-processed job script + # `jobout`: returns the job output/log, e.g., %ECF_JOB%.out + # `manual`: returns the job manual + # `kill`: returns the output of ECF_KILL_CMD, e.g., %ECF_JOB%.kill + # `stat`: returns the output of ECF_STATUS_CMD, e.g., %ECF_JOB%.stat + + max_lines: (optional) + # default is 10000 + + The client considers, for both user and task commands, the following environment variables: ECF_HOST [mandatory*] The main server hostname; default value is 'localhost' diff --git a/docs/client_api/api/force-dep-eval.rst b/docs/client_api/api/force-dep-eval.rst index 53bfc47d1..9210bbaa7 100644 --- a/docs/client_api/api/force-dep-eval.rst +++ b/docs/client_api/api/force-dep-eval.rst @@ -23,7 +23,7 @@ The following help text is generated by :code:`ecflow_client --help=force-dep-ev Force dependency evaluation. Used for DEBUG only. - The client considers, for both user and child commands, the following environment variables: + The client considers, for both user and task commands, the following environment variables: ECF_HOST [mandatory*] The main server hostname; default value is 'localhost' diff --git a/docs/client_api/api/force.rst b/docs/client_api/api/force.rst index ef428243f..d6ae7593e 100644 --- a/docs/client_api/api/force.rst +++ b/docs/client_api/api/force.rst @@ -29,29 +29,42 @@ The following help text is generated by :code:`ecflow_client --help=force` This behaviour allow Repeat values to be incremented interactively. A repeat attribute is incremented when all the child nodes are complete in this case the child nodes are automatically re-queued. - arg1 = [ unknown | complete | queued | submitted | active | aborted | clear | set ] - arg2 = (optional) recursive - Applies state to node and recursively to all its children - arg3 = (optional) full - Set repeat variables to last value, only works in conjunction - with recursive option - arg4 = path_to_node or path_to_node:: paths must begin with '/' + + Argument(s): + + state: [ unknown | complete | queued | submitted | active | aborted | clear | set ] + # The state to be set. If the state is either `set` or `clear`, then the event is updated. + + recursive: (optional) + # Applies state to node, and recursively to all its children + + full: (optional) + # Set repeat variables to last value, only works in conjunction + # with recursive option + + path: | : + # The path to a node or event. The path must begin with '/' + Usage: - --force=complete /suite/t1 /suite/t2 # Set task t1 & t2 to complete - --force=clear /suite/task:ev # Clear the event 'ev' on task /suite/task - --force=complete recursive /suite/f1 # Recursively set complete all children of /suite/f1 + + --force=complete /suite/t1 /suite/t2 # Set task t1 & t2 to complete + --force=clear /suite/task:ev # Clear the event 'ev' on task /suite/task + --force=complete recursive /suite/f1 # Recursively set complete all children of /suite/f1 + Effect: - Consider the effect of forcing complete when the current time is at 09:00 - suite s1 - task t1; time 12:00 # will complete straight away - task t2; time 10:00 13:00 01:00 # will complete on fourth attempt - --force=complete /s1/t1 /s1/t2 - When we have a time range(i.e as shown with task t2), it is re-queued and the - next time slot is incremented for each complete, until it expires, and the task completes. - Use the Why command, to show next run time (i.e. next time slot) + Consider the effect of applying `--force=complete /s1/t1 /s1/t2` at 09:00 + + suite s1 + task t1; time 12:00 # will complete straight away + task t2; time 10:00 13:00 01:00 # will complete on fourth attempt + + Specifying a time range, as shown with task t2, means the task is re-queued and the + next time slot is incremented for each complete, until it expires, and the task completes. + + Use the Why command, to show next run time (i.e. next time slot) - The client considers, for both user and child commands, the following environment variables: + The client considers, for both user and task commands, the following environment variables: ECF_HOST [mandatory*] The main server hostname; default value is 'localhost' diff --git a/docs/client_api/api/free-dep.rst b/docs/client_api/api/free-dep.rst index 07e335d7e..180c4f50b 100644 --- a/docs/client_api/api/free-dep.rst +++ b/docs/client_api/api/free-dep.rst @@ -24,20 +24,29 @@ The following help text is generated by :code:`ecflow_client --help=free-dep` Free dependencies for a node. Defaults to triggers After freeing the time related dependencies (i.e time,today,cron) the next time slot will be missed. - arg1 = (optional) trigger - arg2 = (optional) all - Free trigger, date and all time dependencies - arg3 = (optional) date - Free date dependencies - arg4 = (optional) time - Free all time dependencies i.e time, day, today, cron - arg5 = List of paths. At least one required. Must start with a leading '/' + + Argument(s): + + all: (optional) + # Free trigger, date and all time dependencies + + date: (optional) + # Free date dependencies + + time: (optional) + # Free all time dependencies i.e time, day, today, cron + + paths + # A space separated list of paths. + # At least one path must be provided. Paths must start with a leading '/' + Usage: - --free-dep=/s1/t1 /s2/t2 # free trigger dependencies for task's t1,t2 - --free-dep=all /s1/f1/t1 # free all dependencies of /s1/f1/t1 - --free-dep=date /s1/f1 # free holding date dependencies of /s1/f1 - The client considers, for both user and child commands, the following environment variables: + --free-dep=/s1/t1 /s2/t2 # free trigger dependencies for task's t1,t2 + --free-dep=all /s1/f1/t1 # free all dependencies of /s1/f1/t1 + --free-dep=date /s1/f1 # free holding date dependencies of /s1/f1 + + The client considers, for both user and task commands, the following environment variables: ECF_HOST [mandatory*] The main server hostname; default value is 'localhost' diff --git a/docs/client_api/api/get.rst b/docs/client_api/api/get.rst index fb53ebf57..3cb0d0e13 100644 --- a/docs/client_api/api/get.rst +++ b/docs/client_api/api/get.rst @@ -52,11 +52,13 @@ The following help text is generated by :code:`ecflow_client --help=get` Get all suite node tree's from the server and write to standard out. The output is parse-able, and can be used to re-load the definition arg = NULL | arg = node path + Usage: - --get # gets the definition from the server,and writes to standard out - --get=/s1 # gets the suite from the server,and writes to standard out - The client considers, for both user and child commands, the following environment variables: + --get # gets the definition from the server,and writes to standard out + --get=/s1 # gets the suite from the server,and writes to standard out + + The client considers, for both user and task commands, the following environment variables: ECF_HOST [mandatory*] The main server hostname; default value is 'localhost' diff --git a/docs/client_api/api/get_state.rst b/docs/client_api/api/get_state.rst index 980420e7f..d07250151 100644 --- a/docs/client_api/api/get_state.rst +++ b/docs/client_api/api/get_state.rst @@ -25,11 +25,13 @@ The following help text is generated by :code:`ecflow_client --help=get_state` This will include event, meter, node state, trigger and time state. The output is written to standard out. arg = NULL | arg = node path + Usage: - --get_state # gets the definition from the server,and writes to standard out - --get_state=/s1 # gets the suite from the server,and writes to standard out - The client considers, for both user and child commands, the following environment variables: + --get_state # gets the definition from the server,and writes to standard out + --get_state=/s1 # gets the suite from the server,and writes to standard out + + The client considers, for both user and task commands, the following environment variables: ECF_HOST [mandatory*] The main server hostname; default value is 'localhost' diff --git a/docs/client_api/api/group.rst b/docs/client_api/api/group.rst index e1f0c2d15..bb018ed80 100644 --- a/docs/client_api/api/group.rst +++ b/docs/client_api/api/group.rst @@ -24,15 +24,26 @@ The following help text is generated by :code:`ecflow_client --help=group` Allows a series of ';' separated commands to be grouped and executed as one. Some commands like halt, shutdown and terminate will prompt the user. To bypass the prompt provide 'yes' as an additional parameter. See example below. - arg = string + + Argument(s): + + arg: string + # A string of ';' separated commands. Each command is the same as if it was + # provided on the command line. The string must be quoted. + Usage: - --group="halt=yes; reloadwsfile; restart;" - # halt server,bypass the confirmation prompt, - # reload white list file, restart server - --group="get; show" # get server defs, and write to standard output - --group="get=/s1; show state" # get suite 's1', and write state to standard output - The client considers, for both user and child commands, the following environment variables: + --group="halt=yes; reloadwsfile; restart;" + # halt server,bypass the confirmation prompt, + # reload white list file, restart server + + --group="get; show" + # get server defs, and write to standard output + + --group="get=/s1; show state" + # get suite 's1', and write state to standard output + + The client considers, for both user and task commands, the following environment variables: ECF_HOST [mandatory*] The main server hostname; default value is 'localhost' diff --git a/docs/client_api/api/halt.rst b/docs/client_api/api/halt.rst index 277457cd7..aa61c3095 100644 --- a/docs/client_api/api/halt.rst +++ b/docs/client_api/api/halt.rst @@ -23,8 +23,16 @@ The following help text is generated by :code:`ecflow_client --help=halt` Stop server communication with jobs, and new job scheduling. Also stops automatic check pointing - arg1 = yes(optional) # use to bypass confirmation prompt,i.e. - --halt=yes + + Argument(s): + + confirm: (optional) + # value must be "yes"; bypasses the confirmation prompt + + Usage: + + --halt=yes + The following table shows server behaviour in the different states. |----------------------------------------------------------------------------------| | Server State | User Request | Task Request |Job Scheduling | Auto-Check-pointing | @@ -34,7 +42,7 @@ The following help text is generated by :code:`ecflow_client --help=halt` | HALTED | yes | no | no | no | |--------------|--------------|--------------|---------------|---------------------| - The client considers, for both user and child commands, the following environment variables: + The client considers, for both user and task commands, the following environment variables: ECF_HOST [mandatory*] The main server hostname; default value is 'localhost' diff --git a/docs/client_api/api/help.rst b/docs/client_api/api/help.rst index ed8cb37fb..22a08e324 100644 --- a/docs/client_api/api/help.rst +++ b/docs/client_api/api/help.rst @@ -23,7 +23,7 @@ The following help text is generated by :code:`ecflow_client --help=help` Produce help message - The client considers, for both user and child commands, the following environment variables: + The client considers, for both user and task commands, the following environment variables: ECF_HOST [mandatory*] The main server hostname; default value is 'localhost' diff --git a/docs/client_api/api/http.rst b/docs/client_api/api/http.rst index 2142647ee..80b289168 100644 --- a/docs/client_api/api/http.rst +++ b/docs/client_api/api/http.rst @@ -24,6 +24,5 @@ The following help text is generated by :code:`ecflow_client --help=http` Enables communication over HTTP between client/server. - diff --git a/docs/client_api/api/https.rst b/docs/client_api/api/https.rst index b1e0c594c..e3459f988 100644 --- a/docs/client_api/api/https.rst +++ b/docs/client_api/api/https.rst @@ -24,6 +24,5 @@ The following help text is generated by :code:`ecflow_client --help=https` Enables communication over HTTPS between client/server. - diff --git a/docs/client_api/api/init.rst b/docs/client_api/api/init.rst index b20168598..c9f2e5cd2 100644 --- a/docs/client_api/api/init.rst +++ b/docs/client_api/api/init.rst @@ -23,19 +23,27 @@ The following help text is generated by :code:`ecflow_client --help=init` Mark task as started(active). For use in the '.ecf' script file *only* Hence the context is supplied via environment variables. - arg1(string) = process_or_remote_id The process id of the job or remote_id - Using remote id allows the jobs to be killed - arg2(--add)(optional)= add/update variables as name value pairs - If this child command is a zombie, then the default action will be to *block*. + Argument(s): + + process_or_remote_id: string + # The process id of the job or remote_id. + # Using remote id allows the jobs to be killed. + + --add: (optional) + # add/update variables as name value pairs + # a list of space separated variables to add/update on this task + + If this task command is a zombie, then the default action will be to *block*. The default can be overridden by using zombie attributes. Otherwise the blocking period is defined by ECF_TIMEOUT. Usage: - ecflow_client --init=$$ - ecflow_client --init=$$ --add name=value name2=value2 # add/update variables to task - The client considers, for both user and child commands, the following environment variables: + --init=$$ + --init=$$ --add name=value name2=value2 # add/update variables to task + + The client considers, for both user and task commands, the following environment variables: ECF_HOST [mandatory*] The main server hostname; default value is 'localhost' @@ -52,20 +60,20 @@ The following help text is generated by :code:`ecflow_client --help=init` with the server, either by setting the environment variables or by specifying the command line options. - The following environment variables are used specifically by child commands: + The following environment variables are used specifically by task commands: ECF_NAME [mandatory] Full path name to the task ECF_PASS [mandatory] The job password (defined by the server, and used to authenticate client requests) ECF_TRYNO [mandatory] - The run number of the job (defined by the server, and used in job/output file name generation. - ECF_RID [mandatory] + The run number of the job (defined by the server, and used in job/output file name generation). + ECF_RID [mandatory*] The process identifier. Supports identifying zombies and automated killing of running jobs ECF_TIMEOUT [optional] Maximum time in *seconds* for client to deliver message to main server; default is 24 hours ECF_DENIED [optional] - Allows task to exit with an error, upon connection failure, thus avoids ECF_TIMEOUTs wait. + Allows task to exit with an error, upon connection failure, thus avoids ECF_TIMEOUT's wait. NO_ECF [optional] If set, ecflow_client exits immediately with success; useful to test the scripts without a server diff --git a/docs/client_api/api/job_gen.rst b/docs/client_api/api/job_gen.rst index 2b1c74acf..6a4a253c5 100644 --- a/docs/client_api/api/job_gen.rst +++ b/docs/client_api/api/job_gen.rst @@ -28,7 +28,7 @@ The following help text is generated by :code:`ecflow_client --help=job_gen` arg = node path | arg = NULL If no node path specified generates for full definition. - The client considers, for both user and child commands, the following environment variables: + The client considers, for both user and task commands, the following environment variables: ECF_HOST [mandatory*] The main server hostname; default value is 'localhost' diff --git a/docs/client_api/api/kill.rst b/docs/client_api/api/kill.rst index 598a06982..83ad7706e 100644 --- a/docs/client_api/api/kill.rst +++ b/docs/client_api/api/kill.rst @@ -27,11 +27,13 @@ The following help text is generated by :code:`ecflow_client --help=kill` The command should be written in such a way that the output is written to %ECF_JOB%.kill as this allow the --file command to report the output: .e.e. /home/ma/emos/bin/ecfkill %USER% %HOST% %ECF_RID% %ECF_JOB% > %ECF_JOB%.kill 2>&1:: - Usage:: - --kill=/s1/f1/t1 /s1/f2/t2 # kill the jobs for tasks t1 and t2 - --file=/s1/f1/t1 kill # write to standard out the '.kill' file for task /s1/f1/t1 - The client considers, for both user and child commands, the following environment variables: + Usage: + + --kill=/s1/f1/t1 /s1/f2/t2 # kill the jobs for tasks t1 and t2 + --file=/s1/f1/t1 kill # write to standard out the '.kill' file for task /s1/f1/t1 + + The client considers, for both user and task commands, the following environment variables: ECF_HOST [mandatory*] The main server hostname; default value is 'localhost' diff --git a/docs/client_api/api/label.rst b/docs/client_api/api/label.rst index d19aa7e52..2882df7c6 100644 --- a/docs/client_api/api/label.rst +++ b/docs/client_api/api/label.rst @@ -23,18 +23,24 @@ The following help text is generated by :code:`ecflow_client --help=label` Change Label. For use in the '.ecf' script file *only* Hence the context is supplied via environment variables - arg1 = label-name - arg2 = The new label value - The labels values can be single or multi-line(space separated quoted strings) - If this child command is a zombie, then the default action will be to *fob*, + Argument(s): + + label-name + # The name of the label to be set. + + value + # The new label value. Can be single or multi-line (space separated quoted strings). + + If this task command is a zombie, then the default action will be to *fob*, i.e allow the ecflow client command to complete without an error The default can be overridden by using zombie attributes. Usage: - ecflow_client --label=progressed merlin - The client considers, for both user and child commands, the following environment variables: + --label=progressed merlin + + The client considers, for both user and task commands, the following environment variables: ECF_HOST [mandatory*] The main server hostname; default value is 'localhost' @@ -51,20 +57,20 @@ The following help text is generated by :code:`ecflow_client --help=label` with the server, either by setting the environment variables or by specifying the command line options. - The following environment variables are used specifically by child commands: + The following environment variables are used specifically by task commands: ECF_NAME [mandatory] Full path name to the task ECF_PASS [mandatory] The job password (defined by the server, and used to authenticate client requests) ECF_TRYNO [mandatory] - The run number of the job (defined by the server, and used in job/output file name generation. - ECF_RID [mandatory] + The run number of the job (defined by the server, and used in job/output file name generation). + ECF_RID [mandatory*] The process identifier. Supports identifying zombies and automated killing of running jobs ECF_TIMEOUT [optional] Maximum time in *seconds* for client to deliver message to main server; default is 24 hours ECF_DENIED [optional] - Allows task to exit with an error, upon connection failure, thus avoids ECF_TIMEOUTs wait. + Allows task to exit with an error, upon connection failure, thus avoids ECF_TIMEOUT's wait. NO_ECF [optional] If set, ecflow_client exits immediately with success; useful to test the scripts without a server diff --git a/docs/client_api/api/load.rst b/docs/client_api/api/load.rst index 8083fa962..e6949e7af 100644 --- a/docs/client_api/api/load.rst +++ b/docs/client_api/api/load.rst @@ -28,18 +28,25 @@ The following help text is generated by :code:`ecflow_client --help=load` The suite's can be overwritten if the force option is used. To just check the definition and not send to server, use 'check_only' This command can also be used to load a checkpoint file into the server - arg1 = path to the definition file or checkpoint file - arg2 = (optional) [ force | check_only | print | stats ] # default = false for all + + Argument(s): + + path: + # The path to the definition file or checkpoint file. + + mode: (optional) [ force | check_only | print | stats ] + # The default value is to apply none of the mode options. + Usage: - --load=/my/home/exotic.def # will error if suites of same name exists - --load=/my/home/exotic.def force # overwrite suite's of same name in the server - --load=/my/home/exotic.def check_only # Just check, don't send to server - --load=/my/home/exotic.def stats # Show defs statistics, don't send to server - --load=host1.3141.check # Load checkpoint file to the server - --load=host1.3141.check print # print definition to standard out in defs format + --load=/my/home/exotic.def # will error if suites of same name exists + --load=/my/home/exotic.def force # overwrite suite's of same name in the server + --load=/my/home/exotic.def check_only # Just check, don't send to server + --load=/my/home/exotic.def stats # Show defs statistics, don't send to server + --load=host1.3141.check # Load checkpoint file to the server + --load=host1.3141.check print # print definition to standard out in defs format - The client considers, for both user and child commands, the following environment variables: + The client considers, for both user and task commands, the following environment variables: ECF_HOST [mandatory*] The main server hostname; default value is 'localhost' diff --git a/docs/client_api/api/log.rst b/docs/client_api/api/log.rst index 2d9454692..53aa15bea 100644 --- a/docs/client_api/api/log.rst +++ b/docs/client_api/api/log.rst @@ -21,38 +21,48 @@ The following help text is generated by :code:`ecflow_client --help=log` log --- - Get,clear,flush or create a new log file. + Get, clear, flush or create a new log file. The user must ensure that a valid path is specified. Specifying '--log=get' with a large number of lines from the server, can consume a lot of **memory**. The log file can be a very large file, hence we use a default of 100 lines, optionally the number of lines can be specified. - arg1 = [ get | clear | flush | new | path ] - get - Outputs the log file to standard out. - defaults to return the last 100 lines - The second argument can specify how many lines to return - clear - Clear the log file of its contents. - flush - Flush and close the log file. (only temporary) next time - server writes to log, it will be opened again. Hence it best - to halt the server first - new - Flush and close the existing log file, and start using the - the path defined for ECF_LOG. By changing this variable - a new log file path can be used - Alternatively an explicit path can also be provided - in which case ECF_LOG is also updated - path - Returns the path name to the existing log file - arg2 = [ new_path | optional last n lines ] - if get specified can specify lines to get. Value must be convertible to an integer - Otherwise if arg1 is 'new' then the second argument must be a path + + Argument(s): + + action: [ get | clear | flush | new | path ] + # get: Outputs the log file to standard out. + # defaults to return the last 100 lines + # The second argument can specify how many lines to return + # + # clear: Clear the log file of its contents. + # + # flush: Flush and close the log file. (only temporary) next time + # server writes to log, it will be opened again. Hence it best + # to halt the server first + # + # new: Flush and close the existing log file, and start using the + # the path defined for ECF_LOG. By changing this variable + # a new log file path can be used + # Alternatively an explicit path can also be provided + # in which case ECF_LOG is also updated + # + # path: Returns the path name to the existing log file + + value: (optional) [ new_path | optional last n lines ] + # If 'get' is specified, the value specifies lines to get. + # The value must be an integer. + # If 'new' is specified, the value specifies a new path + Usage: - --log=get # Write the last 100 lines of the log file to standard out - --log=get 200 # Write the last 200 lines of the log file to standard out - --log=clear # Clear the log file. The log is now empty - --log=flush # Flush and close log file, next request will re-open log file - --log=new /path/to/new/log/file # Close and flush log file, and create a new log file, updates ECF_LOG - --log=new # Close and flush log file, and create a new log file using ECF_LOG variable + --log=get # Write the last 100 lines of the log file to standard out + --log=get 200 # Write the last 200 lines of the log file to standard out + --log=clear # Clear the log file. The log is now empty + --log=flush # Flush and close log file, next request will re-open log file + --log=new /path/to/new/log/file # Close and flush log file, and create a new log file, updates ECF_LOG + --log=new # Close and flush log file, and create a new log file using ECF_LOG variable - The client considers, for both user and child commands, the following environment variables: + The client considers, for both user and task commands, the following environment variables: ECF_HOST [mandatory*] The main server hostname; default value is 'localhost' diff --git a/docs/client_api/api/meter.rst b/docs/client_api/api/meter.rst index 7620b2594..85b6e3092 100644 --- a/docs/client_api/api/meter.rst +++ b/docs/client_api/api/meter.rst @@ -23,17 +23,24 @@ The following help text is generated by :code:`ecflow_client --help=meter` Change meter. For use in the '.ecf' script file *only* Hence the context is supplied via environment variables - arg1(string) = meter-name - arg2(int) = the new meter value - If this child command is a zombie, then the default action will be to *fob*, + Argument(s): + + meter-name: string + # The name of the meter to be set. + + value: int + # The new meter value + + If this task command is a zombie, then the default action will be to *fob*, i.e allow the ecflow client command to complete without an error The default can be overridden by using zombie attributes. Usage: - ecflow_client --meter=my_meter 20 - The client considers, for both user and child commands, the following environment variables: + --meter=my_meter 20 + + The client considers, for both user and task commands, the following environment variables: ECF_HOST [mandatory*] The main server hostname; default value is 'localhost' @@ -50,20 +57,20 @@ The following help text is generated by :code:`ecflow_client --help=meter` with the server, either by setting the environment variables or by specifying the command line options. - The following environment variables are used specifically by child commands: + The following environment variables are used specifically by task commands: ECF_NAME [mandatory] Full path name to the task ECF_PASS [mandatory] The job password (defined by the server, and used to authenticate client requests) ECF_TRYNO [mandatory] - The run number of the job (defined by the server, and used in job/output file name generation. - ECF_RID [mandatory] + The run number of the job (defined by the server, and used in job/output file name generation). + ECF_RID [mandatory*] The process identifier. Supports identifying zombies and automated killing of running jobs ECF_TIMEOUT [optional] Maximum time in *seconds* for client to deliver message to main server; default is 24 hours ECF_DENIED [optional] - Allows task to exit with an error, upon connection failure, thus avoids ECF_TIMEOUTs wait. + Allows task to exit with an error, upon connection failure, thus avoids ECF_TIMEOUT's wait. NO_ECF [optional] If set, ecflow_client exits immediately with success; useful to test the scripts without a server diff --git a/docs/client_api/api/migrate.rst b/docs/client_api/api/migrate.rst index b21f1b191..28c5e2887 100644 --- a/docs/client_api/api/migrate.rst +++ b/docs/client_api/api/migrate.rst @@ -36,11 +36,11 @@ The following help text is generated by :code:`ecflow_client --help=migrate` Show trigger AST No Yes No Usage: + --migrate # show all suites --migrate=/s1 # show state for suite s1 - - The client considers, for both user and child commands, the following environment variables: + The client considers, for both user and task commands, the following environment variables: ECF_HOST [mandatory*] The main server hostname; default value is 'localhost' diff --git a/docs/client_api/api/msg.rst b/docs/client_api/api/msg.rst index 280787ea4..df5e0eb3b 100644 --- a/docs/client_api/api/msg.rst +++ b/docs/client_api/api/msg.rst @@ -22,11 +22,17 @@ The following help text is generated by :code:`ecflow_client --help=msg` --- Writes the input string to the log file. - arg1 = string + + Argument(s): + + message: string + # The message to be written to the log file. + Usage: - --msg="place me in the log file" - The client considers, for both user and child commands, the following environment variables: + --msg="place me in the log file" + + The client considers, for both user and task commands, the following environment variables: ECF_HOST [mandatory*] The main server hostname; default value is 'localhost' diff --git a/docs/client_api/api/news.rst b/docs/client_api/api/news.rst index f6fe2f216..25a41fa83 100644 --- a/docs/client_api/api/news.rst +++ b/docs/client_api/api/news.rst @@ -27,7 +27,7 @@ The following help text is generated by :code:`ecflow_client --help=news` This relies on user calling sync after news to update the locally stored modify and change numbers. These numbers are then used in the next call to news. - The client considers, for both user and child commands, the following environment variables: + The client considers, for both user and task commands, the following environment variables: ECF_HOST [mandatory*] The main server hostname; default value is 'localhost' diff --git a/docs/client_api/api/order.rst b/docs/client_api/api/order.rst index 08d295b6f..ca8beb709 100644 --- a/docs/client_api/api/order.rst +++ b/docs/client_api/api/order.rst @@ -22,8 +22,14 @@ The following help text is generated by :code:`ecflow_client --help=order` ----- Re-orders the nodes held by the server - arg1 = node path - arg2 = [ top | bottom | alpha | order | up | down | runtime] + + Argument(s): + + node: path + # The path to the node to be re-ordered. + + sort-type: [ top | bottom | alpha | order | up | down | runtime] + It should be noted that in the absence of triggers and time/date dependencies, the tasks are submitted in order. This changes the order and hence affects the submission order:: @@ -42,10 +48,12 @@ The following help text is generated by :code:`ecflow_client --help=order` This command can fail because: - The node path does not exist in the server - The order_type is not does not match one of arg2 + Usage: - --order=/suite/f1 top # move node f1 to the top - The client considers, for both user and child commands, the following environment variables: + --order=/suite/f1 top # move node f1 to the top + + The client considers, for both user and task commands, the following environment variables: ECF_HOST [mandatory*] The main server hostname; default value is 'localhost' diff --git a/docs/client_api/api/ping.rst b/docs/client_api/api/ping.rst index f93bb272b..c8ade747c 100644 --- a/docs/client_api/api/ping.rst +++ b/docs/client_api/api/ping.rst @@ -22,15 +22,17 @@ The following help text is generated by :code:`ecflow_client --help=ping` ---- Check if server is running on given host/port. Result reported to standard output. + Usage: - --ping --host=mach --port=3144 # Check if server alive on host mach & port 3144 - --ping --host=fred # Check if server alive on host fred and port ECF_PORT, - # otherwise default port of 3141 - --ping # Check if server alive by using environment variables - # ECF_HOST and ECF_PORT + + --ping --host=mach --port=3144 # Check if server alive on host mach & port 3144 + --ping --host=fred # Check if server alive on host fred and port ECF_PORT, + # otherwise default port of 3141 + --ping # Check if server alive by using environment variables + # ECF_HOST and ECF_PORT If ECF_HOST not defined uses 'localhost', if ECF_PORT not defined assumes 3141 - The client considers, for both user and child commands, the following environment variables: + The client considers, for both user and task commands, the following environment variables: ECF_HOST [mandatory*] The main server hostname; default value is 'localhost' diff --git a/docs/client_api/api/plug.rst b/docs/client_api/api/plug.rst index 8b147b448..d1b026289 100644 --- a/docs/client_api/api/plug.rst +++ b/docs/client_api/api/plug.rst @@ -24,8 +24,15 @@ The following help text is generated by :code:`ecflow_client --help=plug` Plug command is used to move nodes. The destination node can be on another server In which case the destination path should be of the form ':/suite/family/task - arg1 = path to source node - arg2 = path to the destination node + + Argument(s): + + source: path + # The path to the source node + + destination: path + # The path to the destination node + This command can fail because: - Source node is in a 'active' or 'submitted' state - Another user already has an lock @@ -34,10 +41,12 @@ The following help text is generated by :code:`ecflow_client --help=plug` then the source node must correspond to a suite. - If the source node is added as a child, then its name must be unique amongst its peers + Usage: - --plug=/suite macX:3141 # move the suite to ecFlow server on host(macX) and port(3141) - The client considers, for both user and child commands, the following environment variables: + --plug=/suite macX:3141 # move the suite to ecFlow server on host(macX) and port(3141) + + The client considers, for both user and task commands, the following environment variables: ECF_HOST [mandatory*] The main server hostname; default value is 'localhost' diff --git a/docs/client_api/api/query.rst b/docs/client_api/api/query.rst index 6db52a455..f625593d3 100644 --- a/docs/client_api/api/query.rst +++ b/docs/client_api/api/query.rst @@ -146,30 +146,36 @@ The following help text is generated by :code:`ecflow_client --help=query` - variable No user or generated variable or repeat of that name found on node or its parents, or (when path is '/') no user or server variable of that name found on the server - trigger Trigger does not parse, or reference to nodes/attributes in the expression are not valid - Arguments: - arg1 = [ state | dstate | repeat | event | meter | label | variable | trigger | limit | limit_max ] - arg2 = | :name where name is name of a event, meter, label, limit or variable. - path '/' represents the server itself, and can only be used with 'state' or 'variable' - arg3 = trigger expression | prev | next # prev,next only used when arg1 is repeat + + Argument(s): + + attribute: [ state | dstate | repeat | event | meter | label | variable | trigger | limit | limit_max ] + # The kind of the attribute to be queried. + + target: | :name + # The path to the node or the node and attribute name. + + value: trigger expression | prev | next + # The values `prev` and `next` are only used when the attribute is a repeat Usage: - ecflow_client --query state / # return top level state to standard out - ecflow_client --query state /path/to/node # return node state to standard out - ecflow_client --query dstate /path/to/node # state that can included suspended - ecflow_client --query repeat /path/to/node # return the current value as a string - ecflow_client --query repeat /path/to/node prev # return the previous value as a string - ecflow_client --query repeat /path/to/node next # return the next value as a string - ecflow_client --query event /path/to/task/with/event:event_name # return set | clear to standard out - ecflow_client --query meter /path/to/task/with/meter:meter_name # returns the current value of the meter to standard out - ecflow_client --query limit /path/to/task/with/limit:limit_name # returns the current value of the limit to standard out - ecflow_client --query limit_max /path/to/task/with/limit:limit_name # returns the max value of the limit to standard out - ecflow_client --query label /path/to/task/with/label:label_name # returns the current value of the label to standard out - ecflow_client --query variable /path/to/task/with/var:var_name # returns the variable value to standard out - ecflow_client --query variable /:var_name # returns the server variable value to standard out - ecflow_client --query trigger /path/to/node/with/trigger "/suite/task == complete" # return true if expression evaluates false otherwise + --query state / # return top level state to standard out + --query state /path/to/node # return node state to standard out + --query dstate /path/to/node # state that can included suspended + --query repeat /path/to/node # return the current value as a string + --query repeat /path/to/node prev # return the previous value as a string + --query repeat /path/to/node next # return the next value as a string + --query event /path/to/task/with/event:event_name # return set | clear to standard out + --query meter /path/to/task/with/meter:meter_name # returns the current value of the meter to standard out + --query limit /path/to/task/with/limit:limit_name # returns the current value of the limit to standard out + --query limit_max /path/to/task/with/limit:limit_name # returns the max value of the limit to standard out + --query label /path/to/task/with/label:label_name # returns the current value of the label to standard out + --query variable /path/to/task/with/var:var_name # returns the variable value to standard out + --query variable /:var_name # returns the server variable value to standard out + --query trigger /path/to/node/with/trigger "/suite/task == complete" # return true if expression evaluates false otherwise - The client considers, for both user and child commands, the following environment variables: + The client considers, for both user and task commands, the following environment variables: ECF_HOST [mandatory*] The main server hostname; default value is 'localhost' diff --git a/docs/client_api/api/queue.rst b/docs/client_api/api/queue.rst index 2624dda60..4152ea8cb 100644 --- a/docs/client_api/api/queue.rst +++ b/docs/client_api/api/queue.rst @@ -23,21 +23,33 @@ The following help text is generated by :code:`ecflow_client --help=queue` QueueCmd. For use in the '.ecf' script file *only* Hence the context is supplied via environment variables - arg1(string) = queue-name: - arg2(string) = action: [active | aborted | complete | no_of_aborted | reset ] - active: returns the first queued/aborted step, the return string is the queue value from the definition - no_of_aborted: returns number of aborted steps as a string, i.e 10 - reset: sets the index to the first queued/aborted step. Allows steps to be reprocessed for errors - arg3(string) = step: value returned from step=$(ecflow_client --queue=queue_name active) - This is only valid for complete and aborted steps - arg4(string) = path: (optional). The path where the queue is defined. - By default we search for the queue up the node tree. - - If this child command is a zombie, then the default action will be to *block*, - The default can be overridden by using zombie attributes.If the path to the queue is not defined, then this command will + + Argument(s): + + queue-name: string + # The name of the queue to be used. + + action: string + # [active | aborted | complete | no_of_aborted | reset ] + # active: returns the first queued/aborted step, the return string is the queue value from the definition + # no_of_aborted: returns number of aborted steps as a string, i.e 10 + # reset: sets the index to the first queued/aborted step. Allows steps to be reprocessed for errors + + step: string + # The value returned from `step=$(ecflow_client --queue=queue_name active)` + # This is only valid for complete and aborted steps + + path: (optional) string + # The path where the queue is defined. + # By default we search for the queue up the node tree. + + If this task command is a zombie, then the default action will be to *block*, + The default can be overridden by using zombie attributes. + If the path to the queue is not defined, then this command will search for the queue up the node hierarchy. If no queue found, command fails Usage: + step="" QNAME="my_queue_name" while [1 == 1 ] ; do @@ -52,8 +64,7 @@ The following help text is generated by :code:`ecflow_client --help=queue` trap() { ecflow_client --queue=$QNAME aborted $step # tell ecflow this step failed } - - The client considers, for both user and child commands, the following environment variables: + The client considers, for both user and task commands, the following environment variables: ECF_HOST [mandatory*] The main server hostname; default value is 'localhost' @@ -70,20 +81,20 @@ The following help text is generated by :code:`ecflow_client --help=queue` with the server, either by setting the environment variables or by specifying the command line options. - The following environment variables are used specifically by child commands: + The following environment variables are used specifically by task commands: ECF_NAME [mandatory] Full path name to the task ECF_PASS [mandatory] The job password (defined by the server, and used to authenticate client requests) ECF_TRYNO [mandatory] - The run number of the job (defined by the server, and used in job/output file name generation. - ECF_RID [mandatory] + The run number of the job (defined by the server, and used in job/output file name generation). + ECF_RID [mandatory*] The process identifier. Supports identifying zombies and automated killing of running jobs ECF_TIMEOUT [optional] Maximum time in *seconds* for client to deliver message to main server; default is 24 hours ECF_DENIED [optional] - Allows task to exit with an error, upon connection failure, thus avoids ECF_TIMEOUTs wait. + Allows task to exit with an error, upon connection failure, thus avoids ECF_TIMEOUT's wait. NO_ECF [optional] If set, ecflow_client exits immediately with success; useful to test the scripts without a server diff --git a/docs/client_api/api/reloadcustompasswdfile.rst b/docs/client_api/api/reloadcustompasswdfile.rst index d24afe281..5215b11d6 100644 --- a/docs/client_api/api/reloadcustompasswdfile.rst +++ b/docs/client_api/api/reloadcustompasswdfile.rst @@ -68,9 +68,10 @@ The following help text is generated by :code:`ecflow_client --help=reloadcustom - On the client, the password file should be readable only by the 'user' itself Usage: - --reloadcustompasswdfile - The client considers, for both user and child commands, the following environment variables: + --reloadcustompasswdfile + + The client considers, for both user and task commands, the following environment variables: ECF_HOST [mandatory*] The main server hostname; default value is 'localhost' diff --git a/docs/client_api/api/reloadpasswdfile.rst b/docs/client_api/api/reloadpasswdfile.rst index 685192cd8..da42b87d9 100644 --- a/docs/client_api/api/reloadpasswdfile.rst +++ b/docs/client_api/api/reloadpasswdfile.rst @@ -68,9 +68,10 @@ The following help text is generated by :code:`ecflow_client --help=reloadpasswd - On the client, the password file should be readable only by the 'user' itself Usage: - --reloadpasswdfile - The client considers, for both user and child commands, the following environment variables: + --reloadpasswdfile + + The client considers, for both user and task commands, the following environment variables: ECF_HOST [mandatory*] The main server hostname; default value is 'localhost' diff --git a/docs/client_api/api/reloadwsfile.rst b/docs/client_api/api/reloadwsfile.rst index 9598b34b1..920f6172e 100644 --- a/docs/client_api/api/reloadwsfile.rst +++ b/docs/client_api/api/reloadwsfile.rst @@ -59,11 +59,11 @@ The following help text is generated by :code:`ecflow_client --help=reloadwsfile -user4 -* # use this form if you want all users to have read access - Usage: - --reloadwsfile - The client considers, for both user and child commands, the following environment variables: + --reloadwsfile + + The client considers, for both user and task commands, the following environment variables: ECF_HOST [mandatory*] The main server hostname; default value is 'localhost' diff --git a/docs/client_api/api/remove.rst b/docs/client_api/api/remove.rst index e229ef0b1..8d1d04663 100644 --- a/docs/client_api/api/remove.rst +++ b/docs/client_api/api/remove.rst @@ -23,7 +23,7 @@ The following help text is generated by :code:`ecflow_client --help=remove` remove variables i.e name name2 - The client considers, for both user and child commands, the following environment variables: + The client considers, for both user and task commands, the following environment variables: ECF_HOST [mandatory*] The main server hostname; default value is 'localhost' diff --git a/docs/client_api/api/replace.rst b/docs/client_api/api/replace.rst index 885f8857e..1883ed02e 100644 --- a/docs/client_api/api/replace.rst +++ b/docs/client_api/api/replace.rst @@ -23,16 +23,26 @@ The following help text is generated by :code:`ecflow_client --help=replace` Replaces a node in the server, with the given path Can also be used to add nodes in the server - arg1 = path to node - must exist in the client defs(arg2). This is also the node we want to - replace in the server - arg2 = path to client definition file - provides the definition of the new node - arg3 = (optional) [ parent | false ] (default = parent) - create parent families or suite as needed, when arg1 does not - exist in the server - arg4 = (optional) force (default = false) - Force the replacement even if it causes zombies to be created + + Argument(s): + + node: path + # The path to the node to be replaced. + # This path must exist in both the argument `definition` file + # and on the definition currently loaded on the server. + + definition: path + # The path to the client definition file, + # which provides the definition of the new node. + + parent: (optional) [ parent | false ] (default = parent) + # Allows automatically creating parent families or suite as needed, + # when argument `node` does not exist in the server + + force: (optional) (default = false) + # Force the replacement, even if any node is queued/executing. + # Using this option might causes zombies to be created + Replace can fail if: - The node path(arg1) does not exist in the provided client definition(arg2) - The client definition(arg2) must be free of errors @@ -46,10 +56,11 @@ The following help text is generated by :code:`ecflow_client --help=replace` For more information use --help check. Usage: - --replace=/suite/f1/t1 /tmp/client.def parent # Add/replace node tree /suite/f1/t1 - --replace=/suite/f1/t1 /tmp/client.def false force # replace t1 even if its active or submitted - The client considers, for both user and child commands, the following environment variables: + --replace=/suite/f1/t1 /tmp/client.def parent # Add/replace node tree /suite/f1/t1 + --replace=/suite/f1/t1 /tmp/client.def false force # replace t1 even if its active or submitted + + The client considers, for both user and task commands, the following environment variables: ECF_HOST [mandatory*] The main server hostname; default value is 'localhost' diff --git a/docs/client_api/api/requeue.rst b/docs/client_api/api/requeue.rst index 22933426f..dcb9da54a 100644 --- a/docs/client_api/api/requeue.rst +++ b/docs/client_api/api/requeue.rst @@ -24,19 +24,25 @@ The following help text is generated by :code:`ecflow_client --help=requeue` Re queues the specified node(s) If any child of the specified node(s) is in a suspended state, this state is cleared Repeats are reset to their starting values, relative time attributes are reset. - arg1 = (optional) [ abort | force ] - abort = re-queue only aborted tasks below node - force = Force the re-queueing even if there are nodes that are active or submitted - = Checks if any tasks are in submitted or active states below the node - if so does nothing. Otherwise re-queues the node. - arg2 = list of node paths. The node paths must begin with a leading '/' character + + Argument(s): + + mode: (optional) [ abort | force ] + # abort = re-queue only aborted tasks below node + # force = Force the re-queueing even if there are nodes that are active or submitted + # = Checks if any tasks are in submitted or active states below the node, + # and if so does nothing. Otherwise, re-queues the node. + + node: path + # A space separared list of node paths. Paths must begin with a leading '/' character Usage: - --requeue=abort /suite/f1 # re-queue all aborted tasks of /suite/f1 - --requeue=force /suite/f1 # forcibly re-queue /suite/f1 and all its children.May cause zombies. - --requeue=/s1/f1/t1 /s1/t2 # Re-queue node '/suite/f1/t1' and '/s1/t2' - The client considers, for both user and child commands, the following environment variables: + --requeue=abort /suite/f1 # re-queue all aborted tasks of /suite/f1 + --requeue=force /suite/f1 # forcibly re-queue /suite/f1 and all its children.May cause zombies. + --requeue=/s1/f1/t1 /s1/t2 # Re-queue node '/suite/f1/t1' and '/s1/t2' + + The client considers, for both user and task commands, the following environment variables: ECF_HOST [mandatory*] The main server hostname; default value is 'localhost' diff --git a/docs/client_api/api/restart.rst b/docs/client_api/api/restart.rst index 6a5cda3dc..d2cd62769 100644 --- a/docs/client_api/api/restart.rst +++ b/docs/client_api/api/restart.rst @@ -31,7 +31,7 @@ The following help text is generated by :code:`ecflow_client --help=restart` | HALTED | yes | no | no | no | |--------------|--------------|--------------|---------------|---------------------| - The client considers, for both user and child commands, the following environment variables: + The client considers, for both user and task commands, the following environment variables: ECF_HOST [mandatory*] The main server hostname; default value is 'localhost' diff --git a/docs/client_api/api/restore.rst b/docs/client_api/api/restore.rst index 7cc5921ff..9c2a25117 100644 --- a/docs/client_api/api/restore.rst +++ b/docs/client_api/api/restore.rst @@ -45,11 +45,13 @@ The following help text is generated by :code:`ecflow_client --help=restore` In this example task '/s/frestore_from_task/t1' is only triggered if 'farchive_now' is archived, then when t1 completes it will restore family 'farchive_now' - Usage:: - --restore=/s1/f1 # restore family /s1/f1 - --restore=/s1 /s2 # restore suites /s1 and /s2 - The client considers, for both user and child commands, the following environment variables: + Usage: + + --restore=/s1/f1 # restore family /s1/f1 + --restore=/s1 /s2 # restore suites /s1 and /s2 + + The client considers, for both user and task commands, the following environment variables: ECF_HOST [mandatory*] The main server hostname; default value is 'localhost' diff --git a/docs/client_api/api/restore_from_checkpt.rst b/docs/client_api/api/restore_from_checkpt.rst index a5eaf2b5e..0a2aa16ba 100644 --- a/docs/client_api/api/restore_from_checkpt.rst +++ b/docs/client_api/api/restore_from_checkpt.rst @@ -25,7 +25,7 @@ The following help text is generated by :code:`ecflow_client --help=restore_from The server must be halted and the definition in the server must be deleted first, otherwise an error is returned - The client considers, for both user and child commands, the following environment variables: + The client considers, for both user and task commands, the following environment variables: ECF_HOST [mandatory*] The main server hostname; default value is 'localhost' diff --git a/docs/client_api/api/resume.rst b/docs/client_api/api/resume.rst index 5a77f03f9..76e675638 100644 --- a/docs/client_api/api/resume.rst +++ b/docs/client_api/api/resume.rst @@ -22,12 +22,13 @@ The following help text is generated by :code:`ecflow_client --help=resume` ------ Resume the given node. This allows job generation for the given node, or any child node. - Usage:: - --resume=/s1/f1/t1 # resume task s1/f1/t1 - --resume=/s1 /s2 # resume suites /s1 and /s2 + Usage: - The client considers, for both user and child commands, the following environment variables: + --resume=/s1/f1/t1 # resume task s1/f1/t1 + --resume=/s1 /s2 # resume suites /s1 and /s2 + + The client considers, for both user and task commands, the following environment variables: ECF_HOST [mandatory*] The main server hostname; default value is 'localhost' diff --git a/docs/client_api/api/rid.rst b/docs/client_api/api/rid.rst index 2ed988b8b..e49c53e2a 100644 --- a/docs/client_api/api/rid.rst +++ b/docs/client_api/api/rid.rst @@ -21,7 +21,7 @@ The following help text is generated by :code:`ecflow_client --help=rid` rid --- - When specified overrides the environment variable ECF_RID. Can only be used for child commands. + When specified overrides the environment variable ECF_RID. Can only be used for task commands. diff --git a/docs/client_api/api/run.rst b/docs/client_api/api/run.rst index a69bf9902..b3c4b29e2 100644 --- a/docs/client_api/api/run.rst +++ b/docs/client_api/api/run.rst @@ -30,22 +30,34 @@ The following help text is generated by :code:`ecflow_client --help=run` A repeat attribute is incremented when all the child nodes are complete in this case the child nodes are automatically re-queued. Hence this command can be aid, in allowing a Repeat attribute to be incremented - arg1 = (optional)force - Forcibly run, even if there are nodes that are active or submitted - This can result in zombie creation - arg2 = node path(s). The paths must begin with a leading '/' character. - If the path is /suite/family will recursively run all tasks - When providing multiple paths avoid running the same task twice - Example: + + Argument(s): + + force: (optional) + # Forcibly run, even if there are nodes that are active or submitted + # This might cause the appearance of zombie + + node: path + # A space separared list of node paths. + # The paths must begin with a leading '/' character. + # If the path points to a suite or a family, all tasks will be run recursively. + # When providing multiple paths avoid running the same task twice + + Usage: + --run=/suite/t1 # run task t1 + Effect: + task t1; time 12:00 # will complete if run manually task t2; time 10:00 13:00 01:00 # will run 4 times before completing - When we have a time range(i.e as shown with task t2), then next time slot - is incremented for each run, until it expires, and the task completes. - Use the Why command, to show next run time (i.e. next time slot) - The client considers, for both user and child commands, the following environment variables: + Specifying a time range, as shown with task t2, means the next time slot + is incremented for each run, until it expires, and the task completes. + + Use the Why command, to show next run time (i.e. next time slot) + + The client considers, for both user and task commands, the following environment variables: ECF_HOST [mandatory*] The main server hostname; default value is 'localhost' diff --git a/docs/client_api/api/server_load.rst b/docs/client_api/api/server_load.rst index 734eb3ba1..7d42a9b93 100644 --- a/docs/client_api/api/server_load.rst +++ b/docs/client_api/api/server_load.rst @@ -25,24 +25,30 @@ The following help text is generated by :code:`ecflow_client --help=server_load` This is done by parsing the log file. If no log file is provided, then the log file path is obtained from the server. If the returned log file path is not accessible an error is returned - This command produces a three files in the CWD. - o ..gnuplot.dat - o ..gnuplot.script - o ..png + This command produces a three files in the CWD: + + - ..gnuplot.dat + - ..gnuplot.script + - ..png The generated script can be manually changed, to see different rendering effects. i.e. just run 'gnuplot ..gnuplot.script' - arg1 = path to log file + Argument(s): + + log: (optional) path + # The path to the log file. + # If not provided, the log file path is requested from the server. If the path to log file is known, it is *preferable* to use this, rather than requesting the log path from the server. Usage: - --server_load=/path/to_log_file # Parses log and generate gnuplot files - --server_load # Log file path is requested from server - # which is then used to generate gnuplot files - # *AVOID* if log file path is accessible + + --server_load=/path/to_log_file # Parses log and generate gnuplot files + --server_load # Log file path is requested from server + # which is then used to generate gnuplot files + # *AVOID* if log file path is accessible Now use any png viewer to see the output i.e @@ -52,8 +58,7 @@ The following help text is generated by :code:`ecflow_client --help=server_load` > xdg-open ..png > w3m ..png - - The client considers, for both user and child commands, the following environment variables: + The client considers, for both user and task commands, the following environment variables: ECF_HOST [mandatory*] The main server hostname; default value is 'localhost' diff --git a/docs/client_api/api/server_version.rst b/docs/client_api/api/server_version.rst index 9e9afe618..0dc001003 100644 --- a/docs/client_api/api/server_version.rst +++ b/docs/client_api/api/server_version.rst @@ -22,12 +22,13 @@ The following help text is generated by :code:`ecflow_client --help=server_versi -------------- Returns the version number of the server + Usage: - --server_version - Writes the version to standard output + --server_version + Writes the version to standard output - The client considers, for both user and child commands, the following environment variables: + The client considers, for both user and task commands, the following environment variables: ECF_HOST [mandatory*] The main server hostname; default value is 'localhost' diff --git a/docs/client_api/api/show.rst b/docs/client_api/api/show.rst index b0a18fe5e..d12bc7a8d 100644 --- a/docs/client_api/api/show.rst +++ b/docs/client_api/api/show.rst @@ -24,21 +24,27 @@ The following help text is generated by :code:`ecflow_client --help=show` Used to print state of the definition returned from the server to standard output. This command can *only* be used in a group command, and will only work if it is preceded with a get command. See examples below. - arg1 = [ defs | state | migrate ] - The output of show has several options: i.e - o no arguments: With no arguments, print the definition structure to standard output - Extern's are automatically added, allowing the output to be reloaded into the server - i.e --group="get ; show" - o state: - This will output definition structure along with all the state information. - This will include the trigger expressions, abstract syntax tree as comments. - Excludes the edit history - o migrate: - This will output definition structure along with all the state information. - The node state is shown in the comments. - This format allows the definition to be migrated to future version of ecflow. - The output includes edit history but excludes externs. - When the definition is reloaded *NO* checking is done. + + Argument(s): + + output: (optional) [ defs | state | migrate ] + # defs: + # Return the definition structure only, without any state information. + # Necessary `extern`s are automatically added, allowing the output to be + # reloaded into the server e.g., --group="get ; show" + # This is the default if no `output` argument is provided. + # + # state: + # Return definition structure along with all the state information. + # Output includes the trigger expressions' abstract syntax tree (as comment). + # Does not include the edit history + # + # migrate: + # Return definition structure along with all the state information. + # The node state is shown in the comments. + # Use this format to migrate the definition to future version of ecflow. + # The output includes edit history but excludes externs. + # When the definition is reloaded *NO* checking is done. The following shows a summary of the features associated with each choice DEFS STATE MIGRATE @@ -48,6 +54,7 @@ The following help text is generated by :code:`ecflow_client --help=show` trigger AST No Yes No Usage: + --group="get ; show" --group="get ; show defs" # same as the previous example --group="get ; show state" # Show all state for the node tree @@ -55,7 +62,7 @@ The following help text is generated by :code:`ecflow_client --help=show` --group="get=/s1; show" # show state for the node only --group="get=/s1; show state" - The client considers, for both user and child commands, the following environment variables: + The client considers, for both user and task commands, the following environment variables: ECF_HOST [mandatory*] The main server hostname; default value is 'localhost' diff --git a/docs/client_api/api/shutdown.rst b/docs/client_api/api/shutdown.rst index 6f7747006..81479356d 100644 --- a/docs/client_api/api/shutdown.rst +++ b/docs/client_api/api/shutdown.rst @@ -22,8 +22,16 @@ The following help text is generated by :code:`ecflow_client --help=shutdown` -------- Stop server from scheduling new jobs. - arg1 = yes(optional) # use to bypass confirmation prompt,i.e - --shutdown=yes + + Argument(s): + + confirm: (optional) + # value must be "yes"; bypasses the confirmation prompt + + Usage: + + --shutdown=yes + The following table shows server behaviour in the different states. |----------------------------------------------------------------------------------| | Server State | User Request | Task Request |Job Scheduling | Auto-Check-pointing | @@ -33,7 +41,7 @@ The following help text is generated by :code:`ecflow_client --help=shutdown` | HALTED | yes | no | no | no | |--------------|--------------|--------------|---------------|---------------------| - The client considers, for both user and child commands, the following environment variables: + The client considers, for both user and task commands, the following environment variables: ECF_HOST [mandatory*] The main server hostname; default value is 'localhost' diff --git a/docs/client_api/api/stats.rst b/docs/client_api/api/stats.rst index d824a8e8b..140788fcb 100644 --- a/docs/client_api/api/stats.rst +++ b/docs/client_api/api/stats.rst @@ -23,7 +23,7 @@ The following help text is generated by :code:`ecflow_client --help=stats` Returns the server statistics as a string. - The client considers, for both user and child commands, the following environment variables: + The client considers, for both user and task commands, the following environment variables: ECF_HOST [mandatory*] The main server hostname; default value is 'localhost' diff --git a/docs/client_api/api/stats_reset.rst b/docs/client_api/api/stats_reset.rst index 4c7d258f7..eae6d315c 100644 --- a/docs/client_api/api/stats_reset.rst +++ b/docs/client_api/api/stats_reset.rst @@ -23,7 +23,7 @@ The following help text is generated by :code:`ecflow_client --help=stats_reset` Resets the server statistics. - The client considers, for both user and child commands, the following environment variables: + The client considers, for both user and task commands, the following environment variables: ECF_HOST [mandatory*] The main server hostname; default value is 'localhost' diff --git a/docs/client_api/api/stats_server.rst b/docs/client_api/api/stats_server.rst index 19f9675a9..28b10f4df 100644 --- a/docs/client_api/api/stats_server.rst +++ b/docs/client_api/api/stats_server.rst @@ -23,7 +23,7 @@ The following help text is generated by :code:`ecflow_client --help=stats_server Returns the server statistics as a struct and string. For test use only. - The client considers, for both user and child commands, the following environment variables: + The client considers, for both user and task commands, the following environment variables: ECF_HOST [mandatory*] The main server hostname; default value is 'localhost' diff --git a/docs/client_api/api/status.rst b/docs/client_api/api/status.rst index 77f1475c2..de191660d 100644 --- a/docs/client_api/api/status.rst +++ b/docs/client_api/api/status.rst @@ -35,11 +35,13 @@ The following help text is generated by :code:`ecflow_client --help=status` - state is active but it can't find process id, i.e. ECF_RID - the status command does not exit cleanly When this happens a flag is set, STATUSCMD_FAILED, which is visible in the GUI - Usage:: - --status=/s1/f1/t1 # ECF_STATUS_CMD should output to %ECF_JOB%.stat - --file=/s1/f1/t1 stat # Return contents of %ECF_JOB%.stat file - The client considers, for both user and child commands, the following environment variables: + Usage: + + --status=/s1/f1/t1 # ECF_STATUS_CMD should output to %ECF_JOB%.stat + --file=/s1/f1/t1 stat # Return contents of %ECF_JOB%.stat file + + The client considers, for both user and task commands, the following environment variables: ECF_HOST [mandatory*] The main server hostname; default value is 'localhost' diff --git a/docs/client_api/api/suites.rst b/docs/client_api/api/suites.rst index 8eecd7e97..5c700d0d1 100644 --- a/docs/client_api/api/suites.rst +++ b/docs/client_api/api/suites.rst @@ -23,7 +23,7 @@ The following help text is generated by :code:`ecflow_client --help=suites` Returns the list of suites, in the order defined in the server. - The client considers, for both user and child commands, the following environment variables: + The client considers, for both user and task commands, the following environment variables: ECF_HOST [mandatory*] The main server hostname; default value is 'localhost' diff --git a/docs/client_api/api/suspend.rst b/docs/client_api/api/suspend.rst index 9a897478d..590411997 100644 --- a/docs/client_api/api/suspend.rst +++ b/docs/client_api/api/suspend.rst @@ -22,12 +22,13 @@ The following help text is generated by :code:`ecflow_client --help=suspend` ------- Suspend the given node. This prevents job generation for the given node, or any child node. - Usage:: - --suspend=/s1/f1/t1 # suspend task s1/f1/t1 - --suspend=/s1 /s2 # suspend suites /s1 and /s2 + Usage: - The client considers, for both user and child commands, the following environment variables: + --suspend=/s1/f1/t1 # suspend task s1/f1/t1 + --suspend=/s1 /s2 # suspend suites /s1 and /s2 + + The client considers, for both user and task commands, the following environment variables: ECF_HOST [mandatory*] The main server hostname; default value is 'localhost' diff --git a/docs/client_api/api/sync.rst b/docs/client_api/api/sync.rst index 3a3f1873a..ecf6fcd74 100644 --- a/docs/client_api/api/sync.rst +++ b/docs/client_api/api/sync.rst @@ -28,7 +28,7 @@ The following help text is generated by :code:`ecflow_client --help=sync` Requires a client handle, change and modify number, to get the incremental changes from server. The change in server state is then and merged with the client definition. - The client considers, for both user and child commands, the following environment variables: + The client considers, for both user and task commands, the following environment variables: ECF_HOST [mandatory*] The main server hostname; default value is 'localhost' diff --git a/docs/client_api/api/sync_clock.rst b/docs/client_api/api/sync_clock.rst index 6d3a130c5..3a25ad9b8 100644 --- a/docs/client_api/api/sync_clock.rst +++ b/docs/client_api/api/sync_clock.rst @@ -29,7 +29,7 @@ The following help text is generated by :code:`ecflow_client --help=sync_clock` Requires a client handle, change and modify number, to get the incremental changes from server. The change in server state is then and merged with the client definition. - The client considers, for both user and child commands, the following environment variables: + The client considers, for both user and task commands, the following environment variables: ECF_HOST [mandatory*] The main server hostname; default value is 'localhost' diff --git a/docs/client_api/api/sync_full.rst b/docs/client_api/api/sync_full.rst index 704a54448..59cef6604 100644 --- a/docs/client_api/api/sync_full.rst +++ b/docs/client_api/api/sync_full.rst @@ -25,7 +25,7 @@ The following help text is generated by :code:`ecflow_client --help=sync_full` *Important* for use with c++/python interface only. Requires a client_handle. The returned definition is stored on the client. - The client considers, for both user and child commands, the following environment variables: + The client considers, for both user and task commands, the following environment variables: ECF_HOST [mandatory*] The main server hostname; default value is 'localhost' diff --git a/docs/client_api/api/terminate.rst b/docs/client_api/api/terminate.rst index c77512177..24de64cb3 100644 --- a/docs/client_api/api/terminate.rst +++ b/docs/client_api/api/terminate.rst @@ -22,10 +22,17 @@ The following help text is generated by :code:`ecflow_client --help=terminate` --------- Terminate the server. - arg1 = yes(optional) # use to bypass confirmation prompt.i.e - --terminate=yes - The client considers, for both user and child commands, the following environment variables: + Argument(s): + + confirm: (optional) + # value must be "yes"; bypasses the confirmation prompt + + Usage: + + --terminate=yes + + The client considers, for both user and task commands, the following environment variables: ECF_HOST [mandatory*] The main server hostname; default value is 'localhost' diff --git a/docs/client_api/api/user.rst b/docs/client_api/api/user.rst index 4f3ff45d2..e9eb354f7 100644 --- a/docs/client_api/api/user.rst +++ b/docs/client_api/api/user.rst @@ -2,7 +2,18 @@ .. _user_cli: user -//// +**** + + + + + + +.. rubric:: Output of :code:`--help=user` + + + +The following help text is generated by :code:`ecflow_client --help=user` :: @@ -13,3 +24,5 @@ user Specifies the user name used to contact the server. Must be used in combination with option --password. + + diff --git a/docs/client_api/api/version.rst b/docs/client_api/api/version.rst index 901a42821..77143bff9 100644 --- a/docs/client_api/api/version.rst +++ b/docs/client_api/api/version.rst @@ -23,7 +23,7 @@ The following help text is generated by :code:`ecflow_client --help=version` Show ecflow client version number, and version of the boost library used - The client considers, for both user and child commands, the following environment variables: + The client considers, for both user and task commands, the following environment variables: ECF_HOST [mandatory*] The main server hostname; default value is 'localhost' diff --git a/docs/client_api/api/wait.rst b/docs/client_api/api/wait.rst index e09461e32..0b5cf1c9c 100644 --- a/docs/client_api/api/wait.rst +++ b/docs/client_api/api/wait.rst @@ -23,12 +23,17 @@ The following help text is generated by :code:`ecflow_client --help=wait` Evaluates an expression, and block while the expression is false. For use in the '.ecf' file *only*, hence the context is supplied via environment variables - arg1 = string(expression) + + Argument(s): + + expression: string + # The expression to be evaluated. Usage: - ecflow_client --wait="/suite/taskx == complete" - The client considers, for both user and child commands, the following environment variables: + --wait="/suite/taskx == complete" + + The client considers, for both user and task commands, the following environment variables: ECF_HOST [mandatory*] The main server hostname; default value is 'localhost' @@ -45,20 +50,20 @@ The following help text is generated by :code:`ecflow_client --help=wait` with the server, either by setting the environment variables or by specifying the command line options. - The following environment variables are used specifically by child commands: + The following environment variables are used specifically by task commands: ECF_NAME [mandatory] Full path name to the task ECF_PASS [mandatory] The job password (defined by the server, and used to authenticate client requests) ECF_TRYNO [mandatory] - The run number of the job (defined by the server, and used in job/output file name generation. - ECF_RID [mandatory] + The run number of the job (defined by the server, and used in job/output file name generation). + ECF_RID [mandatory*] The process identifier. Supports identifying zombies and automated killing of running jobs ECF_TIMEOUT [optional] Maximum time in *seconds* for client to deliver message to main server; default is 24 hours ECF_DENIED [optional] - Allows task to exit with an error, upon connection failure, thus avoids ECF_TIMEOUTs wait. + Allows task to exit with an error, upon connection failure, thus avoids ECF_TIMEOUT's wait. NO_ECF [optional] If set, ecflow_client exits immediately with success; useful to test the scripts without a server diff --git a/docs/client_api/api/why.rst b/docs/client_api/api/why.rst index 5cea8e7a2..23c1fb882 100644 --- a/docs/client_api/api/why.rst +++ b/docs/client_api/api/why.rst @@ -28,11 +28,13 @@ The following help text is generated by :code:`ecflow_client --help=why` Will return reason why the node is holding and for all its children. If no arguments supplied will report on all nodes arg = node path | arg = NULL + Usage: - --group="get; why" # returns why for all holding nodes - --group="get; why=/suite/family" # returns why for a specific node - The client considers, for both user and child commands, the following environment variables: + --group="get; why" # returns why for all holding nodes + --group="get; why=/suite/family" # returns why for a specific node + + The client considers, for both user and task commands, the following environment variables: ECF_HOST [mandatory*] The main server hostname; default value is 'localhost' diff --git a/docs/client_api/api/zombie_adopt.rst b/docs/client_api/api/zombie_adopt.rst index 80675941a..9329f1005 100644 --- a/docs/client_api/api/zombie_adopt.rst +++ b/docs/client_api/api/zombie_adopt.rst @@ -26,9 +26,12 @@ The following help text is generated by :code:`ecflow_client --help=zombie_adopt This is only allowed if the process id matches, otherwise an error is issued. The zombie reference stored in the server is then deleted. args = list of task paths, at least one expected - --zombie_adopt=/path/to/task /path/to/task2 - The client considers, for both user and child commands, the following environment variables: + Usage: + + --zombie_adopt=/path/to/task /path/to/task2 + + The client considers, for both user and task commands, the following environment variables: ECF_HOST [mandatory*] The main server hostname; default value is 'localhost' diff --git a/docs/client_api/api/zombie_block.rst b/docs/client_api/api/zombie_block.rst index d549ee99b..59a2055dd 100644 --- a/docs/client_api/api/zombie_block.rst +++ b/docs/client_api/api/zombie_block.rst @@ -27,9 +27,12 @@ The following help text is generated by :code:`ecflow_client --help=zombie_block attempting to connect to the server for 24 hours, and will then return an error. The connection timeout can be configured with environment ECF_TIMEOUT args = list of task paths, at least one expected - --zombie_block=/path/to/task /path/to/task2 - The client considers, for both user and child commands, the following environment variables: + Usage: + + --zombie_block=/path/to/task /path/to/task2 + + The client considers, for both user and task commands, the following environment variables: ECF_HOST [mandatory*] The main server hostname; default value is 'localhost' diff --git a/docs/client_api/api/zombie_fail.rst b/docs/client_api/api/zombie_fail.rst index 7e3669c17..b3e4eae8e 100644 --- a/docs/client_api/api/zombie_fail.rst +++ b/docs/client_api/api/zombie_fail.rst @@ -28,9 +28,12 @@ The following help text is generated by :code:`ecflow_client --help=zombie_fail` infinite recursion. The references to the zombie in the server is automatically deleted after 1 hour args = list of task paths, at least one expected - --zombie_fail=/path/to/task /path/to/task2 - The client considers, for both user and child commands, the following environment variables: + Usage: + + --zombie_fail=/path/to/task /path/to/task2 + + The client considers, for both user and task commands, the following environment variables: ECF_HOST [mandatory*] The main server hostname; default value is 'localhost' diff --git a/docs/client_api/api/zombie_fob.rst b/docs/client_api/api/zombie_fob.rst index bbb9807f2..b855ae14f 100644 --- a/docs/client_api/api/zombie_fob.rst +++ b/docs/client_api/api/zombie_fob.rst @@ -28,9 +28,12 @@ The following help text is generated by :code:`ecflow_client --help=zombie_fob` allowing the job to finish. The references to the zombie in the server is automatically deleted after 1 hour args = list of task paths, at least one expected - --zombie_fob=/path/to/task1 /path/to/task2 - The client considers, for both user and child commands, the following environment variables: + Usage: + + --zombie_fob=/path/to/task1 /path/to/task2 + + The client considers, for both user and task commands, the following environment variables: ECF_HOST [mandatory*] The main server hostname; default value is 'localhost' diff --git a/docs/client_api/api/zombie_get.rst b/docs/client_api/api/zombie_get.rst index 455f1a82e..44399da20 100644 --- a/docs/client_api/api/zombie_get.rst +++ b/docs/client_api/api/zombie_get.rst @@ -24,7 +24,7 @@ The following help text is generated by :code:`ecflow_client --help=zombie_get` Returns the list of zombies from the server. Results reported to standard output. - The client considers, for both user and child commands, the following environment variables: + The client considers, for both user and task commands, the following environment variables: ECF_HOST [mandatory*] The main server hostname; default value is 'localhost' diff --git a/docs/client_api/api/zombie_kill.rst b/docs/client_api/api/zombie_kill.rst index 36ca1b442..711c13863 100644 --- a/docs/client_api/api/zombie_kill.rst +++ b/docs/client_api/api/zombie_kill.rst @@ -27,9 +27,12 @@ The following help text is generated by :code:`ecflow_client --help=zombie_kill` Can only kill zombies that have an associated Task, hence path zombies must be killed manually. arg = list of task paths, at least one expected - --zombie_kill=/path/to/task /path/to/task2 - The client considers, for both user and child commands, the following environment variables: + Usage: + + --zombie_kill=/path/to/task /path/to/task2 + + The client considers, for both user and task commands, the following environment variables: ECF_HOST [mandatory*] The main server hostname; default value is 'localhost' diff --git a/docs/client_api/api/zombie_remove.rst b/docs/client_api/api/zombie_remove.rst index da1174d98..c7f95d2e6 100644 --- a/docs/client_api/api/zombie_remove.rst +++ b/docs/client_api/api/zombie_remove.rst @@ -25,9 +25,12 @@ The following help text is generated by :code:`ecflow_client --help=zombie_remov Since a job typically has many child commands (i.e init, complete, event, meter, label, wait, queue) the zombie may reappear args = list of task paths, at least one expected - --zombie_remove=/path/to/task /path/to/task2 - The client considers, for both user and child commands, the following environment variables: + Usage: + + --zombie_remove=/path/to/task /path/to/task2 + + The client considers, for both user and task commands, the following environment variables: ECF_HOST [mandatory*] The main server hostname; default value is 'localhost' diff --git a/docs/client_api/build.py b/docs/client_api/build.py index b74f30fc6..7cafa9e99 100755 --- a/docs/client_api/build.py +++ b/docs/client_api/build.py @@ -1,10 +1,21 @@ #!/usr/bin/env python3 -import os -import re import pathlib -import subprocess +import sys -cmd_type_terms = {"child": "child command", "user": "user command", "option": ""} +HERE = pathlib.Path(__file__).resolve().parent +sys.path.insert(0, str(HERE)) + +from validate_help_manifest import check_cross_references, check_schema, load_json # noqa: E402 + +cmd_type_terms = {"task": "child command", "user": "user command"} + +# Mirrors Help.cpp's CommandFilter::known_options exactly: the set of names for which +# show_command_help() suppresses the environment-variable banner. This disagrees with the +# manifest's own options[] array in both directions ('help'/'version'/'remove' are manifest +# options but are absent here, so they still get the banner; there is no equivalent entry for +# every manifest option) -- a pre-existing Help.cpp quirk tracked as Future Work, not something +# for build.py to paper over. +KNOWN_OPTIONS = {"add", "debug", "host", "password", "port", "rid", "ssl", "user", "http", "https"} # Maps each ecflow_client command name to the C++ command class section (anchor) that documents its # wire-level protocol in command_internals.rst. Several classes implement many command-line options, so the @@ -100,12 +111,6 @@ } -def run_command(cmd): - print(f"*** Executing: {cmd}") - p = subprocess.run(cmd, capture_output=True, text=True) - return p.stdout, p.stderr - - class Entry: def __init__(self, name, type, desc): self.name = name @@ -116,24 +121,38 @@ def __repr__(self): return f"Entry('{self.name}', '{self.type}', '{self.desc}')" -def load_help_table(command): - entries = [] - o, _ = run_command(command) - o = o.split("\n") - for line in o: - m = re.search(r"""(\S+)\s+(child|user|option)\s+(.*)""", line) - if m and m.groups and len(m.groups()) == 3: - entry = Entry(m.group(1), m.group(2), m.group(3)) - entries.append(entry) - return entries +def load_manifest(): + manifest = load_json(HERE / "help.json") + schema = load_json(HERE / "help.schema.json") + schema_errors = check_schema(manifest, schema) + if schema_errors: + for error in schema_errors: + location = "/".join(str(part) for part in error.path) or "" + print(f"help.json: {location}: {error.message}", file=sys.stderr) + sys.exit(f"{len(schema_errors)} schema violation(s) found in help.json; aborting docs build.") -def load_commands(): - return load_help_table(["ecflow_client", "--help=summary"]) + reference_problems = check_cross_references(manifest) + if reference_problems: + for problem in reference_problems: + print(f"help.json: {problem}", file=sys.stderr) + sys.exit(f"{len(reference_problems)} cross-reference problem(s) found in help.json; aborting docs build.") + return manifest -def load_options(): - return load_help_table(["ecflow_client", "--help=option"]) + +def load_commands(manifest): + # 'internal' commands (e.g. move, constructed on the fly by PlugCmd) are never registered in + # CtsCmdRegistry and have no CLI-reachable --help output; they are intentionally undocumented, + # matching today's behaviour. + visible = [command for command in manifest["commands"] if command["visibility"] != "internal"] + entries = [Entry(command["name"], command["kind"], command["summary"]) for command in visible] + return sorted(entries, key=lambda entry: entry.name) + + +def load_options(manifest): + entries = [Entry(option["name"], option["kind"], option["summary"]) for option in manifest["options"]] + return sorted(entries, key=lambda entry: entry.name) def load_description_file(name): @@ -147,13 +166,69 @@ def load_description_file(name): return f"" -def render_single_page_rst(name): +def join_description(lines): + # Reproduces HelpCatalog::description_for(): the description holds one line per element (a blank + # line is an empty string), joined by a newline to reconstruct the original text verbatim, + # including pre-formatted content. + return "\n".join(lines) + + +def format_env_var(var): + required = var["required"] + if var.get("overridable_by"): + required += "*" + return f" {var['name']} <{var['type']}> [{required}]\n {var['description']}\n" + + +def render_client_env_description(env_vars): + txt = "The client considers, for both user and task commands, the following environment variables:\n\n" + for var in env_vars: + if var["applies_to"] == "both": + txt += format_env_var(var) + txt += ( + "\nThe options marked with (*) must be specified in order for the client to communicate\n" + "with the server, either by setting the environment variables or by specifying the\n" + "command line options.\n" + ) + return txt + + +def render_task_env_description(env_vars): + txt = "The following environment variables are used specifically by task commands:\n\n" + for var in env_vars: + if var["applies_to"] == "task": + txt += format_env_var(var) + txt += "\nThe scripts are expected to export the mandatory variables, typically in shared include files\n" + return txt + + +def render_help_output(manifest, name): + # Reproduces Documentation::show_command_help()'s output byte-for-byte, without invoking the + # built ecflow_client binary: the name banner, the manifest description, and (for anything other + # than an option) the environment-variable sections that show_command_help() appends. + commands_by_name = {command["name"]: command for command in manifest["commands"]} + options_by_name = {option["name"]: option for option in manifest["options"]} + + entry = commands_by_name.get(name) or options_by_name[name] + is_option = name in KNOWN_OPTIONS + is_task_command = name in commands_by_name and commands_by_name[name]["kind"] == "task" + + description = join_description(entry["description"]) + txt = f"\n{name}\n{'-' * len(name)}\n\n{description}\n\n" + if not is_option: + txt += render_client_env_description(manifest["environment_variables"]) + if is_task_command: + txt += "\n" + render_task_env_description(manifest["environment_variables"]) + return txt + + +def render_single_page_rst(manifest, name): description = load_description_file(name) description_title = "Description" description_header = "" if not description else f"\n.. rubric:: {description_title}\n\n" - output, _ = run_command(["ecflow_client", f"--help={name}"]) + output = render_help_output(manifest, name) output = '\n'.join([' ' + line for line in output.split('\n')]) + '\n' # prefix each line of the output with the necessary indentation output_title = f"Output of :code:`--help={name}`" output_header = f"\n.. rubric:: {output_title}\n\n" @@ -191,27 +266,6 @@ def render_single_page_rst(name): return txt -def render_user_option_rst(): - title = f"user" - txt = f""" -.. _user_cli: - -{title} -{"/" * len(title)} - -:: - - - user - ---- - - Specifies the user name used to contact the server. Must be used in combination with option --password. - - -""" - return txt - - def render_index_rst(): title = "Command line interface (CLI)" txt = f""" @@ -337,16 +391,18 @@ def render_options_rst(entries): if __name__ == "__main__": + manifest = load_manifest() + # Render and store index.rst content = render_index_rst() with open("index.rst", "w") as f: f.write(content) - command_entries = load_commands() + command_entries = load_commands(manifest) with open("cli_commands.rst", "w") as f: f.write(render_commands_rst(command_entries)) - option_entries = load_options() + option_entries = load_options(manifest) with open("cli_options.rst", "w") as f: f.write(render_options_rst(option_entries)) @@ -355,14 +411,11 @@ def render_options_rst(entries): # Render and store each of the command/option.rst for entry in command_entries: - content = render_single_page_rst(entry.name) + content = render_single_page_rst(manifest, entry.name) with open(f"api/{entry.name}.rst", "w") as f: f.write(content) for entry in option_entries: - if entry.name == "user": - content = render_user_option_rst() - else: - content = render_single_page_rst(entry.name) + content = render_single_page_rst(manifest, entry.name) with open(f"api/{entry.name}.rst", "w") as f: f.write(content) diff --git a/docs/client_api/cli_commands.rst b/docs/client_api/cli_commands.rst index 3f5ce88c8..67aacfa14 100644 --- a/docs/client_api/cli_commands.rst +++ b/docs/client_api/cli_commands.rst @@ -128,10 +128,6 @@ Commands - :term:`user command` - Stop server communication with jobs, and new job scheduling. - * - :ref:`help_cli` - - :term:`user command` - - Produce help message - * - :ref:`init_cli` - :term:`child command` - Mark task as started(active). For use in the '.ecf' script file *only* @@ -154,7 +150,7 @@ Commands * - :ref:`log_cli` - :term:`user command` - - Get,clear,flush or create a new log file. + - Get, clear, flush or create a new log file. * - :ref:`meter_cli` - :term:`child command` @@ -204,10 +200,6 @@ Commands - :term:`user command` - Reload the white list file. - * - :ref:`remove_cli` - - :term:`user command` - - remove variables i.e name name2 - * - :ref:`replace_cli` - :term:`user command` - Replaces a node in the server, with the given path @@ -292,10 +284,6 @@ Commands - :term:`user command` - Terminate the server. - * - :ref:`version_cli` - - :term:`user command` - - Show ecflow client version number, and version of the boost library used - * - :ref:`wait_cli` - :term:`child command` - Evaluates an expression, and block while the expression is false. @@ -366,7 +354,6 @@ Commands get_state group halt - help init job_gen kill @@ -385,7 +372,6 @@ Commands reloadcustompasswdfile reloadpasswdfile reloadwsfile - remove replace requeue restart @@ -407,7 +393,6 @@ Commands sync_clock sync_full terminate - version wait why zombie_adopt diff --git a/docs/client_api/cli_options.rst b/docs/client_api/cli_options.rst index 72236bbcc..02431b6f8 100644 --- a/docs/client_api/cli_options.rst +++ b/docs/client_api/cli_options.rst @@ -17,6 +17,9 @@ Options * - :ref:`debug_cli` - Enables the display of client environment settings and execution details. + * - :ref:`help_cli` + - Produce help message + * - :ref:`host_cli` - When specified overrides the environment variable ECF_HOST and default host: 'localhost' @@ -32,8 +35,11 @@ Options * - :ref:`port_cli` - When specified overrides the environment variable ECF_PORT and default port: '3141' + * - :ref:`remove_cli` + - remove variables i.e name name2 + * - :ref:`rid_cli` - - When specified overrides the environment variable ECF_RID. Can only be used for child commands. + - When specified overrides the environment variable ECF_RID. Can only be used for task commands. * - :ref:`ssl_cli` - Enables the use of SSL when contacting the server. @@ -41,6 +47,9 @@ Options * - :ref:`user_cli` - Specifies the user name used to contact the server. Must be used in combination with option --password. + * - :ref:`version_cli` + - Show ecflow client version number, and version of the boost library used + .. toctree:: :maxdepth: 1 @@ -48,11 +57,14 @@ Options add (option) debug (option) + help (option) host (option) http (option) https (option) password (option) port (option) + remove (option) rid (option) ssl (option) user (option) + version (option) diff --git a/docs/client_api/command_internals.rst b/docs/client_api/command_internals.rst index ee2496427..0c11ee795 100644 --- a/docs/client_api/command_internals.rst +++ b/docs/client_api/command_internals.rst @@ -240,7 +240,7 @@ The zombie handling is the same as for ``InitCmd``. .. code-block:: shell - ecflow_client --abort=reasonX + ecflow_client --abort= # a bare --abort is valid; the server substitutes a default reason ecflow_client --abort @@ -256,15 +256,19 @@ The zombie handling is the same as for ``InitCmd``. Mark task as aborted. For use in the '.ecf' script file *only* Hence the context is supplied via environment variables - arg1 = (optional) string(reason) - Optionally provide a reason why the abort was raised - If this child command is a zombie, then the default action will be to *block*. + Argument(s): + + reason: (optional) string + # Optionally provide a reason why the abort was raised + + If this task command is a zombie, then the default action will be to *block*. The default can be overridden by using zombie attributes. Otherwise the blocking period is defined by ECF_TIMEOUT. Usage: - ecflow_client --abort=reasonX + + --abort= .. tab:: Request @@ -373,16 +377,21 @@ variables as the job finishes. The zombie handling is the same as for ``InitCmd` Mark task as complete. For use in the '.ecf' script file *only* Hence the context is supplied via environment variables - If this child command is a zombie, then the default action will be to *block*. + If this task command is a zombie, then the default action will be to *block*. The default can be overridden by using zombie attributes. Otherwise the blocking period is defined by ECF_TIMEOUT. The init command allows variables to be added, and complete command allows for them to be removed. - arg1(--remove)(optional) = a list of variables to removed from this task + + Argument(s): + + --remove: (optional) + # A list of space separated variables to removed from this task Usage: - ecflow_client --complete - ecflow_client --complete --remove name1 name2 # delete variables name1 and name2 on the task + + --complete + --complete --remove name1 name2 # delete variables name1 and name2 on the task .. tab:: Request @@ -517,10 +526,15 @@ synchronisation on the state of other nodes. Evaluates an expression, and block while the expression is false. For use in the '.ecf' file *only*, hence the context is supplied via environment variables - arg1 = string(expression) + + Argument(s): + + expression: string + # The expression to be evaluated. Usage: - ecflow_client --wait="/suite/taskx == complete" + + --wait="/suite/taskx == complete" .. tab:: Request @@ -649,18 +663,24 @@ complete without error); this can be overridden with zombie attributes. Change event. For use in the '.ecf' script file *only* Hence the context is supplied via environment variables - arg1(string | int) = event-name - arg2(string)(optional) = [ set | clear] default value is set + Argument(s): + + event-name: string | int + # The name of the event to be set or cleared. - If this child command is a zombie, then the default action will be to *fob*, + state: (optional) string + # [ set | clear], default value is set + + If this task command is a zombie, then the default action will be to *fob*, i.e allow the ecflow client command to complete without an error The default can be overridden by using zombie attributes. Usage: - ecflow_client --event=ev # set the event, default since event initial value is clear - ecflow_client --event=ev set # set the event, explicit - ecflow_client --event=ev clear # clear the event, use when event initial value is set + + --event=ev # set the event, default since event initial value is clear + --event=ev set # set the event, explicit + --event=ev clear # clear the event, use when event initial value is set .. tab:: Request @@ -802,17 +822,25 @@ zombie attributes, and the blocking period is bounded by ``ECF_TIMEOUT``. Mark task as started(active). For use in the '.ecf' script file *only* Hence the context is supplied via environment variables. - arg1(string) = process_or_remote_id The process id of the job or remote_id - Using remote id allows the jobs to be killed - arg2(--add)(optional)= add/update variables as name value pairs - If this child command is a zombie, then the default action will be to *block*. + Argument(s): + + process_or_remote_id: string + # The process id of the job or remote_id. + # Using remote id allows the jobs to be killed. + + --add: (optional) + # add/update variables as name value pairs + # a list of space separated variables to add/update on this task + + If this task command is a zombie, then the default action will be to *block*. The default can be overridden by using zombie attributes. Otherwise the blocking period is defined by ECF_TIMEOUT. Usage: - ecflow_client --init=$$ - ecflow_client --init=$$ --add name=value name2=value2 # add/update variables to task + + --init=$$ + --init=$$ --add name=value name2=value2 # add/update variables to task .. tab:: Request @@ -953,16 +981,22 @@ zombie action is to *fob*. Change Label. For use in the '.ecf' script file *only* Hence the context is supplied via environment variables - arg1 = label-name - arg2 = The new label value - The labels values can be single or multi-line(space separated quoted strings) - If this child command is a zombie, then the default action will be to *fob*, + Argument(s): + + label-name + # The name of the label to be set. + + value + # The new label value. Can be single or multi-line (space separated quoted strings). + + If this task command is a zombie, then the default action will be to *fob*, i.e allow the ecflow client command to complete without an error The default can be overridden by using zombie attributes. Usage: - ecflow_client --label=progressed merlin + + --label=progressed merlin .. tab:: Request @@ -1066,15 +1100,22 @@ zombie action is to *fob*. Change meter. For use in the '.ecf' script file *only* Hence the context is supplied via environment variables - arg1(string) = meter-name - arg2(int) = the new meter value - If this child command is a zombie, then the default action will be to *fob*, + Argument(s): + + meter-name: string + # The name of the meter to be set. + + value: int + # The new meter value + + If this task command is a zombie, then the default action will be to *fob*, i.e allow the ecflow client command to complete without an error The default can be overridden by using zombie attributes. Usage: - ecflow_client --meter=my_meter 20 + + --meter=my_meter 20 .. tab:: Request @@ -1190,21 +1231,33 @@ The ``active`` and ``no_of_aborted`` actions return a string; the others return QueueCmd. For use in the '.ecf' script file *only* Hence the context is supplied via environment variables - arg1(string) = queue-name: - arg2(string) = action: [active | aborted | complete | no_of_aborted | reset ] - active: returns the first queued/aborted step, the return string is the queue value from the definition - no_of_aborted: returns number of aborted steps as a string, i.e 10 - reset: sets the index to the first queued/aborted step. Allows steps to be reprocessed for errors - arg3(string) = step: value returned from step=$(ecflow_client --queue=queue_name active) - This is only valid for complete and aborted steps - arg4(string) = path: (optional). The path where the queue is defined. - By default we search for the queue up the node tree. - - If this child command is a zombie, then the default action will be to *block*, - The default can be overridden by using zombie attributes.If the path to the queue is not defined, then this command will + + Argument(s): + + queue-name: string + # The name of the queue to be used. + + action: string + # [active | aborted | complete | no_of_aborted | reset ] + # active: returns the first queued/aborted step, the return string is the queue value from the definition + # no_of_aborted: returns number of aborted steps as a string, i.e 10 + # reset: sets the index to the first queued/aborted step. Allows steps to be reprocessed for errors + + step: string + # The value returned from `step=$(ecflow_client --queue=queue_name active)` + # This is only valid for complete and aborted steps + + path: (optional) string + # The path where the queue is defined. + # By default we search for the queue up the node tree. + + If this task command is a zombie, then the default action will be to *block*, + The default can be overridden by using zombie attributes. + If the path to the queue is not defined, then this command will search for the queue up the node hierarchy. If no queue found, command fails Usage: + step="" QNAME="my_queue_name" while [1 == 1 ] ; do @@ -1561,45 +1614,45 @@ carried by ``flag_type_``, and ``flag_`` selects set (``true``) versus clear (`` Usage: - ecflow_client --alter=add variable "value" / # add server variable - ecflow_client --alter=add variable "value" /path/to/node # add node variable + --alter=add variable "value" / # add server variable + --alter=add variable "value" /path/to/node # add node variable - ecflow_client --alter=add time "+00:20" /path/to/node + --alter=add time "+00:20" /path/to/node - ecflow_client --alter=add date "01.*.*" /path/to/node + --alter=add date "01.*.*" /path/to/node - ecflow_client --alter=add day "sunday" /path/to/node + --alter=add day "sunday" /path/to/node - ecflow_client --alter=add label "label_value" /path/to/node + --alter=add label "label_value" /path/to/node - ecflow_client --alter=add event "set"|"clear" /path/to/node + --alter=add event "set"|"clear" /path/to/node - ecflow_client --alter=add meter ",,value" /path/to/node + --alter=add meter ",,value" /path/to/node - ecflow_client --alter=add late "-s 00:01 -a 14:30 -c +00:01" /path/to/node + --alter=add late "-s 00:01 -a 14:30 -c +00:01" /path/to/node - ecflow_client --alter=add limit mars "100" /path/to/node + --alter=add limit mars "100" /path/to/node - ecflow_client --alter=add inlimit /path/to/node/withlimit:limit_name "10" /path/to/node + --alter=add inlimit /path/to/node/withlimit:limit_name "10" /path/to/node - ecflow_client --alter=add inlimit /path/to/node/withlimit:limit_name "-s 10" /path/to/node + --alter=add inlimit /path/to/node/withlimit:limit_name "-s 10" /path/to/node - ecflow_client --alter=add inlimit /path/to/node/withlimit:limit_name "-n 10" /path/to/node + --alter=add inlimit /path/to/node/withlimit:limit_name "-n 10" /path/to/node - # zombie attributes have the following structure: - `zombie_type`:(`client_side_action` | `server_side_action`):`child`:`zombie_life_time` - zombie_type = "user" | "ecf" | "path" | "ecf_pid" | "ecf_passwd" | "ecf_pid_passwd" - client_side_action = "fob" | "fail" | "block" - server_side_action = "adopt" | "delete" | "kill" - child = "init" | "event" | "meter" | "label" | "wait" | "abort" | "complete" | "queue" - zombie_life_time = unsigned integer default: user(300), ecf(3600), path(900) minimum is 60 + # zombie attributes have the following structure: + `zombie_type`:(`client_side_action` | `server_side_action`):`child`:`zombie_life_time` + zombie_type = "user" | "ecf" | "path" | "ecf_pid" | "ecf_passwd" | "ecf_pid_passwd" + client_side_action = "fob" | "fail" | "block" + server_side_action = "adopt" | "delete" | "kill" + child = "init" | "event" | "meter" | "label" | "wait" | "abort" | "complete" | "queue" + zombie_life_time = unsigned integer default: user(300), ecf(3600), path(900) minimum is 60 - ecflow_client --alter=add zombie "ecf:fail::" /path/to/node # ask system zombies to fail - ecflow_client --alter=add zombie "user:fail::" /path/to/node # ask user generated zombies to fail - ecflow_client --alter=add zombie "path:fail::" /path/to/node # ask path zombies to fail + --alter=add zombie "ecf:fail::" /path/to/node # ask system zombies to fail + --alter=add zombie "user:fail::" /path/to/node # ask user generated zombies to fail + --alter=add zombie "path:fail::" /path/to/node # ask path zombies to fail - ecflow_client --alter=delete variable FRED /path/to/node # delete variable FRED - ecflow_client --alter=delete variable /path/to/node # delete *ALL* variables on the given snode + --alter=delete variable FRED /path/to/node # delete variable FRED + --alter=delete variable /path/to/node # delete *ALL* variables on the given snode .. tab:: Request @@ -1854,16 +1907,20 @@ starts. The ``force`` flag is stored in ``force_``; the request below is for ``- Begin playing the definition in the server. Expects zero or a single quoted string. - arg1 = suite-name | Nothing | force - play the chosen suite, if no arg specified, play all suites, in the definition - force means reset the begin status on the suites and bypass checks. - This is only required if suite-name is provide as the first argument - Using force can cause the creation of zombies + + Argument(s): + + suite-name: (optional) + # The name of the selected suite; if not specified, means all suites. + # Including `--force` in the value means reset the begin status and bypass checks. + # Important: using `--force` might cause the appearance of zombies. + Usage: - --begin # will begin all suites - --begin="--force" # reset and then begin all suites, bypassing any checks. Note: string must be quoted - --begin="mySuite" # begin playing suite of name 'mySuite' - --begin="mySuite --force" # reset and begin playing suite 'mySuite', bypass check + + --begin # will begin all suites + --begin="--force" # reset and then begin all suites, bypassing any checks. Note: string must be quoted + --begin="mySuite" # begin playing suite of name 'mySuite' + --begin="mySuite --force" # reset and begin playing suite 'mySuite', bypass check .. tab:: Request @@ -1965,11 +2022,22 @@ the line limit by ``max_lines_``. The reply is always a string. Return the chosen file. Select from [ script | job | jobout | manual | kill | stat ] By default will return the script. - arg1 = path to node - arg2 = (optional) [ script | job | jobout | manual | kill | stat ] - kill will attempt to return output of ECF_KILL_CMD, i.e the file %ECF_JOB%.kill - stat will attempt to return output of ECF_STATUS_CMD, i.e the file %ECF_JOB%.stat - arg3 = (optional) max_lines = 10000 + + Argument(s): + + path: + # The path to the node + + kind: (optional) [ script | job | jobout | manual | kill | stat ] + # `script`: returns the script file, e.g., %ECF_JOB%.ecf + # `job`: returns the pre-processed job script + # `jobout`: returns the job output/log, e.g., %ECF_JOB%.out + # `manual`: returns the job manual + # `kill`: returns the output of ECF_KILL_CMD, e.g., %ECF_JOB%.kill + # `stat`: returns the output of ECF_STATUS_CMD, e.g., %ECF_JOB%.stat + + max_lines: (optional) + # default is 10000 .. tab:: Request @@ -2080,29 +2148,35 @@ save-time alarm. Called with no argument it checkpoints immediately. or by using --alter functionality Note excessive save times can interfere with job scheduling. The alarm threshold can be changed. See below. - arg1 = (optional) mode [ never | on_time | on_time: | always | ] - never : Never check point the definition in the server - on_time : Turn on automatic check pointing at interval stored on server - on_time : Turn on automatic check point, with the specified interval in seconds - alarm : Modify the alarm notification time for check pt saving to disk - always : Check point at any change in node tree, *NOT* recommended for large definitions - : This specifies the interval in seconds when server should automatically check pt. - This will only take effect of mode is on_time/CHECK_ON_TIME - Should ideally be a value greater than 60 seconds, default is 120 seconds + + Argument(s): + + mode: (optional) [ never | on_time | on_time: | always | ] + # never : Never check point the definition in the server + # on_time : Turn on automatic check pointing at interval stored on server + # on_time : Turn on automatic check point, with the specified interval in seconds + # alarm : Modify the alarm notification time for check pt saving to disk + # always : Check point at any change in node tree, *NOT* recommended for large definitions + # : This specifies the interval in seconds when server should automatically check pt. + # + # This only takes effect if mode is on_time/CHECK_ON_TIME + # The default value is 120 seconds, and should be greater than 60 seconds + Usage: - --check_pt - Immediately check point the definition held in the server - --check_pt=never - Switch off check pointing - --check_pt=on_time - Start automatic check pointing at the interval stored in the server - --check_pt=180 - Change the check pt interval to 180 seconds - --check_pt=on_time:90 - Change mode and interval, to automatic check pointing every 90 seconds - --check_pt=alarm:35 - Change the alarm time for check pt saves. i.e if saving the check pt takes longer than 35 seconds - set the late flag on the server. + + --check_pt + Immediately check point the definition held in the server + --check_pt=never + Switch off check pointing + --check_pt=on_time + Start automatic check pointing at the interval stored in the server + --check_pt=180 + Change the check pt interval to 180 seconds + --check_pt=on_time:90 + Change mode and interval, to automatic check pointing every 90 seconds + --check_pt=alarm:35 + Change the alarm time for check pt saves. i.e if saving the check pt takes longer than 35 seconds + set the late flag on the server. .. tab:: Request @@ -2259,24 +2333,42 @@ track a chosen subset of suites and receive only their changes. The operation is Deleted suites will stay registered, and must be explicitly removed/dropped. Note: Suites can be registered before they are loaded into the server This command affects news() and sync() commands - arg1 = true | false # true means add new suites to my list, when they are created - arg2 = names # should be a list of suite names, names not in the definition are ignored + + Argument(s): + + auto-add: true | false + # `true` means add any new suites to user list, when they are created + + names + # A space separated list of suite names. + # Note: names not in the definition are ignored. + Usage: - --ch_register=true s1 s2 s3 # register interest in suites s1,s2,s3 and any new suites - --ch_register=false s1 s2 s3 # register interest in suites s1,s2,s3 only - --ch_register=false # register handle, suites will be added later on - --ch_register=1 true s1 s2 s3 # drop handle 1 then register interest in suites s1,s2,s3 and any new suites - # The client handle as the first argument is typically used by GUI/python # When the client handle is no zero, then it is dropped first + + --ch_register=true s1 s2 s3 # register interest in suites s1,s2,s3 and any new suites + --ch_register=false s1 s2 s3 # register interest in suites s1,s2,s3 only + --ch_register=false # register handle, suites will be added later on + --ch_register=1 true s1 s2 s3 # drop handle 1 then register interest in suites s1,s2,s3 and any new suites + # The client handle as the first argument is typically used by GUI/python + # When the client handle is no zero, then it is dropped first + To list all suites and handles use --ch_suites @@ -2306,21 +2415,41 @@ track a chosen subset of suites and receive only their changes. The operation is ------ Remove a set of suites, from an existing handle. - arg1 = handle(integer) # The handle must be an integer that is > 0 - arg2 = names # should be a list of suite names, names not in the definition are ignored + + Argument(s): + + handle: integer + # The handle value (must be an integer > 0) + + names + # A space separated list of suite names. + # Note: names not in the definition are ignored. + Usage: - --ch_rem=10 s2 s3 s4 # remove suites s2 s3,s4 from handle 10 + + --ch_rem=10 s2 s3 s4 # remove suites s2 s3,s4 from handle 10 + The handle is created with --ch_register command + To list all suites and handles use --ch_suites + + Argument(s): + + confirm: (optional) + # value must be "yes"; bypasses the confirmation prompt + + Usage: + + --terminate=yes + + --reloadwsfile + + --reloadpasswdfile + + --reloadcustompasswdfile .. tab:: Request @@ -3181,9 +3343,11 @@ target by ``absNodePath_``: Get all suite node tree's from the server and write to standard out. The output is parse-able, and can be used to re-load the definition arg = NULL | arg = node path + Usage: - --get # gets the definition from the server,and writes to standard out - --get=/s1 # gets the suite from the server,and writes to standard out + + --get # gets the definition from the server,and writes to standard out + --get=/s1 # gets the suite from the server,and writes to standard out + + --group="get; why" # returns why for all holding nodes + --group="get; why=/suite/family" # returns why for a specific node + + --get_state # gets the definition from the server,and writes to standard out + --get_state=/s1 # gets the suite from the server,and writes to standard out + + Usage: + + --suspend=/s1/f1/t1 # suspend task s1/f1/t1 + --suspend=/s1 /s2 # suspend suites /s1 and /s2 + + Usage: + + --resume=/s1/f1/t1 # resume task s1/f1/t1 + --resume=/s1 /s2 # resume suites /s1 and /s2 + + Usage: + + --kill=/s1/f1/t1 /s1/f2/t2 # kill the jobs for tasks t1 and t2 + --file=/s1/f1/t1 kill # write to standard out the '.kill' file for task /s1/f1/t1 + + Usage: + + --status=/s1/f1/t1 # ECF_STATUS_CMD should output to %ECF_JOB%.stat + --file=/s1/f1/t1 stat # Return contents of %ECF_JOB%.stat file + + --check=_all_ # Checks all the suites + --check=/ # Checks all the suites + --check=/s1 /s2/f1/t1 # Check suite /s1 and task t1 + + Usage: + + --edit_history=/s1/f1/t1 # return history of changes for the given node + --edit_history=clear # clear/purge *ALL* edit history from all nodes. + Usage: + + --archive=/s1 # archive suite s1 + --archive=/s1/f1 /s2 # archive family /s1/f1 and suite /s2 + --archive=force /s1 /s2 # archive suites /s1,/s2 even if they have active tasks + + Usage: + + --restore=/s1/f1 # restore family /s1/f1 + --restore=/s1 /s2 # restore suites /s1 and /s2 .. tab:: Request @@ -4818,8 +5097,15 @@ Moves a node subtree to a new parent, possibly on a different server. Internally Plug command is used to move nodes. The destination node can be on another server In which case the destination path should be of the form ':/suite/family/task - arg1 = path to source node - arg2 = path to the destination node + + Argument(s): + + source: path + # The path to the source node + + destination: path + # The path to the destination node + This command can fail because: - Source node is in a 'active' or 'submitted' state - Another user already has an lock @@ -4828,8 +5114,10 @@ Moves a node subtree to a new parent, possibly on a different server. Internally then the source node must correspond to a suite. - If the source node is added as a child, then its name must be unique amongst its peers + Usage: - --plug=/suite macX:3141 # move the suite to ecFlow server on host(macX) and port(3141) + + --plug=/suite macX:3141 # move the suite to ecFlow server on host(macX) and port(3141) .. tab:: Request @@ -4957,27 +5245,34 @@ inside scripts to branch on a node's state. - variable No user or generated variable or repeat of that name found on node or its parents, or (when path is '/') no user or server variable of that name found on the server - trigger Trigger does not parse, or reference to nodes/attributes in the expression are not valid - Arguments: - arg1 = [ state | dstate | repeat | event | meter | label | variable | trigger | limit | limit_max ] - arg2 = | :name where name is name of a event, meter, label, limit or variable. - path '/' represents the server itself, and can only be used with 'state' or 'variable' - arg3 = trigger expression | prev | next # prev,next only used when arg1 is repeat + + Argument(s): + + attribute: [ state | dstate | repeat | event | meter | label | variable | trigger | limit | limit_max ] + # The kind of the attribute to be queried. + + target: | :name + # The path to the node or the node and attribute name. + + value: trigger expression | prev | next + # The values `prev` and `next` are only used when the attribute is a repeat Usage: - ecflow_client --query state / # return top level state to standard out - ecflow_client --query state /path/to/node # return node state to standard out - ecflow_client --query dstate /path/to/node # state that can included suspended - ecflow_client --query repeat /path/to/node # return the current value as a string - ecflow_client --query repeat /path/to/node prev # return the previous value as a string - ecflow_client --query repeat /path/to/node next # return the next value as a string - ecflow_client --query event /path/to/task/with/event:event_name # return set | clear to standard out - ecflow_client --query meter /path/to/task/with/meter:meter_name # returns the current value of the meter to standard out - ecflow_client --query limit /path/to/task/with/limit:limit_name # returns the current value of the limit to standard out - ecflow_client --query limit_max /path/to/task/with/limit:limit_name # returns the max value of the limit to standard out - ecflow_client --query label /path/to/task/with/label:label_name # returns the current value of the label to standard out - ecflow_client --query variable /path/to/task/with/var:var_name # returns the variable value to standard out - ecflow_client --query variable /:var_name # returns the server variable value to standard out - ecflow_client --query trigger /path/to/node/with/trigger "/suite/task == complete" # return true if expression evaluates false otherwise + + --query state / # return top level state to standard out + --query state /path/to/node # return node state to standard out + --query dstate /path/to/node # state that can included suspended + --query repeat /path/to/node # return the current value as a string + --query repeat /path/to/node prev # return the previous value as a string + --query repeat /path/to/node next # return the next value as a string + --query event /path/to/task/with/event:event_name # return set | clear to standard out + --query meter /path/to/task/with/meter:meter_name # returns the current value of the meter to standard out + --query limit /path/to/task/with/limit:limit_name # returns the current value of the limit to standard out + --query limit_max /path/to/task/with/limit:limit_name # returns the max value of the limit to standard out + --query label /path/to/task/with/label:label_name # returns the current value of the label to standard out + --query variable /path/to/task/with/var:var_name # returns the variable value to standard out + --query variable /:var_name # returns the server variable value to standard out + --query trigger /path/to/node/with/trigger "/suite/task == complete" # return true if expression evaluates false otherwise .. tab:: Request @@ -5175,17 +5470,23 @@ Requeues nodes so that they may run again. ``option_`` encodes the modifier (``0 Re queues the specified node(s) If any child of the specified node(s) is in a suspended state, this state is cleared Repeats are reset to their starting values, relative time attributes are reset. - arg1 = (optional) [ abort | force ] - abort = re-queue only aborted tasks below node - force = Force the re-queueing even if there are nodes that are active or submitted - = Checks if any tasks are in submitted or active states below the node - if so does nothing. Otherwise re-queues the node. - arg2 = list of node paths. The node paths must begin with a leading '/' character + + Argument(s): + + mode: (optional) [ abort | force ] + # abort = re-queue only aborted tasks below node + # force = Force the re-queueing even if there are nodes that are active or submitted + # = Checks if any tasks are in submitted or active states below the node, + # and if so does nothing. Otherwise, re-queues the node. + + node: path + # A space separared list of node paths. Paths must begin with a leading '/' character Usage: - --requeue=abort /suite/f1 # re-queue all aborted tasks of /suite/f1 - --requeue=force /suite/f1 # forcibly re-queue /suite/f1 and all its children.May cause zombies. - --requeue=/s1/f1/t1 /s1/t2 # Re-queue node '/suite/f1/t1' and '/s1/t2' + + --requeue=abort /suite/f1 # re-queue all aborted tasks of /suite/f1 + --requeue=force /suite/f1 # forcibly re-queue /suite/f1 and all its children.May cause zombies. + --requeue=/s1/f1/t1 /s1/t2 # Re-queue node '/suite/f1/t1' and '/s1/t2' .. tab:: Request @@ -5291,20 +5592,32 @@ ignored. A repeat attribute is incremented when all the child nodes are complete in this case the child nodes are automatically re-queued. Hence this command can be aid, in allowing a Repeat attribute to be incremented - arg1 = (optional)force - Forcibly run, even if there are nodes that are active or submitted - This can result in zombie creation - arg2 = node path(s). The paths must begin with a leading '/' character. - If the path is /suite/family will recursively run all tasks - When providing multiple paths avoid running the same task twice - Example: + + Argument(s): + + force: (optional) + # Forcibly run, even if there are nodes that are active or submitted + # This might cause the appearance of zombie + + node: path + # A space separared list of node paths. + # The paths must begin with a leading '/' character. + # If the path points to a suite or a family, all tasks will be run recursively. + # When providing multiple paths avoid running the same task twice + + Usage: + --run=/suite/t1 # run task t1 + Effect: + task t1; time 12:00 # will complete if run manually task t2; time 10:00 13:00 01:00 # will run 4 times before completing - When we have a time range(i.e as shown with task t2), then next time slot - is incremented for each run, until it expires, and the task completes. - Use the Why command, to show next run time (i.e. next time slot) + + Specifying a time range, as shown with task t2, means the next time slot + is incremented for each run, until it expires, and the task completes. + + Use the Why command, to show next run time (i.e. next time slot) .. tab:: Request @@ -5400,9 +5713,11 @@ payload holds only the base-class ``cl_host_`` and ``user_``. -------------- Returns the version number of the server + Usage: - --server_version - Writes the version to standard output + + --server_version + Writes the version to standard output .. tab:: Request @@ -5501,21 +5816,27 @@ not part of the serialized payload, so the request carries only the base-class ` Used to print state of the definition returned from the server to standard output. This command can *only* be used in a group command, and will only work if it is preceded with a get command. See examples below. - arg1 = [ defs | state | migrate ] - The output of show has several options: i.e - o no arguments: With no arguments, print the definition structure to standard output - Extern's are automatically added, allowing the output to be reloaded into the server - i.e --group="get ; show" - o state: - This will output definition structure along with all the state information. - This will include the trigger expressions, abstract syntax tree as comments. - Excludes the edit history - o migrate: - This will output definition structure along with all the state information. - The node state is shown in the comments. - This format allows the definition to be migrated to future version of ecflow. - The output includes edit history but excludes externs. - When the definition is reloaded *NO* checking is done. + + Argument(s): + + output: (optional) [ defs | state | migrate ] + # defs: + # Return the definition structure only, without any state information. + # Necessary `extern`s are automatically added, allowing the output to be + # reloaded into the server e.g., --group="get ; show" + # This is the default if no `output` argument is provided. + # + # state: + # Return definition structure along with all the state information. + # Output includes the trigger expressions' abstract syntax tree (as comment). + # Does not include the edit history + # + # migrate: + # Return definition structure along with all the state information. + # The node state is shown in the comments. + # Use this format to migrate the definition to future version of ecflow. + # The output includes edit history but excludes externs. + # When the definition is reloaded *NO* checking is done. The following shows a summary of the features associated with each choice DEFS STATE MIGRATE @@ -5525,6 +5846,7 @@ not part of the serialized payload, so the request carries only the base-class ` trigger AST No Yes No Usage: + --group="get ; show" --group="get ; show defs" # same as the previous example --group="get ; show state" # Show all state for the node tree @@ -5668,7 +5990,10 @@ carried by ``user_action_``: allowing the job to finish. The references to the zombie in the server is automatically deleted after 1 hour args = list of task paths, at least one expected - --zombie_fob=/path/to/task1 /path/to/task2 + + Usage: + + --zombie_fob=/path/to/task1 /path/to/task2 + + Usage: + + --zombie_fail=/path/to/task /path/to/task2 + + Usage: + + --zombie_adopt=/path/to/task /path/to/task2 + + Usage: + + --zombie_remove=/path/to/task /path/to/task2 + + Usage: + + --zombie_block=/path/to/task /path/to/task2 + + Usage: + + --zombie_kill=/path/to/task /path/to/task2 .. tab:: Request diff --git a/docs/client_api/help.json b/docs/client_api/help.json new file mode 100644 index 000000000..c0eea29d3 --- /dev/null +++ b/docs/client_api/help.json @@ -0,0 +1,2457 @@ +{ + "schema_version": 1, + "topics": [ + { + "name": "all", + "summary": "List all commands, verbosely" + }, + { + "name": "summary", + "summary": "One line summary of all commands" + }, + { + "name": "task", + "summary": "One line summary of task commands" + }, + { + "name": "user", + "summary": "One line summary of user command" + }, + { + "name": "option", + "summary": "One line summary of options" + } + ], + "commands": [ + { + "name": "abort", + "kind": "task", + "visibility": "public", + "implementation": "AbortCmd", + "summary": "Mark task as aborted. For use in the '.ecf' script file *only*", + "description": [ + "Mark task as aborted. For use in the '.ecf' script file *only*", + "Hence the context is supplied via environment variables", + "", + "Argument(s):", + "", + " reason: (optional) string", + " # Optionally provide a reason why the abort was raised", + "", + "If this task command is a zombie, then the default action will be to *block*.", + "The default can be overridden by using zombie attributes.", + "Otherwise the blocking period is defined by ECF_TIMEOUT.", + "", + "Usage:", + "", + " --abort=" + ], + "arguments": [ + { + "name": "reason", + "type": "string", + "optional": true, + "description": "Optionally provide a reason why the abort was raised" + } + ] + }, + { + "name": "alter", + "kind": "user", + "visibility": "public", + "implementation": "AlterCmd", + "summary": "Alter the node according to the options.", + "description": [ + "Alter the node according to the options.", + "", + "arg1 = [ delete | change | add | set_flag | clear_flag | sort ]", + "", + "arg2 = 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 add:", + " [ variable | time | today | date | day | zombie | event | meter | late | limit |", + " inlimit | label | 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 ]", + "", + "arg3 = [ | ]", + "", + "arg4 = ", + "", + "arg5 = ( (...)) - at least one node path required.", + "", + "*Important Notes*", + "", + " * All paths must start with a leading '/' character.", + "", + " * To update, create or remove server variables use '/' for path.", + "", + " * When updating unnamed attributes (Repeat, Trigger, Complete, ...) the name/arg3 is not necessary.", + "", + " * After changing the clock the suite needs to be re-queued for the change to take effect.", + "", + " * When adding or updating node attributes (e.g. variable, meter, event, label, limits, late)", + " the name (arg3) and value (arg4) must be quoted.", + "", + " * When sorting attributes, 'recursive' can be used as the value (arg3).", + "", + " * When adding a meter, the value (arg4) is expected to be a comma-separated triplet", + " of numerical values the form \",,\" (n.b. no spaces are allowed).", + "", + " * When adding an event, the non-optional value (arg4) must be either \"set\" or \"clear\".", + "", + " * When adding or updating aviso and mirror attributes, the value (arg4) is expected to be", + " a quoted list of configuration options. For example:", + " * for aviso, \"--remote_path /s1/f1/t2 --remote_host host --polling 20 --remote_port 3141 --ssl)\"", + " * for mirror, \"--listener '{ \\\"event\\\": \\\"mars\\\", \\\"request\\\": { \\\"class\\\": \"od\" } }'", + " --url http://aviso/ --schema /path/to/schema --polling 60\"", + "", + " * For both aviso and mirror, the special value \"reload\" forces reloading the configuration.", + " This is typically useful after updating variables used to configure these kind of attributes.", + "", + "Usage:", + "", + " --alter=add variable \"value\" / # add server variable", + " --alter=add variable \"value\" /path/to/node # add node variable", + "", + " --alter=add time \"+00:20\" /path/to/node", + "", + " --alter=add date \"01.*.*\" /path/to/node", + "", + " --alter=add day \"sunday\" /path/to/node", + "", + " --alter=add label \"label_value\" /path/to/node", + "", + " --alter=add event \"set\"|\"clear\" /path/to/node", + "", + " --alter=add meter \",,value\" /path/to/node", + "", + " --alter=add late \"-s 00:01 -a 14:30 -c +00:01\" /path/to/node", + "", + " --alter=add limit mars \"100\" /path/to/node", + "", + " --alter=add inlimit /path/to/node/withlimit:limit_name \"10\" /path/to/node", + "", + " --alter=add inlimit /path/to/node/withlimit:limit_name \"-s 10\" /path/to/node", + "", + " --alter=add inlimit /path/to/node/withlimit:limit_name \"-n 10\" /path/to/node", + "", + " # zombie attributes have the following structure:", + " `zombie_type`:(`client_side_action` | `server_side_action`):`child`:`zombie_life_time`", + " zombie_type = \"user\" | \"ecf\" | \"path\" | \"ecf_pid\" | \"ecf_passwd\" | \"ecf_pid_passwd\"", + " client_side_action = \"fob\" | \"fail\" | \"block\"", + " server_side_action = \"adopt\" | \"delete\" | \"kill\"", + " child = \"init\" | \"event\" | \"meter\" | \"label\" | \"wait\" | \"abort\" | \"complete\" | \"queue\"", + " zombie_life_time = unsigned integer default: user(300), ecf(3600), path(900) minimum is 60", + "", + " --alter=add zombie \"ecf:fail::\" /path/to/node # ask system zombies to fail", + " --alter=add zombie \"user:fail::\" /path/to/node # ask user generated zombies to fail", + " --alter=add zombie \"path:fail::\" /path/to/node # ask path zombies to fail", + "", + " --alter=delete variable FRED /path/to/node # delete variable FRED", + " --alter=delete variable /path/to/node # delete *ALL* variables on the given snode" + ] + }, + { + "name": "archive", + "kind": "user", + "visibility": "public", + "implementation": "PathsCmd", + "summary": "Archives suite or family nodes *IF* they have child nodes(otherwise does nothing).", + "description": [ + "Archives suite or family nodes *IF* they have child nodes(otherwise does nothing).", + "Saves the suite/family nodes to disk, and then removes the child nodes from the definition.", + "This saves memory in the server, when dealing with huge definitions that are not needed.", + "It improves time taken to checkpoint and reduces network bandwidth.", + "If the node is re-queued or begun, the child nodes are automatically restored.", + "Use --restore to reload the archived nodes manually", + "Care must be taken if you have trigger reference to the archived nodes", + "The nodes are saved to the file ECF_HOME/...check,", + "where '/' has been replaced with ':' in ECF_NAME", + "", + "Nodes like (family and suites) can also to automatically archived by using,", + "the 'autoarchive' attribute. The attribute is only applied once the node is complete", + "", + "suite autoarchive", + " family f1", + " autoarchive +01:00 # archive one hour after complete", + " task t1", + " endfamily", + " family f2", + " autoarchive 01:00 # archive at 1 am in morning after complete", + " task t1", + " endfamily", + " family f3", + " autoarchive 10 # archive 10 days after complete", + " task t1", + " endfamily", + " family f4", + " autoarchive 0 # archive immediately (upto 60s) after complete", + " task t1", + " endfamily", + "endsuite", + "", + "Usage:", + "", + " --archive=/s1 # archive suite s1", + " --archive=/s1/f1 /s2 # archive family /s1/f1 and suite /s2", + " --archive=force /s1 /s2 # archive suites /s1,/s2 even if they have active tasks" + ] + }, + { + "name": "begin", + "kind": "user", + "visibility": "public", + "implementation": "BeginCmd", + "summary": "Begin playing the definition in the server.", + "description": [ + "Begin playing the definition in the server.", + "Expects zero or a single quoted string.", + "", + "Argument(s):", + "", + " suite-name: (optional)", + " # The name of the selected suite; if not specified, means all suites.", + " # Including `--force` in the value means reset the begin status and bypass checks.", + " # Important: using `--force` might cause the appearance of zombies.", + "", + "Usage:", + "", + " --begin # will begin all suites", + " --begin=\"--force\" # reset and then begin all suites, bypassing any checks. Note: string must be quoted", + " --begin=\"mySuite\" # begin playing suite of name 'mySuite'", + " --begin=\"mySuite --force\" # reset and begin playing suite 'mySuite', bypass check" + ] + }, + { + "name": "ch_add", + "kind": "user", + "visibility": "public", + "implementation": "ClientHandleCmd", + "summary": "Add a set of suites, to an existing handle.", + "description": [ + "Add a set of suites, to an existing handle.", + "", + "Argument(s):", + "", + " handle: integer", + " # The handle value (must be an integer > 0)", + "", + " names", + " # A space separated list of suite names.", + " # Note: names not in the definition are ignored.", + "", + "Usage:", + "", + " --ch_add=10 s2 s3 s4 # add suites s2 s3,s4 to handle 10", + "", + "An error is returned if the handle had not previously been registered", + "The handle is created with --ch_register command", + "To list all suites and handles use --ch_suites" + ] + }, + { + "name": "ch_auto_add", + "kind": "user", + "visibility": "public", + "implementation": "ClientHandleCmd", + "summary": "Change an existing handle so that new suites can be added automatically.", + "description": [ + "Change an existing handle so that new suites can be added automatically.", + "", + "Argument(s):", + "", + " handle: integer", + " # The handle value (must be an integer > 0)", + "", + " auto-add: true | false", + " # `true` means add new suites to my list, when they are created", + "", + "Usage:", + "", + " --ch_auto_add=10 true # modify handle 10 so that new suites, get added automatically to it", + " --ch_auto_add=10 false # modify handle 10 so that no new suites are added", + "", + "The handle is created with --ch_register command", + "To list all suites and handles use --ch_suites" + ] + }, + { + "name": "ch_drop", + "kind": "user", + "visibility": "public", + "implementation": "ClientHandleCmd", + "summary": "Drop/de-register the client handle.", + "description": [ + "Drop/de-register the client handle.", + "Un-used handle should be dropped otherwise they will stay, in the server.", + "", + "Argument(s):", + "", + " handle: integer", + " # The handle value (must be an integer > 0)", + "", + "Usage:", + "", + " --ch_drop=10 # drop the client handle 10", + "", + "An error is returned if the handle was not previously been registered.", + "The handle stored on the local client is set to zero", + "To list all suites and handles use --ch_suites" + ] + }, + { + "name": "ch_drop_user", + "kind": "user", + "visibility": "public", + "implementation": "ClientHandleCmd", + "summary": "Drop/de-register all handles associated with the given user.", + "description": [ + "Drop/de-register all handles associated with the given user.", + "If no user provided will drop for current user. Client must ensure un-used handle are dropped", + "otherwise they will stay, in the server.", + "", + "Argument(s):", + "", + " user", + " # The user to be dropped; if empty, means drop current user", + "", + "Usage:", + "", + " --ch_drop_user=ma0 # drop all handles associated with user ma0", + " --ch_drop_user # drop all handles associated with current user", + "", + "An error is returned if no registered handles", + "To list all suites and handles use --ch_suites" + ] + }, + { + "name": "ch_register", + "kind": "user", + "visibility": "public", + "implementation": "ClientHandleCmd", + "summary": "Register interest in a set of suites.", + "description": [ + "Register interest in a set of suites.", + "If a definition has lots of suites, but the client. is only interested in a small subset,", + "Then using this command can reduce network bandwidth and synchronisation will be quicker.", + "This command will create a client handle, which must be used for any other changes.", + "The newly created handle can be shown with the --ch_suites command", + "Deleted suites will stay registered, and must be explicitly removed/dropped.", + "Note: Suites can be registered before they are loaded into the server", + "This command affects news() and sync() commands", + "", + "Argument(s):", + "", + " auto-add: true | false", + " # `true` means add any new suites to user list, when they are created", + "", + " names", + " # A space separated list of suite names.", + " # Note: names not in the definition are ignored.", + "", + "Usage:", + "", + " --ch_register=true s1 s2 s3 # register interest in suites s1,s2,s3 and any new suites", + " --ch_register=false s1 s2 s3 # register interest in suites s1,s2,s3 only", + " --ch_register=false # register handle, suites will be added later on", + " --ch_register=1 true s1 s2 s3 # drop handle 1 then register interest in suites s1,s2,s3 and any new suites", + " # The client handle as the first argument is typically used by GUI/python", + " # When the client handle is no zero, then it is dropped first", + "", + "To list all suites and handles use --ch_suites" + ] + }, + { + "name": "ch_rem", + "kind": "user", + "visibility": "public", + "implementation": "ClientHandleCmd", + "summary": "Remove a set of suites, from an existing handle.", + "description": [ + "Remove a set of suites, from an existing handle.", + "", + "Argument(s):", + "", + " handle: integer", + " # The handle value (must be an integer > 0)", + "", + " names", + " # A space separated list of suite names.", + " # Note: names not in the definition are ignored.", + "", + "Usage:", + "", + " --ch_rem=10 s2 s3 s4 # remove suites s2 s3,s4 from handle 10", + "", + "The handle is created with --ch_register command", + "", + "To list all suites and handles use --ch_suites" + ] + }, + { + "name": "ch_suites", + "kind": "user", + "visibility": "public", + "implementation": "ClientHandleCmd", + "summary": "Shows all the client handles, and the suites they reference", + "description": ["Shows all the client handles, and the suites they reference"] + }, + { + "name": "check", + "kind": "user", + "visibility": "public", + "implementation": "PathsCmd", + "summary": "Checks the expression and limits in the server. Will also check trigger references.", + "description": [ + "Checks the expression and limits in the server. Will also check trigger references.", + "Trigger expressions that reference paths that don't exist, will be reported as errors.", + "(Note: On the client side unresolved paths in trigger expressions must", + "have an associated 'extern' specified)", + " arg = [ _all_ | / | list of node paths ]", + "", + "Usage:", + "", + " --check=_all_ # Checks all the suites", + " --check=/ # Checks all the suites", + " --check=/s1 /s2/f1/t1 # Check suite /s1 and task t1" + ] + }, + { + "name": "checkJobGenOnly", + "kind": "user", + "visibility": "public", + "implementation": "CtsNodeCmd", + "summary": "Test hierarchical Job generation only, for chosen Node.", + "description": [ + "Test hierarchical Job generation only, for chosen Node.", + "The jobs are generated independent of the dependencies", + "This will generate the jobs *only*, i.e. no job submission. Used for checking job generation only", + " arg = node path | arg = NULL", + " If no node path specified generates for all Tasks in the definition. For Test only" + ] + }, + { + "name": "check_pt", + "kind": "user", + "visibility": "public", + "implementation": "CheckPtCmd", + "summary": "Forces the definition file in the server to be written to disk *or* allow mode,", + "description": [ + "Forces the definition file in the server to be written to disk *or* allow mode,", + "interval and alarm to be changed.", + "Whenever the check pt file is written to disk, it is measured.", + "If the time to save to disk is greater than the default of 30 seconds,", + "then an alarm is raised. This can be seen in the GUI as a late flag on the server.", + "Once the late flag has been set it will need to manually cleared in the GUI", + "or by using --alter functionality", + "Note excessive save times can interfere with job scheduling.", + "The alarm threshold can be changed. See below.", + "", + "Argument(s):", + "", + " mode: (optional) [ never | on_time | on_time: | always | ]", + " # never : Never check point the definition in the server", + " # on_time : Turn on automatic check pointing at interval stored on server", + " # on_time : Turn on automatic check point, with the specified interval in seconds", + " # alarm : Modify the alarm notification time for check pt saving to disk", + " # always : Check point at any change in node tree, *NOT* recommended for large definitions", + " # : This specifies the interval in seconds when server should automatically check pt.", + " #", + " # This only takes effect if mode is on_time/CHECK_ON_TIME", + " # The default value is 120 seconds, and should be greater than 60 seconds", + "", + "Usage:", + "", + " --check_pt", + " Immediately check point the definition held in the server", + " --check_pt=never", + " Switch off check pointing", + " --check_pt=on_time", + " Start automatic check pointing at the interval stored in the server", + " --check_pt=180", + " Change the check pt interval to 180 seconds", + " --check_pt=on_time:90", + " Change mode and interval, to automatic check pointing every 90 seconds", + " --check_pt=alarm:35", + " Change the alarm time for check pt saves. i.e if saving the check pt takes longer than 35 seconds", + " set the late flag on the server." + ] + }, + { + "name": "complete", + "kind": "task", + "visibility": "public", + "implementation": "CompleteCmd", + "summary": "Mark task as complete. For use in the '.ecf' script file *only*", + "description": [ + "Mark task as complete. For use in the '.ecf' script file *only*", + "Hence the context is supplied via environment variables", + "", + "If this task command is a zombie, then the default action will be to *block*.", + "The default can be overridden by using zombie attributes.", + "Otherwise the blocking period is defined by ECF_TIMEOUT.", + "The init command allows variables to be added, and complete command", + "allows for them to be removed.", + "", + "Argument(s):", + "", + " --remove: (optional)", + " # A list of space separated variables to removed from this task", + "", + "Usage:", + "", + " --complete", + " --complete --remove name1 name2 # delete variables name1 and name2 on the task" + ], + "arguments": [ + { + "name": "remove", + "optional": true, + "description": "A list of space separated variables to removed from this task" + } + ] + }, + { + "name": "debug_server_off", + "kind": "user", + "visibility": "public", + "implementation": "CtsCmd", + "summary": "Disables debug output from the server", + "description": ["Disables debug output from the server"] + }, + { + "name": "debug_server_on", + "kind": "user", + "visibility": "public", + "implementation": "CtsCmd", + "summary": "Enables debug output from the server", + "description": ["Enables debug output from the server"] + }, + { + "name": "delete", + "kind": "user", + "visibility": "public", + "implementation": "DeleteCmd", + "summary": "Deletes the specified node(s) or _ALL_ existing definitions( i.e delete all suites) in the server.", + "description": [ + "Deletes the specified node(s) or _ALL_ existing definitions( i.e delete all suites) in the server.", + "", + "Argument(s):", + "", + " force: (optional) [ force | yes ]", + " # Use this parameter to bypass checks, i.e. for active or submitted tasks", + "", + " confirm: (optional)", + " # value must be \"yes\"; bypasses the confirmation prompt", + "", + " target: node paths | _all_", + " # The value `_all_` means delete all suites.", + " # The node paths must start with a leading '/'.", + "", + "Usage:", + "", + " --delete=_all_ # Delete all suites in server. Use with care.", + " --delete=/suite/f1/t1 # Delete node at /suite/f1/t1. This will prompt", + " --delete=force /suite/f1/t1 # Delete node at /suite/f1/t1 even if active or submitted", + " --delete=force yes /s1 /s2 # Delete suites s1,s2 even if active or submitted, bypassing prompt" + ] + }, + { + "name": "edit_history", + "kind": "user", + "visibility": "public", + "implementation": "PathsCmd", + "summary": "Returns the edit history associated with a Node.", + "description": [ + "Returns the edit history associated with a Node.", + "Can also be used to clear the edit history.", + "", + "Usage:", + "", + " --edit_history=/s1/f1/t1 # return history of changes for the given node", + " --edit_history=clear # clear/purge *ALL* edit history from all nodes." + ] + }, + { + "name": "edit_script", + "kind": "user", + "visibility": "public", + "implementation": "EditScriptCmd", + "summary": "Allows user to edit, pre-process and submit the script.", + "description": [ + "Allows user to edit, pre-process and submit the script.", + "Will allow pre-processing of arbitrary file with 'pre_process_file' option", + "", + "Argument(s):", + "", + " path to task", + " # The path to the task/alias", + "", + " action: [ edit | pre_process | submit | pre_process_file | submit_file ]", + " # edit : Returns the script file to standard out. The script will", + " # include used variables enclosed between %comment/%end at the", + " # start of the file", + " #", + " # pre_process: Returns the script file to standard out. The script will", + " # include used variables enclosed between %comment/%end at the", + " # start of the file and with all %include expanded", + " #", + " # submit: Extracts the used variables from the supplied file, i.e", + " # between the %comment/%end and use these them to generate the", + " # job using the ecf file accessible from the server", + " #", + " # pre_process_file: Pre-process the user supplied file, ", + " # expanding includes, performing variable substitution,", + " # removing manual & comment sections.", + " #", + " # submit_file: Like submit, but the supplied file is submitted by the server.", + " #", + " # Options `pre_process_file` and `submit_file` facilitate debugging the script file", + "", + " path_to_script_file: (optional)", + " # The path to the script file", + " # Required for options `pre_process_file` and `submit_file`", + "", + " create_alias: (optional)", + " # If specified, will create an alias for the task and submit the job using this alias", + " # Default value is false, to be used in combination with `submit_file` option", + "", + " no_run: (optional)", + " # If specified, will create an alias for the task but will not submit the job", + " # Default value is false, to be used in combination with `submit_file` option", + "", + "Usage:", + "", + " --edit_script=/path/to/task edit > script_file", + " server returns script with the used variables to standard out", + " The user can choose to edit this file", + "", + " --edit_script=/path/to/task pre_process > pre_processed_script_file", + " server will pre process the ecf file accessible from the server", + " (i.e expand all %includes) and return the file to standard out", + "", + " --edit_script=/path/to/task submit script_file", + " Will extract the used variables in the 'script_file' and will uses these", + " variables during variable substitution of the ecf file accessible by the", + " server. This is then submitted as a job", + "", + " --edit_script=/path/to/task pre_process_file file_to_pre_process", + " The server will pre-process the user supplied file and return the contents", + " to standard out. This pre-processing is the same as job file processing,", + " but on a arbitrary file", + "", + " --edit_script=/path/to/task submit_file file_to_submit", + " Will extract the used variables in the 'file_to_submit' and will uses these", + " variables during variable substitution, the file is then submitted for job", + " generation by the server", + "", + " --edit_script=/path/to/task submit_file file_to_submit create_alias", + " Like the the previous example but will create and run as an alias" + ] + }, + { + "name": "event", + "kind": "task", + "visibility": "public", + "implementation": "EventCmd", + "summary": "Change event. For use in the '.ecf' script file *only*", + "description": [ + "Change event. For use in the '.ecf' script file *only*", + "Hence the context is supplied via environment variables", + "", + "Argument(s):", + "", + " event-name: string | int", + " # The name of the event to be set or cleared.", + "", + " state: (optional) string", + " # [ set | clear], default value is set", + "", + "If this task command is a zombie, then the default action will be to *fob*,", + "i.e allow the ecflow client command to complete without an error", + "The default can be overridden by using zombie attributes.", + "", + "Usage:", + "", + " --event=ev # set the event, default since event initial value is clear", + " --event=ev set # set the event, explicit", + " --event=ev clear # clear the event, use when event initial value is set" + ], + "arguments": [ + { + "name": "event-name", + "type": "string | int", + "optional": false + }, + { + "name": "[ set | clear] default value is set", + "type": "string", + "optional": true + } + ] + }, + { + "name": "file", + "kind": "user", + "visibility": "public", + "implementation": "CFileCmd", + "summary": "Return the chosen file. Select from [ script | job | jobout | manual | kill | stat ]", + "description": [ + "Return the chosen file. Select from [ script | job | jobout | manual | kill | stat ]", + "By default will return the script.", + "", + "Argument(s):", + "", + " path: ", + " # The path to the node", + "", + " kind: (optional) [ script | job | jobout | manual | kill | stat ]", + " # `script`: returns the script file, e.g., %ECF_JOB%.ecf", + " # `job`: returns the pre-processed job script", + " # `jobout`: returns the job output/log, e.g., %ECF_JOB%.out", + " # `manual`: returns the job manual", + " # `kill`: returns the output of ECF_KILL_CMD, e.g., %ECF_JOB%.kill", + " # `stat`: returns the output of ECF_STATUS_CMD, e.g., %ECF_JOB%.stat", + "", + " max_lines: (optional)", + " # default is 10000" + ] + }, + { + "name": "force", + "kind": "user", + "visibility": "public", + "implementation": "ForceCmd", + "summary": "Force a node to a given state, or set its event.", + "description": [ + "Force a node to a given state, or set its event.", + "When a task is set to complete, it may be automatically re-queued if it has", + "multiple future time dependencies. However each time we force a complete it will", + "expire any time based attribute on that node. When the last time based attribute", + "expires, the node will stay in a complete state.", + "This behaviour allow Repeat values to be incremented interactively.", + "A repeat attribute is incremented when all the child nodes are complete", + "in this case the child nodes are automatically re-queued.", + "", + "Argument(s):", + "", + " state: [ unknown | complete | queued | submitted | active | aborted | clear | set ]", + " # The state to be set. If the state is either `set` or `clear`, then the event is updated.", + "", + " recursive: (optional)", + " # Applies state to node, and recursively to all its children", + "", + " full: (optional)", + " # Set repeat variables to last value, only works in conjunction", + " # with recursive option", + "", + " path: | :", + " # The path to a node or event. The path must begin with '/'", + "", + "Usage:", + "", + " --force=complete /suite/t1 /suite/t2 # Set task t1 & t2 to complete", + " --force=clear /suite/task:ev # Clear the event 'ev' on task /suite/task", + " --force=complete recursive /suite/f1 # Recursively set complete all children of /suite/f1", + "", + "Effect:", + "", + " Consider the effect of applying `--force=complete /s1/t1 /s1/t2` at 09:00", + "", + " suite s1", + " task t1; time 12:00 # will complete straight away", + " task t2; time 10:00 13:00 01:00 # will complete on fourth attempt", + "", + " Specifying a time range, as shown with task t2, means the task is re-queued and the", + " next time slot is incremented for each complete, until it expires, and the task completes.", + "", + " Use the Why command, to show next run time (i.e. next time slot)" + ] + }, + { + "name": "force-dep-eval", + "kind": "user", + "visibility": "public", + "implementation": "CtsCmd", + "summary": "Force dependency evaluation. Used for DEBUG only.", + "description": ["Force dependency evaluation. Used for DEBUG only."] + }, + { + "name": "free-dep", + "kind": "user", + "visibility": "public", + "implementation": "FreeDepCmd", + "summary": "Free dependencies for a node. Defaults to triggers", + "description": [ + "Free dependencies for a node. Defaults to triggers", + "After freeing the time related dependencies (i.e time,today,cron)", + "the next time slot will be missed.", + "", + "Argument(s):", + "", + " all: (optional)", + " # Free trigger, date and all time dependencies", + "", + " date: (optional)", + " # Free date dependencies", + "", + " time: (optional)", + " # Free all time dependencies i.e time, day, today, cron", + "", + " paths", + " # A space separated list of paths.", + " # At least one path must be provided. Paths must start with a leading '/'", + "", + "Usage:", + "", + " --free-dep=/s1/t1 /s2/t2 # free trigger dependencies for task's t1,t2", + " --free-dep=all /s1/f1/t1 # free all dependencies of /s1/f1/t1", + " --free-dep=date /s1/f1 # free holding date dependencies of /s1/f1" + ] + }, + { + "name": "get", + "kind": "user", + "visibility": "public", + "implementation": "CtsNodeCmd", + "summary": "Get the suite definition or node tree in form that is re-parse able", + "description": [ + "Get the suite definition or node tree in form that is re-parse able", + "Get all suite node tree's from the server and write to standard out.", + "The output is parse-able, and can be used to re-load the definition", + " arg = NULL | arg = node path", + "", + "Usage:", + "", + " --get # gets the definition from the server,and writes to standard out", + " --get=/s1 # gets the suite from the server,and writes to standard out" + ] + }, + { + "name": "get_state", + "kind": "user", + "visibility": "public", + "implementation": "CtsNodeCmd", + "summary": "Get state data. For the whole suite definition or individual nodes.", + "description": [ + "Get state data. For the whole suite definition or individual nodes.", + "This will include event, meter, node state, trigger and time state.", + "The output is written to standard out.", + " arg = NULL | arg = node path", + "", + "Usage:", + "", + " --get_state # gets the definition from the server,and writes to standard out", + " --get_state=/s1 # gets the suite from the server,and writes to standard out" + ] + }, + { + "name": "group", + "kind": "user", + "visibility": "public", + "implementation": "GroupCTSCmd", + "summary": "Allows a series of ';' separated commands to be grouped and executed as one.", + "description": [ + "Allows a series of ';' separated commands to be grouped and executed as one.", + "Some commands like halt, shutdown and terminate will prompt the user. To bypass the prompt", + "provide 'yes' as an additional parameter. See example below.", + "", + "Argument(s):", + "", + " arg: string", + " # A string of ';' separated commands. Each command is the same as if it was", + " # provided on the command line. The string must be quoted.", + "", + "Usage:", + "", + " --group=\"halt=yes; reloadwsfile; restart;\"", + " # halt server,bypass the confirmation prompt,", + " # reload white list file, restart server", + "", + " --group=\"get; show\"", + " # get server defs, and write to standard output", + "", + " --group=\"get=/s1; show state\"", + " # get suite 's1', and write state to standard output" + ] + }, + { + "name": "halt", + "kind": "user", + "visibility": "public", + "implementation": "CtsCmd", + "summary": "Stop server communication with jobs, and new job scheduling.", + "description": [ + "Stop server communication with jobs, and new job scheduling.", + "Also stops automatic check pointing", + "", + "Argument(s):", + "", + " confirm: (optional)", + " # value must be \"yes\"; bypasses the confirmation prompt", + "", + "Usage:", + "", + " --halt=yes", + "", + "The following table shows server behaviour in the different states.", + "|----------------------------------------------------------------------------------|", + "| Server State | User Request | Task Request |Job Scheduling | Auto-Check-pointing |", + "|--------------|--------------|--------------|---------------|---------------------|", + "| RUNNING | yes | yes | yes | yes |", + "| SHUTDOWN | yes | yes | no | yes |", + "| HALTED | yes | no | no | no |", + "|--------------|--------------|--------------|---------------|---------------------|" + ] + }, + { + "name": "init", + "kind": "task", + "visibility": "public", + "implementation": "InitCmd", + "summary": "Mark task as started(active). For use in the '.ecf' script file *only*", + "description": [ + "Mark task as started(active). For use in the '.ecf' script file *only*", + "Hence the context is supplied via environment variables.", + "", + "Argument(s):", + "", + " process_or_remote_id: string", + " # The process id of the job or remote_id.", + " # Using remote id allows the jobs to be killed.", + "", + " --add: (optional)", + " # add/update variables as name value pairs", + " # a list of space separated variables to add/update on this task", + "", + "If this task command is a zombie, then the default action will be to *block*.", + "The default can be overridden by using zombie attributes.", + "Otherwise the blocking period is defined by ECF_TIMEOUT.", + "", + "Usage:", + "", + " --init=$$", + " --init=$$ --add name=value name2=value2 # add/update variables to task" + ], + "arguments": [ + { + "name": "process_or_remote_id", + "type": "string", + "optional": false, + "description": "The process id of the job or remote_id\nUsing remote id allows the jobs to be killed" + }, + { + "name": "add", + "optional": true, + "description": "add/update variables as name value pairs" + } + ] + }, + { + "name": "job_gen", + "kind": "user", + "visibility": "public", + "implementation": "CtsNodeCmd", + "summary": "Job submission for chosen Node *based* on dependencies.", + "description": [ + "Job submission for chosen Node *based* on dependencies.", + "The server traverses the node tree every 60 seconds, and if the dependencies are free", + "does job generation and submission. Sometimes the user may free time/date dependencies", + "to avoid waiting for the server poll, this commands allows early job generation", + " arg = node path | arg = NULL", + " If no node path specified generates for full definition." + ] + }, + { + "name": "kill", + "kind": "user", + "visibility": "public", + "implementation": "PathsCmd", + "summary": "Kills the job associated with the node.", + "description": [ + "Kills the job associated with the node.", + "If a family or suite is selected, will kill hierarchically.", + "Kill uses the ECF_KILL_CMD variable. After 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%.kill", + "as this allow the --file command to report the output: .e.e.", + " /home/ma/emos/bin/ecfkill %USER% %HOST% %ECF_RID% %ECF_JOB% > %ECF_JOB%.kill 2>&1::", + "", + "Usage:", + "", + " --kill=/s1/f1/t1 /s1/f2/t2 # kill the jobs for tasks t1 and t2", + " --file=/s1/f1/t1 kill # write to standard out the '.kill' file for task /s1/f1/t1" + ] + }, + { + "name": "label", + "kind": "task", + "visibility": "public", + "implementation": "LabelCmd", + "summary": "Change Label. For use in the '.ecf' script file *only*", + "description": [ + "Change Label. For use in the '.ecf' script file *only*", + "Hence the context is supplied via environment variables", + "", + "Argument(s):", + "", + " label-name", + " # The name of the label to be set.", + "", + " value", + " # The new label value. Can be single or multi-line (space separated quoted strings).", + "", + "If this task command is a zombie, then the default action will be to *fob*,", + "i.e allow the ecflow client command to complete without an error", + "The default can be overridden by using zombie attributes.", + "", + "Usage:", + "", + " --label=progressed merlin" + ], + "arguments": [ + { + "name": "label-name", + "optional": false + }, + { + "name": "The new label value", + "optional": false, + "description": "The labels values can be single or multi-line(space separated quoted strings)" + } + ] + }, + { + "name": "load", + "kind": "user", + "visibility": "public", + "implementation": "LoadDefsCmd", + "summary": "Check and load definition or checkpoint file into server.", + "description": [ + "Check and load definition or checkpoint file into server.", + "The loaded definition will be checked for valid trigger and complete expressions,", + "additionally in-limit references to limits will be validated.", + "If the server already has the 'suites' of the same name, then a error message is issued.", + "The suite's can be overwritten if the force option is used.", + "To just check the definition and not send to server, use 'check_only'", + "This command can also be used to load a checkpoint file into the server", + "", + "Argument(s):", + "", + " path: ", + " # The path to the definition file or checkpoint file.", + "", + " mode: (optional) [ force | check_only | print | stats ]", + " # The default value is to apply none of the mode options.", + "", + "Usage:", + "", + " --load=/my/home/exotic.def # will error if suites of same name exists", + " --load=/my/home/exotic.def force # overwrite suite's of same name in the server", + " --load=/my/home/exotic.def check_only # Just check, don't send to server", + " --load=/my/home/exotic.def stats # Show defs statistics, don't send to server", + " --load=host1.3141.check # Load checkpoint file to the server", + " --load=host1.3141.check print # print definition to standard out in defs format" + ] + }, + { + "name": "log", + "kind": "user", + "visibility": "public", + "implementation": "LogCmd", + "summary": "Get, clear, flush or create a new log file.", + "description": [ + "Get, clear, flush or create a new log file.", + "The user must ensure that a valid path is specified.", + "Specifying '--log=get' with a large number of lines from the server,", + "can consume a lot of **memory**. The log file can be a very large file,", + "hence we use a default of 100 lines, optionally the number of lines can be specified.", + "", + "Argument(s):", + "", + " action: [ get | clear | flush | new | path ]", + " # get: Outputs the log file to standard out.", + " # defaults to return the last 100 lines", + " # The second argument can specify how many lines to return", + " #", + " # clear: Clear the log file of its contents.", + " #", + " # flush: Flush and close the log file. (only temporary) next time", + " # server writes to log, it will be opened again. Hence it best", + " # to halt the server first", + " #", + " # new: Flush and close the existing log file, and start using the", + " # the path defined for ECF_LOG. By changing this variable", + " # a new log file path can be used", + " # Alternatively an explicit path can also be provided", + " # in which case ECF_LOG is also updated", + " #", + " # path: Returns the path name to the existing log file", + "", + " value: (optional) [ new_path | optional last n lines ]", + " # If 'get' is specified, the value specifies lines to get.", + " # The value must be an integer.", + " # If 'new' is specified, the value specifies a new path", + "", + "Usage:", + "", + " --log=get # Write the last 100 lines of the log file to standard out", + " --log=get 200 # Write the last 200 lines of the log file to standard out", + " --log=clear # Clear the log file. The log is now empty", + " --log=flush # Flush and close log file, next request will re-open log file", + " --log=new /path/to/new/log/file # Close and flush log file, and create a new log file, updates ECF_LOG", + " --log=new # Close and flush log file, and create a new log file using ECF_LOG variable" + ] + }, + { + "name": "meter", + "kind": "task", + "visibility": "public", + "implementation": "MeterCmd", + "summary": "Change meter. For use in the '.ecf' script file *only*", + "description": [ + "Change meter. For use in the '.ecf' script file *only*", + "Hence the context is supplied via environment variables", + "", + "Argument(s):", + "", + " meter-name: string", + " # The name of the meter to be set.", + "", + " value: int", + " # The new meter value", + "", + "If this task command is a zombie, then the default action will be to *fob*,", + "i.e allow the ecflow client command to complete without an error", + "The default can be overridden by using zombie attributes.", + "", + "Usage:", + "", + " --meter=my_meter 20" + ], + "arguments": [ + { + "name": "meter-name", + "type": "string", + "optional": false + }, + { + "name": "the new meter value", + "type": "int", + "optional": false + } + ] + }, + { + "name": "migrate", + "kind": "user", + "visibility": "public", + "implementation": "CtsNodeCmd", + "summary": "Used to print state of the definition returned from the server to standard output.", + "description": [ + "Used to print state of the definition returned from the server to standard output.", + "The node state is shown in the comments.", + "This is the format used in the check point file, but with indentation.", + "Since this is essentially the defs format with state in the comments,it allows the definition to be migrated to future version of ecflow.", + "The output includes edit history but excludes externs.", + "When the definition is reloaded *NO* checking is done.", + "", + "The following shows a summary of the features associated with each choice:", + " --get --get_state --migrate", + "Auto generate externs Yes Yes No", + "Checking on reload Yes Yes No", + "Edit History No No Yes", + "Show trigger AST No Yes No", + "", + "Usage:", + "", + " --migrate # show all suites", + " --migrate=/s1 # show state for suite s1" + ] + }, + { + "name": "move", + "kind": "user", + "visibility": "internal", + "implementation": "MoveCmd", + "summary": "The move command is an internal cmd, Called by the plug cmd. Does not appear on public api.", + "description": ["The move command is an internal cmd, Called by the plug cmd. Does not appear on public api."] + }, + { + "name": "msg", + "kind": "user", + "visibility": "public", + "implementation": "LogMessageCmd", + "summary": "Writes the input string to the log file.", + "description": [ + "Writes the input string to the log file.", + "", + "Argument(s):", + "", + " message: string", + " # The message to be written to the log file.", + "", + "Usage:", + "", + " --msg=\"place me in the log file\"" + ] + }, + { + "name": "news", + "kind": "user", + "visibility": "public", + "implementation": "CSyncCmd", + "summary": "Returns true if state of server definition changed.", + "description": [ + "Returns true if state of server definition changed.", + "*Important* for use with c++/python interface only.", + "Requires Given a client handle, change and modify number determine if server changed since last call", + "This relies on user calling sync after news to update the locally stored modify and change numbers.", + "These numbers are then used in the next call to news." + ] + }, + { + "name": "order", + "kind": "user", + "visibility": "public", + "implementation": "OrderNodeCmd", + "summary": "Re-orders the nodes held by the server", + "description": [ + "Re-orders the nodes held by the server", + "", + "Argument(s):", + "", + " node: path", + " # The path to the node to be re-ordered.", + "", + " sort-type: [ top | bottom | alpha | order | up | down | runtime]", + "", + "It should be noted that in the absence of triggers and time/date dependencies,", + "the tasks are submitted in order.", + "This changes the order and hence affects the submission order::", + "", + " o top raises the node within its parent, so that it is first", + " o bottom lowers the node within its parent, so that it is last", + " o alpha Arranges for all the peers of selected note to be sorted alphabetically (case-insensitive)", + " o order Arranges for all the peers of selected note to be sorted in reverse alphabet(case-insensitive)", + " o up Moves the selected node up one place amongst its peers", + " o down Moves the selected node down one place amongst its peers", + "", + " o runtime Orders the nodes according to state change runtime", + " for families by accumulated runtime of its children", + " useful to submit the task that take longer earlier", + "", + "This command can fail because:", + "- The node path does not exist in the server", + "- The order_type is not does not match one of arg2", + "", + "Usage:", + "", + " --order=/suite/f1 top # move node f1 to the top" + ] + }, + { + "name": "ping", + "kind": "user", + "visibility": "public", + "implementation": "CtsCmd", + "summary": "Check if server is running on given host/port. Result reported to standard output.", + "description": [ + "Check if server is running on given host/port. Result reported to standard output.", + "", + "Usage:", + "", + " --ping --host=mach --port=3144 # Check if server alive on host mach & port 3144", + " --ping --host=fred # Check if server alive on host fred and port ECF_PORT,", + " # otherwise default port of 3141", + " --ping # Check if server alive by using environment variables", + " # ECF_HOST and ECF_PORT", + "If ECF_HOST not defined uses 'localhost', if ECF_PORT not defined assumes 3141" + ] + }, + { + "name": "plug", + "kind": "user", + "visibility": "public", + "implementation": "PlugCmd", + "summary": "Plug command is used to move nodes.", + "description": [ + "Plug command is used to move nodes.", + "The destination node can be on another server In which case the destination", + "path should be of the form ':/suite/family/task", + "", + "Argument(s):", + "", + " source: path", + " # The path to the source node", + "", + " destination: path", + " # The path to the destination node", + "", + "This command can fail because:", + "- Source node is in a 'active' or 'submitted' state", + "- Another user already has an lock", + "- source/destination paths do not exist on the corresponding servers", + "- If the destination node path is empty, i.e. only host:port is specified,", + " then the source node must correspond to a suite.", + "- If the source node is added as a child, then its name must be unique", + " amongst its peers", + "", + "Usage:", + "", + " --plug=/suite macX:3141 # move the suite to ecFlow server on host(macX) and port(3141)" + ] + }, + { + "name": "query", + "kind": "user", + "visibility": "public", + "implementation": "QueryCmd", + "summary": "Query the status of attributes", + "description": [ + "Query the status of attributes", + " i.e state,dstate,repeat,event,meter,label,variable or trigger expression without blocking", + " - state return [unknown | complete | queued | aborted | submitted | active] to standard out", + " - dstate return [unknown | complete | queued | suspended | aborted | submitted | active] to standard out", + " - repeat returns current value as a string to standard out", + " - event return 'set' | 'clear' to standard out", + " - meter return value of the meter to standard out", + " - limit return current value of limit to standard out", + " - limit_max return limit max value to standard out", + " - label return new value otherwise the old value", + " - variable return value of the variable, repeat or generated variable to standard out,", + " will search up the node tree. When path is '/', the variable is looked up on the", + " server itself, i.e. 'ecflow_client --query variable /:name'", + " - trigger returns 'true' if the expression is true, otherwise 'false'", + "", + "If this command is called within a '.ecf' script we will additionally log the task calling this command", + "This is required to aid debugging for excessive use of this command", + "The command will fail if the node path to the attribute does not exist in the definition and if:", + " - repeat The repeat is not found", + " - event The event is not found", + " - meter The meter is not found", + " - limit/limit_max The limit is not found", + " - label The label is not found", + " - variable No user or generated variable or repeat of that name found on node or its parents,", + " or (when path is '/') no user or server variable of that name found on the server", + " - trigger Trigger does not parse, or reference to nodes/attributes in the expression are not valid", + "", + "Argument(s):", + "", + " attribute: [ state | dstate | repeat | event | meter | label | variable | trigger | limit | limit_max ]", + " # The kind of the attribute to be queried.", + "", + " target: | :name", + " # The path to the node or the node and attribute name.", + "", + " value: trigger expression | prev | next", + " # The values `prev` and `next` are only used when the attribute is a repeat", + "", + "Usage:", + "", + " --query state / # return top level state to standard out", + " --query state /path/to/node # return node state to standard out", + " --query dstate /path/to/node # state that can included suspended", + " --query repeat /path/to/node # return the current value as a string", + " --query repeat /path/to/node prev # return the previous value as a string", + " --query repeat /path/to/node next # return the next value as a string", + " --query event /path/to/task/with/event:event_name # return set | clear to standard out", + " --query meter /path/to/task/with/meter:meter_name # returns the current value of the meter to standard out", + " --query limit /path/to/task/with/limit:limit_name # returns the current value of the limit to standard out", + " --query limit_max /path/to/task/with/limit:limit_name # returns the max value of the limit to standard out", + " --query label /path/to/task/with/label:label_name # returns the current value of the label to standard out", + " --query variable /path/to/task/with/var:var_name # returns the variable value to standard out", + " --query variable /:var_name # returns the server variable value to standard out", + " --query trigger /path/to/node/with/trigger \"/suite/task == complete\" # return true if expression evaluates false otherwise" + ] + }, + { + "name": "queue", + "kind": "task", + "visibility": "public", + "implementation": "QueueCmd", + "summary": "QueueCmd. For use in the '.ecf' script file *only*", + "description": [ + "QueueCmd. For use in the '.ecf' script file *only*", + "Hence the context is supplied via environment variables", + "", + "Argument(s):", + "", + " queue-name: string", + " # The name of the queue to be used.", + "", + " action: string", + " # [active | aborted | complete | no_of_aborted | reset ]", + " # active: returns the first queued/aborted step, the return string is the queue value from the definition", + " # no_of_aborted: returns number of aborted steps as a string, i.e 10", + " # reset: sets the index to the first queued/aborted step. Allows steps to be reprocessed for errors", + "", + " step: string", + " # The value returned from `step=$(ecflow_client --queue=queue_name active)`", + " # This is only valid for complete and aborted steps", + "", + " path: (optional) string", + " # The path where the queue is defined.", + " # By default we search for the queue up the node tree.", + "", + "If this task command is a zombie, then the default action will be to *block*,", + "The default can be overridden by using zombie attributes.", + "If the path to the queue is not defined, then this command will", + "search for the queue up the node hierarchy. If no queue found, command fails", + "", + "Usage:", + "", + "step=\"\"", + "QNAME=\"my_queue_name\"", + "while [1 == 1 ] ; do", + " # this return the first queued/aborted step, then increments to next step, return when all steps processed", + " step=$(ecflow_client --queue=$QNAME active) # of the form string i.e \"003\". this step is now active", + " if [[ $step == \"\" ]] ; then", + " break;", + " fi", + " ...", + " ecflow_client --queue=$QNAME complete $step # tell ecflow this step completed", + "done", + "", + "trap() { ecflow_client --queue=$QNAME aborted $step # tell ecflow this step failed }" + ], + "arguments": [ + { + "name": "queue-name", + "type": "string", + "optional": false + }, + { + "name": "action", + "type": "string", + "optional": false, + "description": "[active | aborted | complete | no_of_aborted | reset ]\nactive: returns the first queued/aborted step, the return string is the queue value from the definition\nno_of_aborted: returns number of aborted steps as a string, i.e 10\nreset: sets the index to the first queued/aborted step. Allows steps to be reprocessed for errors" + }, + { + "name": "step", + "type": "string", + "optional": false, + "description": "value returned from step=$(ecflow_client --queue=queue_name active)\nThis is only valid for complete and aborted steps" + }, + { + "name": "path", + "type": "string", + "optional": true, + "description": "(optional). The path where the queue is defined.\nBy default we search for the queue up the node tree." + } + ] + }, + { + "name": "reloadcustompasswdfile", + "kind": "user", + "visibility": "public", + "implementation": "CtsCmd", + "summary": "Reload the server custom password file.", + "description": [ + "Reload the server custom password file.", + "", + "The custom password file (authentication) is used by the server to authenticate a 'user' by", + "verifying if the password provided by the user matches the one held by the server. This", + "particular file is used for authentication of users that explicitly specify the user name", + "(either via the environment variable ECF_USER or the --user option).", + "", + "This mechanism should be used when most users use the machine login name, but a few users", + "specify their own user name, in which case the password must also be explicitly provided.", + "", + "The file path is specified as the ECF_CUSTOM_PASSWD environment variable, both for the", + "client and server, and is loaded only by the server on *startup*. This means that the file", + "contents can be updated (i.e., add/remove users), but the file location cannot change during", + "the server execution.", + "", + "The server automatically loads the password file content as part of the startup procedure.", + "", + "The ECF_CUSTOM_PASSWD environment variable is used to specify the password file location,", + "considering that", + " - On the server the default file name is ..ecf.custom_passwd", + " - On the client the default file name is ecf.custom_passwd", + "", + "The format of the file is same for client and server:", + "", + "", + "4.5.0", + "# comment", + " # comment", + "", + "The following is an example", + "", + "4.5.0 # the version", + "fred machine1 3142 xxyyyd", + "fred machine2 3133 xxyyyd # comment", + "bill machine2 3133 xxyggyyd", + "", + "Notice that the same user may appear multiple times (associated with different host/port).", + "This allows the client to use the same password file to contact multiple servers.", + "", + "For the password authentication to work, ensure the following:", + " - The password is defined for the client and server", + " - On the server, add at least the server administrator to the password file", + " Note: If an empty password file (i.e., containing just the version) is used,", + " no user is allowed access.", + " - On the client, the password file should be readable only by the 'user' itself", + "", + "Usage:", + "", + " --reloadcustompasswdfile" + ] + }, + { + "name": "reloadpasswdfile", + "kind": "user", + "visibility": "public", + "implementation": "CtsCmd", + "summary": "Reload the server password file.", + "description": [ + "Reload the server password file.", + "", + "The password file (authentication) is used by the server to authenticate a 'user' by", + "verifying if the password provided by the user matches the one held by the server.", + "The password file is also used on the client to automatically load the password for the", + "'user' when connecting to the server.", + "", + "When the server is configured to use a password file, then ALL users must have a password.", + "", + "The file path is specified as the ECF_PASSWD environment variable, both for the client and", + "server, and is loaded only by the server on *startup*. This means that the file contents", + "can be updated (i.e., add/remove users), but the file location cannot change during the", + "server execution.", + "", + "The server automatically loads the password file content as part of the startup procedure.", + "", + "The ECF_PASSWD environment variable is used to specify the password file location,", + "considering that", + " - On the server, the default file name is ..ecf.passwd", + " - On the client, the default file name is ecf.passwd", + "", + "The format of the file is same for client and server:", + "", + "", + "4.5.0", + "# comment", + " # comment", + "", + "The following is an example", + "", + "4.5.0 # the version", + "fred machine1 3142 xxyyyd", + "fred machine2 3133 xxyyyd # comment", + "bill machine2 3133 xxyggyyd", + "", + "", + "Notice that the same user may appear multiple times (associated with different host/port).", + "This allows the client to use the same password file to contact multiple servers.", + "", + "For the password authentication to work, ensure the following:", + " - The password is defined for the client and server", + " - On the server, add at least the server administrator to the password file", + " Note: If an empty password file (i.e., containing just the version) is used,", + " no user is allowed access.", + " - On the client, the password file should be readable only by the 'user' itself", + "", + "Usage:", + "", + " --reloadpasswdfile" + ] + }, + { + "name": "reloadwsfile", + "kind": "user", + "visibility": "public", + "implementation": "CtsCmd", + "summary": "Reload the white list file.", + "description": [ + "Reload the white list file.", + "", + "The white list file (authorisation) is used to verify if a 'user' is allowed to perform a", + "specific command.", + "", + "The file path is specified as the ECF_LISTS variable, and loaded only once by the server", + "(on *startup*). This means that the file contents can be updated, but the file location", + "cannot change during the server execution.", + "", + "The ECF_LISTS variable can be used as follows:", + " - if ECF_LISTS is not specified, or if it is specified with value `ecf.lists`,", + " then the server will use the value `..ecf.lists`", + " - if ECF_LISTS is specified to be a path, such as /var/tmp/ecf.lists,", + " then the server will use this path to reload the white list file", + "", + "The server automatically loads the white list file content as part of the startup procedure,", + "considering that if the file is not present or is empty (i.e., just contains the version", + "number) then all users have read/write access.", + "", + "The reload operation will fail if file does not exist or if the content is invalid.", + "", + "Expected format for this file is:", + "", + "", + "# all characters after the first # in a line are considered comments and are discarded", + "# empty lines are also discarded", + "", + "4.4.14 # the version number is mandatory, even if no users are specified", + "# Users with read/write access", + "user1", + "user2 # comment", + "* # use this form if you want all users to have read/write access", + "", + "# Users with read access, must have - before user name", + "-user3 # comment", + "-user4", + "-* # use this form if you want all users to have read access", + "", + "Usage:", + "", + " --reloadwsfile" + ] + }, + { + "name": "replace", + "kind": "user", + "visibility": "public", + "implementation": "ReplaceNodeCmd", + "summary": "Replaces a node in the server, with the given path", + "description": [ + "Replaces a node in the server, with the given path", + "Can also be used to add nodes in the server", + "", + "Argument(s):", + "", + " node: path", + " # The path to the node to be replaced.", + " # This path must exist in both the argument `definition` file", + " # and on the definition currently loaded on the server.", + "", + " definition: path", + " # The path to the client definition file,", + " # which provides the definition of the new node.", + "", + " parent: (optional) [ parent | false ] (default = parent)", + " # Allows automatically creating parent families or suite as needed,", + " # when argument `node` does not exist in the server", + "", + " force: (optional) (default = false)", + " # Force the replacement, even if any node is queued/executing.", + " # Using this option might causes zombies to be created", + "", + "Replace can fail if:", + "- The node path(arg1) does not exist in the provided client definition(arg2)", + "- The client definition(arg2) must be free of errors", + "- If the third argument is not provided, then node path(arg1) must exist in the server", + "- Nodes to be replaced are in active/submitted state, in which case arg4(force) can be used", + "", + "Replace will preserve the suspended status, if this is not required please re-queue first", + "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.", + "For more information use --help check.", + "", + "Usage:", + "", + " --replace=/suite/f1/t1 /tmp/client.def parent # Add/replace node tree /suite/f1/t1", + " --replace=/suite/f1/t1 /tmp/client.def false force # replace t1 even if its active or submitted" + ] + }, + { + "name": "requeue", + "kind": "user", + "visibility": "public", + "implementation": "RequeueNodeCmd", + "summary": "Re queues the specified node(s)", + "description": [ + "Re queues the specified node(s)", + " If any child of the specified node(s) is in a suspended state, this state is cleared", + "Repeats are reset to their starting values, relative time attributes are reset.", + "", + "Argument(s):", + "", + " mode: (optional) [ abort | force ]", + " # abort = re-queue only aborted tasks below node", + " # force = Force the re-queueing even if there are nodes that are active or submitted", + " # = Checks if any tasks are in submitted or active states below the node,", + " # and if so does nothing. Otherwise, re-queues the node.", + "", + " node: path", + " # A space separared list of node paths. Paths must begin with a leading '/' character", + "", + "Usage:", + "", + " --requeue=abort /suite/f1 # re-queue all aborted tasks of /suite/f1", + " --requeue=force /suite/f1 # forcibly re-queue /suite/f1 and all its children.May cause zombies.", + " --requeue=/s1/f1/t1 /s1/t2 # Re-queue node '/suite/f1/t1' and '/s1/t2'" + ] + }, + { + "name": "restart", + "kind": "user", + "visibility": "public", + "implementation": "CtsCmd", + "summary": "Start job scheduling, communication with jobs, and respond to all requests.", + "description": [ + "Start job scheduling, communication with jobs, and respond to all requests.", + "The following table shows server behaviour in the different states.", + "|----------------------------------------------------------------------------------|", + "| Server State | User Request | Task Request |Job Scheduling | Auto-Check-pointing |", + "|--------------|--------------|--------------|---------------|---------------------|", + "| RUNNING | yes | yes | yes | yes |", + "| SHUTDOWN | yes | yes | no | yes |", + "| HALTED | yes | no | no | no |", + "|--------------|--------------|--------------|---------------|---------------------|" + ] + }, + { + "name": "restore", + "kind": "user", + "visibility": "public", + "implementation": "PathsCmd", + "summary": "Manually restore archived nodes.", + "description": [ + "Manually restore archived nodes.", + "Restore will fail if:", + " - Node has not been archived", + " - Node has children, (since archived nodes have no children)", + " - If the file ECF_HOME/...check does not exist", + "Nodes can be restored manually(as in this command) but also automatically", + "", + "Automatic restore is done using the 'autorestore' attribute.", + "Once the node containing the 'autorestore' completes, the attribute is applied", + "", + " suite s", + " family farchive_now", + " autoarchive 0 # archive immediately after complete", + " task tx", + " endfamily", + " family frestore_from_task", + " task t1", + " trigger ../farchive_nowarchived", + " autorestore ../farchive_now # call autorestore when t1 completes", + " endfamily", + " endsuite", + "", + "In this example task '/s/frestore_from_task/t1' is only triggered if 'farchive_now'", + "is archived, then when t1 completes it will restore family 'farchive_now'", + "", + "Usage:", + "", + " --restore=/s1/f1 # restore family /s1/f1", + " --restore=/s1 /s2 # restore suites /s1 and /s2" + ] + }, + { + "name": "restore_from_checkpt", + "kind": "user", + "visibility": "public", + "implementation": "CtsCmd", + "summary": "Ask the server to load the definition from an check pt file.", + "description": [ + "Ask the server to load the definition from an check pt file.", + "The server must be halted and the definition in the server must be deleted", + "first, otherwise an error is returned" + ] + }, + { + "name": "resume", + "kind": "user", + "visibility": "public", + "implementation": "PathsCmd", + "summary": "Resume the given node. This allows job generation for the given node, or any child node.", + "description": [ + "Resume the given node. This allows job generation for the given node, or any child node.", + "", + "Usage:", + "", + " --resume=/s1/f1/t1 # resume task s1/f1/t1", + " --resume=/s1 /s2 # resume suites /s1 and /s2" + ] + }, + { + "name": "run", + "kind": "user", + "visibility": "public", + "implementation": "RunNodeCmd", + "summary": "Ignore triggers, limits, time or date dependencies, just run the Task.", + "description": [ + "Ignore triggers, limits, time or date dependencies, just run the Task.", + "When a job completes, it may be automatically re-queued if it has a cron", + "or multiple time dependencies. If we have multiple time based attributes,", + "then each run, will expire the time.", + "When we run before the time, we want to avoid re-running the task then", + "a flag is set, so that it is not automatically re-queued.", + "A repeat attribute is incremented when all the child nodes are complete", + "in this case the child nodes are automatically re-queued.", + "Hence this command can be aid, in allowing a Repeat attribute to be incremented", + "", + "Argument(s):", + "", + " force: (optional)", + " # Forcibly run, even if there are nodes that are active or submitted", + " # This might cause the appearance of zombie", + "", + " node: path", + " # A space separared list of node paths.", + " # The paths must begin with a leading '/' character.", + " # If the path points to a suite or a family, all tasks will be run recursively.", + " # When providing multiple paths avoid running the same task twice", + "", + "Usage:", + "", + " --run=/suite/t1 # run task t1", + "", + "Effect:", + "", + " task t1; time 12:00 # will complete if run manually", + " task t2; time 10:00 13:00 01:00 # will run 4 times before completing", + "", + " Specifying a time range, as shown with task t2, means the next time slot", + " is incremented for each run, until it expires, and the task completes.", + "", + " Use the Why command, to show next run time (i.e. next time slot)" + ] + }, + { + "name": "server_load", + "kind": "user", + "visibility": "public", + "implementation": "CtsCmd", + "summary": "Generates gnuplot files that show the server load graphically.", + "description": [ + "Generates gnuplot files that show the server load graphically.", + "This is done by parsing the log file. If no log file is provided,", + "then the log file path is obtained from the server. If the returned", + "log file path is not accessible an error is returned", + "This command produces a three files in the CWD:", + "", + " - ..gnuplot.dat", + " - ..gnuplot.script", + " - ..png", + "", + "The generated script can be manually changed, to see different rendering", + "effects. i.e. just run 'gnuplot ..gnuplot.script'", + "", + "Argument(s):", + "", + " log: (optional) path", + " # The path to the log file.", + " # If not provided, the log file path is requested from the server.", + "", + "If the path to log file is known, it is *preferable* to use this,", + "rather than requesting the log path from the server.", + "", + "Usage:", + "", + " --server_load=/path/to_log_file # Parses log and generate gnuplot files", + " --server_load # Log file path is requested from server", + " # which is then used to generate gnuplot files", + " # *AVOID* if log file path is accessible", + "", + "Now use any png viewer to see the output i.e", + "", + "> display ..png", + "> feh ..png", + "> eog ..png", + "> xdg-open ..png", + "> w3m ..png" + ] + }, + { + "name": "server_version", + "kind": "user", + "visibility": "public", + "implementation": "ServerVersionCmd", + "summary": "Returns the version number of the server", + "description": [ + "Returns the version number of the server", + "", + "Usage:", + "", + " --server_version", + " Writes the version to standard output" + ] + }, + { + "name": "show", + "kind": "user", + "visibility": "group-only", + "implementation": "ShowCmd", + "summary": "Used to print state of the definition returned from the server to standard output.", + "description": [ + "Used to print state of the definition returned from the server to standard output.", + "This command can *only* be used in a group command, and will only work if it is", + "preceded with a get command. See examples below.", + "", + "Argument(s):", + "", + " output: (optional) [ defs | state | migrate ]", + " # defs:", + " # Return the definition structure only, without any state information.", + " # Necessary `extern`s are automatically added, allowing the output to be", + " # reloaded into the server e.g., --group=\"get ; show\"", + " # This is the default if no `output` argument is provided.", + " #", + " # state:", + " # Return definition structure along with all the state information.", + " # Output includes the trigger expressions' abstract syntax tree (as comment).", + " # Does not include the edit history", + " #", + " # migrate:", + " # Return definition structure along with all the state information.", + " # The node state is shown in the comments.", + " # Use this format to migrate the definition to future version of ecflow.", + " # The output includes edit history but excludes externs.", + " # When the definition is reloaded *NO* checking is done.", + "", + "The following shows a summary of the features associated with each choice", + " DEFS STATE MIGRATE", + "Auto generate externs Yes Yes No", + "Checking on reload Yes Yes No", + "Edit History No No Yes", + "trigger AST No Yes No", + "", + "Usage:", + "", + " --group=\"get ; show\"", + " --group=\"get ; show defs\" # same as the previous example", + " --group=\"get ; show state\" # Show all state for the node tree", + " --group=\"get ; show migrate\" # Shows state and allows migration", + " --group=\"get=/s1; show\" # show state for the node only", + " --group=\"get=/s1; show state\"" + ] + }, + { + "name": "shutdown", + "kind": "user", + "visibility": "public", + "implementation": "CtsCmd", + "summary": "Stop server from scheduling new jobs.", + "description": [ + "Stop server from scheduling new jobs.", + "", + "Argument(s):", + "", + " confirm: (optional)", + " # value must be \"yes\"; bypasses the confirmation prompt", + "", + "Usage:", + "", + " --shutdown=yes", + "", + "The following table shows server behaviour in the different states.", + "|----------------------------------------------------------------------------------|", + "| Server State | User Request | Task Request |Job Scheduling | Auto-Check-pointing |", + "|--------------|--------------|--------------|---------------|---------------------|", + "| RUNNING | yes | yes | yes | yes |", + "| SHUTDOWN | yes | yes | no | yes |", + "| HALTED | yes | no | no | no |", + "|--------------|--------------|--------------|---------------|---------------------|" + ] + }, + { + "name": "stats", + "kind": "user", + "visibility": "public", + "implementation": "CtsCmd", + "summary": "Returns the server statistics as a string.", + "description": ["Returns the server statistics as a string."] + }, + { + "name": "stats_reset", + "kind": "user", + "visibility": "public", + "implementation": "CtsCmd", + "summary": "Resets the server statistics.", + "description": ["Resets the server statistics."] + }, + { + "name": "stats_server", + "kind": "user", + "visibility": "public", + "implementation": "CtsCmd", + "summary": "Returns the server statistics as a struct and string. For test use only.", + "description": ["Returns the server statistics as a struct and string. For test use only."] + }, + { + "name": "status", + "kind": "user", + "visibility": "public", + "implementation": "PathsCmd", + "summary": "Shows the status of a job associated with a task, in %ECF_JOB%.stat file", + "description": [ + "Shows the status of a job associated with a task, in %ECF_JOB%.stat file", + "If a family or suite is selected, will invoke status command hierarchically.", + "Status uses the ECF_STATUS_CMD variable. After 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", + "This will allow the output of status command to be shown by the --file command", + "i.e /home/ma/emos/bin/ecfstatus %USER% %HOST% %ECF_RID% %ECF_JOB% > %ECF_JOB%.stat 2>&1::", + "If the process id cannot be found on the remote system, then the status command can also", + "arrange for the task to be aborted", + "The status command can fail for the following reasons:", + " - ECF_STATUS_CMD not found", + " - variable substitution fails", + " - state is active but it can't find process id, i.e. ECF_RID", + " - the status command does not exit cleanly", + "When this happens a flag is set, STATUSCMD_FAILED, which is visible in the GUI", + "", + "Usage:", + "", + " --status=/s1/f1/t1 # ECF_STATUS_CMD should output to %ECF_JOB%.stat", + " --file=/s1/f1/t1 stat # Return contents of %ECF_JOB%.stat file" + ] + }, + { + "name": "suites", + "kind": "user", + "visibility": "public", + "implementation": "CtsCmd", + "summary": "Returns the list of suites, in the order defined in the server.", + "description": ["Returns the list of suites, in the order defined in the server."] + }, + { + "name": "suspend", + "kind": "user", + "visibility": "public", + "implementation": "PathsCmd", + "summary": "Suspend the given node. This prevents job generation for the given node, or any child node.", + "description": [ + "Suspend the given node. This prevents job generation for the given node, or any child node.", + "", + "Usage:", + "", + " --suspend=/s1/f1/t1 # suspend task s1/f1/t1", + " --suspend=/s1 /s2 # suspend suites /s1 and /s2" + ] + }, + { + "name": "sync", + "kind": "user", + "visibility": "public", + "implementation": "CSyncCmd", + "summary": "Incrementally synchronise the local definition with the one in the server.", + "description": [ + "Incrementally synchronise the local definition with the one in the server.", + "*Important* for use with c++/python interface only.", + "Preference should be given to this method as only the changes are returned.", + "This reduces the network bandwidth required to keep in sync with the server", + "Requires a client handle, change and modify number, to get the incremental changes from server.", + "The change in server state is then and merged with the client definition." + ] + }, + { + "name": "sync_clock", + "kind": "user", + "visibility": "public", + "implementation": "CSyncCmd", + "summary": "Incrementally synchronise the local definition with the one in the server.", + "description": [ + "Incrementally synchronise the local definition with the one in the server.", + "*Important* for use with c++/python interface only.", + "Same as sync, but will *always* sync with suite clock if it has changed.", + "Preference should be given to this method as only the changes are returned.", + "This reduces the network bandwidth required to keep in sync with the server", + "Requires a client handle, change and modify number, to get the incremental changes from server.", + "The change in server state is then and merged with the client definition." + ] + }, + { + "name": "sync_full", + "kind": "user", + "visibility": "public", + "implementation": "CSyncCmd", + "summary": "Returns the full definition from the server.", + "description": [ + "Returns the full definition from the server.", + "*Important* for use with c++/python interface only.", + "Requires a client_handle. The returned definition is stored on the client." + ] + }, + { + "name": "terminate", + "kind": "user", + "visibility": "public", + "implementation": "CtsCmd", + "summary": "Terminate the server.", + "description": [ + "Terminate the server.", + "", + "Argument(s):", + "", + " confirm: (optional)", + " # value must be \"yes\"; bypasses the confirmation prompt", + "", + "Usage:", + "", + " --terminate=yes" + ] + }, + { + "name": "wait", + "kind": "task", + "visibility": "public", + "implementation": "CtsWaitCmd", + "summary": "Evaluates an expression, and block while the expression is false.", + "description": [ + "Evaluates an expression, and block while the expression is false.", + "For use in the '.ecf' file *only*, hence the context is supplied via environment variables", + "", + "Argument(s):", + "", + " expression: string", + " # The expression to be evaluated.", + "", + "Usage:", + "", + " --wait=\"/suite/taskx == complete\"" + ], + "arguments": [ + { + "name": "expression", + "type": "string", + "optional": false + } + ] + }, + { + "name": "why", + "kind": "user", + "visibility": "group-only", + "implementation": "CtsNodeCmd", + "summary": "Show the reason why a node is not running.", + "description": [ + "Show the reason why a node is not running.", + "Can only be used with the group command. The group command must include a ", + "'get' command(i.e returns the server defs)", + "The why command take a optional string argument representing a node path", + "Will return reason why the node is holding and for all its children.", + "If no arguments supplied will report on all nodes", + " arg = node path | arg = NULL", + "", + "Usage:", + "", + " --group=\"get; why\" # returns why for all holding nodes", + " --group=\"get; why=/suite/family\" # returns why for a specific node" + ] + }, + { + "name": "zombie_get", + "kind": "user", + "visibility": "public", + "implementation": "CtsCmd", + "summary": "Returns the list of zombies from the server.", + "description": [ + "Returns the list of zombies from the server.", + "Results reported to standard output." + ] + }, + { + "name": "zombie_adopt", + "kind": "user", + "visibility": "public", + "implementation": "ZombieCmd", + "summary": "Locates the task in the servers list of zombies, and sets to adopt.", + "description": [ + "Locates the task in the servers list of zombies, and sets to adopt.", + "Next time a child command (init,event,meter,label,abort,complete,wait queue) communicates with the server, the password on the zombie is adopted by the task.", + "This is only allowed if the process id matches, otherwise an error is issued.", + "The zombie reference stored in the server is then deleted.", + " args = list of task paths, at least one expected", + "", + "Usage:", + "", + " --zombie_adopt=/path/to/task /path/to/task2" + ] + }, + { + "name": "zombie_block", + "kind": "user", + "visibility": "public", + "implementation": "ZombieCmd", + "summary": "Locates the task in the servers list of zombies, and sets flags to block it.", + "description": [ + "Locates the task in the servers list of zombies, and sets flags to block it.", + "This is default behaviour of the child commands(init,abort,complete,wait,queue)", + "when the server cannot match the passwords. Each child commands will continue", + "attempting to connect to the server for 24 hours, and will then return an error.", + "The connection timeout can be configured with environment ECF_TIMEOUT", + " args = list of task paths, at least one expected", + "", + "Usage:", + "", + " --zombie_block=/path/to/task /path/to/task2" + ] + }, + { + "name": "zombie_fail", + "kind": "user", + "visibility": "public", + "implementation": "ZombieCmd", + "summary": "Locates the task in the servers list of zombies, and sets to fail.", + "description": [ + "Locates the task in the servers list of zombies, and sets to fail.", + "Next time a child command (init,event,meter,label,abort,complete) which matches zombie, communicates with the server, will be set to fail.", + "Depending on the job setup this may force a abort, the abort will also fail.", + "Hence job structure should use 'set -e' in the error trapping functions to prevent", + "infinite recursion.", + "The references to the zombie in the server is automatically deleted after 1 hour", + " args = list of task paths, at least one expected", + "", + "Usage:", + "", + " --zombie_fail=/path/to/task /path/to/task2" + ] + }, + { + "name": "zombie_fob", + "kind": "user", + "visibility": "public", + "implementation": "ZombieCmd", + "summary": "Locates the task in the servers list of zombies, and sets to fob.", + "description": [ + "Locates the task in the servers list of zombies, and sets to fob.", + "This default behaviour of the child commands(label,event,meter) is to fob", + "Next time the child commands (init,event,meter,label,abort,complete,wait,queue) communicate", + "with the server, they will complete successfully (but without updating the node tree)", + "allowing the job to finish.", + "The references to the zombie in the server is automatically deleted after 1 hour", + " args = list of task paths, at least one expected", + "", + "Usage:", + "", + " --zombie_fob=/path/to/task1 /path/to/task2" + ] + }, + { + "name": "zombie_kill", + "kind": "user", + "visibility": "public", + "implementation": "ZombieCmd", + "summary": "Locates the task in the servers list of zombies, and sets flags to kill", + "description": [ + "Locates the task in the servers list of zombies, and sets flags to kill", + "The kill is done using ECF_KILL_CMD, but using the process_id from the zombie", + "The job is allowed to continue until the kill is received", + "Can only kill zombies that have an associated Task, hence path zombies", + "must be killed manually.", + " arg = list of task paths, at least one expected", + "", + "Usage:", + "", + " --zombie_kill=/path/to/task /path/to/task2" + ] + }, + { + "name": "zombie_remove", + "kind": "user", + "visibility": "public", + "implementation": "ZombieCmd", + "summary": "Locates the task in the servers list of zombies, and removes it.", + "description": [ + "Locates the task in the servers list of zombies, and removes it.", + "Since a job typically has many child commands (i.e init, complete, event, meter, label, wait, queue)", + "the zombie may reappear", + " args = list of task paths, at least one expected", + "", + "Usage:", + "", + " --zombie_remove=/path/to/task /path/to/task2" + ] + } + ], + "options": [ + { + "name": "host", + "kind": "global-option", + "summary": "When specified overrides the environment variable ECF_HOST and default host: 'localhost'", + "description": ["When specified overrides the environment variable ECF_HOST and default host: 'localhost'"] + }, + { + "name": "port", + "kind": "global-option", + "summary": "When specified overrides the environment variable ECF_PORT and default port: '3141'", + "description": ["When specified overrides the environment variable ECF_PORT and default port: '3141'"] + }, + { + "name": "user", + "kind": "global-option", + "summary": "Specifies the user name used to contact the server. Must be used in combination with option --password.", + "description": ["Specifies the user name used to contact the server. Must be used in combination with option --password."], + "pairs_with": "password" + }, + { + "name": "password", + "kind": "global-option", + "summary": "Specifies the password used to contact the server. Must be used in combination with option --user.", + "description": ["Specifies the password used to contact the server. Must be used in combination with option --user."], + "pairs_with": "user" + }, + { + "name": "rid", + "kind": "global-option", + "summary": "When specified overrides the environment variable ECF_RID. Can only be used for task commands.", + "description": ["When specified overrides the environment variable ECF_RID. Can only be used for task commands."] + }, + { + "name": "ssl", + "kind": "global-option", + "requires": "ECF_OPENSSL", + "summary": "Enables the use of SSL when contacting the server.", + "description": [ + "Enables the use of SSL when contacting the server.", + "When specified overrides the environment variable ECF_SSL." + ] + }, + { + "name": "http", + "kind": "global-option", + "summary": "Enables communication over HTTP between client/server.", + "description": ["Enables communication over HTTP between client/server."] + }, + { + "name": "https", + "kind": "global-option", + "summary": "Enables communication over HTTPS between client/server.", + "description": ["Enables communication over HTTPS between client/server."] + }, + { + "name": "help", + "kind": "global-option", + "summary": "Produce help message", + "description": ["Produce help message"] + }, + { + "name": "version", + "kind": "global-option", + "summary": "Show ecflow client version number, and version of the boost library used", + "description": ["Show ecflow client version number, and version of the boost library used"] + }, + { + "name": "debug", + "kind": "global-option", + "summary": "Enables the display of client environment settings and execution details.", + "description": [ + "Enables the display of client environment settings and execution details.", + "Has the same effect as setting environment variable ECF_DEBUG_CLIENT." + ] + }, + { + "name": "add", + "kind": "command-option", + "command": "init", + "summary": "Add variables e.g. name1=value1 name2=value2. Can only be used in combination with --init command.", + "description": ["Add variables e.g. name1=value1 name2=value2. Can only be used in combination with --init command."] + }, + { + "name": "remove", + "kind": "command-option", + "command": "complete", + "summary": "remove variables i.e name name2", + "description": ["remove variables i.e name name2"] + } + ], + "environment_variables": [ + { + "name": "ECF_HOST", + "type": "string", + "required": "mandatory", + "applies_to": "both", + "overridable_by": "host", + "description": "The main server hostname; default value is 'localhost'" + }, + { + "name": "ECF_PORT", + "type": "int", + "required": "mandatory", + "applies_to": "both", + "overridable_by": "port", + "description": "The main server port; default value is '3141'" + }, + { + "name": "ECF_SSL", + "type": "any", + "required": "optional", + "applies_to": "both", + "requires": "ECF_OPENSSL", + "overridable_by": "ssl", + "description": "Enable secure communication between client and server." + }, + { + "name": "ECF_HOSTFILE", + "type": "string", + "required": "optional", + "applies_to": "both", + "description": "File that lists alternate hosts to try, if connection to main host fails" + }, + { + "name": "ECF_HOSTFILE_POLICY", + "type": "string", + "required": "optional", + "applies_to": "both", + "description": "The policy ('task' or 'all') to define which commands consider using alternate hosts." + }, + { + "name": "ECF_NAME", + "type": "string", + "required": "mandatory", + "applies_to": "task", + "description": "Full path name to the task" + }, + { + "name": "ECF_PASS", + "type": "string", + "required": "mandatory", + "applies_to": "task", + "description": "The job password (defined by the server, and used to authenticate client requests)" + }, + { + "name": "ECF_TRYNO", + "type": "int", + "required": "mandatory", + "applies_to": "task", + "description": "The run number of the job (defined by the server, and used in job/output file name generation)." + }, + { + "name": "ECF_RID", + "type": "string", + "required": "mandatory", + "applies_to": "task", + "overridable_by": "rid", + "description": "The process identifier. Supports identifying zombies and automated killing of running jobs" + }, + { + "name": "ECF_TIMEOUT", + "type": "int", + "required": "optional", + "applies_to": "task", + "description": "Maximum time in *seconds* for client to deliver message to main server; default is 24 hours" + }, + { + "name": "ECF_DENIED", + "type": "any", + "required": "optional", + "applies_to": "task", + "description": "Allows task to exit with an error, upon connection failure, thus avoids ECF_TIMEOUT's wait." + }, + { + "name": "NO_ECF", + "type": "any", + "required": "optional", + "applies_to": "task", + "description": "If set, ecflow_client exits immediately with success; useful to test the scripts without a server" + } + ] +} diff --git a/docs/client_api/help.schema.json b/docs/client_api/help.schema.json new file mode 100644 index 000000000..d470ad7ac --- /dev/null +++ b/docs/client_api/help.schema.json @@ -0,0 +1,155 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://ecflow.readthedocs.io/schemas/help-manifest.json", + "title": "ecflow_client help manifest", + "type": "object", + "additionalProperties": false, + "required": ["schema_version", "topics", "commands", "options", "environment_variables"], + "properties": { + "schema_version": { + "type": "integer", + "minimum": 1 + }, + "topics": { + "type": "array", + "items": { "$ref": "#/$defs/topic" } + }, + "commands": { + "type": "array", + "items": { "$ref": "#/$defs/command" } + }, + "options": { + "type": "array", + "items": { "$ref": "#/$defs/option" } + }, + "environment_variables": { + "type": "array", + "items": { "$ref": "#/$defs/environment_variable" } + } + }, + "$defs": { + "description_line": { + "description": "A single line of the help text, with no embedded newline. The lines are joined with a newline to reconstruct the original text verbatim (including preformatted content, e.g. CtsCmd's ASCII-art tables); a blank line in the source is represented as an empty string, so an empty string is allowed.", + "type": "string" + }, + "description": { + "type": "array", + "items": { "$ref": "#/$defs/description_line" } + }, + "argument": { + "description": "Structured, best-effort metadata alongside the verbatim argN prose kept in the command's own description; the source's argN convention is inconsistent per-command (some omit type, some omit a separate description line, one uses a union type), so type and description are not always present, and type is free text rather than a closed set.", + "type": "object", + "additionalProperties": false, + "required": ["name", "optional"], + "properties": { + "name": { "type": "string", "minLength": 1 }, + "type": { + "description": "Free text matching whatever the source literally shows in parentheses, e.g. string, int, any, or a union such as 'string | int'. Absent when the source shows no type at all.", + "type": "string", + "minLength": 1 + }, + "optional": { "type": "boolean" }, + "description": { + "description": "Absent when the source gives no separate elaboration beyond the name/value itself.", + "type": "string", + "minLength": 1 + } + } + }, + "command": { + "type": "object", + "additionalProperties": false, + "required": ["name", "kind", "visibility", "implementation", "summary", "description"], + "properties": { + "name": { "type": "string", "minLength": 1 }, + "kind": { + "description": "Task and user are the only two command kinds; there is no third 'child' category.", + "enum": ["task", "user"] + }, + "visibility": { + "description": "public: usable standalone; group-only: usable only inside --group (e.g. why, show); internal: never registered in CtsCmdRegistry (e.g. MoveCmd).", + "enum": ["public", "group-only", "internal"] + }, + "implementation": { "type": "string", "minLength": 1 }, + "summary": { "type": "string", "minLength": 1 }, + "description": { "$ref": "#/$defs/description" }, + "arguments": { + "type": "array", + "items": { "$ref": "#/$defs/argument" } + } + } + }, + "option": { + "type": "object", + "additionalProperties": false, + "required": ["name", "kind", "summary", "description"], + "properties": { + "name": { "type": "string", "minLength": 1 }, + "kind": { + "description": "global-option: registered once, applies regardless of command; command-option: registered locally by one command.", + "enum": ["global-option", "command-option"] + }, + "command": { + "description": "Required, and only meaningful, when kind is command-option: names the single owning command.", + "type": "string", + "minLength": 1 + }, + "summary": { "type": "string", "minLength": 1 }, + "description": { "$ref": "#/$defs/description" }, + "requires": { + "description": "Build-feature flag gating this option, e.g. ECF_OPENSSL.", + "type": "string", + "minLength": 1 + }, + "pairs_with": { + "description": "Name of another option that must be supplied alongside this one; the reference must be symmetric.", + "type": "string", + "minLength": 1 + } + }, + "if": { + "properties": { "kind": { "const": "command-option" } } + }, + "then": { + "required": ["command"] + }, + "else": { + "not": { "required": ["command"] } + } + }, + "environment_variable": { + "type": "object", + "additionalProperties": false, + "required": ["name", "type", "required", "applies_to", "description"], + "properties": { + "name": { "type": "string", "pattern": "^[A-Z][A-Z0-9_]*$" }, + "type": { "enum": ["string", "int", "any"] }, + "required": { "enum": ["mandatory", "optional"] }, + "applies_to": { + "description": "both: every command; task: task commands only, in addition to the both entries.", + "enum": ["both", "task"] + }, + "requires": { + "description": "Build-feature flag gating this variable, e.g. ECF_OPENSSL.", + "type": "string", + "minLength": 1 + }, + "overridable_by": { + "description": "Name of the global-option whose value can substitute for this variable (e.g. ECF_HOST is overridable_by host). Today's Help.cpp marks exactly these as mandatory*/optional*; absence means no CLI override exists.", + "type": "string", + "minLength": 1 + }, + "description": { "type": "string", "minLength": 1 } + } + }, + "topic": { + "type": "object", + "additionalProperties": false, + "required": ["name", "summary"], + "properties": { + "name": { "type": "string", "minLength": 1 }, + "summary": { "type": "string", "minLength": 1 } + } + } + } +} diff --git a/docs/client_api/update_internal.py b/docs/client_api/update_internal.py new file mode 100755 index 000000000..ee1eaa5ee --- /dev/null +++ b/docs/client_api/update_internal.py @@ -0,0 +1,169 @@ +#!/usr/bin/env python3 +""" +Refresh the ``Help`` blocks in command_internals.rst from help.json. + +``command_internals.rst`` is a hand-maintained developer reference. + +Each per-command section carries a ``.. tab:: Help`` block that reproduces the +``ecflow_client --help=`` text (without the common environment-variable +footer). That text is the one part that must stay in step with the help manifest; +this script rewrites it in place from docs/client_api/help.json, leaving the +surrounding prose, tables and the other tabs (Usage, Request, Reply) untouched. + +The update is content-only and markup-driven: command names are read from the +existing markup (the code-block's first line, or the panel ids of the HTML +selector), and only the rendered help text is replaced. Adding or removing a +command from a selector remains a manual edit. + +Two Help-block shapes are handled: + + * single-command sections use a ``.. code-block:: none`` block; + * multi-command sections use a ``.. raw:: html`` selector with one ``
``
+   panel per action.
+"""
+import html
+import pathlib
+import re
+import sys
+
+HERE = pathlib.Path(__file__).resolve().parent
+sys.path.insert(0, str(HERE))
+
+from build import join_description, load_manifest  # noqa: E402
+
+RST = HERE / "command_internals.rst"
+BODY_INDENT = " " * 12  # indentation of code-block / raw-html content
+
+
+def help_text(command):
+    """Render the ``--help=`` body: name banner plus the joined description."""
+    name = command["name"]
+    return f"{name}\n{'-' * len(name)}\n\n{join_description(command['description'])}"
+
+
+def render_code_block(command):
+    """Return the code-block body lines (single-command Help tab), indented for the source."""
+    return [BODY_INDENT + line if line else "" for line in help_text(command).split("\n")]
+
+
+def render_pre(open_tag, command):
+    """Return the ``
`` panel lines (multi-command Help tab), HTML-escaped and indented.
+
+    @param open_tag The verbatim ``            
`` opening (indent included).
+    """
+    escaped = html.escape(help_text(command), quote=True).split("\n")
+    lines = [open_tag + escaped[0]]
+    for line in escaped[1:-1]:
+        lines.append(BODY_INDENT + line if line else "")
+    last = escaped[-1]
+    lines.append((BODY_INDENT + last if last else "") + "
") + return lines + + +def indent_of(line): + return len(line) - len(line.lstrip(" ")) + + +def replace_help_block(lines, start, commands): + """Rewrite the Help tab that begins at ``lines[start]`` (``.. tab:: Help``). + + @param commands Mapping of command name to its manifest entry. + @return The index one past the rewritten block (unchanged length otherwise). + """ + # The block runs until the next tab (``.. tab::`` at 4 spaces) or a dedent out of the tab. + end = start + 1 + while end < len(lines): + stripped = lines[end].strip() + if stripped.startswith(".. tab::") and indent_of(lines[end]) == 4: + break + if stripped and indent_of(lines[end]) < 4: + break + end += 1 + + block = lines[start:end] + + raw_at = next((i for i, l in enumerate(block) if l.strip() == ".. raw:: html"), None) + code_at = next((i for i, l in enumerate(block) if l.strip() == ".. code-block:: none"), None) + + if raw_at is not None: + new_block = _rewrite_raw_html(block, raw_at, commands) + elif code_at is not None: + new_block = _rewrite_code_block(block, code_at, commands) + else: + raise ValueError(f"Help tab at line {start + 1} has neither a code-block nor a raw-html body") + + lines[start:end] = new_block + return start + len(new_block) + + +def _lookup(commands, name, where): + command = commands.get(name) + if command is None: + raise KeyError(f"{where}: '{name}' is not a command in help.json") + return command + + +def _rewrite_code_block(block, code_at, commands): + # Body is everything after the code-block directive and its blank line. + body_start = code_at + 1 + while body_start < len(block) and block[body_start].strip() == "": + body_start += 1 + body_end = len(block) + while body_end > body_start and block[body_end - 1].strip() == "": + body_end -= 1 + + name = next((l.strip() for l in block[body_start:body_end] if l.strip()), None) + if name is None: + raise ValueError("empty code-block Help body") + command = _lookup(commands, name, "code-block Help") + + head = block[:code_at + 1] + [""] + tail = block[body_end:] # trailing blank line(s) before the next tab + return head + render_code_block(command) + tail + + +_PRE_OPEN = re.compile(r'^(\s*
]*>)(.*)$')
+
+
+def _rewrite_raw_html(block, raw_at, commands):
+    out = list(block[:raw_at + 1])
+    i = raw_at + 1
+    while i < len(block):
+        line = block[i]
+        m = _PRE_OPEN.match(line)
+        if not m:
+            out.append(line)
+            i += 1
+            continue
+        open_tag, first = m.group(1), m.group(2)
+        name = html.unescape(first).strip()
+        # consume the panel body up to and including the line closing 
+ j = i + while "
" not in block[j]: + j += 1 + command = _lookup(commands, name, "raw-html panel") + out.extend(render_pre(open_tag, command)) + i = j + 1 + return out + + +def main(): + manifest = load_manifest() + commands = {c["name"]: c for c in manifest["commands"]} + + lines = RST.read_text(encoding="utf-8").split("\n") + updated = 0 + i = 0 + while i < len(lines): + if lines[i].strip() == ".. tab:: Help": + i = replace_help_block(lines, i, commands) + updated += 1 + else: + i += 1 + + RST.write_text("\n".join(lines), encoding="utf-8") + print(f"{RST.name}: refreshed {updated} Help tab(s) from help.json") + + +if __name__ == "__main__": + main() diff --git a/docs/client_api/validate_help_manifest.py b/docs/client_api/validate_help_manifest.py new file mode 100755 index 000000000..fc7d1605a --- /dev/null +++ b/docs/client_api/validate_help_manifest.py @@ -0,0 +1,130 @@ +#!/usr/bin/env python3 +"""Validate docs/client_api/help.json against docs/client_api/help.schema.json. + +Beyond JSON Schema conformance, this also checks the cross-referential rules that a +schema alone cannot express: pairs_with symmetry, command-option -> command +linkage, and name uniqueness within each array. +""" +import argparse +import json +import pathlib +import sys + +import jsonschema + + +def load_json(path: pathlib.Path): + with path.open("r", encoding="utf-8") as handle: + return json.load(handle) + + +def check_schema(manifest, schema): + validator_cls = jsonschema.validators.validator_for(schema) + validator_cls.check_schema(schema) + validator = validator_cls(schema) + return sorted(validator.iter_errors(manifest), key=lambda error: list(map(str, error.path))) + + +def duplicates(values): + seen = set() + dupes = set() + for value in values: + if value in seen: + dupes.add(value) + seen.add(value) + return dupes + + +def check_cross_references(manifest): + problems = [] + + command_names = [command["name"] for command in manifest.get("commands", [])] + if dupes := duplicates(command_names): + problems.append(f"duplicate command name(s): {', '.join(sorted(dupes))}") + + option_names = [option["name"] for option in manifest.get("options", [])] + if dupes := duplicates(option_names): + problems.append(f"duplicate option name(s): {', '.join(sorted(dupes))}") + + env_var_names = [variable["name"] for variable in manifest.get("environment_variables", [])] + if dupes := duplicates(env_var_names): + problems.append(f"duplicate environment variable name(s): {', '.join(sorted(dupes))}") + + topic_names = [topic["name"] for topic in manifest.get("topics", [])] + if dupes := duplicates(topic_names): + problems.append(f"duplicate topic name(s): {', '.join(sorted(dupes))}") + + known_commands = set(command_names) + known_options = set(option_names) + pairs_with = {} + for option in manifest.get("options", []): + if option.get("kind") == "command-option": + owner = option.get("command") + if owner not in known_commands: + problems.append(f"option '{option['name']}' names unknown command '{owner}'") + if "pairs_with" in option: + pairs_with[option["name"]] = option["pairs_with"] + + for name, partner in pairs_with.items(): + if partner not in known_options: + problems.append(f"option '{name}' pairs_with unknown option '{partner}'") + elif pairs_with.get(partner) != name: + problems.append(f"option '{name}' pairs_with '{partner}', which does not pair back") + + global_options = { + option["name"] for option in manifest.get("options", []) if option.get("kind") == "global-option" + } + for variable in manifest.get("environment_variables", []): + overridable_by = variable.get("overridable_by") + if overridable_by is not None and overridable_by not in global_options: + problems.append( + f"environment variable '{variable['name']}' overridable_by unknown global-option '{overridable_by}'" + ) + + # A description holds one line per array element; no element may contain an embedded newline. + for kind, entries in (("command", manifest.get("commands", [])), ("option", manifest.get("options", []))): + for entry in entries: + for index, line in enumerate(entry.get("description", [])): + if "\n" in line: + problems.append(f"{kind} '{entry['name']}' description line {index} contains an embedded newline") + + return problems + + +def main(): + here = pathlib.Path(__file__).resolve().parent + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--manifest", type=pathlib.Path, default=here / "help.json") + parser.add_argument("--schema", type=pathlib.Path, default=here / "help.schema.json") + args = parser.parse_args() + + schema = load_json(args.schema) + manifest = load_json(args.manifest) + + schema_errors = check_schema(manifest, schema) + if schema_errors: + for error in schema_errors: + location = "/".join(str(part) for part in error.path) or "" + print(f"{args.manifest}: {location}: {error.message}", file=sys.stderr) + print(f"{len(schema_errors)} schema violation(s) found.", file=sys.stderr) + return 1 + + reference_problems = check_cross_references(manifest) + if reference_problems: + for problem in reference_problems: + print(f"{args.manifest}: {problem}", file=sys.stderr) + print(f"{len(reference_problems)} cross-reference problem(s) found.", file=sys.stderr) + return 1 + + print( + f"{args.manifest}: valid " + f"({len(manifest.get('commands', []))} commands, " + f"{len(manifest.get('options', []))} options, " + f"{len(manifest.get('environment_variables', []))} environment variables, " + f"{len(manifest.get('topics', []))} topics)" + ) + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/docs/glossary.rst b/docs/glossary.rst index bc8c5b5a9..2eb3d9d36 100644 --- a/docs/glossary.rst +++ b/docs/glossary.rst @@ -12,14 +12,14 @@ Glossary aborted Is a :term:`node` :term:`status`. - When the :term:`ECF_JOB_CMD` fails or the :term:`job file` sends a :term:`ecflow_client` --abort :term:`child command`, then + When the :term:`ECF_JOB_CMD` fails or the :term:`job file` sends a :term:`ecflow_client` --abort :term:`task command`, then the task is placed into a aborted state. active Is a :term:`node` :term:`status`. If :term:`job creation` was successful, and :term:`job file` has started, then the :term:`ecflow_client` --init - :term:`child command` is received by the :term:`ecflow_server` and the :term:`task` is placed into a active state + :term:`task command` is received by the :term:`ecflow_server` and the :term:`task` is placed into a active state autocancel autocancel is a way to automatically delete a :term:`node` which has completed. @@ -135,44 +135,10 @@ Glossary .. code-block:: shell ecflow_client --load= print check_only - - child command - Child (or Task) commands are called from within the :term:`ecf script` files. The table also includes the default action (from version 4.0.4) if the child command is part of a zombie. 'block' means the job will be held by the :term:`ecflow_client` command. Until time out, or manual/automatic intervention. - .. list-table:: - :header-rows: 1 - - * - Child (or Task) Command - - Description - - Zombie (default action) - * - :ref:`ecflow_client --init ` - - Sets the :term:`task` to the :term:`active` :term:`status` - - block - * - :ref:`ecflow_client --wait ` - - Wait for a expression to evaluate - - block - * - :ref:`ecflow_client --queue ` - - Update :term:`queue` step in server - - block - * - :ref:`ecflow_client --abort ` - - Sets the :term:`task` to the :term:`aborted` :term:`status` - - block - * - :ref:`ecflow_client --complete ` - - Sets the :term:`task` to the :term:`complete` :term:`status` - - block - * - :ref:`ecflow_client --event ` - - Set an :term:`event` - - fob - * - :ref:`ecflow_client --meter ` - - Change a :term:`meter` - - fob - * - :ref:`ecflow_client --label ` - - Change a :term:`label` - - fob + child command + See :term:`task command`. - The following environment variables must be set for the child commands. ECF_HOST, :term:`ECF_NAME` , :term:`ECF_PASS` and ECF_RID. See :term:`ecflow_client`. - - clock A clock is an attribute of a :term:`suite`. @@ -231,7 +197,7 @@ Glossary The node can be set to complete: - By the :term:`complete expression` - - At job end when the :term:`task` receives the :ref:`ecflow_client –complete ` :term:`child command` + - At job end when the :term:`task` receives the :ref:`ecflow_client –complete ` :term:`task command` - Manually via the command line or GUI. When this happens any time attributes are expired in order. @@ -336,7 +302,7 @@ Glossary A :term:`task` that is dependent cannot be started as long as some dependency is holding it or any of its **parent** :term:`node` s. The :term:`ecflow_server` will check the dependencies every minute, during normal :term:`scheduling` **and** when any - :term:`child command` causes a state change in the :term:`suite definition`. + :term:`task command` causes a state change in the :term:`suite definition`. directives Directives appear in a ecf script. (i.e. typically .ecf file, but could be .py file).Directives start with a % character. This is referred to as :term:`ECF_MICRO` character. @@ -479,7 +445,7 @@ Glossary The ecFlow script is similar to a UNIX shell script. The differences, however, includes the addition of “C” like pre-processing :term:`directives` and ecFlow :term:`variable`\ s. - Also the script *must* include calls to the **init** and **complete** :term:`child command`\ s so that + Also the script *must* include calls to the **init** and **complete** :term:`task command`\ s so that the :term:`ecflow_server` is aware when the job starts (i.e changes state to :term:`active`) and finishes (i.e changes state to :term:`complete`) ECF_BASENAME @@ -539,7 +505,7 @@ Glossary .. important:: - This configuration is, by default, only applicable to :term:`child commands `. + This configuration is, by default, only applicable to :term:`task commands `. But the behaviour can be customised by setting the environment variable ECF_HOSTFILE_POLICY. ECF_INCLUDE @@ -699,7 +665,7 @@ Glossary The user must ensure that all the directories exists, including :term:`suite`/:term:`family`. If this is not done, you may well find task remains stuck in a submitted state. At ECMWF our submission scripts will ensure that directories exists. ECF_PASS - This is a generated :term:`variable`. During job generation process in the server, a unique password is generated and stored in the task. It then replaces %ECF_PASS% in the scripts(.ecf), with the actual value. When the job runs, ecflow_client reads this, as an environment variable, and passes it to the server. The server then compares this password with the one held on the task. This is used as a part of the authentication for child commands, and is used to detect zombies. + This is a generated :term:`variable`. During job generation process in the server, a unique password is generated and stored in the task. It then replaces %ECF_PASS% in the scripts(.ecf), with the actual value. When the job runs, ecflow_client reads this, as an environment variable, and passes it to the server. The server then compares this password with the one held on the task. This is used as a part of the authentication for task commands, and is used to detect zombies. The authentication process can be bypassed, and allow the job to proceed (i.e.. when the user is sure that there is only a single process, trying to communicate with the server), by adding it as a user variable. i.e.: @@ -776,11 +742,11 @@ Glossary ECF_TIMEOUT ECF_TIMEOUT is an environment variable, used by the :term:`ecflow_client`, and specifies how much time - (measured in seconds) a *child command* will continue to try contacting the server(s) before giving up. + (measured in seconds) a *task command* will continue to try contacting the server(s) before giving up. .. important:: - This timeout is only applicable to :term:`child commands `. + This timeout is only applicable to :term:`task commands `. When ECF_TIMEOUT is not set, the client uses the default value of 24 hours (24 * 60 * 60 seconds). The minimum value allowed is 60 seconds, while the maximum value is 24 hours. @@ -825,13 +791,13 @@ Glossary ECF_ZOMBIE_TIMEOUT ECF_ZOMBIE_TIMEOUT is an environment variable used by the :term:`ecflow_client`, and that specifies how much time - (measured in seconds) a *zombie* child command will continue to try contacting the server(s) before giving up. + (measured in seconds) a *zombie* task command will continue to try contacting the server(s) before giving up. .. important:: - This timeout is only applicable to :term:`child commands `. + This timeout is only applicable to :term:`task commands `. - A child command knows that the related :term:`task` has been deemed a :term:`zombie`, because the reply from + A task command knows that the related :term:`task` has been deemed a :term:`zombie`, because the reply from the server includes a *zombie* flag. When the client receives this flag, it will used ECF_ZOMBIE_TIMEOUT (instead of ECF_TIMEOUT). The ECF_ZOMBIE_TIMEOUT, in combination with the attempt to contact all hosts in ECF_HOSTFILE, allows a :term:`zombie` @@ -864,7 +830,7 @@ Glossary The following variables affect the execution of ecflow_client. - Since the :term:`ecf script` can call ecflow_client(i.e :term:`child command`) then typically + Since the :term:`ecf script` can call ``ecflow_client`` (i.e :term:`task command`) then typically some are set in an include header. .. list-table:: Environment variables used by *ecflow_client* @@ -876,7 +842,7 @@ Glossary * - ECF_HOST - (User + Child) + (User + Task) - The name of the :term:`ecflow_server` host | @@ -892,7 +858,7 @@ Glossary * - ECF_PORT - (User + Child) + (User + Task) - The port on the :term:`ecflow_server` host | @@ -908,7 +874,7 @@ Glossary * - NO_ECF - (User + Child) + (User + Task) - Speficies if client terminates immediately with success (to allow the scripts to be tested independently from the server) @@ -922,7 +888,7 @@ Glossary * - ECF_DENIED - (User + Child) + (User + Task) - Specifies if client terminates with error when the server denies contacts, in order to avoid waiting for ECF_TIMEOUT. @@ -937,7 +903,7 @@ Glossary * - ECF_SSL - (User + Child) + (User + Task) - Enables secure communication between server and client. | @@ -968,12 +934,12 @@ Glossary * - :term:`ECF_NAME` - (Child only) + (Task only) - The path to the task | - **This variable is mandatory for child commands** + **This variable is mandatory for task commands** | @@ -985,12 +951,12 @@ Glossary * - :term:`ECF_PASS` - (Child only) + (Task only) - The password of the task. | - **This variable is mandatory for child commands** + **This variable is mandatory for task commands** | @@ -998,12 +964,12 @@ Glossary * - ECF_RID - (Child only) + (Task only) - The remote id of the task. | - **This variable is mandatory for child commands** + **This variable is mandatory for task commands** | @@ -1011,7 +977,7 @@ Glossary * - :term:`ECF_TRYNO` - (Child only) + (Task only) - The number of times the task has run. | @@ -1020,7 +986,7 @@ Glossary * - ECF_HOSTFILE - (User + Child) + (User + Task) - The path to the file that lists alternate hosts to try, if connection to main host fails | @@ -1031,9 +997,9 @@ Glossary * - ECF_HOSTFILE_POLICY - (User + Child) + (User + Task) - The policy, either "task" or "all", indicates when to perform retry based on the ECF_HOSTFILE. - The default policy is "task", meaning that the retry will only be performed for task (i.e. child) commands. + The default policy is "task", meaning that the retry will only be performed for task commands. If the policy is "all", the retry will be performed for both task and user commands (including :code:`ping`). | @@ -1048,12 +1014,12 @@ Glossary * - ECF_TIMEOUT - (Child only) + (Task only) - Maximum time (in seconds) for the client to perform operations (e.g. :code:`init`, :code:`abort`, :code:`complete`) with the server before giving up. | - This limit applies to all child commands, and user commands that have ECF_HOSTFILE_POLICY set to "all". When the client is unable to contact the server within the timeout period, it will exit with an error code. + This limit applies to all task commands, and user commands that have ECF_HOSTFILE_POLICY set to "all". When the client is unable to contact the server within the timeout period, it will exit with an error code. | @@ -1068,7 +1034,7 @@ Glossary * - ECF_CONNECT_TIMEOUT - (Child only) + (Task only) - Maximum time (in seconds) for the client to establish connection with the server before giving up. | @@ -1077,12 +1043,12 @@ Glossary * - ECF_ZOMBIE_TIMEOUT - (Child only) + (Task only) - Maximum time (in seconds) for the client to perform operations (e.g. :code:`init`, :code:`abort`, :code:`complete`) to get a reply from the server. | - This limit applies to child commands that have been marked as zombies by the server. When the client is unable to contact the server within the timeout period, it will exit with an error code. + This limit applies to task commands that have been marked as zombies by the server. When the client is unable to contact the server within the timeout period, it will exit with an error code. | @@ -1291,7 +1257,7 @@ Glossary There can be many events and they are displayed as nodes. - The event is updated by placing the ``--event`` :term:`child command` in a :term:`ecf script`. + The event is updated by placing the ``--event`` :term:`task command` in a :term:`ecf script`. An event has a number and possibly a name. If it is only defined as a number, its name is the text representation of the number without leading zeroes. @@ -1451,7 +1417,7 @@ Glossary The steps above transforms an :term:`ecf script` to a :term:`job file` that can be submitted by performing :term:`variable substitution` on the :term:`ECF_JOB_CMD` :term:`variable` and invoking the command. - The running jobs will communicate back to the :term:`ecflow_server` by calling :term:`child command`\ s. + The running jobs will communicate back to the :term:`ecflow_server` by calling :term:`task command`\ s. This causes :term:`status` changes on the :term:`node`\ s in the :term:`ecflow_server` and flags can be set to indicate various events. @@ -1479,7 +1445,7 @@ Glossary label A label has a name and a value and is a way of **displaying** information in :term:`ecflow_ui` - By placing a label :term:`child command`\ s in the :term:`ecf script` the user can be informed about progress + By placing a label :term:`task command`\ s in the :term:`ecf script` the user can be informed about progress in :term:`ecflow_ui`. Labels can be added to family nodes. To change the labels, scripts should use: @@ -1488,7 +1454,7 @@ Glossary ecflow_client --alter change label /path/to/family_node/with/label - If the label :term:`child command`\ s results in a :term:`zombie` then the default action if for the server to **fob**, this allows the ecflow_client command to exit normally. (i.e. without any errors). This default can be overridden by using a :term:`zombie` attribute. + If the label :term:`task command`\ s results in a :term:`zombie` then the default action if for the server to **fob**, this allows the ecflow_client command to exit normally. (i.e. without any errors). This default can be overridden by using a :term:`zombie` attribute. .. list-table:: @@ -1535,7 +1501,7 @@ Glossary suite late late -s +00:15 # report late for all task taking longer than 15 minutes in submitted state family familyName - late -c +02:00 # all child task that take longer than 2 hours to complete should raise a late flag + late -c +02:00 # all tasks that take longer than 2 hours to complete should raise a late flag task t1 # effective late -s +00:05 -c +02:00 late -s +00:05 @@ -1648,13 +1614,13 @@ Glossary The purpose of a meter is to signal proportional completion of a task and to be able to trigger another job which is waiting on this proportional completion. - The meter is updated by placing the --meter :term:`child command` in a :term:`ecf script`. Meters can be added to family nodes. To change the meters, in the scripts should use: + The meter is updated by placing the --meter :term:`task command` in a :term:`ecf script`. Meters can be added to family nodes. To change the meters, in the scripts should use: .. code-block:: shell ecflow_client --alter change meter /path/to/family_node/with/meter - If the meter :term:`child command` results in a zombie, then the default action if for the server to **fob**, this allows the ecflow_client command to exit normally (i.e. without any errors). This default can be overridden by using a zombie attribute. + If the meter :term:`task command` results in a zombie, then the default action if for the server to **fob**, this allows the ecflow_client command to exit normally (i.e. without any errors). This default can be overridden by using a zombie attribute. See also: @@ -1803,11 +1769,11 @@ Glossary endfamily endsuite - The :ref:`queue_cli` :term:`child command` will signal when a step is active, complete, or has aborted: + The :ref:`queue_cli` :term:`task command` will signal when a step is active, complete, or has aborted: .. code-block:: shell - # Note: because --queue is treated like a child command(init,complete,event,label,meter,abort,wait), the task path ECF_NAME is read from the environment + # Note: because --queue is treated like a task command(init,complete,event,label,meter,abort,wait), the task path ECF_NAME is read from the environment # The --queue command will search up the node hierarchy for the queue name. If not found it fails. @@ -2003,7 +1969,45 @@ Glossary - :py:class:`ecflow.Task` * - :ref:`grammar` - :token:`task` - + + task command + Task commands (sometimes called Child commands) are executed within the :term:`ecf script` files. + The table also includes the default action (from version 4.0.4) if the task command is part of a zombie. + 'block' means the job will be held by the :term:`ecflow_client` command. Until time out, or manual/automatic intervention. + + .. list-table:: + :header-rows: 1 + + * - Task Command + - Description + - Zombie (default action) + * - :ref:`ecflow_client --init ` + - Sets the :term:`task` to the :term:`active` :term:`status` + - block + * - :ref:`ecflow_client --wait ` + - Wait for a expression to evaluate + - block + * - :ref:`ecflow_client --queue ` + - Update :term:`queue` step in server + - block + * - :ref:`ecflow_client --abort ` + - Sets the :term:`task` to the :term:`aborted` :term:`status` + - block + * - :ref:`ecflow_client --complete ` + - Sets the :term:`task` to the :term:`complete` :term:`status` + - block + * - :ref:`ecflow_client --event ` + - Set an :term:`event` + - fob + * - :ref:`ecflow_client --meter ` + - Change a :term:`meter` + - fob + * - :ref:`ecflow_client --label ` + - Change a :term:`label` + - fob + + The following environment variables must be set for the task commands. ECF_HOST, :term:`ECF_NAME` , :term:`ECF_PASS` and ECF_RID. See :term:`ecflow_client`. + time This defines a time dependency for a node. @@ -2113,7 +2117,7 @@ Glossary A node with a trigger can only be activated when its trigger has expired. A trigger holds the node as long as the trigger expression evaluation returns false. - Trigger evaluation occurs when ever the :term:`child command` communicates with the server. i.e whenever + Trigger evaluation occurs when ever the :term:`task command` communicates with the server. i.e whenever there is a state change in the suite definition. The keywords in trigger expressions are: :term:`unknown`, :term:`suspended`, :term:`complete`, :term:`queued`, :term:`submitted`, :term:`active`, :term:`aborted` @@ -2213,7 +2217,7 @@ Glossary This is the default :term:`node` :term:`status` when a :term:`suite definition` is loaded into the :term:`ecflow_server` user command - User commands are any client to server requests that are **not** :term:`child command`\ s. + User commands are any client to server requests that are **not** :term:`task command`\ s. variable ecFlow makes heavy use of different kinds of variables.There are several kinds of variables: @@ -2288,22 +2292,22 @@ Glossary zombie Zombies are running jobs that fail authentication when communicating with the :term:`ecflow_server` - :term:`child command`\ s like (init, event,meter, label, abort,complete) are placed in the :term:`ecf script` + :term:`task command`\ s like (init, event,meter, label, abort,complete) are placed in the :term:`ecf script` file and are used to communicate with the :term:`ecflow_server`. - The :term:`ecflow_server` authenticates each connection attempt made by the :term:`child command`. + The :term:`ecflow_server` authenticates each connection attempt made by the :term:`task command`. Authentication can fail for a number of reasons: - - password(ECF_PASS) supplied with the :term:`child command`, does not match the one in the :term:`ecflow_server` - - path name(ECF_NAME) supplied with the :term:`child command`, does not locate a :term:`task` in the :term:`ecflow_server` - - process id(ECF_RID) supplied with :term:`child command`, does not correspond with the one stored in the :term:`ecflow_server` - - :term:`task` is already :term:`active`, but receives another init :term:`child command` - - :term:`task` is already :term:`complete`, but receives another :term:`child command` - - :term:`task` is already :term:`aborted`, but receives another :term:`child command` + - password(ECF_PASS) supplied with the :term:`task command`, does not match the one in the :term:`ecflow_server` + - path name(ECF_NAME) supplied with the :term:`task command`, does not locate a :term:`task` in the :term:`ecflow_server` + - process id(ECF_RID) supplied with :term:`task command`, does not correspond with the one stored in the :term:`ecflow_server` + - :term:`task` is already :term:`active`, but receives another init :term:`task command` + - :term:`task` is already :term:`complete`, but receives another :term:`task command` + - :term:`task` is already :term:`aborted`, but receives another :term:`task command` When authentication fails the job is considered to be a zombie. The :term:`ecflow_server` will keep a note of the zombie for a period of time, before it is automatically removed. - However the removed zombie, may well re-appear. (this is because each :term:`child command` will continue + However the removed zombie, may well re-appear. (this is because each :term:`task command` will continue attempting to contact the :term:`ecflow_server` for 24 hours. This is configurable see ECF_TIMEOUT on :term:`ecflow_client`) @@ -2322,7 +2326,7 @@ Glossary The zombie attribute defines how a :term:`zombie` should be handled in an automated fashion. Very careful consideration should be taken before this attribute is added as it may hide a genuine problem. It can be added to any :term:`node`. But is best defined at the :term:`suite` or :term:`family` level. - If there is no zombie attribute the default behaviour is to block the :term:`child command`. + If there is no zombie attribute the default behaviour is to block the :term:`task command`. To add a zombie attribute in python, please see: :py:class:`ecflow.ZombieAttr` @@ -2349,7 +2353,7 @@ Glossary * **ecf**: Jobs are created as part of the normal :term:`scheduling` - - Two init commands or task complete or aborted but receives another :term:`child command` + - Two init commands or task complete or aborted but receives another :term:`task command` - Server crashed (or terminated and restarted) and the recovered :term:`check point` file is out of date. - A :term:`task` is repeatedly re-run, earlier copies will not be remembered. - Job sent by another :term:`ecflow_server`, but which cannot talk to the original :term:`ecflow_server` diff --git a/docs/requirements.txt b/docs/requirements.txt index 9edf44eab..f2dfdebcd 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -6,3 +6,4 @@ sphinx-rtd-theme>=3.0.0 sphinx-tabs>=3.4.0 sphinx-copybutton>=0.5.0 pyyaml>=6.0 +jsonschema>=4.18 diff --git a/libs/CMakeLists.txt b/libs/CMakeLists.txt index 1c74fce37..57bc4651e 100644 --- a/libs/CMakeLists.txt +++ b/libs/CMakeLists.txt @@ -15,6 +15,25 @@ configure_file( core/src/ecflow/core/ecflow_source_build_dir.h.in ${CMAKE_BINARY_DIR}/generated/src/ecflow/core/ecflow_source_build_dir.h) +# +# Embed the CLI help manifest (docs/client_api/help.json) as a C++ header. +# +# A build-time custom command is used instead of configure_file(), so the +# header regenerates whenever the manifest changes, without requiring a full +# CMake reconfigure. +# +set(ecflow_client_help_manifest ${ecflow_SOURCE_DIR}/docs/client_api/help.json) +set(ecflow_client_help_generated_header + ${CMAKE_BINARY_DIR}/generated/src/ecflow/base/generated_client_help.hpp) +add_custom_command( + OUTPUT ${ecflow_client_help_generated_header} + COMMAND ${CMAKE_COMMAND} + -DMANIFEST=${ecflow_client_help_manifest} + -DOUTPUT=${ecflow_client_help_generated_header} + -P ${ecflow_SOURCE_DIR}/cmake/GenerateClientHelp.cmake + DEPENDS ${ecflow_client_help_manifest} ${ecflow_SOURCE_DIR}/cmake/GenerateClientHelp.cmake + COMMENT "Generating embedded CLI help manifest header from docs/client_api/help.json") + set(srcs # Attribute -- Headers attribute/src/ecflow/attribute/AutoArchiveAttr.hpp @@ -66,7 +85,9 @@ set(srcs base/src/ecflow/base/ClientToServerRequest.hpp base/src/ecflow/base/Cmd.hpp base/src/ecflow/base/Connection.hpp + ${ecflow_client_help_generated_header} base/src/ecflow/base/Gnuplot.hpp + base/src/ecflow/base/HelpCatalog.hpp base/src/ecflow/base/HttpClient.hpp base/src/ecflow/base/Identification.hpp $<$:base/src/ecflow/base/Openssl.hpp> @@ -147,6 +168,7 @@ set(srcs base/src/ecflow/base/ClientToServerRequest.cpp base/src/ecflow/base/Connection.cpp base/src/ecflow/base/Gnuplot.cpp + base/src/ecflow/base/HelpCatalog.cpp base/src/ecflow/base/HttpClient.cpp base/src/ecflow/base/Identification.cpp base/src/ecflow/base/ServerReply.cpp diff --git a/libs/base/CMakeLists.txt b/libs/base/CMakeLists.txt index 2d44467b3..8e41781f3 100644 --- a/libs/base/CMakeLists.txt +++ b/libs/base/CMakeLists.txt @@ -21,6 +21,7 @@ set(test_srcs test/TestDeleteNodeCmd.cpp test/TestForceCmd.cpp test/TestFreeDepCmd.cpp + test/TestHelpCatalog.cpp test/TestInLimitAndLimit.cpp test/TestInLimitParsing.cpp test/TestLogCmd.cpp diff --git a/libs/base/src/ecflow/base/HelpCatalog.cpp b/libs/base/src/ecflow/base/HelpCatalog.cpp new file mode 100644 index 000000000..c598be904 --- /dev/null +++ b/libs/base/src/ecflow/base/HelpCatalog.cpp @@ -0,0 +1,77 @@ +/* + * Copyright 2009- ECMWF. + * + * This software is licensed under the terms of the Apache Licence version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation + * nor does it submit to any jurisdiction. + */ + +#include "ecflow/base/HelpCatalog.hpp" + +#include "ecflow/base/generated_client_help.hpp" + +namespace ecf { + +const std::string HelpCatalog::not_provided = "No description available"; + +const nlohmann::json& HelpCatalog::manifest() { + static const nlohmann::json instance = nlohmann::json::parse(client_help_json); + return instance; +} + +const nlohmann::json* HelpCatalog::find_command(const std::string& name) { + return find_by_name(manifest().at("commands"), name); +} + +const nlohmann::json* HelpCatalog::find_option(const std::string& name) { + return find_by_name(manifest().at("options"), name); +} + +const nlohmann::json* HelpCatalog::find_topic(const std::string& name) { + return find_by_name(manifest().at("topics"), name); +} + +std::optional HelpCatalog::summary_for(const std::string& name) { + if (const nlohmann::json* entry = entry_for(name)) { + return entry->at("summary").get(); + } + return std::nullopt; +} + +std::optional HelpCatalog::description_for(const std::string& name) { + const nlohmann::json* entry = entry_for(name); + if (!entry) { + return std::nullopt; + } + + std::string text; + bool first = true; + for (const auto& line : entry->at("description")) { + if (!first) { + text += "\n"; + } + first = false; + text += line.get(); + } + return text; +} + +const nlohmann::json* HelpCatalog::find_by_name(const nlohmann::json& array, const std::string& name) { + for (const auto& entry : array) { + if (entry.at("name") == name) { + return &entry; + } + } + return nullptr; +} + +const nlohmann::json* HelpCatalog::entry_for(const std::string& name) { + if (const nlohmann::json* command = find_command(name)) { + return command; + } + return find_option(name); +} + +} // namespace ecf diff --git a/libs/base/src/ecflow/base/HelpCatalog.hpp b/libs/base/src/ecflow/base/HelpCatalog.hpp new file mode 100644 index 000000000..110b33996 --- /dev/null +++ b/libs/base/src/ecflow/base/HelpCatalog.hpp @@ -0,0 +1,118 @@ +/* + * 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_base_HelpCatalog_HPP +#define ecflow_base_HelpCatalog_HPP + +#include +#include + +#include + +namespace ecf { + +/// +/// @brief Provides lazy, cached access to the embedded CLI help manifest. +/// +/// Allows access to the manifest (docs/client_api/help.json). +/// This is embedded at build time into `generated_client_help.hpp` and is parsed +/// only on first use and cached for the remaining lifetime of the process. +/// +/// The lazy nature of the load matters because ecflow_client is invoked by a task, +/// potentially thousands of times over a suite run; only a small fraction of those +/// invocations ever request help, and the rest must not pay a JSON-parsing cost. +/// +class HelpCatalog { +public: + HelpCatalog() = delete; + HelpCatalog(const HelpCatalog&) = delete; + HelpCatalog& operator=(const HelpCatalog&) = delete; + + /// + /// @brief Returns the parsed manifest, parsing it once on the first call. + /// + /// @return Reference to the manifest, valid for the remaining lifetime of the process. + /// @throws nlohmann::json::parse_error if the embedded manifest is malformed. + /// + static const nlohmann::json& manifest(); + + /// + /// @brief Finds a command entry by exact name. + /// + /// @param name Exact command name to look up, e.g. "abort". + /// @return Pointer to the matching manifest entry, valid for the remaining lifetime + /// of the process, or nullptr if no command has this name. + /// + static const nlohmann::json* find_command(const std::string& name); + + /// + /// @brief Finds an option entry by exact name. + /// + /// @param name Exact option name to look up, e.g. "host". + /// @return Pointer to the matching manifest entry, valid for the remaining lifetime + /// of the process, or nullptr if no option has this name. + /// + static const nlohmann::json* find_option(const std::string& name); + + /// + /// @brief Finds a topic entry by exact name. + /// + /// @param name Exact topic name to look up, e.g. "summary". + /// @return Pointer to the matching manifest entry, valid for the remaining lifetime + /// of the process, or nullptr if no topic has this name. + /// + static const nlohmann::json* find_topic(const std::string& name); + + /// + /// @brief Returns the one-line summary registered for a command or option. + /// + /// Checks commands first, then options, matching @ref find_command and + /// @ref find_option order. Returned by value so that callers never need to + /// see the underlying nlohmann::json representation. + /// + /// @param name Exact command or option name to look up, e.g. "abort". + /// @return The entry's "summary" field, or std::nullopt if @p name matches + /// neither a command nor an option in the manifest. + /// + static std::optional summary_for(const std::string& name); + + /// + /// @brief Returns the full description text registered for a command or option. + /// + /// Checks commands first, then options, matching @ref find_command and + /// @ref find_option order. The entry's "description" array holds one line + /// per element (a blank line is an empty string); the lines are joined with + /// a newline, reconstructing the original text verbatim, preformatted + /// content included. + /// + /// @param name Exact command or option name to look up, e.g. "abort". + /// @return The reconstructed description text, or std::nullopt if @p name + /// matches neither a command nor an option in the manifest. + /// + static std::optional description_for(const std::string& name); + + /// + /// @brief Placeholder text for a command or option with no manifest entry. + /// + /// Rendered in place of a description whenever @ref summary_for or + /// @ref description_for returns std::nullopt, instead of falling back to a + /// second, independent copy of the text (e.g. a C++ literal that could drift + /// from the manifest). + /// + static const std::string not_provided; + +private: + static const nlohmann::json* find_by_name(const nlohmann::json& array, const std::string& name); + static const nlohmann::json* entry_for(const std::string& name); +}; + +} // namespace ecf + +#endif /* ecflow_base_HelpCatalog_HPP */ diff --git a/libs/base/src/ecflow/base/cts/CtsCmdRegistry.cpp b/libs/base/src/ecflow/base/cts/CtsCmdRegistry.cpp index 49d27f15b..5dbd7df69 100644 --- a/libs/base/src/ecflow/base/cts/CtsCmdRegistry.cpp +++ b/libs/base/src/ecflow/base/cts/CtsCmdRegistry.cpp @@ -178,10 +178,7 @@ void CtsCmdRegistry::addCmdOptions(boost::program_options::options_description& void CtsCmdRegistry::addHelpOption(boost::program_options::options_description& desc) const { /////////1/////////2/////////3/////////4/////////5/////////6/////////7/////////8 - desc.add_options()( - "help,h", boost::program_options::value()->implicit_value(std::string{}), "Produce help message"); - desc.add_options()("version,v", "Show ecflow client version number, and version of the boost library used"); - desc.add_options()("debug,d", - "Enables the display of client environment settings and execution details.\n" - "Has the same effect as setting environment variable ECF_DEBUG_CLIENT."); + desc.add_options()("help,h", boost::program_options::value()->implicit_value(std::string{})); + desc.add_options()("version,v", ""); + desc.add_options()("debug,d", ""); } diff --git a/libs/base/src/ecflow/base/cts/task/AbortCmd.cpp b/libs/base/src/ecflow/base/cts/task/AbortCmd.cpp index e127324b7..e28031bc4 100644 --- a/libs/base/src/ecflow/base/cts/task/AbortCmd.cpp +++ b/libs/base/src/ecflow/base/cts/task/AbortCmd.cpp @@ -98,21 +98,9 @@ STC_Cmd_ptr AbortCmd::doHandleRequest(AbstractServer* as) const { const char* AbortCmd::arg() { return TaskApi::abortArg(); } -const char* AbortCmd::desc() { - return "Mark task as aborted. For use in the '.ecf' script file *only*\n" - "Hence the context is supplied via environment variables\n" - " arg1 = (optional) string(reason)\n" - " Optionally provide a reason why the abort was raised\n\n" - "If this child command is a zombie, then the default action will be to *block*.\n" - "The default can be overridden by using zombie attributes.\n" - "Otherwise the blocking period is defined by ECF_TIMEOUT.\n\n" - "Usage:\n" - " ecflow_client --abort=reasonX"; -} void AbortCmd::addOption(boost::program_options::options_description& desc) const { - desc.add_options()( - AbortCmd::arg(), boost::program_options::value()->implicit_value(std::string{}), AbortCmd::desc()); + desc.add_options()(AbortCmd::arg(), boost::program_options::value()->implicit_value(std::string{})); } void AbortCmd::create(Cmd_ptr& cmd, boost::program_options::variables_map& vm, AbstractClientEnv* clientEnv) const { std::string reason = vm[arg()].as(); diff --git a/libs/base/src/ecflow/base/cts/task/AbortCmd.hpp b/libs/base/src/ecflow/base/cts/task/AbortCmd.hpp index a24c33968..50306964f 100644 --- a/libs/base/src/ecflow/base/cts/task/AbortCmd.hpp +++ b/libs/base/src/ecflow/base/cts/task/AbortCmd.hpp @@ -36,8 +36,7 @@ class AbortCmd final : public TaskCmd { void create(Cmd_ptr& cmd, boost::program_options::variables_map& vm, AbstractClientEnv* clientEnv) const override; private: - static const char* arg(); // used for argument parsing - static const char* desc(); // The description of the argument as provided to user + static const char* arg(); // used for argument parsing STC_Cmd_ptr doHandleRequest(AbstractServer*) const override; ecf::Child::CmdType child_type() const override { return ecf::Child::ABORT; } diff --git a/libs/base/src/ecflow/base/cts/task/CompleteCmd.cpp b/libs/base/src/ecflow/base/cts/task/CompleteCmd.cpp index cb0adb666..0a9d03e4d 100644 --- a/libs/base/src/ecflow/base/cts/task/CompleteCmd.cpp +++ b/libs/base/src/ecflow/base/cts/task/CompleteCmd.cpp @@ -82,25 +82,10 @@ STC_Cmd_ptr CompleteCmd::doHandleRequest(AbstractServer* as) const { const char* CompleteCmd::arg() { return TaskApi::completeArg(); } -const char* CompleteCmd::desc() { - return "Mark task as complete. For use in the '.ecf' script file *only*\n" - "Hence the context is supplied via environment variables\n\n" - "If this child command is a zombie, then the default action will be to *block*.\n" - "The default can be overridden by using zombie attributes.\n" - "Otherwise the blocking period is defined by ECF_TIMEOUT.\n" - "The init command allows variables to be added, and complete command\n" - "allows for them to be removed.\n" - " arg1(--remove)(optional) = a list of variables to removed from this task\n\n" - "Usage:\n" - " ecflow_client --complete\n" - " ecflow_client --complete --remove name1 name2 # delete variables name1 and name2 on the task"; -} void CompleteCmd::addOption(boost::program_options::options_description& desc) const { - desc.add_options()(CompleteCmd::arg(), - CompleteCmd::desc())("remove", - boost::program_options::value>()->multitoken(), - "remove variables i.e name name2"); + desc.add_options()(CompleteCmd::arg(), "")("remove", + boost::program_options::value>()->multitoken()); } void CompleteCmd::create(Cmd_ptr& cmd, boost::program_options::variables_map& vm, AbstractClientEnv* clientEnv) const { if (clientEnv->debug()) { diff --git a/libs/base/src/ecflow/base/cts/task/CompleteCmd.hpp b/libs/base/src/ecflow/base/cts/task/CompleteCmd.hpp index 26b69c77e..fe3b9cb82 100644 --- a/libs/base/src/ecflow/base/cts/task/CompleteCmd.hpp +++ b/libs/base/src/ecflow/base/cts/task/CompleteCmd.hpp @@ -39,8 +39,7 @@ class CompleteCmd final : public TaskCmd { void create(Cmd_ptr& cmd, boost::program_options::variables_map& vm, AbstractClientEnv* clientEnv) const override; private: - static const char* arg(); // used for argument parsing - static const char* desc(); // The description of the argument as provided to user + static const char* arg(); // used for argument parsing STC_Cmd_ptr doHandleRequest(AbstractServer*) const override; ecf::Child::CmdType child_type() const override { return ecf::Child::COMPLETE; } diff --git a/libs/base/src/ecflow/base/cts/task/CtsWaitCmd.cpp b/libs/base/src/ecflow/base/cts/task/CtsWaitCmd.cpp index 054c9eace..805c3f00c 100644 --- a/libs/base/src/ecflow/base/cts/task/CtsWaitCmd.cpp +++ b/libs/base/src/ecflow/base/cts/task/CtsWaitCmd.cpp @@ -103,16 +103,9 @@ STC_Cmd_ptr CtsWaitCmd::doHandleRequest(AbstractServer* as) const { const char* CtsWaitCmd::arg() { return TaskApi::waitArg(); } -const char* CtsWaitCmd::desc() { - return "Evaluates an expression, and block while the expression is false.\n" - "For use in the '.ecf' file *only*, hence the context is supplied via environment variables\n" - " arg1 = string(expression)\n\n" - "Usage:\n" - " ecflow_client --wait=\"/suite/taskx == complete\""; -} void CtsWaitCmd::addOption(boost::program_options::options_description& desc) const { - desc.add_options()(CtsWaitCmd::arg(), boost::program_options::value(), CtsWaitCmd::desc()); + desc.add_options()(CtsWaitCmd::arg(), boost::program_options::value()); } void CtsWaitCmd::create(Cmd_ptr& cmd, boost::program_options::variables_map& vm, AbstractClientEnv* clientEnv) const { std::string expression = vm[arg()].as(); diff --git a/libs/base/src/ecflow/base/cts/task/CtsWaitCmd.hpp b/libs/base/src/ecflow/base/cts/task/CtsWaitCmd.hpp index 438207b70..d0fba9514 100644 --- a/libs/base/src/ecflow/base/cts/task/CtsWaitCmd.hpp +++ b/libs/base/src/ecflow/base/cts/task/CtsWaitCmd.hpp @@ -38,8 +38,7 @@ class CtsWaitCmd final : public TaskCmd { void create(Cmd_ptr& cmd, boost::program_options::variables_map& vm, AbstractClientEnv* clientEnv) const override; private: - static const char* arg(); // used for argument parsing - static const char* desc(); // The description of the argument as provided to user + static const char* arg(); // used for argument parsing STC_Cmd_ptr doHandleRequest(AbstractServer*) const override; ecf::Child::CmdType child_type() const override { return ecf::Child::WAIT; } diff --git a/libs/base/src/ecflow/base/cts/task/EventCmd.cpp b/libs/base/src/ecflow/base/cts/task/EventCmd.cpp index dce9c21f9..27de85934 100644 --- a/libs/base/src/ecflow/base/cts/task/EventCmd.cpp +++ b/libs/base/src/ecflow/base/cts/task/EventCmd.cpp @@ -87,23 +87,9 @@ STC_Cmd_ptr EventCmd::doHandleRequest(AbstractServer* as) const { const char* EventCmd::arg() { return TaskApi::eventArg(); } -const char* EventCmd::desc() { - return "Change event. For use in the '.ecf' script file *only*\n" - "Hence the context is supplied via environment variables\n" - " arg1(string | int) = event-name\n\n" - " arg2(string)(optional) = [ set | clear] default value is set\n\n" - "If this child command is a zombie, then the default action will be to *fob*,\n" - "i.e allow the ecflow client command to complete without an error\n" - "The default can be overridden by using zombie attributes.\n\n" - "Usage:\n" - " ecflow_client --event=ev # set the event, default since event initial value is clear\n" - " ecflow_client --event=ev set # set the event, explicit\n" - " ecflow_client --event=ev clear # clear the event, use when event initial value is set\n"; -} void EventCmd::addOption(boost::program_options::options_description& desc) const { - desc.add_options()( - EventCmd::arg(), boost::program_options::value>()->multitoken(), EventCmd::desc()); + desc.add_options()(EventCmd::arg(), boost::program_options::value>()->multitoken()); } void EventCmd::create(Cmd_ptr& cmd, boost::program_options::variables_map& vm, AbstractClientEnv* clientEnv) const { auto args = vm[arg()].as>(); diff --git a/libs/base/src/ecflow/base/cts/task/EventCmd.hpp b/libs/base/src/ecflow/base/cts/task/EventCmd.hpp index f8115b51b..07380025a 100644 --- a/libs/base/src/ecflow/base/cts/task/EventCmd.hpp +++ b/libs/base/src/ecflow/base/cts/task/EventCmd.hpp @@ -42,8 +42,7 @@ class EventCmd final : public TaskCmd { void create(Cmd_ptr& cmd, boost::program_options::variables_map& vm, AbstractClientEnv* clientEnv) const override; private: - static const char* arg(); // used for argument parsing - static const char* desc(); // The description of the argument as provided to user + static const char* arg(); // used for argument parsing STC_Cmd_ptr doHandleRequest(AbstractServer*) const override; ecf::Child::CmdType child_type() const override { return ecf::Child::EVENT; } diff --git a/libs/base/src/ecflow/base/cts/task/InitCmd.cpp b/libs/base/src/ecflow/base/cts/task/InitCmd.cpp index a28d368b5..0649cd739 100644 --- a/libs/base/src/ecflow/base/cts/task/InitCmd.cpp +++ b/libs/base/src/ecflow/base/cts/task/InitCmd.cpp @@ -80,25 +80,10 @@ STC_Cmd_ptr InitCmd::doHandleRequest(AbstractServer* as) const { const char* InitCmd::arg() { return TaskApi::initArg(); } -const char* InitCmd::desc() { - return "Mark task as started(active). For use in the '.ecf' script file *only*\n" - "Hence the context is supplied via environment variables.\n" - " arg1(string) = process_or_remote_id The process id of the job or remote_id\n" - " Using remote id allows the jobs to be killed\n" - " arg2(--add)(optional)= add/update variables as name value pairs\n\n" - "If this child command is a zombie, then the default action will be to *block*.\n" - "The default can be overridden by using zombie attributes.\n" - "Otherwise the blocking period is defined by ECF_TIMEOUT.\n\n" - "Usage:\n" - " ecflow_client --init=$$\n" - " ecflow_client --init=$$ --add name=value name2=value2 # add/update variables to task"; -} void InitCmd::addOption(boost::program_options::options_description& desc) const { - desc.add_options()(InitCmd::arg(), boost::program_options::value(), InitCmd::desc())( - "add", - boost::program_options::value>()->multitoken(), - "Add variables e.g. name1=value1 name2=value2. Can only be used in combination with --init command."); + desc.add_options()(InitCmd::arg(), boost::program_options::value())( + "add", boost::program_options::value>()->multitoken()); } void InitCmd::create(Cmd_ptr& cmd, boost::program_options::variables_map& vm, AbstractClientEnv* clientEnv) const { diff --git a/libs/base/src/ecflow/base/cts/task/InitCmd.hpp b/libs/base/src/ecflow/base/cts/task/InitCmd.hpp index db9b087cc..acc513417 100644 --- a/libs/base/src/ecflow/base/cts/task/InitCmd.hpp +++ b/libs/base/src/ecflow/base/cts/task/InitCmd.hpp @@ -40,8 +40,7 @@ class InitCmd final : public TaskCmd { void create(Cmd_ptr& cmd, boost::program_options::variables_map& vm, AbstractClientEnv* clientEnv) const override; private: - static const char* arg(); // used for argument parsing - static const char* desc(); // The description of the argument as provided to user + static const char* arg(); // used for argument parsing STC_Cmd_ptr doHandleRequest(AbstractServer*) const override; ecf::Child::CmdType child_type() const override { return ecf::Child::INIT; } diff --git a/libs/base/src/ecflow/base/cts/task/LabelCmd.cpp b/libs/base/src/ecflow/base/cts/task/LabelCmd.cpp index bc0a34849..dfcef1f77 100644 --- a/libs/base/src/ecflow/base/cts/task/LabelCmd.cpp +++ b/libs/base/src/ecflow/base/cts/task/LabelCmd.cpp @@ -83,22 +83,9 @@ STC_Cmd_ptr LabelCmd::doHandleRequest(AbstractServer* as) const { const char* LabelCmd::arg() { return TaskApi::labelArg(); } -const char* LabelCmd::desc() { - return "Change Label. For use in the '.ecf' script file *only*\n" - "Hence the context is supplied via environment variables\n" - " arg1 = label-name\n" - " arg2 = The new label value\n" - " The labels values can be single or multi-line(space separated quoted strings)\n\n" - "If this child command is a zombie, then the default action will be to *fob*,\n" - "i.e allow the ecflow client command to complete without an error\n" - "The default can be overridden by using zombie attributes.\n\n" - "Usage:\n" - " ecflow_client --label=progressed merlin"; -} void LabelCmd::addOption(boost::program_options::options_description& desc) const { - desc.add_options()( - LabelCmd::arg(), boost::program_options::value>()->multitoken(), LabelCmd::desc()); + desc.add_options()(LabelCmd::arg(), boost::program_options::value>()->multitoken()); } void LabelCmd::create(Cmd_ptr& cmd, boost::program_options::variables_map& vm, AbstractClientEnv* clientEnv) const { auto args = vm[arg()].as>(); diff --git a/libs/base/src/ecflow/base/cts/task/LabelCmd.hpp b/libs/base/src/ecflow/base/cts/task/LabelCmd.hpp index cc59217b5..fe001da59 100644 --- a/libs/base/src/ecflow/base/cts/task/LabelCmd.hpp +++ b/libs/base/src/ecflow/base/cts/task/LabelCmd.hpp @@ -41,8 +41,7 @@ class LabelCmd final : public TaskCmd { void create(Cmd_ptr& cmd, boost::program_options::variables_map& vm, AbstractClientEnv* clientEnv) const override; private: - static const char* arg(); // used for argument parsing - static const char* desc(); // The description of the argument as provided to user + static const char* arg(); // used for argument parsing STC_Cmd_ptr doHandleRequest(AbstractServer*) const override; ecf::Child::CmdType child_type() const override { return ecf::Child::LABEL; } diff --git a/libs/base/src/ecflow/base/cts/task/MeterCmd.cpp b/libs/base/src/ecflow/base/cts/task/MeterCmd.cpp index 7d8fff5cc..6fd9711af 100644 --- a/libs/base/src/ecflow/base/cts/task/MeterCmd.cpp +++ b/libs/base/src/ecflow/base/cts/task/MeterCmd.cpp @@ -100,21 +100,9 @@ STC_Cmd_ptr MeterCmd::doHandleRequest(AbstractServer* as) const { const char* MeterCmd::arg() { return TaskApi::meterArg(); } -const char* MeterCmd::desc() { - return "Change meter. For use in the '.ecf' script file *only*\n" - "Hence the context is supplied via environment variables\n" - " arg1(string) = meter-name\n" - " arg2(int) = the new meter value\n\n" - "If this child command is a zombie, then the default action will be to *fob*,\n" - "i.e allow the ecflow client command to complete without an error\n" - "The default can be overridden by using zombie attributes.\n\n" - "Usage:\n" - " ecflow_client --meter=my_meter 20"; -} void MeterCmd::addOption(boost::program_options::options_description& desc) const { - desc.add_options()( - MeterCmd::arg(), boost::program_options::value>()->multitoken(), MeterCmd::desc()); + desc.add_options()(MeterCmd::arg(), boost::program_options::value>()->multitoken()); } void MeterCmd::create(Cmd_ptr& cmd, boost::program_options::variables_map& vm, AbstractClientEnv* clientEnv) const { auto args = vm[arg()].as>(); diff --git a/libs/base/src/ecflow/base/cts/task/MeterCmd.hpp b/libs/base/src/ecflow/base/cts/task/MeterCmd.hpp index 0f09f5e27..52935abd7 100644 --- a/libs/base/src/ecflow/base/cts/task/MeterCmd.hpp +++ b/libs/base/src/ecflow/base/cts/task/MeterCmd.hpp @@ -41,8 +41,7 @@ class MeterCmd final : public TaskCmd { void create(Cmd_ptr& cmd, boost::program_options::variables_map& vm, AbstractClientEnv* clientEnv) const override; private: - static const char* arg(); // used for argument parsing - static const char* desc(); // The description of the argument as provided to user + static const char* arg(); // used for argument parsing STC_Cmd_ptr doHandleRequest(AbstractServer*) const override; ecf::Child::CmdType child_type() const override { return ecf::Child::METER; } diff --git a/libs/base/src/ecflow/base/cts/task/QueueCmd.cpp b/libs/base/src/ecflow/base/cts/task/QueueCmd.cpp index ec5674adc..ebecdb82b 100644 --- a/libs/base/src/ecflow/base/cts/task/QueueCmd.cpp +++ b/libs/base/src/ecflow/base/cts/task/QueueCmd.cpp @@ -170,43 +170,9 @@ std::string QueueCmd::handle_queue(QueueAttr& queue_attr) const { const char* QueueCmd::arg() { return TaskApi::queue_arg(); } -const char* QueueCmd::desc() { - return "QueueCmd. For use in the '.ecf' script file *only*\n" - "Hence the context is supplied via environment variables\n" - " arg1(string) = queue-name:\n" - " arg2(string) = action: [active | aborted | complete | no_of_aborted | reset ]\n" - " active: returns the first queued/aborted step, the return string is the queue value from the " - "definition\n" - " no_of_aborted: returns number of aborted steps as a string, i.e 10\n" - " reset: sets the index to the first queued/aborted step. Allows steps to be reprocessed for errors\n" - " arg3(string) = step: value returned from step=$(ecflow_client --queue=queue_name active)\n" - " This is only valid for complete and aborted steps\n" - " arg4(string) = path: (optional). The path where the queue is defined.\n" - " By default we search for the queue up the node tree.\n\n" - "If this child command is a zombie, then the default action will be to *block*,\n" - "The default can be overridden by using zombie attributes." - "If the path to the queue is not defined, then this command will\n" - "search for the queue up the node hierarchy. If no queue found, command fails\n\n" - "Usage:\n" - "step=\"\"\n" - "QNAME=\"my_queue_name\"\n" - "while [1 == 1 ] ; do\n" - " # this return the first queued/aborted step, then increments to next step, return when all steps " - "processed\n" - " step=$(ecflow_client --queue=$QNAME active) # of the form string i.e \"003\". this step is now active\n" - " if [[ $step == \"\" ]] ; then\n" - " break;\n" - " fi\n" - " ...\n" - " ecflow_client --queue=$QNAME complete $step # tell ecflow this step completed\n" - "done\n" - "\n" - "trap() { ecflow_client --queue=$QNAME aborted $step # tell ecflow this step failed }\n"; -} void QueueCmd::addOption(boost::program_options::options_description& desc) const { - desc.add_options()( - QueueCmd::arg(), boost::program_options::value>()->multitoken(), QueueCmd::desc()); + desc.add_options()(QueueCmd::arg(), boost::program_options::value>()->multitoken()); } void QueueCmd::create(Cmd_ptr& cmd, boost::program_options::variables_map& vm, AbstractClientEnv* clientEnv) const { auto args = vm[arg()].as>(); diff --git a/libs/base/src/ecflow/base/cts/task/QueueCmd.hpp b/libs/base/src/ecflow/base/cts/task/QueueCmd.hpp index 699c12014..b70bcbe0e 100644 --- a/libs/base/src/ecflow/base/cts/task/QueueCmd.hpp +++ b/libs/base/src/ecflow/base/cts/task/QueueCmd.hpp @@ -49,8 +49,7 @@ class QueueCmd final : public TaskCmd { void create(Cmd_ptr& cmd, boost::program_options::variables_map& vm, AbstractClientEnv* clientEnv) const override; private: - static const char* arg(); // used for argument parsing - static const char* desc(); // The description of the argument as provided to user + static const char* arg(); // used for argument parsing STC_Cmd_ptr doHandleRequest(AbstractServer*) const override; ecf::Child::CmdType child_type() const override { return ecf::Child::QUEUE; } diff --git a/libs/base/src/ecflow/base/cts/user/AlterCmd.cpp b/libs/base/src/ecflow/base/cts/user/AlterCmd.cpp index 04886c848..edaca87e6 100644 --- a/libs/base/src/ecflow/base/cts/user/AlterCmd.cpp +++ b/libs/base/src/ecflow/base/cts/user/AlterCmd.cpp @@ -20,6 +20,7 @@ #include "ecflow/base/AbstractServer.hpp" #include "ecflow/base/AuthenticationDetails.hpp" #include "ecflow/base/AuthorisationDetails.hpp" +#include "ecflow/base/HelpCatalog.hpp" #include "ecflow/base/cts/user/CtsApi.hpp" #include "ecflow/core/Converter.hpp" #include "ecflow/core/Enumerate.hpp" @@ -616,123 +617,13 @@ STC_Cmd_ptr AlterCmd::doHandleRequest(AbstractServer* as) const { const char* AlterCmd::arg() { return CtsApi::alterArg(); } -const char* AlterCmd::desc() { - - return - /////////1/////////2/////////3/////////4/////////5/////////6/////////7/////////8//////////9 - "Alter the node according to the options.\n" - "\n" - "arg1 = [ delete | change | add | set_flag | clear_flag | sort ]\n" - "\n" - "arg2 = For delete:\n" - " [ variable | time | today | date | day | cron | event | meter | late | generic |\n" - " queue | label | trigger | complete | repeat | limit | inlimit | limit_path |\n" - " zombie | aviso | mirror ]\n" - "\n" - " For change:\n" - " [ variable | clock_type | clock_gain | clock_date | clock_sync | event | meter |\n" - " label | trigger | complete | repeat | limit_max | limit_value | defstatus |\n" - " late | time | today | aviso | mirror ]\n" - "\n" - " For add:\n" - " [ variable | time | today | date | day | zombie | event | meter | late | limit |\n" - " inlimit | label | aviso | mirror ]\n" - "\n" - " For set_flag or clear_flag:\n" - " [ force_aborted | user_edit | task_aborted | edit_failed | ecfcmd_failed |\n" - " statuscmd_failed | killcmd_failed | no_script | killed | status | late |\n" - " message complete | queue_limit | task_waiting | locked | zombie | archived |\n" - " restored | threshold | log_error | checkpt_error ]\n" - "\n" - " For sort:\n" - " [ event | meter | label | variable| limit | all ]\n" - "\n" - "arg3 = [ | ]\n" - "\n" - "arg4 = \n" - "\n" - "arg5 = ( (...)) - at least one node path required.\n" - "\n" - "*Important Notes*\n" - "\n" - " * All paths must start with a leading '/' character.\n" - "\n" - " * To update, create or remove server variables use '/' for path.\n" - "\n" - " * When updating unnamed attributes (Repeat, Trigger, Complete, ...) the name/arg3 is not necessary.\n" - "\n" - " * After changing the clock the suite needs to be re-queued for the change to take effect.\n" - "\n" - " * When adding or updating node attributes (e.g. variable, meter, event, label, limits, late)\n" - " the name (arg3) and value (arg4) must be quoted.\n" - "\n" - " * When sorting attributes, 'recursive' can be used as the value (arg3).\n" - "\n" - " * When adding a meter, the value (arg4) is expected to be a comma-separated triplet\n" - " of numerical values the form \",,\" (n.b. no spaces are allowed).\n" - "\n" - " * When adding an event, the non-optional value (arg4) must be either \"set\" or \"clear\".\n" - "\n" - " * When adding or updating aviso and mirror attributes, the value (arg4) is expected to be\n" - " a quoted list of configuration options. For example:\n" - " * for aviso, \"--remote_path /s1/f1/t2 --remote_host host --polling 20 --remote_port 3141 --ssl)\"\n" - " * for mirror, \"--listener '{ \\\"event\\\": \\\"mars\\\", \\\"request\\\": { \\\"class\\\": \"od\" } " - "}'\n" - " --url http://aviso/ --schema /path/to/schema --polling 60\"\n" - "\n" - " * For both aviso and mirror, the special value \"reload\" forces reloading the configuration.\n" - " This is typically useful after updating variables used to configure these kind of attributes.\n" - "\n" - "Usage:\n" - "\n" - " ecflow_client --alter=add variable \"value\" / # add server variable\n" - " ecflow_client --alter=add variable \"value\" /path/to/node # add node variable\n" - "\n" - " ecflow_client --alter=add time \"+00:20\" /path/to/node\n" - "\n" - " ecflow_client --alter=add date \"01.*.*\" /path/to/node\n" - "\n" - " ecflow_client --alter=add day \"sunday\" /path/to/node\n" - "\n" - " ecflow_client --alter=add label \"label_value\" /path/to/node\n" - "\n" - " ecflow_client --alter=add event \"set\"|\"clear\" /path/to/node\n" - "\n" - " ecflow_client --alter=add meter \",,value\" /path/to/node\n" - "\n" - " ecflow_client --alter=add late \"-s 00:01 -a 14:30 -c +00:01\" /path/to/node\n" - "\n" - " ecflow_client --alter=add limit mars \"100\" /path/to/node\n" - "\n" - " ecflow_client --alter=add inlimit /path/to/node/withlimit:limit_name \"10\" /path/to/node\n" - "\n" - " ecflow_client --alter=add inlimit /path/to/node/withlimit:limit_name \"-s 10\" /path/to/node\n" - "\n" - " ecflow_client --alter=add inlimit /path/to/node/withlimit:limit_name \"-n 10\" /path/to/node\n" - "\n" - " # zombie attributes have the following structure:\n" - " `zombie_type`:(`client_side_action` | `server_side_action`):`child`:`zombie_life_time`\n" - " zombie_type = \"user\" | \"ecf\" | \"path\" | \"ecf_pid\" | \"ecf_passwd\" | \"ecf_pid_passwd\"\n" - " client_side_action = \"fob\" | \"fail\" | \"block\"\n" - " server_side_action = \"adopt\" | \"delete\" | \"kill\"\n" - " child = \"init\" | \"event\" | \"meter\" | \"label\" | \"wait\" | \"abort\" | \"complete\" " - "| \"queue\"\n" - " zombie_life_time = unsigned integer default: user(300), ecf(3600), path(900) minimum is 60\n" - "\n" - " ecflow_client --alter=add zombie \"ecf:fail::\" /path/to/node # ask system zombies to fail\n" - " ecflow_client --alter=add zombie \"user:fail::\" /path/to/node # ask user generated zombies to fail\n" - " ecflow_client --alter=add zombie \"path:fail::\" /path/to/node # ask path zombies to fail\n" - "\n" - " ecflow_client --alter=delete variable FRED /path/to/node # delete variable FRED\n" - " ecflow_client --alter=delete variable /path/to/node # delete *ALL* variables on the given snode\n"; -} void AlterCmd::addOption(boost::program_options::options_description& desc) const { // Important: this option is, in practice, multi-token (and thus should use // po::value>()->multitoken()). However, because of the special handling // necessary to allow positional values, such as "--help", a custom `style_parser` is used // instead when parsing the CLI options -- see ClientOptions for details. - desc.add_options()(AlterCmd::arg(), boost::program_options::value>(), AlterCmd::desc()); + desc.add_options()(AlterCmd::arg(), boost::program_options::value>()); } void AlterCmd::create(Cmd_ptr& cmd, boost::program_options::variables_map& vm, AbstractClientEnv* ac) const { auto args = vm[arg()].as>(); @@ -810,7 +701,8 @@ AlterCmd::Add_attr_type AlterCmd::get_add_attr_type(const std::string& attr_type } ss << valid[i]; } - ss << "] but found " << attr_type << "\n" << AlterCmd::desc(); + ss << "] but found " << attr_type << "\n" + << HelpCatalog::description_for("alter").value_or(HelpCatalog::not_provided); throw std::runtime_error(ss.str()); } return theAttrType; @@ -1063,7 +955,8 @@ AlterCmd::Delete_attr_type AlterCmd::get_delete_attr_type(const std::string& att } ss << valid[i]; } - ss << "] but found " << attr_type << "\n" << AlterCmd::desc(); + ss << "] but found " << attr_type << "\n" + << HelpCatalog::description_for("alter").value_or(HelpCatalog::not_provided); throw std::runtime_error(ss.str()); } return theAttrType; @@ -1294,7 +1187,7 @@ AlterCmd::Change_attr_type AlterCmd::get_change_attr_type(const std::string& att } ss << valid[i]; } - ss << "]\n" << AlterCmd::desc(); + ss << "]\n" << HelpCatalog::description_for("alter").value_or(HelpCatalog::not_provided); throw std::runtime_error(ss.str()); } return theAttrType; @@ -1760,7 +1653,7 @@ ecf::Flag::Type AlterCmd::get_flag_type(const std::string& flag_type) const { } ss << valid[i]; } - ss << "]\n" << AlterCmd::desc(); + ss << "]\n" << HelpCatalog::description_for("alter").value_or(HelpCatalog::not_provided); throw std::runtime_error(ss.str()); } return theFlagType; @@ -1790,7 +1683,8 @@ void AlterCmd::check_sort_attr_type(const std::string& attr_type) const { } ss << valid[i]; } - ss << "] but found " << attr_type << "\n" << AlterCmd::desc(); + ss << "] but found " << attr_type << "\n" + << HelpCatalog::description_for("alter").value_or(HelpCatalog::not_provided); throw std::runtime_error(ss.str()); } } @@ -1813,9 +1707,10 @@ void AlterCmd::create_sort_attributes(Cmd_ptr& cmd, std::string value; if (options.size() == 3) { if (options[2] != "recursive") { - throw std::runtime_error(MESSAGE("AlterCmd: sort: Expected third argument to be 'recursive' but found '" - << options[2] << "\n" - << AlterCmd::desc())); + throw std::runtime_error( + MESSAGE("AlterCmd: sort: Expected third argument to be 'recursive' but found '" + << options[2] << "\n" + << HelpCatalog::description_for("alter").value_or(HelpCatalog::not_provided))); } value = "recursive"; } diff --git a/libs/base/src/ecflow/base/cts/user/AlterCmd.hpp b/libs/base/src/ecflow/base/cts/user/AlterCmd.hpp index 737444d36..122f3cfeb 100644 --- a/libs/base/src/ecflow/base/cts/user/AlterCmd.hpp +++ b/libs/base/src/ecflow/base/cts/user/AlterCmd.hpp @@ -175,8 +175,7 @@ class AlterCmd final : public UserCmd { void create(Cmd_ptr& cmd, boost::program_options::variables_map& vm, AbstractClientEnv* clientEnv) const override; private: - static const char* arg(); // used for argument parsing - static const char* desc(); // The description of the argument as provided to user + static const char* arg(); // used for argument parsing STC_Cmd_ptr doHandleRequest(AbstractServer*) const override; STC_Cmd_ptr alter_server_state(AbstractServer*) const; diff --git a/libs/base/src/ecflow/base/cts/user/BeginCmd.cpp b/libs/base/src/ecflow/base/cts/user/BeginCmd.cpp index efc0377c9..60e3e886d 100644 --- a/libs/base/src/ecflow/base/cts/user/BeginCmd.cpp +++ b/libs/base/src/ecflow/base/cts/user/BeginCmd.cpp @@ -16,6 +16,7 @@ #include "ecflow/base/AbstractServer.hpp" #include "ecflow/base/AuthenticationDetails.hpp" #include "ecflow/base/AuthorisationDetails.hpp" +#include "ecflow/base/HelpCatalog.hpp" #include "ecflow/base/cts/user/CtsApi.hpp" #include "ecflow/core/Str.hpp" #include "ecflow/node/NodeAlgorithms.hpp" @@ -126,29 +127,12 @@ STC_Cmd_ptr BeginCmd::doHandleRequest(AbstractServer* as) const { const char* BeginCmd::arg() { return CtsApi::beginArg(); } -const char* BeginCmd::desc() { - /////////1/////////2/////////3/////////4/////////5/////////6/////////7/////////8 - return "Begin playing the definition in the server.\n" - "Expects zero or a single quoted string.\n" - " arg1 = suite-name | Nothing | force\n" - " play the chosen suite, if no arg specified, play all suites, in the definition\n" - " force means reset the begin status on the suites and bypass checks.\n" - " This is only required if suite-name is provide as the first argument\n" - " Using force can cause the creation of zombies\n" - "Usage:\n" - "--begin # will begin all suites\n" - "--begin=\"--force\" # reset and then begin all suites, bypassing any checks. Note: string must be " - "quoted\n" - "--begin=\"mySuite\" # begin playing suite of name 'mySuite'\n" - "--begin=\"mySuite --force\" # reset and begin playing suite 'mySuite', bypass check"; -} void BeginCmd::addOption(boost::program_options::options_description& desc) const { // allow options like // client --begin=suitename // begin // client --begin // means begin all suites - desc.add_options()( - BeginCmd::arg(), boost::program_options::value()->implicit_value(std::string{}), BeginCmd::desc()); + desc.add_options()(BeginCmd::arg(), boost::program_options::value()->implicit_value(std::string{})); } void BeginCmd::create(Cmd_ptr& cmd, boost::program_options::variables_map& vm, AbstractClientEnv* ace) const { std::string beginArg = vm[arg()].as(); @@ -182,9 +166,10 @@ void BeginCmd::create(Cmd_ptr& cmd, boost::program_options::variables_map& vm, A force = true; } else { - throw std::runtime_error(MESSAGE("BeginCmd: Expect zero, one or 2 arguments, but found " - << lineTokens.size() << " arguments\n" - << BeginCmd::desc() << "\n")); + throw std::runtime_error( + MESSAGE("BeginCmd: Expect zero, one or 2 arguments, but found " + << lineTokens.size() << " arguments\n" + << HelpCatalog::description_for("begin").value_or(HelpCatalog::not_provided) << "\n")); } } diff --git a/libs/base/src/ecflow/base/cts/user/BeginCmd.hpp b/libs/base/src/ecflow/base/cts/user/BeginCmd.hpp index 4e6e81e58..712adcc3b 100644 --- a/libs/base/src/ecflow/base/cts/user/BeginCmd.hpp +++ b/libs/base/src/ecflow/base/cts/user/BeginCmd.hpp @@ -38,8 +38,7 @@ class BeginCmd final : public UserCmd { void create(Cmd_ptr& cmd, boost::program_options::variables_map& vm, AbstractClientEnv* clientEnv) const override; private: - static const char* arg(); // used for argument parsing - static const char* desc(); // The description of the argument as provided to user + static const char* arg(); // used for argument parsing STC_Cmd_ptr doHandleRequest(AbstractServer*) const override; diff --git a/libs/base/src/ecflow/base/cts/user/CFileCmd.cpp b/libs/base/src/ecflow/base/cts/user/CFileCmd.cpp index 7bb2ec6f5..e4f01391c 100644 --- a/libs/base/src/ecflow/base/cts/user/CFileCmd.cpp +++ b/libs/base/src/ecflow/base/cts/user/CFileCmd.cpp @@ -17,6 +17,7 @@ #include "ecflow/base/AbstractServer.hpp" #include "ecflow/base/AuthenticationDetails.hpp" #include "ecflow/base/AuthorisationDetails.hpp" +#include "ecflow/base/HelpCatalog.hpp" #include "ecflow/base/cts/user/CtsApi.hpp" #include "ecflow/base/stc/PreAllocatedReply.hpp" #include "ecflow/core/Converter.hpp" @@ -333,20 +334,9 @@ STC_Cmd_ptr CFileCmd::doHandleRequest(AbstractServer* as) const { const char* CFileCmd::arg() { return CtsApi::fileArg(); } -const char* CFileCmd::desc() { - /////////1/////////2/////////3/////////4/////////5/////////6/////////7/////////8 - return "Return the chosen file. Select from [ script | job | jobout | manual | kill | stat ]\n" - "By default will return the script.\n" - " arg1 = path to node\n" - " arg2 = (optional) [ script | job | jobout | manual | kill | stat ]\n" - " kill will attempt to return output of ECF_KILL_CMD, i.e the file %ECF_JOB%.kill\n" - " stat will attempt to return output of ECF_STATUS_CMD, i.e the file %ECF_JOB%.stat\n" - " arg3 = (optional) max_lines = 10000 "; -} void CFileCmd::addOption(boost::program_options::options_description& desc) const { - desc.add_options()( - CFileCmd::arg(), boost::program_options::value>()->multitoken(), CFileCmd::desc()); + desc.add_options()(CFileCmd::arg(), boost::program_options::value>()->multitoken()); } void CFileCmd::create(Cmd_ptr& cmd, boost::program_options::variables_map& vm, AbstractClientEnv* ac) const { auto args = vm[arg()].as>(); @@ -358,7 +348,8 @@ void CFileCmd::create(Cmd_ptr& cmd, boost::program_options::variables_map& vm, A if (args.size() < 1) { throw std::runtime_error(MESSAGE("CFileCmd: At least one arguments expected for File. Found " << args.size() << "\n" - << CFileCmd::desc() << "\n")); + << HelpCatalog::description_for("file").value_or(HelpCatalog::not_provided) + << "\n")); } std::string pathToNode = args[0]; diff --git a/libs/base/src/ecflow/base/cts/user/CFileCmd.hpp b/libs/base/src/ecflow/base/cts/user/CFileCmd.hpp index 6e07bcfca..d90ce22f4 100644 --- a/libs/base/src/ecflow/base/cts/user/CFileCmd.hpp +++ b/libs/base/src/ecflow/base/cts/user/CFileCmd.hpp @@ -48,8 +48,7 @@ class CFileCmd final : public UserCmd { void create(Cmd_ptr& cmd, boost::program_options::variables_map& vm, AbstractClientEnv* clientEnv) const override; private: - static const char* arg(); // used for argument parsing - static const char* desc(); // The description of the argument as provided to user + static const char* arg(); // used for argument parsing STC_Cmd_ptr doHandleRequest(AbstractServer*) const override; // bool authenticate(AbstractServer*, STC_Cmd_ptr&) const override; diff --git a/libs/base/src/ecflow/base/cts/user/CSyncCmd.cpp b/libs/base/src/ecflow/base/cts/user/CSyncCmd.cpp index 452a66a77..1c3e1cf32 100644 --- a/libs/base/src/ecflow/base/cts/user/CSyncCmd.cpp +++ b/libs/base/src/ecflow/base/cts/user/CSyncCmd.cpp @@ -170,49 +170,22 @@ STC_Cmd_ptr CSyncCmd::doHandleRequest(AbstractServer* as) const { void CSyncCmd::addOption(boost::program_options::options_description& desc) const { if (api_ == CSyncCmd::NEWS) { - desc.add_options()( - CtsApi::newsArg(), - boost::program_options::value>()->multitoken(), - "Returns true if state of server definition changed.\n" - "*Important* for use with c++/python interface only.\n" - "Requires Given a client handle, change and modify number determine if server changed since last call\n" - "This relies on user calling sync after news to update the locally stored modify and change numbers.\n" - "These numbers are then used in the next call to news."); + desc.add_options()(CtsApi::newsArg(), boost::program_options::value>()->multitoken()); return; } if (api_ == CSyncCmd::SYNC) { - desc.add_options()( - CtsApi::syncArg(), - boost::program_options::value>()->multitoken(), - "Incrementally synchronise the local definition with the one in the server.\n" - "*Important* for use with c++/python interface only.\n" - "Preference should be given to this method as only the changes are returned.\n" - "This reduces the network bandwidth required to keep in sync with the server\n" - "Requires a client handle, change and modify number, to get the incremental changes from server.\n" - "The change in server state is then and merged with the client definition."); + desc.add_options()(CtsApi::syncArg(), boost::program_options::value>()->multitoken()); return; } if (api_ == CSyncCmd::SYNC_CLOCK) { - desc.add_options()( - CtsApi::sync_clock_arg(), - boost::program_options::value>()->multitoken(), - "Incrementally synchronise the local definition with the one in the server.\n" - "*Important* for use with c++/python interface only.\n" - "Same as sync, but will *always* sync with suite clock if it has changed.\n" - "Preference should be given to this method as only the changes are returned.\n" - "This reduces the network bandwidth required to keep in sync with the server\n" - "Requires a client handle, change and modify number, to get the incremental changes from server.\n" - "The change in server state is then and merged with the client definition."); + desc.add_options()(CtsApi::sync_clock_arg(), + boost::program_options::value>()->multitoken()); return; } - desc.add_options()(CtsApi::sync_full_arg(), - boost::program_options::value(), - "Returns the full definition from the server.\n" - "*Important* for use with c++/python interface only.\n" - "Requires a client_handle. The returned definition is stored on the client."); + desc.add_options()(CtsApi::sync_full_arg(), boost::program_options::value()); } void CSyncCmd::create(Cmd_ptr& cmd, boost::program_options::variables_map& vm, AbstractClientEnv* ac) const { diff --git a/libs/base/src/ecflow/base/cts/user/CheckPtCmd.cpp b/libs/base/src/ecflow/base/cts/user/CheckPtCmd.cpp index 3cb45ba19..dbabcea5d 100644 --- a/libs/base/src/ecflow/base/cts/user/CheckPtCmd.cpp +++ b/libs/base/src/ecflow/base/cts/user/CheckPtCmd.cpp @@ -16,6 +16,7 @@ #include "ecflow/base/AbstractServer.hpp" #include "ecflow/base/AuthenticationDetails.hpp" #include "ecflow/base/AuthorisationDetails.hpp" +#include "ecflow/base/HelpCatalog.hpp" #include "ecflow/base/cts/user/CtsApi.hpp" #include "ecflow/base/stc/PreAllocatedReply.hpp" #include "ecflow/core/Converter.hpp" @@ -92,47 +93,9 @@ STC_Cmd_ptr CheckPtCmd::doHandleRequest(AbstractServer* as) const { return PreAllocatedReply::ok_cmd(); } -static const char* arg_desc() { - /////////1/////////2/////////3/////////4/////////5/////////6/////////7/////////8 - - return "Forces the definition file in the server to be written to disk *or* allow mode,\n" - "interval and alarm to be changed.\n" - "Whenever the check pt file is written to disk, it is measured.\n" - "If the time to save to disk is greater than the default of 30 seconds,\n" - "then an alarm is raised. This can be seen in the GUI as a late flag on the server.\n" - "Once the late flag has been set it will need to manually cleared in the GUI\n" - "or by using --alter functionality\n" - "Note excessive save times can interfere with job scheduling.\n" - "The alarm threshold can be changed. See below.\n" - " arg1 = (optional) mode [ never | on_time | on_time: | always | ]\n" - " never : Never check point the definition in the server\n" - " on_time : Turn on automatic check pointing at interval stored on server\n" - " on_time : Turn on automatic check point, with the specified interval in seconds\n" - " alarm : Modify the alarm notification time for check pt saving to disk\n" - " always : Check point at any change in node tree, *NOT* recommended for large definitions\n" - " : This specifies the interval in seconds when server should automatically check pt.\n" - " This will only take effect of mode is on_time/CHECK_ON_TIME\n" - " Should ideally be a value greater than 60 seconds, default is 120 seconds\n" - "Usage:\n" - " --check_pt\n" - " Immediately check point the definition held in the server\n" - " --check_pt=never\n" - " Switch off check pointing\n" - " --check_pt=on_time\n" - " Start automatic check pointing at the interval stored in the server\n" - " --check_pt=180\n" - " Change the check pt interval to 180 seconds\n" - " --check_pt=on_time:90\n" - " Change mode and interval, to automatic check pointing every 90 seconds\n" - " --check_pt=alarm:35\n" - " Change the alarm time for check pt saves. i.e if saving the check pt takes longer than 35 seconds\n" - " set the late flag on the server."; -} - void CheckPtCmd::addOption(boost::program_options::options_description& desc) const { desc.add_options()(CtsApi::checkPtDefsArg(), - boost::program_options::value()->implicit_value(std::string{}), - arg_desc()); + boost::program_options::value()->implicit_value(std::string{})); } static int parse_check_pt_interval(const std::string& the_arg) { @@ -141,14 +104,15 @@ static int parse_check_pt_interval(const std::string& the_arg) { check_pt_interval = ecf::convert_to(the_arg); } catch (...) { - throw std::runtime_error(MESSAGE("check_pt: Illegal argument(" - << the_arg - << "), expected [ never | on_time | on_time: | always | ]\n" - << arg_desc())); + throw std::runtime_error( + MESSAGE("check_pt: Illegal argument(" + << the_arg << "), expected [ never | on_time | on_time: | always | ]\n" + << HelpCatalog::description_for("check_pt").value_or(HelpCatalog::not_provided))); } if (check_pt_interval <= 0) { - throw std::runtime_error(MESSAGE("check_pt: interval(" << check_pt_interval << ") must be greater than zero :\n" - << arg_desc())); + throw std::runtime_error(MESSAGE( + "check_pt: interval(" << check_pt_interval << ") must be greater than zero :\n" + << HelpCatalog::description_for("check_pt").value_or(HelpCatalog::not_provided))); } return check_pt_interval; } @@ -164,12 +128,12 @@ static int parse_check_pt_alarm_time(const std::string& the_arg, int colon_pos) throw std::runtime_error(MESSAGE( "check_pt: Illegal argument(" << the_arg << "), expected [ never | on_time | on_time: | alarm::integer> | always | ]\n" - << arg_desc())); + << HelpCatalog::description_for("check_pt").value_or(HelpCatalog::not_provided))); } if (check_pt_alarm_time <= 0) { - throw std::runtime_error(MESSAGE("check_pt: alarm time(" << check_pt_alarm_time - << ") must be greater than zero :\n" - << arg_desc())); + throw std::runtime_error(MESSAGE( + "check_pt: alarm time(" << check_pt_alarm_time << ") must be greater than zero :\n" + << HelpCatalog::description_for("check_pt").value_or(HelpCatalog::not_provided))); } return check_pt_alarm_time; } @@ -214,7 +178,7 @@ void CheckPtCmd::create(Cmd_ptr& cmd, boost::program_options::variables_map& vm, "check_pt: Illegal argument(" << the_arg << "), expected [ never | on_time | on_time: | alarm: | always | ]\n" - << arg_desc())); + << HelpCatalog::description_for("check_pt").value_or(HelpCatalog::not_provided))); } check_pt_interval = parse_check_pt_interval(interval); } diff --git a/libs/base/src/ecflow/base/cts/user/ClientHandleCmd.cpp b/libs/base/src/ecflow/base/cts/user/ClientHandleCmd.cpp index 46bf4a6df..838176137 100644 --- a/libs/base/src/ecflow/base/cts/user/ClientHandleCmd.cpp +++ b/libs/base/src/ecflow/base/cts/user/ClientHandleCmd.cpp @@ -262,109 +262,42 @@ void ClientHandleCmd::addOption(boost::program_options::options_description& des switch (api_) { case ClientHandleCmd::REGISTER: { - desc.add_options()( - CtsApi::ch_register_arg(), - boost::program_options::value>()->multitoken(), - "Register interest in a set of suites.\n" - "If a definition has lots of suites, but the client. is only interested in a small subset,\n" - "Then using this command can reduce network bandwidth and synchronisation will be quicker.\n" - "This command will create a client handle, which must be used for any other changes.\n" - "The newly created handle can be shown with the --ch_suites command\n" - "Deleted suites will stay registered, and must be explicitly removed/dropped.\n" - "Note: Suites can be registered before they are loaded into the server\n" - "This command affects news() and sync() commands\n" - " arg1 = true | false # true means add new suites to my list, when they are created\n" - " arg2 = names # should be a list of suite names, names not in the definition are " - "ignored\n" - "Usage:\n" - " --ch_register=true s1 s2 s3 # register interest in suites s1,s2,s3 and any new suites\n" - " --ch_register=false s1 s2 s3 # register interest in suites s1,s2,s3 only\n" - " --ch_register=false # register handle, suites will be added later on\n" - " --ch_register=1 true s1 s2 s3 # drop handle 1 then register interest in suites s1,s2,s3 and any " - "new suites\n" - " # The client handle as the first argument is typically used by " - "GUI/python" - " # When the client handle is no zero, then it is dropped first\n" - "To list all suites and handles use --ch_suites"); + desc.add_options()(CtsApi::ch_register_arg(), + boost::program_options::value>()->multitoken()); break; } case ClientHandleCmd::DROP: { - desc.add_options()(CtsApi::ch_drop_arg(), - boost::program_options::value(), - "Drop/de-register the client handle.\n" - "Un-used handle should be dropped otherwise they will stay, in the server.\n" - " arg1 = handle(integer) # The handle must be an integer that is > 0\n" - "Usage:\n" - " --ch_drop=10 # drop the client handle 10\n" - "An error is returned if the handle had not previously been registered\n" - "The handle stored on the local client is set to zero\n" - "To list all suites and handles use --ch_suites"); + desc.add_options()(CtsApi::ch_drop_arg(), boost::program_options::value()); break; } case ClientHandleCmd::DROP_USER: { - desc.add_options()( - CtsApi::ch_drop_user_arg(), - boost::program_options::value()->implicit_value(std::string{}), - "Drop/de-register all handles associated with the given user.\n" - "If no user provided will drop for current user. Client must ensure un-used handle are dropped\n" - "otherwise they will stay, in the server.\n" - " arg1 = user # The user to be drooped, if left empty drop current user \n" - "Usage:\n" - " --ch_drop_user=ma0 # drop all handles associated with user ma0\n" - " --ch_drop_user # drop all handles associated with current user\n" - "An error is returned if no registered handles\n" - "To list all suites and handles use --ch_suites"); + desc.add_options()(CtsApi::ch_drop_user_arg(), + boost::program_options::value()->implicit_value(std::string{})); break; } case ClientHandleCmd::ADD: { desc.add_options()(CtsApi::ch_add_arg(), - boost::program_options::value>()->multitoken(), - "Add a set of suites, to an existing handle.\n" - " arg1 = handle(integer) # The handle must be an integer that is > 0\n" - " arg2 = names # should be a list of suite names, names not in the " - "definition are ignored\n" - "Usage:\n" - " --ch_add=10 s2 s3 s4 # add suites s2 s3,s4 to handle 10\n" - "An error is returned if the handle had not previously been registered\n" - "The handle is created with --ch_register command\n" - "To list all suites and handles use --ch_suites"); + boost::program_options::value>()->multitoken()); break; } case ClientHandleCmd::REMOVE: { desc.add_options()(CtsApi::ch_remove_arg(), - boost::program_options::value>()->multitoken(), - "Remove a set of suites, from an existing handle.\n" - " arg1 = handle(integer) # The handle must be an integer that is > 0\n" - " arg2 = names # should be a list of suite names, names not in the " - "definition are ignored\n" - "Usage:\n" - " --ch_rem=10 s2 s3 s4 # remove suites s2 s3,s4 from handle 10\n" - "The handle is created with --ch_register command\n" - "To list all suites and handles use --ch_suites"); + boost::program_options::value>()->multitoken()); break; } case ClientHandleCmd::AUTO_ADD: { - desc.add_options()( - CtsApi::ch_auto_add_arg(), - boost::program_options::value>()->multitoken(), - "Change an existing handle so that new suites can be added automatically.\n" - " arg1 = handle(integer) # The handle must be an integer that is > 0\n" - " arg2 = true | false # true means add new suites to my list, when they are created\n" - "Usage:\n" - " --ch_auto_add=10 true # modify handle 10 so that new suites, get added automatically to it\n" - " --ch_auto_add=10 false # modify handle 10 so that no new suites are added\n" - "The handle is created with --ch_register command\n" - "To list all suites and handles use --ch_suites"); + desc.add_options()(CtsApi::ch_auto_add_arg(), + boost::program_options::value>()->multitoken()); break; } case ClientHandleCmd::SUITES: { - desc.add_options()(CtsApi::ch_suites_arg(), "Shows all the client handles, and the suites they reference"); + desc.add_options()(CtsApi::ch_suites_arg(), ""); break; } default: diff --git a/libs/base/src/ecflow/base/cts/user/CtsCmd.cpp b/libs/base/src/ecflow/base/cts/user/CtsCmd.cpp index 8f5653193..60a697bfd 100644 --- a/libs/base/src/ecflow/base/cts/user/CtsCmd.cpp +++ b/libs/base/src/ecflow/base/cts/user/CtsCmd.cpp @@ -442,301 +442,83 @@ STC_Cmd_ptr CtsCmd::doHandleRequest(AbstractServer* as) const { return PreAllocatedReply::ok_cmd(); } -static const char* server_load_desc() { - /////////1/////////2/////////3/////////4/////////5/////////6/////////7/////////8 - return "Generates gnuplot files that show the server load graphically.\n" - "This is done by parsing the log file. If no log file is provided,\n" - "then the log file path is obtained from the server. If the returned\n" - "log file path is not accessible an error is returned\n" - "This command produces a three files in the CWD.\n" - " o ..gnuplot.dat\n" - " o ..gnuplot.script\n" - " o ..png\n\n" - "The generated script can be manually changed, to see different rendering\n" - "effects. i.e. just run 'gnuplot ..gnuplot.script'\n\n" - " arg1 = path to log file\n\n" - "If the path to log file is known, it is *preferable* to use this,\n" - "rather than requesting the log path from the server.\n\n" - "Usage:\n" - " --server_load=/path/to_log_file # Parses log and generate gnuplot files\n" - " --server_load # Log file path is requested from server\n" - " # which is then used to generate gnuplot files\n" - " # *AVOID* if log file path is accessible\n\n" - "Now use any png viewer to see the output i.e\n\n" - "> display ..png\n" - "> feh ..png\n" - "> eog ..png\n" - "> xdg-open ..png\n" - "> w3m ..png\n"; -} - void CtsCmd::addOption(boost::program_options::options_description& desc) const { switch (api_) { case CtsCmd::GET_ZOMBIES: { - desc.add_options()(CtsApi::zombieGetArg(), - "Returns the list of zombies from the server.\n" - "Results reported to standard output."); + desc.add_options()(CtsApi::zombieGetArg(), ""); break; } case CtsCmd::RESTORE_DEFS_FROM_CHECKPT: { - desc.add_options()(CtsApi::restoreDefsFromCheckPtArg(), - "Ask the server to load the definition from an check pt file.\n" - "The server must be halted and the definition in the server must be deleted\n" - "first, otherwise an error is returned"); + desc.add_options()(CtsApi::restoreDefsFromCheckPtArg(), ""); break; } case CtsCmd::RESTART_SERVER: { - desc.add_options()(CtsApi::restartServerArg(), - "Start job scheduling, communication with jobs, and respond to all requests.\n" - "The following table shows server behaviour in the different states.\n" - "|----------------------------------------------------------------------------------|\n" - "| Server State | User Request | Task Request |Job Scheduling | Auto-Check-pointing |\n" - "|--------------|--------------|--------------|---------------|---------------------|\n" - "| RUNNING | yes | yes | yes | yes |\n" - "| SHUTDOWN | yes | yes | no | yes |\n" - "| HALTED | yes | no | no | no |\n" - "|--------------|--------------|--------------|---------------|---------------------|"); + desc.add_options()(CtsApi::restartServerArg(), ""); break; } case CtsCmd::SHUTDOWN_SERVER: { desc.add_options()(CtsApi::shutdownServerArg(), - boost::program_options::value()->implicit_value(std::string{}), - "Stop server from scheduling new jobs.\n" - " arg1 = yes(optional) # use to bypass confirmation prompt,i.e\n" - " --shutdown=yes\n" - "The following table shows server behaviour in the different states.\n" - "|----------------------------------------------------------------------------------|\n" - "| Server State | User Request | Task Request |Job Scheduling | Auto-Check-pointing |\n" - "|--------------|--------------|--------------|---------------|---------------------|\n" - "| RUNNING | yes | yes | yes | yes |\n" - "| SHUTDOWN | yes | yes | no | yes |\n" - "| HALTED | yes | no | no | no |\n" - "|--------------|--------------|--------------|---------------|---------------------|"); + boost::program_options::value()->implicit_value(std::string{})); break; } case CtsCmd::HALT_SERVER: { desc.add_options()(CtsApi::haltServerArg(), - boost::program_options::value()->implicit_value(std::string{}), - "Stop server communication with jobs, and new job scheduling.\n" - "Also stops automatic check pointing\n" - " arg1 = yes(optional) # use to bypass confirmation prompt,i.e.\n" - " --halt=yes\n" - "The following table shows server behaviour in the different states.\n" - "|----------------------------------------------------------------------------------|\n" - "| Server State | User Request | Task Request |Job Scheduling | Auto-Check-pointing |\n" - "|--------------|--------------|--------------|---------------|---------------------|\n" - "| RUNNING | yes | yes | yes | yes |\n" - "| SHUTDOWN | yes | yes | no | yes |\n" - "| HALTED | yes | no | no | no |\n" - "|--------------|--------------|--------------|---------------|---------------------|"); + boost::program_options::value()->implicit_value(std::string{})); break; } case CtsCmd::TERMINATE_SERVER: { desc.add_options()(CtsApi::terminateServerArg(), - boost::program_options::value()->implicit_value(std::string{}), - "Terminate the server.\n" - " arg1 = yes(optional) # use to bypass confirmation prompt.i.e\n" - " --terminate=yes"); + boost::program_options::value()->implicit_value(std::string{})); break; } case CtsCmd::RELOAD_WHITE_LIST_FILE: { - desc.add_options()( - CtsApi::reloadwsfileArg(), - "Reload the white list file.\n" - "\n" - "The white list file (authorisation) is used to verify if a 'user' is allowed to perform a\n" - "specific command.\n" - "\n" - "The file path is specified as the ECF_LISTS variable, and loaded only once by the server\n" - "(on *startup*). This means that the file contents can be updated, but the file location\n" - "cannot change during the server execution.\n" - "\n" - "The ECF_LISTS variable can be used as follows:\n" - " - if ECF_LISTS is not specified, or if it is specified with value `ecf.lists`,\n" - " then the server will use the value `..ecf.lists`\n" - " - if ECF_LISTS is specified to be a path, such as /var/tmp/ecf.lists,\n" - " then the server will use this path to reload the white list file\n" - "\n" - "The server automatically loads the white list file content as part of the startup procedure,\n" - "considering that if the file is not present or is empty (i.e., just contains the version\n" - "number) then all users have read/write access.\n" - "\n" - "The reload operation will fail if file does not exist or if the content is invalid.\n" - "\n" - "Expected format for this file is:\n" - "\n" - "\n" - "# all characters after the first # in a line are considered comments and are discarded\n" - "# empty lines are also discarded\n" - "\n" - "4.4.14 # the version number is mandatory, even if no users are specified\n" - "# Users with read/write access\n" - "user1\n" - "user2 # comment\n" - "* # use this form if you want all users to have read/write access\n" - "\n" - "# Users with read access, must have - before user name\n" - "-user3 # comment\n" - "-user4\n" - "-* # use this form if you want all users to have read access\n" - "\n" - "\n" - "Usage:\n" - " --reloadwsfile"); + desc.add_options()(CtsApi::reloadwsfileArg(), ""); break; } case CtsCmd::RELOAD_PASSWD_FILE: { - desc.add_options()( - CtsApi::reloadpasswdfile_arg(), - "Reload the server password file.\n" - "\n" - "The password file (authentication) is used by the server to authenticate a 'user' by\n" - "verifying if the password provided by the user matches the one held by the server.\n" - "The password file is also used on the client to automatically load the password for the\n" - "'user' when connecting to the server.\n" - "\n" - "When the server is configured to use a password file, then ALL users must have a password.\n" - "\n" - "The file path is specified as the ECF_PASSWD environment variable, both for the client and\n" - "server, and is loaded only by the server on *startup*. This means that the file contents\n" - "can be updated (i.e., add/remove users), but the file location cannot change during the\n" - "server execution.\n" - "\n" - "The server automatically loads the password file content as part of the startup procedure.\n" - "\n" - "The ECF_PASSWD environment variable is used to specify the password file location,\n" - "considering that\n" - " - On the server, the default file name is ..ecf.passwd\n" - " - On the client, the default file name is ecf.passwd\n" - "\n" - "The format of the file is same for client and server:\n" - "\n" - "\n" - "4.5.0\n" - "# comment\n" - " # comment\n" - "\n" - "The following is an example\n" - "\n" - "4.5.0 # the version\n" - "fred machine1 3142 xxyyyd\n" - "fred machine2 3133 xxyyyd # comment\n" - "bill machine2 3133 xxyggyyd\n" - "\n" - "\n" - "Notice that the same user may appear multiple times (associated with different host/port).\n" - "This allows the client to use the same password file to contact multiple servers.\n" - "\n" - "For the password authentication to work, ensure the following:\n" - " - The password is defined for the client and server\n" - " - On the server, add at least the server administrator to the password file\n" - " Note: If an empty password file (i.e., containing just the version) is used,\n" - " no user is allowed access.\n" - " - On the client, the password file should be readable only by the 'user' itself\n" - "\n" - "Usage:\n" - " --reloadpasswdfile"); + desc.add_options()(CtsApi::reloadpasswdfile_arg(), ""); break; } case CtsCmd::RELOAD_CUSTOM_PASSWD_FILE: { - desc.add_options()( - CtsApi::reloadcustompasswdfile_arg(), - "Reload the server custom password file.\n" - "\n" - "The custom password file (authentication) is used by the server to authenticate a 'user' by\n" - "verifying if the password provided by the user matches the one held by the server. This\n" - "particular file is used for authentication of users that explicitly specify the user name\n" - "(either via the environment variable ECF_USER or the --user option).\n" - "\n" - "This mechanism should be used when most users use the machine login name, but a few users\n" - "specify their own user name, in which case the password must also be explicitly provided.\n" - "\n" - "The file path is specified as the ECF_CUSTOM_PASSWD environment variable, both for the\n" - "client and server, and is loaded only by the server on *startup*. This means that the file\n" - "contents can be updated (i.e., add/remove users), but the file location cannot change during\n" - "the server execution.\n" - "\n" - "The server automatically loads the password file content as part of the startup procedure.\n" - "\n" - "The ECF_CUSTOM_PASSWD environment variable is used to specify the password file location,\n" - "considering that\n" - " - On the server the default file name is ..ecf.custom_passwd\n" - " - On the client the default file name is ecf.custom_passwd\n" - "\n" - "The format of the file is same for client and server:\n\n" - "\n" - "4.5.0\n" - "# comment\n" - " # comment\n" - "\n" - "The following is an example\n" - "\n" - "4.5.0 # the version\n" - "fred machine1 3142 xxyyyd\n" - "fred machine2 3133 xxyyyd # comment\n" - "bill machine2 3133 xxyggyyd\n" - "\n" - "Notice that the same user may appear multiple times (associated with different host/port).\n" - "This allows the client to use the same password file to contact multiple servers.\n" - "\n" - "For the password authentication to work, ensure the following:\n" - " - The password is defined for the client and server\n" - " - On the server, add at least the server administrator to the password file\n" - " Note: If an empty password file (i.e., containing just the version) is used,\n" - " no user is allowed access.\n" - " - On the client, the password file should be readable only by the 'user' itself\n" - "\n" - "Usage:\n" - " --reloadcustompasswdfile"); + desc.add_options()(CtsApi::reloadcustompasswdfile_arg(), ""); break; } case CtsCmd::FORCE_DEP_EVAL: { - desc.add_options()(CtsApi::forceDependencyEvalArg(), "Force dependency evaluation. Used for DEBUG only."); + desc.add_options()(CtsApi::forceDependencyEvalArg(), ""); break; } case CtsCmd::PING: { - desc.add_options()( - CtsApi::pingServerArg(), - "Check if server is running on given host/port. Result reported to standard output.\n" - "Usage:\n" - " --ping --host=mach --port=3144 # Check if server alive on host mach & port 3144\n" - " --ping --host=fred # Check if server alive on host fred and port ECF_PORT,\n" - " # otherwise default port of 3141\n" - " --ping # Check if server alive by using environment variables\n" - " # ECF_HOST and ECF_PORT\n" - "If ECF_HOST not defined uses 'localhost', if ECF_PORT not defined assumes 3141"); + desc.add_options()(CtsApi::pingServerArg(), ""); break; } case CtsCmd::STATS: { - desc.add_options()(CtsApi::statsArg(), "Returns the server statistics as a string."); + desc.add_options()(CtsApi::statsArg(), ""); break; } case CtsCmd::STATS_SERVER: { - desc.add_options()(CtsApi::stats_server_arg(), - "Returns the server statistics as a struct and string. For test use only."); + desc.add_options()(CtsApi::stats_server_arg(), ""); break; } case CtsCmd::STATS_RESET: { - desc.add_options()(CtsApi::stats_reset_arg(), "Resets the server statistics."); + desc.add_options()(CtsApi::stats_reset_arg(), ""); break; } case CtsCmd::SUITES: { - desc.add_options()(CtsApi::suitesArg(), "Returns the list of suites, in the order defined in the server."); + desc.add_options()(CtsApi::suitesArg(), ""); break; } case CtsCmd::DEBUG_SERVER_ON: { - desc.add_options()(CtsApi::debug_server_on_arg(), "Enables debug output from the server"); + desc.add_options()(CtsApi::debug_server_on_arg(), ""); break; } case CtsCmd::DEBUG_SERVER_OFF: { - desc.add_options()(CtsApi::debug_server_off_arg(), "Disables debug output from the server"); + desc.add_options()(CtsApi::debug_server_off_arg(), ""); break; } case CtsCmd::SERVER_LOAD: { desc.add_options()(CtsApi::server_load_arg(), - boost::program_options::value()->implicit_value(std::string{}), - server_load_desc()); + boost::program_options::value()->implicit_value(std::string{})); break; } case CtsCmd::NO_CMD: diff --git a/libs/base/src/ecflow/base/cts/user/CtsNodeCmd.cpp b/libs/base/src/ecflow/base/cts/user/CtsNodeCmd.cpp index 84f2eb27b..cc3a9fed2 100644 --- a/libs/base/src/ecflow/base/cts/user/CtsNodeCmd.cpp +++ b/libs/base/src/ecflow/base/cts/user/CtsNodeCmd.cpp @@ -253,112 +253,36 @@ STC_Cmd_ptr CtsNodeCmd::doHandleRequest(AbstractServer* as) const { // return do_authenticate(as, cmd, absNodePath_); // } -static const char* job_gen_only_desc() { - return "Test hierarchical Job generation only, for chosen Node.\n" - "The jobs are generated independent of the dependencies\n" - "This will generate the jobs *only*, i.e. no job submission. Used for checking job generation only\n" - " arg = node path | arg = NULL\n" - " If no node path specified generates for all Tasks in the definition. For Test only"; -} - -static const char* job_gen_desc() { // dependency_dependent_job_submission - return "Job submission for chosen Node *based* on dependencies.\n" - "The server traverses the node tree every 60 seconds, and if the dependencies are free\n" - "does job generation and submission. Sometimes the user may free time/date dependencies\n" - "to avoid waiting for the server poll, this commands allows early job generation\n" - " arg = node path | arg = NULL\n" - " If no node path specified generates for full definition."; -} - -static const char* why_desc() { - return "Show the reason why a node is not running.\n" - "Can only be used with the group command. The group command must include a \n" - "'get' command(i.e returns the server defs)\n" - "The why command take a optional string argument representing a node path\n" - "Will return reason why the node is holding and for all its children.\n" - "If no arguments supplied will report on all nodes\n" - " arg = node path | arg = NULL\n" - "Usage:\n" - " --group=\"get; why\" # returns why for all holding nodes\n" - " --group=\"get; why=/suite/family\" # returns why for a specific node"; -} - -static const char* get_desc() { - return "Get the suite definition or node tree in form that is re-parse able\n" - "Get all suite node tree's from the server and write to standard out.\n" - "The output is parse-able, and can be used to re-load the definition\n" - " arg = NULL | arg = node path\n" - "Usage:\n" - " --get # gets the definition from the server,and writes to standard out\n" - " --get=/s1 # gets the suite from the server,and writes to standard out"; -} - -static const char* get_state_desc() { - return "Get state data. For the whole suite definition or individual nodes.\n" - "This will include event, meter, node state, trigger and time state.\n" - "The output is written to standard out.\n" - " arg = NULL | arg = node path\n" - "Usage:\n" - " --get_state # gets the definition from the server,and writes to standard out\n" - " --get_state=/s1 # gets the suite from the server,and writes to standard out"; -} - -const char* migrate_desc() { - return "Used to print state of the definition returned from the server to standard output.\n" - "The node state is shown in the comments.\n" - "This is the format used in the check point file, but with indentation.\n" - "Since this is essentially the defs format with state in the comments," - "it allows the definition to be migrated to future version of ecflow.\n" - "The output includes edit history but excludes externs.\n" - "When the definition is reloaded *NO* checking is done.\n" - "\n" - "The following shows a summary of the features associated with each choice:\n" - " --get --get_state --migrate\n" - "Auto generate externs Yes Yes No\n" - "Checking on reload Yes Yes No\n" - "Edit History No No Yes\n" - "Show trigger AST No Yes No\n\n" - "Usage:\n" - " --migrate # show all suites\n" - " --migrate=/s1 # show state for suite s1\n"; -} - void CtsNodeCmd::addOption(boost::program_options::options_description& desc) const { switch (api_) { case CtsNodeCmd::GET: { desc.add_options()(CtsApi::getArg(), - boost::program_options::value()->implicit_value(std::string{}), - get_desc()); + boost::program_options::value()->implicit_value(std::string{})); break; } case CtsNodeCmd::GET_STATE: { desc.add_options()(CtsApi::get_state_arg(), - boost::program_options::value()->implicit_value(std::string{}), - get_state_desc()); + boost::program_options::value()->implicit_value(std::string{})); break; } case CtsNodeCmd::MIGRATE: { desc.add_options()(CtsApi::migrate_arg(), - boost::program_options::value()->implicit_value(std::string{}), - migrate_desc()); + boost::program_options::value()->implicit_value(std::string{})); break; } case CtsNodeCmd::JOB_GEN: { desc.add_options()(CtsApi::job_genArg(), - boost::program_options::value()->implicit_value(std::string{}), - job_gen_desc()); + boost::program_options::value()->implicit_value(std::string{})); break; } case CtsNodeCmd::CHECK_JOB_GEN_ONLY: { desc.add_options()(CtsApi::checkJobGenOnlyArg(), - boost::program_options::value()->implicit_value(std::string{}), - job_gen_only_desc()); + boost::program_options::value()->implicit_value(std::string{})); break; } case CtsNodeCmd::WHY: { desc.add_options()(CtsApi::whyArg(), - boost::program_options::value()->implicit_value(std::string{}), - why_desc()); + boost::program_options::value()->implicit_value(std::string{})); break; } case CtsNodeCmd::NO_CMD: diff --git a/libs/base/src/ecflow/base/cts/user/DeleteCmd.cpp b/libs/base/src/ecflow/base/cts/user/DeleteCmd.cpp index 7ecd513f9..66bd18a92 100644 --- a/libs/base/src/ecflow/base/cts/user/DeleteCmd.cpp +++ b/libs/base/src/ecflow/base/cts/user/DeleteCmd.cpp @@ -165,24 +165,9 @@ void DeleteCmd::check_for_active_or_submitted_tasks(AbstractServer* as, Node* th } } -static const char* delete_node_desc() { - return "Deletes the specified node(s) or _ALL_ existing definitions( i.e delete all suites) in the server.\n" - " arg1 = [ force | yes ](optional) # Use this parameter to bypass checks, i.e. for active or submitted " - "tasks\n" - " arg2 = yes(optional) # Use 'yes' to bypass the confirmation prompt\n" - " arg3 = node paths | _all_ # _all_ means delete all suites\n" - " # node paths must start with a leading '/'\n" - "Usage:\n" - " --delete=_all_ # Delete all suites in server. Use with care.\n" - " --delete=/suite/f1/t1 # Delete node at /suite/f1/t1. This will prompt\n" - " --delete=force /suite/f1/t1 # Delete node at /suite/f1/t1 even if active or submitted\n" - " --delete=force yes /s1 /s2 # Delete suites s1,s2 even if active or submitted, bypassing prompt"; -} - void DeleteCmd::addOption(boost::program_options::options_description& desc) const { desc.add_options()(CtsApi::delete_node_arg(), - boost::program_options::value>()->multitoken(), - delete_node_desc()); + boost::program_options::value>()->multitoken()); } void DeleteCmd::create(Cmd_ptr& cmd, boost::program_options::variables_map& vm, AbstractClientEnv* ac) const { diff --git a/libs/base/src/ecflow/base/cts/user/EditScriptCmd.cpp b/libs/base/src/ecflow/base/cts/user/EditScriptCmd.cpp index 44a0b62ea..7bac79ac7 100644 --- a/libs/base/src/ecflow/base/cts/user/EditScriptCmd.cpp +++ b/libs/base/src/ecflow/base/cts/user/EditScriptCmd.cpp @@ -16,6 +16,7 @@ #include "ecflow/base/AbstractServer.hpp" #include "ecflow/base/AuthenticationDetails.hpp" #include "ecflow/base/AuthorisationDetails.hpp" +#include "ecflow/base/HelpCatalog.hpp" #include "ecflow/base/cts/user/CtsApi.hpp" #include "ecflow/base/stc/PreAllocatedReply.hpp" #include "ecflow/core/File.hpp" @@ -291,58 +292,9 @@ STC_Cmd_ptr EditScriptCmd::doHandleRequest(AbstractServer* as) const { const char* EditScriptCmd::arg() { return CtsApi::edit_script_arg(); } -const char* EditScriptCmd::desc() { - /////////1/////////2/////////3/////////4/////////5/////////6/////////7/////////8 - return "Allows user to edit, pre-process and submit the script.\n" - "Will allow pre-processing of arbitrary file with 'pre_process_file' option\n" - " arg1 = path to task # The path to the task/alias\n" - " arg2 = [ edit | pre_process | submit | pre_process_file | submit_file ]\n" - " edit : will return the script file to standard out. The script will\n" - " include used variables enclosed between %comment/%end at the\n" - " start of the file\n" - " pre_process: Will return the script file to standard out.The script will\n" - " include used variables enclosed between %comment/%end at the\n" - " start of the file and with all %include expanded\n" - " submit: Will extract the used variables from the supplied file, i.e\n" - " between the %comment/%end and use these them to generate the\n" - " job using the ecf file accessible from the server\n" - " pre_process_file: Will pre process the user supplied file.\n" - " Will expand includes,variable substitution,\n" - " remove manual & comment sections.\n" - " submit_file: Like submit, but the supplied file, is submitted by the server\n" - " The last 2 options allow complete freedom to debug the script file\n" - " arg3 = [ path_to_script_file ]\n" - " needed for option [ pre_process_file | submit_file ]\n" - " arg4 = create_alias (optional) default value is false, for use with 'submit_file' option\n" - " arg5 = no_run (optional) default value is false, i.e immediately run the alias\n" - " is no_run is specified the alias in only created\n" - "Usage:\n" - "--edit_script=/path/to/task edit > script_file\n" - " server returns script with the used variables to standard out\n" - " The user can choose to edit this file\n\n" - "--edit_script=/path/to/task pre_process > pre_processed_script_file\n" - " server will pre process the ecf file accessible from the server\n" - " (i.e expand all %includes) and return the file to standard out\n\n" - "--edit_script=/path/to/task submit script_file\n" - " Will extract the used variables in the 'script_file' and will uses these\n" - " variables during variable substitution of the ecf file accessible by the\n" - " server. This is then submitted as a job\n\n" - "--edit_script=/path/to/task pre_process_file file_to_pre_process\n" - " The server will pre-process the user supplied file and return the contents\n" - " to standard out. This pre-processing is the same as job file processing,\n" - " but on a arbitrary file\n\n" - "--edit_script=/path/to/task submit_file file_to_submit\n" - " Will extract the used variables in the 'file_to_submit' and will uses these\n" - " variables during variable substitution, the file is then submitted for job\n" - " generation by the server\n\n" - "--edit_script=/path/to/task submit_file file_to_submit create_alias\n" - " Like the the previous example but will create and run as an alias"; -} void EditScriptCmd::addOption(boost::program_options::options_description& desc) const { - desc.add_options()(EditScriptCmd::arg(), - boost::program_options::value>()->multitoken(), - EditScriptCmd::desc()); + desc.add_options()(EditScriptCmd::arg(), boost::program_options::value>()->multitoken()); } void EditScriptCmd::create(Cmd_ptr& cmd, boost::program_options::variables_map& vm, AbstractClientEnv* ac) const { @@ -353,7 +305,9 @@ void EditScriptCmd::create(Cmd_ptr& cmd, boost::program_options::variables_map& } if (args.size() < 2) { - throw std::runtime_error(MESSAGE("EditScriptCmd:At least 2 arguments required:\n" << EditScriptCmd::desc())); + throw std::runtime_error( + MESSAGE("EditScriptCmd:At least 2 arguments required:\n" + << HelpCatalog::description_for("edit_script").value_or(HelpCatalog::not_provided))); } std::string path_to_task = args[0]; @@ -397,7 +351,7 @@ void EditScriptCmd::create(Cmd_ptr& cmd, boost::program_options::variables_map& } ss << edit_types[i]; } - ss << "]\n" << EditScriptCmd::desc(); + ss << "]\n" << HelpCatalog::description_for("edit_script").value_or(HelpCatalog::not_provided); throw std::runtime_error(ss.str()); } @@ -409,7 +363,7 @@ void EditScriptCmd::create(Cmd_ptr& cmd, boost::program_options::variables_map& else { throw std::runtime_error( MESSAGE("When two arguments specified, the second argument must be one of [ edit | pre_process ]\n" - << EditScriptCmd::desc())); + << HelpCatalog::description_for("edit_script").value_or(HelpCatalog::not_provided))); } } @@ -426,7 +380,7 @@ void EditScriptCmd::create(Cmd_ptr& cmd, boost::program_options::variables_map& if ((create_alias || !run_alias) && edit_type != EditScriptCmd::SUBMIT_USER_FILE) { throw std::runtime_error( MESSAGE("The create_alias option is only valid when the second argument is 'submit_file' \n" - << EditScriptCmd::desc())); + << HelpCatalog::description_for("edit_script").value_or(HelpCatalog::not_provided))); } if (args.size() >= 3 && args.size() <= 5) { @@ -468,7 +422,9 @@ void EditScriptCmd::create(Cmd_ptr& cmd, boost::program_options::variables_map& } } - throw std::runtime_error(MESSAGE("Wrong number of arguments specified\n" << EditScriptCmd::desc())); + throw std::runtime_error( + MESSAGE("Wrong number of arguments specified\n" + << HelpCatalog::description_for("edit_script").value_or(HelpCatalog::not_provided))); } std::ostream& operator<<(std::ostream& os, const EditScriptCmd& c) { diff --git a/libs/base/src/ecflow/base/cts/user/EditScriptCmd.hpp b/libs/base/src/ecflow/base/cts/user/EditScriptCmd.hpp index 8ad110f32..8ab92a527 100644 --- a/libs/base/src/ecflow/base/cts/user/EditScriptCmd.hpp +++ b/libs/base/src/ecflow/base/cts/user/EditScriptCmd.hpp @@ -69,8 +69,7 @@ class EditScriptCmd final : public UserCmd { void create(Cmd_ptr& cmd, boost::program_options::variables_map& vm, AbstractClientEnv* clientEnv) const override; private: - static const char* arg(); // used for argument parsing - static const char* desc(); // The description of the argument as provided to user + static const char* arg(); // used for argument parsing STC_Cmd_ptr doHandleRequest(AbstractServer*) const override; // bool authenticate(AbstractServer*, STC_Cmd_ptr&) const override; diff --git a/libs/base/src/ecflow/base/cts/user/ForceCmd.cpp b/libs/base/src/ecflow/base/cts/user/ForceCmd.cpp index cba96f4a3..665aa99b0 100644 --- a/libs/base/src/ecflow/base/cts/user/ForceCmd.cpp +++ b/libs/base/src/ecflow/base/cts/user/ForceCmd.cpp @@ -16,6 +16,7 @@ #include "ecflow/base/AbstractServer.hpp" #include "ecflow/base/AuthenticationDetails.hpp" #include "ecflow/base/AuthorisationDetails.hpp" +#include "ecflow/base/HelpCatalog.hpp" #include "ecflow/base/cts/user/CtsApi.hpp" #include "ecflow/core/Converter.hpp" #include "ecflow/core/Extract.hpp" @@ -208,41 +209,9 @@ STC_Cmd_ptr ForceCmd::doHandleRequest(AbstractServer* as) const { const char* ForceCmd::arg() { return CtsApi::forceArg(); } -const char* ForceCmd::desc() { - /////////1/////////2/////////3/////////4/////////5/////////6/////////7/////////8 - return "Force a node to a given state, or set its event.\n" - "When a task is set to complete, it may be automatically re-queued if it has\n" - "multiple future time dependencies. However each time we force a complete it will\n" - "expire any time based attribute on that node. When the last time based attribute\n" - "expires, the node will stay in a complete state.\n" - "This behaviour allow Repeat values to be incremented interactively.\n" - "A repeat attribute is incremented when all the child nodes are complete\n" - "in this case the child nodes are automatically re-queued.\n" - " arg1 = [ unknown | complete | queued | submitted | active | aborted | clear | set ]\n" - " arg2 = (optional) recursive\n" - " Applies state to node and recursively to all its children\n" - " arg3 = (optional) full\n" - " Set repeat variables to last value, only works in conjunction\n" - " with recursive option\n" - " arg4 = path_to_node or path_to_node:: paths must begin with '/'\n" - "Usage:\n" - " --force=complete /suite/t1 /suite/t2 # Set task t1 & t2 to complete\n" - " --force=clear /suite/task:ev # Clear the event 'ev' on task /suite/task\n" - " --force=complete recursive /suite/f1 # Recursively set complete all children of /suite/f1\n" - "Effect:\n" - " Consider the effect of forcing complete when the current time is at 09:00\n" - " suite s1\n" - " task t1; time 12:00 # will complete straight away\n" - " task t2; time 10:00 13:00 01:00 # will complete on fourth attempt\n\n" - " --force=complete /s1/t1 /s1/t2\n" - " When we have a time range(i.e as shown with task t2), it is re-queued and the\n" - " next time slot is incremented for each complete, until it expires, and the task completes.\n" - " Use the Why command, to show next run time (i.e. next time slot)"; -} void ForceCmd::addOption(boost::program_options::options_description& desc) const { - desc.add_options()( - ForceCmd::arg(), boost::program_options::value>()->multitoken(), ForceCmd::desc()); + desc.add_options()(ForceCmd::arg(), boost::program_options::value>()->multitoken()); } void ForceCmd::create(Cmd_ptr& cmd, boost::program_options::variables_map& vm, AbstractClientEnv* ac) const { auto args = vm[arg()].as>(); @@ -254,7 +223,8 @@ void ForceCmd::create(Cmd_ptr& cmd, boost::program_options::variables_map& vm, A if (args.size() < 2) { throw std::runtime_error(MESSAGE("ForceCmd: At least two arguments expected for Force. Found " << args.size() << "\n" - << ForceCmd::desc() << "\n")); + << HelpCatalog::description_for("force").value_or(HelpCatalog::not_provided) + << "\n")); } std::vector options, paths; @@ -262,13 +232,14 @@ void ForceCmd::create(Cmd_ptr& cmd, boost::program_options::variables_map& vm, A args, options, paths, true /*treat_colon_in_path_as_path*/); // relative order is still preserved if (paths.empty()) { throw std::runtime_error(MESSAGE("ForceCmd: No paths specified. Paths must begin with a leading '/' character\n" - << ForceCmd::desc() << "\n")); + << HelpCatalog::description_for("force").value_or(HelpCatalog::not_provided) + << "\n")); } if (options.empty()) { throw std::runtime_error( MESSAGE("ForceCmd: Invalid argument list. Expected of:\n" << "[ unknown | complete | queued | submitted | active | aborted | clear | set]\n" - << ForceCmd::desc() << "\n")); + << HelpCatalog::description_for("force").value_or(HelpCatalog::not_provided) << "\n")); } bool is_valid_state = false; @@ -293,7 +264,9 @@ void ForceCmd::create(Cmd_ptr& cmd, boost::program_options::variables_map& vm, A stateOrEvent = options[i]; } else { - throw std::runtime_error(MESSAGE("ForceCmd: Invalid argument \n" << ForceCmd::desc() << "\n")); + throw std::runtime_error( + MESSAGE("ForceCmd: Invalid argument \n" + << HelpCatalog::description_for("force").value_or(HelpCatalog::not_provided) << "\n")); } } diff --git a/libs/base/src/ecflow/base/cts/user/ForceCmd.hpp b/libs/base/src/ecflow/base/cts/user/ForceCmd.hpp index 3d60fe6ef..644f434d1 100644 --- a/libs/base/src/ecflow/base/cts/user/ForceCmd.hpp +++ b/libs/base/src/ecflow/base/cts/user/ForceCmd.hpp @@ -56,8 +56,7 @@ class ForceCmd final : public UserCmd { void create(Cmd_ptr& cmd, boost::program_options::variables_map& vm, AbstractClientEnv* clientEnv) const override; private: - static const char* arg(); // used for argument parsing - static const char* desc(); // The description of the argument as provided to user + static const char* arg(); // used for argument parsing STC_Cmd_ptr doHandleRequest(AbstractServer*) const override; // bool authenticate(AbstractServer*, STC_Cmd_ptr&) const override; diff --git a/libs/base/src/ecflow/base/cts/user/FreeDepCmd.cpp b/libs/base/src/ecflow/base/cts/user/FreeDepCmd.cpp index 8cfdfa114..0432835b4 100644 --- a/libs/base/src/ecflow/base/cts/user/FreeDepCmd.cpp +++ b/libs/base/src/ecflow/base/cts/user/FreeDepCmd.cpp @@ -16,6 +16,7 @@ #include "ecflow/base/AbstractServer.hpp" #include "ecflow/base/AuthenticationDetails.hpp" #include "ecflow/base/AuthorisationDetails.hpp" +#include "ecflow/base/HelpCatalog.hpp" #include "ecflow/base/cts/user/CtsApi.hpp" #include "ecflow/core/Log.hpp" #include "ecflow/node/Node.hpp" @@ -116,27 +117,9 @@ STC_Cmd_ptr FreeDepCmd::doHandleRequest(AbstractServer* as) const { const char* FreeDepCmd::arg() { return CtsApi::freeDepArg(); } -const char* FreeDepCmd::desc() { - return "Free dependencies for a node. Defaults to triggers\n" - "After freeing the time related dependencies (i.e time,today,cron)\n" - "the next time slot will be missed.\n" - " arg1 = (optional) trigger\n" - " arg2 = (optional) all\n" - " Free trigger, date and all time dependencies\n" - " arg3 = (optional) date\n" - " Free date dependencies\n" - " arg4 = (optional) time\n" - " Free all time dependencies i.e time, day, today, cron\n" - " arg5 = List of paths. At least one required. Must start with a leading '/'\n" - "Usage:\n" - " --free-dep=/s1/t1 /s2/t2 # free trigger dependencies for task's t1,t2\n" - " --free-dep=all /s1/f1/t1 # free all dependencies of /s1/f1/t1\n" - " --free-dep=date /s1/f1 # free holding date dependencies of /s1/f1"; -} void FreeDepCmd::addOption(boost::program_options::options_description& desc) const { - desc.add_options()( - FreeDepCmd::arg(), boost::program_options::value>()->multitoken(), FreeDepCmd::desc()); + desc.add_options()(FreeDepCmd::arg(), boost::program_options::value>()->multitoken()); } void FreeDepCmd::create(Cmd_ptr& cmd, boost::program_options::variables_map& vm, AbstractClientEnv* ac) const { auto args = vm[arg()].as>(); @@ -148,7 +131,8 @@ void FreeDepCmd::create(Cmd_ptr& cmd, boost::program_options::variables_map& vm, if (args.size() < 1) { throw std::runtime_error(MESSAGE("FreeDepCmd: At least one arguments expected for Free dependencies. Found " << args.size() << "\n" - << FreeDepCmd::desc() << "\n")); + << HelpCatalog::description_for("free-dep").value_or(HelpCatalog::not_provided) + << "\n")); } std::vector options, paths; @@ -156,7 +140,7 @@ void FreeDepCmd::create(Cmd_ptr& cmd, boost::program_options::variables_map& vm, if (paths.empty()) { throw std::runtime_error( MESSAGE("FreeDepCmd: No paths specified. Paths must begin with a leading '/' character\n" - << FreeDepCmd::desc() << "\n")); + << HelpCatalog::description_for("free-dep").value_or(HelpCatalog::not_provided) << "\n")); } bool trigger = options.empty(); // If no options default to freeing trigger dependencies @@ -178,8 +162,10 @@ void FreeDepCmd::create(Cmd_ptr& cmd, boost::program_options::variables_map& vm, time = true; } else { - throw std::runtime_error(MESSAGE("FreeDepCmd: Invalid argument(" << options[i] << ")\n" - << FreeDepCmd::desc() << "\n")); + throw std::runtime_error( + MESSAGE("FreeDepCmd: Invalid argument(" + << options[i] << ")\n" + << HelpCatalog::description_for("free-dep").value_or(HelpCatalog::not_provided) << "\n")); } } assert(trigger || all || date || time); // at least one must be true diff --git a/libs/base/src/ecflow/base/cts/user/FreeDepCmd.hpp b/libs/base/src/ecflow/base/cts/user/FreeDepCmd.hpp index 88f2f4b62..7cc1d044a 100644 --- a/libs/base/src/ecflow/base/cts/user/FreeDepCmd.hpp +++ b/libs/base/src/ecflow/base/cts/user/FreeDepCmd.hpp @@ -63,8 +63,7 @@ class FreeDepCmd final : public UserCmd { void create(Cmd_ptr& cmd, boost::program_options::variables_map& vm, AbstractClientEnv* clientEnv) const override; private: - static const char* arg(); // used for argument parsing - static const char* desc(); // The description of the argument as provided to user + static const char* arg(); // used for argument parsing STC_Cmd_ptr doHandleRequest(AbstractServer*) const override; // bool authenticate(AbstractServer*, STC_Cmd_ptr&) const override; diff --git a/libs/base/src/ecflow/base/cts/user/GroupCTSCmd.cpp b/libs/base/src/ecflow/base/cts/user/GroupCTSCmd.cpp index a861d47f1..ddfe94024 100644 --- a/libs/base/src/ecflow/base/cts/user/GroupCTSCmd.cpp +++ b/libs/base/src/ecflow/base/cts/user/GroupCTSCmd.cpp @@ -386,21 +386,9 @@ STC_Cmd_ptr GroupCTSCmd::doHandleRequest(AbstractServer* as) const { const char* GroupCTSCmd::arg() { return CtsApi::groupArg(); } -const char* GroupCTSCmd::desc() { - return "Allows a series of ';' separated commands to be grouped and executed as one.\n" - "Some commands like halt, shutdown and terminate will prompt the user. To bypass the prompt\n" - "provide 'yes' as an additional parameter. See example below.\n" - " arg = string\n" - "Usage:\n" - " --group=\"halt=yes; reloadwsfile; restart;\"\n" - " # halt server,bypass the confirmation prompt,\n" - " # reload white list file, restart server\n" - " --group=\"get; show\" # get server defs, and write to standard output\n" - " --group=\"get=/s1; show state\" # get suite 's1', and write state to standard output"; -} void GroupCTSCmd::addOption(boost::program_options::options_description& desc) const { - desc.add_options()(GroupCTSCmd::arg(), boost::program_options::value(), GroupCTSCmd::desc()); + desc.add_options()(GroupCTSCmd::arg(), boost::program_options::value()); } void GroupCTSCmd::create(Cmd_ptr& cmd, boost::program_options::variables_map& vm, AbstractClientEnv* clientEnv) const { diff --git a/libs/base/src/ecflow/base/cts/user/GroupCTSCmd.hpp b/libs/base/src/ecflow/base/cts/user/GroupCTSCmd.hpp index 3e5c6d621..198331ea8 100644 --- a/libs/base/src/ecflow/base/cts/user/GroupCTSCmd.hpp +++ b/libs/base/src/ecflow/base/cts/user/GroupCTSCmd.hpp @@ -64,8 +64,7 @@ class GroupCTSCmd final : public UserCmd { void add_edit_history(Defs*) const override; private: - static const char* arg(); // used for argument parsing - static const char* desc(); // The description of the argument as provided to user + static const char* arg(); // used for argument parsing void setup_user_authentification(const std::string& user, const std::string& passwd) override; bool setup_user_authentification(AbstractClientEnv&) override; diff --git a/libs/base/src/ecflow/base/cts/user/LoadDefsCmd.cpp b/libs/base/src/ecflow/base/cts/user/LoadDefsCmd.cpp index 5b62056c3..437ab19f6 100644 --- a/libs/base/src/ecflow/base/cts/user/LoadDefsCmd.cpp +++ b/libs/base/src/ecflow/base/cts/user/LoadDefsCmd.cpp @@ -16,6 +16,7 @@ #include "ecflow/base/AbstractServer.hpp" #include "ecflow/base/AuthenticationDetails.hpp" #include "ecflow/base/AuthorisationDetails.hpp" +#include "ecflow/base/HelpCatalog.hpp" #include "ecflow/base/cts/user/CtsApi.hpp" #include "ecflow/base/stc/PreAllocatedReply.hpp" #include "ecflow/core/Filesystem.hpp" @@ -45,7 +46,7 @@ LoadDefsCmd::LoadDefsCmd(const std::string& defs_filename, if (defs_filename_.empty()) { throw std::runtime_error( MESSAGE("LoadDefsCmd::LoadDefsCmd: The pathname to the definition file must be provided\n" - << LoadDefsCmd::desc())); + << HelpCatalog::description_for("load").value_or(HelpCatalog::not_provided))); } defs_ptr defs = Defs::create(); @@ -163,29 +164,9 @@ void LoadDefsCmd::print_only(std::string& os) const { const char* LoadDefsCmd::arg() { return CtsApi::loadDefsArg(); } -const char* LoadDefsCmd::desc() { - return "Check and load definition or checkpoint file into server.\n" - "The loaded definition will be checked for valid trigger and complete expressions,\n" - "additionally in-limit references to limits will be validated.\n" - "If the server already has the 'suites' of the same name, then a error message is issued.\n" - "The suite's can be overwritten if the force option is used.\n" - "To just check the definition and not send to server, use 'check_only'\n" - "This command can also be used to load a checkpoint file into the server\n" - " arg1 = path to the definition file or checkpoint file\n" - " arg2 = (optional) [ force | check_only | print | stats ] # default = false for all\n" - "Usage:\n" - "--load=/my/home/exotic.def # will error if suites of same name exists\n" - "--load=/my/home/exotic.def force # overwrite suite's of same name in the server\n" - "--load=/my/home/exotic.def check_only # Just check, don't send to server\n" - "--load=/my/home/exotic.def stats # Show defs statistics, don't send to server\n" - "--load=host1.3141.check # Load checkpoint file to the server\n" - "--load=host1.3141.check print # print definition to standard out in defs format\n"; -} void LoadDefsCmd::addOption(boost::program_options::options_description& desc) const { - desc.add_options()(LoadDefsCmd::arg(), - boost::program_options::value>()->multitoken(), - LoadDefsCmd::desc()); + desc.add_options()(LoadDefsCmd::arg(), boost::program_options::value>()->multitoken()); } void LoadDefsCmd::create(Cmd_ptr& cmd, boost::program_options::variables_map& vm, AbstractClientEnv* clientEnv) const { diff --git a/libs/base/src/ecflow/base/cts/user/LoadDefsCmd.hpp b/libs/base/src/ecflow/base/cts/user/LoadDefsCmd.hpp index a392b9bb7..9d84c896f 100644 --- a/libs/base/src/ecflow/base/cts/user/LoadDefsCmd.hpp +++ b/libs/base/src/ecflow/base/cts/user/LoadDefsCmd.hpp @@ -51,8 +51,7 @@ class LoadDefsCmd final : public UserCmd { AbstractClientEnv* clientEnv); private: - static const char* arg(); // used for argument parsing - static const char* desc(); // The description of the command as provided to user + static const char* arg(); // used for argument parsing STC_Cmd_ptr doHandleRequest(AbstractServer*) const override; diff --git a/libs/base/src/ecflow/base/cts/user/LogCmd.cpp b/libs/base/src/ecflow/base/cts/user/LogCmd.cpp index 95c439b44..77c98a279 100644 --- a/libs/base/src/ecflow/base/cts/user/LogCmd.cpp +++ b/libs/base/src/ecflow/base/cts/user/LogCmd.cpp @@ -16,6 +16,7 @@ #include "ecflow/base/AbstractServer.hpp" #include "ecflow/base/AuthenticationDetails.hpp" #include "ecflow/base/AuthorisationDetails.hpp" +#include "ecflow/base/HelpCatalog.hpp" #include "ecflow/base/cts/user/CtsApi.hpp" #include "ecflow/base/stc/PreAllocatedReply.hpp" #include "ecflow/core/Converter.hpp" @@ -185,42 +186,9 @@ STC_Cmd_ptr LogCmd::doHandleRequest(AbstractServer* as) const { const char* LogCmd::arg() { return "log"; } -const char* LogCmd::desc() { - return "Get,clear,flush or create a new log file.\n" - "The user must ensure that a valid path is specified.\n" - "Specifying '--log=get' with a large number of lines from the server,\n" - "can consume a lot of **memory**. The log file can be a very large file,\n" - "hence we use a default of 100 lines, optionally the number of lines can be specified.\n" - " arg1 = [ get | clear | flush | new | path ]\n" - " get - Outputs the log file to standard out.\n" - " defaults to return the last 100 lines\n" - " The second argument can specify how many lines to return\n" - " clear - Clear the log file of its contents.\n" - " flush - Flush and close the log file. (only temporary) next time\n" - " server writes to log, it will be opened again. Hence it best\n" - " to halt the server first\n" - " new - Flush and close the existing log file, and start using the\n" - " the path defined for ECF_LOG. By changing this variable\n" - " a new log file path can be used\n" - " Alternatively an explicit path can also be provided\n" - " in which case ECF_LOG is also updated\n" - " path - Returns the path name to the existing log file\n" - " arg2 = [ new_path | optional last n lines ]\n" - " if get specified can specify lines to get. Value must be convertible to an integer\n" - " Otherwise if arg1 is 'new' then the second argument must be a path\n" - "Usage:\n" - " --log=get # Write the last 100 lines of the log file to standard out\n" - " --log=get 200 # Write the last 200 lines of the log file to standard out\n" - " --log=clear # Clear the log file. The log is now empty\n" - " --log=flush # Flush and close log file, next request will re-open log file\n" - " --log=new /path/to/new/log/file # Close and flush log file, and create a new log file, updates ECF_LOG\n" - " --log=new # Close and flush log file, and create a new log file using ECF_LOG " - "variable\n"; -} void LogCmd::addOption(boost::program_options::options_description& desc) const { - desc.add_options()( - LogCmd::arg(), boost::program_options::value>()->multitoken(), LogCmd::desc()); + desc.add_options()(LogCmd::arg(), boost::program_options::value>()->multitoken()); } void LogCmd::create(Cmd_ptr& cmd, boost::program_options::variables_map& vm, AbstractClientEnv* ac) const { @@ -299,7 +267,8 @@ void LogCmd::create(Cmd_ptr& cmd, boost::program_options::variables_map& vm, Abs return; } - throw std::runtime_error(MESSAGE("LogCmd: The arguments have not been specified correctly\n" << LogCmd::desc())); + throw std::runtime_error(MESSAGE("LogCmd: The arguments have not been specified correctly\n" + << HelpCatalog::description_for("log").value_or(HelpCatalog::not_provided))); } std::ostream& operator<<(std::ostream& os, const LogCmd& c) { diff --git a/libs/base/src/ecflow/base/cts/user/LogCmd.hpp b/libs/base/src/ecflow/base/cts/user/LogCmd.hpp index 231eb874b..f325f1dd2 100644 --- a/libs/base/src/ecflow/base/cts/user/LogCmd.hpp +++ b/libs/base/src/ecflow/base/cts/user/LogCmd.hpp @@ -42,8 +42,7 @@ class LogCmd final : public UserCmd { void create(Cmd_ptr& cmd, boost::program_options::variables_map& vm, AbstractClientEnv* clientEnv) const override; private: - static const char* arg(); // used for argument parsing - static const char* desc(); // The description of the argument as provided to user + static const char* arg(); // used for argument parsing STC_Cmd_ptr doHandleRequest(AbstractServer*) const override; diff --git a/libs/base/src/ecflow/base/cts/user/LogMessageCmd.cpp b/libs/base/src/ecflow/base/cts/user/LogMessageCmd.cpp index 33785f4dc..eee754133 100644 --- a/libs/base/src/ecflow/base/cts/user/LogMessageCmd.cpp +++ b/libs/base/src/ecflow/base/cts/user/LogMessageCmd.cpp @@ -56,15 +56,9 @@ STC_Cmd_ptr LogMessageCmd::doHandleRequest(AbstractServer* as) const { const char* LogMessageCmd::arg() { return CtsApi::logMsgArg(); } -const char* LogMessageCmd::desc() { - return "Writes the input string to the log file.\n" - " arg1 = string\n" - "Usage:\n" - " --msg=\"place me in the log file\""; -} void LogMessageCmd::addOption(boost::program_options::options_description& desc) const { - desc.add_options()(LogMessageCmd::arg(), boost::program_options::value(), LogMessageCmd::desc()); + desc.add_options()(LogMessageCmd::arg(), boost::program_options::value()); } void LogMessageCmd::create(Cmd_ptr& cmd, boost::program_options::variables_map& vm, AbstractClientEnv* ace) const { diff --git a/libs/base/src/ecflow/base/cts/user/LogMessageCmd.hpp b/libs/base/src/ecflow/base/cts/user/LogMessageCmd.hpp index c741dd945..8e28092e5 100644 --- a/libs/base/src/ecflow/base/cts/user/LogMessageCmd.hpp +++ b/libs/base/src/ecflow/base/cts/user/LogMessageCmd.hpp @@ -35,8 +35,7 @@ class LogMessageCmd final : public UserCmd { private: STC_Cmd_ptr doHandleRequest(AbstractServer*) const override; - static const char* arg(); // used for argument parsing - static const char* desc(); // The description of the argument as provided to user + static const char* arg(); // used for argument parsing std::string msg_; diff --git a/libs/base/src/ecflow/base/cts/user/MoveCmd.cpp b/libs/base/src/ecflow/base/cts/user/MoveCmd.cpp index e63b09ab5..d8a872ff9 100644 --- a/libs/base/src/ecflow/base/cts/user/MoveCmd.cpp +++ b/libs/base/src/ecflow/base/cts/user/MoveCmd.cpp @@ -195,13 +195,9 @@ STC_Cmd_ptr MoveCmd::doHandleRequest(AbstractServer* as) const { const char* MoveCmd::arg() { return "move"; } -const char* MoveCmd::desc() { - return "The move command is an internal cmd, Called by the plug cmd. Does not appear on public api."; -} void MoveCmd::addOption(boost::program_options::options_description& desc) const { - desc.add_options()( - MoveCmd::arg(), boost::program_options::value>()->multitoken(), MoveCmd::desc()); + desc.add_options()(MoveCmd::arg(), boost::program_options::value>()->multitoken()); } void MoveCmd::create(Cmd_ptr&, boost::program_options::variables_map&, AbstractClientEnv*) const { diff --git a/libs/base/src/ecflow/base/cts/user/MoveCmd.hpp b/libs/base/src/ecflow/base/cts/user/MoveCmd.hpp index 85dbc5c0e..a269bd71e 100644 --- a/libs/base/src/ecflow/base/cts/user/MoveCmd.hpp +++ b/libs/base/src/ecflow/base/cts/user/MoveCmd.hpp @@ -38,8 +38,7 @@ class MoveCmd final : public UserCmd { void create(Cmd_ptr& cmd, boost::program_options::variables_map& vm, AbstractClientEnv* clientEnv) const override; private: - static const char* arg(); // used for argument parsing - static const char* desc(); // The description of the argument as provided to user + static const char* arg(); // used for argument parsing STC_Cmd_ptr doHandleRequest(AbstractServer*) const override; diff --git a/libs/base/src/ecflow/base/cts/user/OrderNodeCmd.cpp b/libs/base/src/ecflow/base/cts/user/OrderNodeCmd.cpp index cb867a096..d74e22b4c 100644 --- a/libs/base/src/ecflow/base/cts/user/OrderNodeCmd.cpp +++ b/libs/base/src/ecflow/base/cts/user/OrderNodeCmd.cpp @@ -77,34 +77,9 @@ STC_Cmd_ptr OrderNodeCmd::doHandleRequest(AbstractServer* as) const { const char* OrderNodeCmd::arg() { return CtsApi::orderArg(); } -const char* OrderNodeCmd::desc() { - return "Re-orders the nodes held by the server\n" - " arg1 = node path\n" - " arg2 = [ top | bottom | alpha | order | up | down | runtime]\n" - "It should be noted that in the absence of triggers and time/date dependencies,\n" - "the tasks are submitted in order.\n" - "This changes the order and hence affects the submission order::\n\n" - " o top raises the node within its parent, so that it is first\n" - " o bottom lowers the node within its parent, so that it is last\n" - " o alpha Arranges for all the peers of selected note to be sorted alphabetically (case-insensitive)\n" - " o order Arranges for all the peers of selected note to be sorted in reverse " - "alphabet(case-insensitive)\n" - " o up Moves the selected node up one place amongst its peers\n" - " o down Moves the selected node down one place amongst its peers\n\n" - " o runtime Orders the nodes according to state change runtime\n" - " for families by accumulated runtime of its children\n" - " useful to submit the task that take longer earlier\n\n" - "This command can fail because:\n" - "- The node path does not exist in the server\n" - "- The order_type is not does not match one of arg2\n" - "Usage:\n" - " --order=/suite/f1 top # move node f1 to the top"; -} void OrderNodeCmd::addOption(boost::program_options::options_description& desc) const { - desc.add_options()(OrderNodeCmd::arg(), - boost::program_options::value>()->multitoken(), - OrderNodeCmd::desc()); + desc.add_options()(OrderNodeCmd::arg(), boost::program_options::value>()->multitoken()); } void OrderNodeCmd::create(Cmd_ptr& cmd, boost::program_options::variables_map& vm, AbstractClientEnv* ac) const { auto args = vm[OrderNodeCmd::arg()].as>(); diff --git a/libs/base/src/ecflow/base/cts/user/OrderNodeCmd.hpp b/libs/base/src/ecflow/base/cts/user/OrderNodeCmd.hpp index 56cbd34fb..861953836 100644 --- a/libs/base/src/ecflow/base/cts/user/OrderNodeCmd.hpp +++ b/libs/base/src/ecflow/base/cts/user/OrderNodeCmd.hpp @@ -38,8 +38,7 @@ class OrderNodeCmd final : public UserCmd { void create(Cmd_ptr& cmd, boost::program_options::variables_map& vm, AbstractClientEnv* clientEnv) const override; private: - static const char* arg(); // used for argument parsing - static const char* desc(); // The description of the argument as provided to user + static const char* arg(); // used for argument parsing STC_Cmd_ptr doHandleRequest(AbstractServer*) const override; // bool authenticate(AbstractServer*, STC_Cmd_ptr&) const override; diff --git a/libs/base/src/ecflow/base/cts/user/PathsCmd.cpp b/libs/base/src/ecflow/base/cts/user/PathsCmd.cpp index f21257383..5ece919cd 100644 --- a/libs/base/src/ecflow/base/cts/user/PathsCmd.cpp +++ b/libs/base/src/ecflow/base/cts/user/PathsCmd.cpp @@ -464,29 +464,6 @@ STC_Cmd_ptr PathsCmd::doHandleRequest(AbstractServer* as) const { // return do_authenticate(as, cmd, paths_); // } -static const char* get_check_desc() { - return "Checks the expression and limits in the server. Will also check trigger references.\n" - "Trigger expressions that reference paths that don't exist, will be reported as errors.\n" - "(Note: On the client side unresolved paths in trigger expressions must\n" - "have an associated 'extern' specified)\n" - " arg = [ _all_ | / | list of node paths ]\n" - "Usage:\n" - " --check=_all_ # Checks all the suites\n" - " --check=/ # Checks all the suites\n" - " --check=/s1 /s2/f1/t1 # Check suite /s1 and task t1"; -} - -static const char* get_kill_desc() { - return "Kills the job associated with the node.\n" - "If a family or suite is selected, will kill hierarchically.\n" - "Kill uses the ECF_KILL_CMD variable. After variable substitution it is invoked as a command.\n" - "The command should be written in such a way that the output is written to %ECF_JOB%.kill\n" - "as this allow the --file command to report the output: .e.e.\n" - " /home/ma/emos/bin/ecfkill %USER% %HOST% %ECF_RID% %ECF_JOB% > %ECF_JOB%.kill 2>&1::\n" - "Usage::\n" - " --kill=/s1/f1/t1 /s1/f2/t2 # kill the jobs for tasks t1 and t2\n" - " --file=/s1/f1/t1 kill # write to standard out the '.kill' file for task /s1/f1/t1"; -} const char* get_status_desc() { return "Shows the status of a job associated with a task, in %ECF_JOB%.stat file\n" "If a family or suite is selected, will invoke status command hierarchically.\n" @@ -594,44 +571,35 @@ void PathsCmd::addOption(boost::program_options::options_description& desc) cons switch (api_) { case PathsCmd::CHECK: { - desc.add_options()( - CtsApi::check_arg(), po::value>()->multitoken(), get_check_desc()); + desc.add_options()(CtsApi::check_arg(), po::value>()->multitoken()); break; } case PathsCmd::SUSPEND: { - desc.add_options()( - CtsApi::suspend_arg(), po::value>()->multitoken(), suspend_desc()); + desc.add_options()(CtsApi::suspend_arg(), po::value>()->multitoken()); break; } case PathsCmd::RESUME: { - desc.add_options()( - CtsApi::resume_arg(), po::value>()->multitoken(), resume_desc()); + desc.add_options()(CtsApi::resume_arg(), po::value>()->multitoken()); break; } case PathsCmd::KILL: { - desc.add_options()( - CtsApi::kill_arg(), po::value>()->multitoken(), get_kill_desc()); + desc.add_options()(CtsApi::kill_arg(), po::value>()->multitoken()); break; } case PathsCmd::STATUS: { - desc.add_options()( - CtsApi::statusArg(), po::value>()->multitoken(), get_status_desc()); + desc.add_options()(CtsApi::statusArg(), po::value>()->multitoken()); break; } case PathsCmd::EDIT_HISTORY: { - desc.add_options()(CtsApi::edit_history_arg(), - po::value>()->multitoken(), - get_edit_history_desc()); + desc.add_options()(CtsApi::edit_history_arg(), po::value>()->multitoken()); break; } case PathsCmd::ARCHIVE: { - desc.add_options()( - CtsApi::archive_arg(), po::value>()->multitoken(), archive_desc()); + desc.add_options()(CtsApi::archive_arg(), po::value>()->multitoken()); break; } case PathsCmd::RESTORE: { - desc.add_options()( - CtsApi::restore_arg(), po::value>()->multitoken(), restore_desc()); + desc.add_options()(CtsApi::restore_arg(), po::value>()->multitoken()); break; } case PathsCmd::NO_CMD: diff --git a/libs/base/src/ecflow/base/cts/user/PlugCmd.cpp b/libs/base/src/ecflow/base/cts/user/PlugCmd.cpp index d4cc7dfbc..afc2b89b3 100644 --- a/libs/base/src/ecflow/base/cts/user/PlugCmd.cpp +++ b/libs/base/src/ecflow/base/cts/user/PlugCmd.cpp @@ -17,6 +17,7 @@ #include "ecflow/base/AuthenticationDetails.hpp" #include "ecflow/base/AuthorisationDetails.hpp" #include "ecflow/base/Client.hpp" +#include "ecflow/base/HelpCatalog.hpp" #include "ecflow/base/cts/user/CtsApi.hpp" #include "ecflow/base/cts/user/MoveCmd.hpp" #include "ecflow/base/stc/PreAllocatedReply.hpp" @@ -273,27 +274,9 @@ STC_Cmd_ptr PlugCmd::doHandleRequest(AbstractServer* as) const { const char* PlugCmd::arg() { return CtsApi::plugArg(); } -const char* PlugCmd::desc() { - return "Plug command is used to move nodes.\n" - "The destination node can be on another server In which case the destination\n" - "path should be of the form ':/suite/family/task\n" - " arg1 = path to source node\n" - " arg2 = path to the destination node\n" - "This command can fail because:\n" - "- Source node is in a 'active' or 'submitted' state\n" - "- Another user already has an lock\n" - "- source/destination paths do not exist on the corresponding servers\n" - "- If the destination node path is empty, i.e. only host:port is specified,\n" - " then the source node must correspond to a suite.\n" - "- If the source node is added as a child, then its name must be unique\n" - " amongst its peers\n" - "Usage:\n" - " --plug=/suite macX:3141 # move the suite to ecFlow server on host(macX) and port(3141)"; -} void PlugCmd::addOption(boost::program_options::options_description& desc) const { - desc.add_options()( - PlugCmd::arg(), boost::program_options::value>()->multitoken(), PlugCmd::desc()); + desc.add_options()(PlugCmd::arg(), boost::program_options::value>()->multitoken()); } void PlugCmd::create(Cmd_ptr& cmd, boost::program_options::variables_map& vm, AbstractClientEnv* ace) const { @@ -304,8 +287,10 @@ void PlugCmd::create(Cmd_ptr& cmd, boost::program_options::variables_map& vm, Ab } if (args.size() != 2) { - throw std::runtime_error(MESSAGE("PlugCmd: Two arguments are expected, found " << args.size() << "\n" - << PlugCmd::desc() << "\n")); + throw std::runtime_error(MESSAGE("PlugCmd: Two arguments are expected, found " + << args.size() << "\n" + << HelpCatalog::description_for("plug").value_or(HelpCatalog::not_provided) + << "\n")); } std::string sourceNode = args[0]; diff --git a/libs/base/src/ecflow/base/cts/user/PlugCmd.hpp b/libs/base/src/ecflow/base/cts/user/PlugCmd.hpp index ca92bf731..a4c045429 100644 --- a/libs/base/src/ecflow/base/cts/user/PlugCmd.hpp +++ b/libs/base/src/ecflow/base/cts/user/PlugCmd.hpp @@ -39,8 +39,7 @@ class PlugCmd final : public UserCmd { void create(Cmd_ptr& cmd, boost::program_options::variables_map& vm, AbstractClientEnv* clientEnv) const override; private: - static const char* arg(); // used for argument parsing - static const char* desc(); // The description of the argument as provided to user + static const char* arg(); // used for argument parsing STC_Cmd_ptr doHandleRequest(AbstractServer*) const override; diff --git a/libs/base/src/ecflow/base/cts/user/QueryCmd.cpp b/libs/base/src/ecflow/base/cts/user/QueryCmd.cpp index 17922afa8..739aba9e0 100644 --- a/libs/base/src/ecflow/base/cts/user/QueryCmd.cpp +++ b/libs/base/src/ecflow/base/cts/user/QueryCmd.cpp @@ -17,6 +17,7 @@ #include "ecflow/base/AbstractServer.hpp" #include "ecflow/base/AuthenticationDetails.hpp" #include "ecflow/base/AuthorisationDetails.hpp" +#include "ecflow/base/HelpCatalog.hpp" #include "ecflow/base/cts/user/CtsApi.hpp" #include "ecflow/base/stc/PreAllocatedReply.hpp" #include "ecflow/core/Converter.hpp" @@ -71,8 +72,7 @@ ecf::authorisation_t QueryCmd::authorise(AbstractServer& server) const { } void QueryCmd::addOption(boost::program_options::options_description& desc) const { - desc.add_options()( - QueryCmd::arg(), boost::program_options::value>()->multitoken(), QueryCmd::desc()); + desc.add_options()(QueryCmd::arg(), boost::program_options::value>()->multitoken()); } void QueryCmd::create(Cmd_ptr& cmd, boost::program_options::variables_map& vm, AbstractClientEnv* clientEnv) const { @@ -109,9 +109,10 @@ void QueryCmd::create(Cmd_ptr& cmd, boost::program_options::variables_map& vm, A << query_type << " args size = " << args.size() << " expected 2 arguments")); } if (attribute.empty()) { - throw std::runtime_error(MESSAGE("QueryCmd: no attribute specified: query type: " - << query_type << " path+attribute: " << path_and_name << "\n" - << QueryCmd::desc())); + throw std::runtime_error( + MESSAGE("QueryCmd: no attribute specified: query type: " + << query_type << " path+attribute: " << path_and_name << "\n" + << HelpCatalog::description_for("query").value_or(HelpCatalog::not_provided))); } } else if (query_type == "trigger") { @@ -125,8 +126,9 @@ void QueryCmd::create(Cmd_ptr& cmd, boost::program_options::variables_map& vm, A } } if (attribute.empty()) { - throw std::runtime_error(MESSAGE("QueryCmd: no attribute specified: query type: trigger\n" - << QueryCmd::desc())); + throw std::runtime_error( + MESSAGE("QueryCmd: no attribute specified: query type: trigger\n" + << HelpCatalog::description_for("query").value_or(HelpCatalog::not_provided))); } } else if (query_type == "state" || query_type == "dstate") { @@ -179,65 +181,6 @@ const char* QueryCmd::arg() { return CtsApi::queryArg(); } -const char* QueryCmd::desc() { - return "Query the status of attributes\n" - " i.e state,dstate,repeat,event,meter,label,variable or trigger expression without blocking\n" - " - state return [unknown | complete | queued | aborted | submitted | active] to standard " - "out\n" - " - dstate return [unknown | complete | queued | suspended | aborted | submitted | active] to standard " - "out\n" - " - repeat returns current value as a string to standard out\n" - " - event return 'set' | 'clear' to standard out\n" - " - meter return value of the meter to standard out\n" - " - limit return current value of limit to standard out\n" - " - limit_max return limit max value to standard out\n" - " - label return new value otherwise the old value\n" - " - variable return value of the variable, repeat or generated variable to standard out,\n" - " will search up the node tree. When path is '/', the variable is looked up on the\n" - " server itself, i.e. 'ecflow_client --query variable /:name'\n" - " - trigger returns 'true' if the expression is true, otherwise 'false'\n\n" - "If this command is called within a '.ecf' script we will additionally log the task calling this command\n" - "This is required to aid debugging for excessive use of this command\n" - "The command will fail if the node path to the attribute does not exist in the definition and if:\n" - " - repeat The repeat is not found\n" - " - event The event is not found\n" - " - meter The meter is not found\n" - " - limit/limit_max The limit is not found\n" - " - label The label is not found\n" - " - variable No user or generated variable or repeat of that name found on node or its parents,\n" - " or (when path is '/') no user or server variable of that name found on the server\n" - " - trigger Trigger does not parse, or reference to nodes/attributes in the expression are not valid\n" - "Arguments:\n" - " arg1 = [ state | dstate | repeat | event | meter | label | variable | trigger | limit | limit_max ]\n" - " arg2 = | :name where name is name of a event, meter, label, limit or variable.\n" - " path '/' represents the server itself, and can only be used with 'state' or 'variable'\n" - " arg3 = trigger expression | prev | next # prev,next only used when arg1 is repeat\n\n" - "Usage:\n" - " ecflow_client --query state / # return top level state to standard " - "out\n" - " ecflow_client --query state /path/to/node # return node state to standard out\n" - " ecflow_client --query dstate /path/to/node # state that can included suspended\n" - " ecflow_client --query repeat /path/to/node # return the current value as a string\n" - " ecflow_client --query repeat /path/to/node prev # return the previous value as a " - "string\n" - " ecflow_client --query repeat /path/to/node next # return the next value as a string\n" - " ecflow_client --query event /path/to/task/with/event:event_name # return set | clear to standard out\n" - " ecflow_client --query meter /path/to/task/with/meter:meter_name # returns the current value of the " - "meter to standard out\n" - " ecflow_client --query limit /path/to/task/with/limit:limit_name # returns the current value of the " - "limit to standard out\n" - " ecflow_client --query limit_max /path/to/task/with/limit:limit_name # returns the max value of the limit " - "to standard out\n" - " ecflow_client --query label /path/to/task/with/label:label_name # returns the current value of the " - "label to standard out\n" - " ecflow_client --query variable /path/to/task/with/var:var_name # returns the variable value to " - "standard out\n" - " ecflow_client --query variable /:var_name # returns the server variable value " - "to standard out\n" - " ecflow_client --query trigger /path/to/node/with/trigger \"/suite/task == complete\" # return true if " - "expression evaluates false otherwise\n"; -} - STC_Cmd_ptr QueryCmd::doHandleRequest(AbstractServer* as) const { as->update_stats().query_++; diff --git a/libs/base/src/ecflow/base/cts/user/QueryCmd.hpp b/libs/base/src/ecflow/base/cts/user/QueryCmd.hpp index c23c3c895..84867ea03 100644 --- a/libs/base/src/ecflow/base/cts/user/QueryCmd.hpp +++ b/libs/base/src/ecflow/base/cts/user/QueryCmd.hpp @@ -44,8 +44,7 @@ class QueryCmd final : public UserCmd { void create(Cmd_ptr& cmd, boost::program_options::variables_map& vm, AbstractClientEnv* clientEnv) const override; private: - static const char* arg(); // used for argument parsing - static const char* desc(); // The description of the argument as provided to user + static const char* arg(); // used for argument parsing bool handleRequestIsTestable() const override { return false; } STC_Cmd_ptr doHandleRequest(AbstractServer*) const override; diff --git a/libs/base/src/ecflow/base/cts/user/ReplaceNodeCmd.cpp b/libs/base/src/ecflow/base/cts/user/ReplaceNodeCmd.cpp index 49df9fd44..7e3e31823 100644 --- a/libs/base/src/ecflow/base/cts/user/ReplaceNodeCmd.cpp +++ b/libs/base/src/ecflow/base/cts/user/ReplaceNodeCmd.cpp @@ -181,39 +181,9 @@ void ReplaceNodeCmd::print_only(std::string& os) const { const char* ReplaceNodeCmd::arg() { return CtsApi::replace_arg(); } -const char* ReplaceNodeCmd::desc() { - /////////1/////////2/////////3/////////4/////////5/////////6/////////7/////////8 - return "Replaces a node in the server, with the given path\n" - "Can also be used to add nodes in the server\n" - " arg1 = path to node\n" - " must exist in the client defs(arg2). This is also the node we want to\n" - " replace in the server\n" - " arg2 = path to client definition file\n" - " provides the definition of the new node\n" - " arg3 = (optional) [ parent | false ] (default = parent)\n" - " create parent families or suite as needed, when arg1 does not\n" - " exist in the server\n" - " arg4 = (optional) force (default = false) \n" - " Force the replacement even if it causes zombies to be created\n" - "Replace can fail if:\n" - "- The node path(arg1) does not exist in the provided client definition(arg2)\n" - "- The client definition(arg2) must be free of errors\n" - "- If the third argument is not provided, then node path(arg1) must exist in the server\n" - "- Nodes to be replaced are in active/submitted state, in which case arg4(force) can be used\n\n" - "Replace will preserve the suspended status, if this is not required please re-queue first\n" - "After replace is done, we check trigger expressions. These are reported to standard output.\n" - "It is up to the user to correct invalid trigger expressions, otherwise the tasks will *not* run.\n" - "Please note, you can use --check to check trigger expression and limits in the server.\n" - "For more information use --help check.\n\n" - "Usage:\n" - " --replace=/suite/f1/t1 /tmp/client.def parent # Add/replace node tree /suite/f1/t1\n" - " --replace=/suite/f1/t1 /tmp/client.def false force # replace t1 even if its active or submitted"; -} void ReplaceNodeCmd::addOption(boost::program_options::options_description& desc) const { - desc.add_options()(ReplaceNodeCmd::arg(), - boost::program_options::value>()->multitoken(), - ReplaceNodeCmd::desc()); + desc.add_options()(ReplaceNodeCmd::arg(), boost::program_options::value>()->multitoken()); } void ReplaceNodeCmd::create(Cmd_ptr& cmd, boost::program_options::variables_map& vm, diff --git a/libs/base/src/ecflow/base/cts/user/ReplaceNodeCmd.hpp b/libs/base/src/ecflow/base/cts/user/ReplaceNodeCmd.hpp index a67a7cde5..5d1a0eb6a 100644 --- a/libs/base/src/ecflow/base/cts/user/ReplaceNodeCmd.hpp +++ b/libs/base/src/ecflow/base/cts/user/ReplaceNodeCmd.hpp @@ -42,8 +42,7 @@ class ReplaceNodeCmd final : public UserCmd { // used in test private: - static const char* arg(); // used for argument parsing - static const char* desc(); // The description of the argument as provided to user + static const char* arg(); // used for argument parsing STC_Cmd_ptr doHandleRequest(AbstractServer*) const override; // bool authenticate(AbstractServer*, STC_Cmd_ptr&) const override; diff --git a/libs/base/src/ecflow/base/cts/user/RequeueNodeCmd.cpp b/libs/base/src/ecflow/base/cts/user/RequeueNodeCmd.cpp index ba40b3e1f..02b50e024 100644 --- a/libs/base/src/ecflow/base/cts/user/RequeueNodeCmd.cpp +++ b/libs/base/src/ecflow/base/cts/user/RequeueNodeCmd.cpp @@ -16,6 +16,7 @@ #include "ecflow/base/AbstractServer.hpp" #include "ecflow/base/AuthenticationDetails.hpp" #include "ecflow/base/AuthorisationDetails.hpp" +#include "ecflow/base/HelpCatalog.hpp" #include "ecflow/base/cts/user/CtsApi.hpp" #include "ecflow/base/stc/PreAllocatedReply.hpp" #include "ecflow/core/Log.hpp" @@ -186,26 +187,9 @@ STC_Cmd_ptr RequeueNodeCmd::doHandleRequest(AbstractServer* as) const { const char* RequeueNodeCmd::arg() { return CtsApi::requeueArg(); } -const char* RequeueNodeCmd::desc() { - return "Re queues the specified node(s)\n" - " If any child of the specified node(s) is in a suspended state, this state is cleared\n" - "Repeats are reset to their starting values, relative time attributes are reset.\n" - " arg1 = (optional) [ abort | force ]\n" - " abort = re-queue only aborted tasks below node\n" - " force = Force the re-queueing even if there are nodes that are active or submitted\n" - " = Checks if any tasks are in submitted or active states below the node\n" - " if so does nothing. Otherwise re-queues the node.\n" - " arg2 = list of node paths. The node paths must begin with a leading '/' character\n\n" - "Usage:\n" - " --requeue=abort /suite/f1 # re-queue all aborted tasks of /suite/f1\n" - " --requeue=force /suite/f1 # forcibly re-queue /suite/f1 and all its children.May cause zombies.\n" - " --requeue=/s1/f1/t1 /s1/t2 # Re-queue node '/suite/f1/t1' and '/s1/t2'"; -} void RequeueNodeCmd::addOption(boost::program_options::options_description& desc) const { - desc.add_options()(RequeueNodeCmd::arg(), - boost::program_options::value>()->multitoken(), - RequeueNodeCmd::desc()); + desc.add_options()(RequeueNodeCmd::arg(), boost::program_options::value>()->multitoken()); } void RequeueNodeCmd::create(Cmd_ptr& cmd, boost::program_options::variables_map& vm, AbstractClientEnv* ac) const { auto args = vm[RequeueNodeCmd::arg()].as>(); @@ -228,7 +212,7 @@ void RequeueNodeCmd::create(Cmd_ptr& cmd, boost::program_options::variables_map& throw std::runtime_error(MESSAGE( "RequeueNodeCmd: No paths specified. At least one path expected. Paths must begin with a leading '/' " "character\n" - << RequeueNodeCmd::desc() << "\n")); + << HelpCatalog::description_for("requeue").value_or(HelpCatalog::not_provided) << "\n")); } RequeueNodeCmd::Option option = RequeueNodeCmd::NO_OPTION; @@ -247,13 +231,15 @@ void RequeueNodeCmd::create(Cmd_ptr& cmd, boost::program_options::variables_map& } } else { - throw std::runtime_error(MESSAGE("RequeueNodeCmd: RequeueNodeCmd: Expected : [force | abort ] paths.\n" - << RequeueNodeCmd::desc() << "\n")); + throw std::runtime_error( + MESSAGE("RequeueNodeCmd: RequeueNodeCmd: Expected : [force | abort ] paths.\n" + << HelpCatalog::description_for("requeue").value_or(HelpCatalog::not_provided) << "\n")); } } if (options.size() > 1) { throw std::runtime_error(MESSAGE("RequeueNodeCmd: Expected only a single option i.e [ force | abort ]\n" - << RequeueNodeCmd::desc() << "\n")); + << HelpCatalog::description_for("requeue").value_or(HelpCatalog::not_provided) + << "\n")); } cmd = std::make_shared(paths, option); diff --git a/libs/base/src/ecflow/base/cts/user/RequeueNodeCmd.hpp b/libs/base/src/ecflow/base/cts/user/RequeueNodeCmd.hpp index 730335150..b814a391d 100644 --- a/libs/base/src/ecflow/base/cts/user/RequeueNodeCmd.hpp +++ b/libs/base/src/ecflow/base/cts/user/RequeueNodeCmd.hpp @@ -44,8 +44,7 @@ class RequeueNodeCmd final : public UserCmd { void create(Cmd_ptr& cmd, boost::program_options::variables_map& vm, AbstractClientEnv* clientEnv) const override; private: - static const char* arg(); // used for argument parsing - static const char* desc(); // The description of the argument as provided to user + static const char* arg(); // used for argument parsing STC_Cmd_ptr doHandleRequest(AbstractServer*) const override; // bool authenticate(AbstractServer*, STC_Cmd_ptr&) const override; diff --git a/libs/base/src/ecflow/base/cts/user/RunNodeCmd.cpp b/libs/base/src/ecflow/base/cts/user/RunNodeCmd.cpp index cfc9966b3..9733f929c 100644 --- a/libs/base/src/ecflow/base/cts/user/RunNodeCmd.cpp +++ b/libs/base/src/ecflow/base/cts/user/RunNodeCmd.cpp @@ -16,6 +16,7 @@ #include "ecflow/base/AbstractServer.hpp" #include "ecflow/base/AuthenticationDetails.hpp" #include "ecflow/base/AuthorisationDetails.hpp" +#include "ecflow/base/HelpCatalog.hpp" #include "ecflow/base/cts/user/CtsApi.hpp" #include "ecflow/base/stc/PreAllocatedReply.hpp" #include "ecflow/core/Log.hpp" @@ -125,35 +126,9 @@ STC_Cmd_ptr RunNodeCmd::doHandleRequest(AbstractServer* as) const { const char* RunNodeCmd::arg() { return CtsApi::runArg(); } -const char* RunNodeCmd::desc() { - return "Ignore triggers, limits, time or date dependencies, just run the Task.\n" - "When a job completes, it may be automatically re-queued if it has a cron\n" - "or multiple time dependencies. If we have multiple time based attributes,\n" - "then each run, will expire the time.\n" - "When we run before the time, we want to avoid re-running the task then\n" - "a flag is set, so that it is not automatically re-queued.\n" - "A repeat attribute is incremented when all the child nodes are complete\n" - "in this case the child nodes are automatically re-queued.\n" - "Hence this command can be aid, in allowing a Repeat attribute to be incremented\n" - " arg1 = (optional)force\n" - " Forcibly run, even if there are nodes that are active or submitted\n" - " This can result in zombie creation\n" - " arg2 = node path(s). The paths must begin with a leading '/' character.\n" - " If the path is /suite/family will recursively run all tasks\n" - " When providing multiple paths avoid running the same task twice\n" - "Example:\n" - " --run=/suite/t1 # run task t1\n" - "Effect:\n" - " task t1; time 12:00 # will complete if run manually\n" - " task t2; time 10:00 13:00 01:00 # will run 4 times before completing\n" - " When we have a time range(i.e as shown with task t2), then next time slot\n" - " is incremented for each run, until it expires, and the task completes.\n" - " Use the Why command, to show next run time (i.e. next time slot)"; -} void RunNodeCmd::addOption(boost::program_options::options_description& desc) const { - desc.add_options()( - RunNodeCmd::arg(), boost::program_options::value>()->multitoken(), RunNodeCmd::desc()); + desc.add_options()(RunNodeCmd::arg(), boost::program_options::value>()->multitoken()); } void RunNodeCmd::create(Cmd_ptr& cmd, boost::program_options::variables_map& vm, AbstractClientEnv* ace) const { auto args = vm[RunNodeCmd::arg()].as>(); @@ -167,20 +142,23 @@ void RunNodeCmd::create(Cmd_ptr& cmd, boost::program_options::variables_map& vm, if (paths.empty()) { throw std::runtime_error( MESSAGE("RunNodeCmd: No paths specified. Paths must begin with a leading '/' character\n" - << RunNodeCmd::desc() << "\n")); + << HelpCatalog::description_for("run").value_or(HelpCatalog::not_provided) << "\n")); } bool force = false; if (!options.empty()) { if (options.size() != 1) { throw std::runtime_error(MESSAGE("RunNodeCmd: Invalid arguments. Expected a single optional 'force'\n" - << RunNodeCmd::desc() << "\n")); + << HelpCatalog::description_for("run").value_or(HelpCatalog::not_provided) + << "\n")); } if (options[0].find("force") != std::string::npos) { force = true; } else { - throw std::runtime_error(MESSAGE("RunNodeCmd: Expected force \n" << RunNodeCmd::desc() << "\n")); + throw std::runtime_error(MESSAGE("RunNodeCmd: Expected force \n" + << HelpCatalog::description_for("run").value_or(HelpCatalog::not_provided) + << "\n")); } } cmd = std::make_shared(paths, force); diff --git a/libs/base/src/ecflow/base/cts/user/RunNodeCmd.hpp b/libs/base/src/ecflow/base/cts/user/RunNodeCmd.hpp index 80e3f0269..83ac06e00 100644 --- a/libs/base/src/ecflow/base/cts/user/RunNodeCmd.hpp +++ b/libs/base/src/ecflow/base/cts/user/RunNodeCmd.hpp @@ -45,8 +45,7 @@ class RunNodeCmd final : public UserCmd { void create(Cmd_ptr& cmd, boost::program_options::variables_map& vm, AbstractClientEnv* clientEnv) const override; private: - static const char* arg(); // used for argument parsing - static const char* desc(); // The description of the argument as provided to user + static const char* arg(); // used for argument parsing STC_Cmd_ptr doHandleRequest(AbstractServer*) const override; // bool authenticate(AbstractServer*, STC_Cmd_ptr&) const override; diff --git a/libs/base/src/ecflow/base/cts/user/ServerVersionCmd.cpp b/libs/base/src/ecflow/base/cts/user/ServerVersionCmd.cpp index fa382ad4d..865f197e5 100644 --- a/libs/base/src/ecflow/base/cts/user/ServerVersionCmd.cpp +++ b/libs/base/src/ecflow/base/cts/user/ServerVersionCmd.cpp @@ -52,16 +52,8 @@ STC_Cmd_ptr ServerVersionCmd::doHandleRequest(AbstractServer* as) const { return PreAllocatedReply::string_cmd(Version::full()); } -static const char* arg_desc() { - /////////1/////////2/////////3/////////4/////////5/////////6/////////7/////////8 - return "Returns the version number of the server\n" - "Usage:\n" - " --server_version\n" - " Writes the version to standard output\n"; -} - void ServerVersionCmd::addOption(boost::program_options::options_description& desc) const { - desc.add_options()(CtsApi::server_version_arg(), arg_desc()); + desc.add_options()(CtsApi::server_version_arg(), ""); } void ServerVersionCmd::create(Cmd_ptr& cmd, boost::program_options::variables_map& vm, AbstractClientEnv* ace) const { diff --git a/libs/base/src/ecflow/base/cts/user/ShowCmd.cpp b/libs/base/src/ecflow/base/cts/user/ShowCmd.cpp index 9afe6f90c..2b18b2e74 100644 --- a/libs/base/src/ecflow/base/cts/user/ShowCmd.cpp +++ b/libs/base/src/ecflow/base/cts/user/ShowCmd.cpp @@ -49,45 +49,9 @@ STC_Cmd_ptr ShowCmd::doHandleRequest(AbstractServer* as) const { const char* ShowCmd::arg() { return "show"; } -const char* ShowCmd::desc() { - return "Used to print state of the definition returned from the server to standard output.\n" - "This command can *only* be used in a group command, and will only work if it is\n" - "preceded with a get command. See examples below.\n" - " arg1 = [ defs | state | migrate ] \n" - "The output of show has several options: i.e\n" - " o no arguments: With no arguments, print the definition structure to standard output\n" - " Extern's are automatically added, allowing the output to be reloaded into the server\n" - " i.e --group=\"get ; show\"\n" - " o state:\n" - " This will output definition structure along with all the state information.\n" - " This will include the trigger expressions, abstract syntax tree as comments.\n" - " Excludes the edit history\n" - " o migrate:\n" - " This will output definition structure along with all the state information.\n" - " The node state is shown in the comments.\n" - " This format allows the definition to be migrated to future version of ecflow.\n" - " The output includes edit history but excludes externs.\n" - " When the definition is reloaded *NO* checking is done.\n" - "\n" - "The following shows a summary of the features associated with each choice\n" - " DEFS STATE MIGRATE\n" - "Auto generate externs Yes Yes No\n" - "Checking on reload Yes Yes No\n" - "Edit History No No Yes\n" - "trigger AST No Yes No\n" - "\n" - "Usage:\n" - " --group=\"get ; show\"\n" - " --group=\"get ; show defs\" # same as the previous example\n" - " --group=\"get ; show state\" # Show all state for the node tree\n" - " --group=\"get ; show migrate\" # Shows state and allows migration\n" - " --group=\"get=/s1; show\" # show state for the node only\n" - " --group=\"get=/s1; show state\""; -} void ShowCmd::addOption(boost::program_options::options_description& desc) const { - desc.add_options()( - ShowCmd::arg(), boost::program_options::value()->implicit_value(std::string{}), ShowCmd::desc()); + desc.add_options()(ShowCmd::arg(), boost::program_options::value()->implicit_value(std::string{})); } void ShowCmd::create(Cmd_ptr& cmd, boost::program_options::variables_map& vm, AbstractClientEnv* ac) const { std::string show_state = vm[ShowCmd::arg()].as(); diff --git a/libs/base/src/ecflow/base/cts/user/ShowCmd.hpp b/libs/base/src/ecflow/base/cts/user/ShowCmd.hpp index 2949421e8..688207c75 100644 --- a/libs/base/src/ecflow/base/cts/user/ShowCmd.hpp +++ b/libs/base/src/ecflow/base/cts/user/ShowCmd.hpp @@ -37,8 +37,7 @@ class ShowCmd final : public UserCmd { void create(Cmd_ptr& cmd, boost::program_options::variables_map& vm, AbstractClientEnv* clientEnv) const override; private: - static const char* arg(); // used for argument parsing - static const char* desc(); // The description of the argument as provided to user + static const char* arg(); // used for argument parsing // The Show Cmd is processed on the client side, // Likewise the doHandleRequest does nothing, diff --git a/libs/base/src/ecflow/base/cts/user/ZombieCmd.cpp b/libs/base/src/ecflow/base/cts/user/ZombieCmd.cpp index 9704e6e8c..e780d655a 100644 --- a/libs/base/src/ecflow/base/cts/user/ZombieCmd.cpp +++ b/libs/base/src/ecflow/base/cts/user/ZombieCmd.cpp @@ -212,78 +212,33 @@ void ZombieCmd::addOption(boost::program_options::options_description& desc) con switch (user_action_) { case ZombieCtrlAction::FOB: { - desc.add_options()( - CtsApi::zombieFobArg(), - boost::program_options::value>()->multitoken(), - "Locates the task in the servers list of zombies, and sets to fob.\n" - "This default behaviour of the child commands(label,event,meter) is to fob\n" - "Next time the child commands (init,event,meter,label,abort,complete,wait,queue) communicate\n" - "with the server, they will complete successfully (but without updating the node tree)\n" - "allowing the job to finish.\n" - "The references to the zombie in the server is automatically deleted after 1 hour\n" - " args = list of task paths, at least one expected\n" - " --zombie_fob=/path/to/task1 /path/to/task2"); + desc.add_options()(CtsApi::zombieFobArg(), + boost::program_options::value>()->multitoken()); break; } case ZombieCtrlAction::FAIL: { desc.add_options()(CtsApi::zombieFailArg(), - boost::program_options::value>()->multitoken(), - "Locates the task in the servers list of zombies, and sets to fail.\n" - "Next time a child command (init,event,meter,label,abort,complete) which " - "matches zombie, communicates with the server, will be set to fail.\n" - "Depending on the job setup this may force a abort, the abort will also fail.\n" - "Hence job structure should use 'set -e' in the error trapping functions to prevent\n" - "infinite recursion.\n" - "The references to the zombie in the server is automatically deleted after 1 hour\n" - " args = list of task paths, at least one expected\n" - " --zombie_fail=/path/to/task /path/to/task2"); + boost::program_options::value>()->multitoken()); break; } case ZombieCtrlAction::ADOPT: { desc.add_options()(CtsApi::zombieAdoptArg(), - boost::program_options::value>()->multitoken(), - "Locates the task in the servers list of zombies, and sets to adopt.\n" - "Next time a child command (init,event,meter,label,abort,complete,wait queue) " - "communicates with the server, the password on the zombie is adopted by the task.\n" - "This is only allowed if the process id matches, otherwise an error is issued.\n" - "The zombie reference stored in the server is then deleted.\n" - " args = list of task paths, at least one expected\n" - " --zombie_adopt=/path/to/task /path/to/task2"); + boost::program_options::value>()->multitoken()); break; } case ZombieCtrlAction::REMOVE: { - desc.add_options()( - CtsApi::zombieRemoveArg(), - boost::program_options::value>()->multitoken(), - "Locates the task in the servers list of zombies, and removes it.\n" - "Since a job typically has many child commands (i.e init, complete, event, meter, label, wait, queue)\n" - "the zombie may reappear\n" - " args = list of task paths, at least one expected\n" - " --zombie_remove=/path/to/task /path/to/task2"); + desc.add_options()(CtsApi::zombieRemoveArg(), + boost::program_options::value>()->multitoken()); break; } case ZombieCtrlAction::BLOCK: { desc.add_options()(CtsApi::zombieBlockArg(), - boost::program_options::value>()->multitoken(), - "Locates the task in the servers list of zombies, and sets flags to block it.\n" - "This is default behaviour of the child commands(init,abort,complete,wait,queue)\n" - "when the server cannot match the passwords. Each child commands will continue\n" - "attempting to connect to the server for 24 hours, and will then return an error.\n" - "The connection timeout can be configured with environment ECF_TIMEOUT\n" - " args = list of task paths, at least one expected\n" - " --zombie_block=/path/to/task /path/to/task2"); + boost::program_options::value>()->multitoken()); break; } case ZombieCtrlAction::KILL: { desc.add_options()(CtsApi::zombieKillArg(), - boost::program_options::value>()->multitoken(), - "Locates the task in the servers list of zombies, and sets flags to kill\n" - "The kill is done using ECF_KILL_CMD, but using the process_id from the zombie\n" - "The job is allowed to continue until the kill is received\n" - "Can only kill zombies that have an associated Task, hence path zombies\n" - "must be killed manually.\n" - " arg = list of task paths, at least one expected\n" - " --zombie_kill=/path/to/task /path/to/task2"); + boost::program_options::value>()->multitoken()); break; } default: diff --git a/libs/base/test/TestHelpCatalog.cpp b/libs/base/test/TestHelpCatalog.cpp new file mode 100644 index 000000000..a86447cdb --- /dev/null +++ b/libs/base/test/TestHelpCatalog.cpp @@ -0,0 +1,153 @@ +/* + * 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 "ecflow/base/HelpCatalog.hpp" +#include "ecflow/test/scaffold/Naming.hpp" + +/// +/// \brief Tests ecf::HelpCatalog's lazy access to the embedded CLI help manifest +/// + +BOOST_AUTO_TEST_SUITE(U_Base) + +BOOST_AUTO_TEST_SUITE(T_HelpCatalog) + +BOOST_AUTO_TEST_CASE(test_manifest_parses_and_has_expected_top_level_shape) { + ECF_NAME_THIS_TEST(); + + const nlohmann::json& manifest = ecf::HelpCatalog::manifest(); + + BOOST_REQUIRE(manifest.is_object()); + BOOST_CHECK_EQUAL(manifest.at("schema_version").get(), 1); + BOOST_CHECK(manifest.at("topics").is_array()); + BOOST_CHECK(manifest.at("commands").is_array()); + BOOST_CHECK(manifest.at("options").is_array()); + BOOST_CHECK(manifest.at("environment_variables").is_array()); +} + +BOOST_AUTO_TEST_CASE(test_manifest_contains_known_entries) { + ECF_NAME_THIS_TEST(); + + const nlohmann::json& manifest = ecf::HelpCatalog::manifest(); + + auto has_name = [](const nlohmann::json& array, const std::string& name) { + for (const auto& entry : array) { + if (entry.at("name") == name) { + return true; + } + } + return false; + }; + + BOOST_CHECK(has_name(manifest.at("commands"), "abort")); + BOOST_CHECK(has_name(manifest.at("options"), "host")); + BOOST_CHECK(has_name(manifest.at("environment_variables"), "ECF_HOST")); + BOOST_CHECK(has_name(manifest.at("topics"), "summary")); +} + +BOOST_AUTO_TEST_CASE(test_manifest_is_parsed_once_and_cached) { + ECF_NAME_THIS_TEST(); + + // Same reference on every call confirms the manifest is parsed lazily, + // once, and cached -- not re-parsed on every invocation. + const nlohmann::json& first = ecf::HelpCatalog::manifest(); + const nlohmann::json& second = ecf::HelpCatalog::manifest(); + BOOST_CHECK_EQUAL(&first, &second); +} + +BOOST_AUTO_TEST_CASE(test_find_command_by_exact_name) { + ECF_NAME_THIS_TEST(); + + const nlohmann::json* found = ecf::HelpCatalog::find_command("abort"); + BOOST_REQUIRE(found != nullptr); + BOOST_CHECK_EQUAL(found->at("implementation").get(), "AbortCmd"); + + BOOST_CHECK(ecf::HelpCatalog::find_command("no-such-command") == nullptr); +} + +BOOST_AUTO_TEST_CASE(test_find_option_by_exact_name) { + ECF_NAME_THIS_TEST(); + + const nlohmann::json* found = ecf::HelpCatalog::find_option("host"); + BOOST_REQUIRE(found != nullptr); + BOOST_CHECK_EQUAL(found->at("kind").get(), "global-option"); + + BOOST_CHECK(ecf::HelpCatalog::find_option("no-such-option") == nullptr); +} + +BOOST_AUTO_TEST_CASE(test_find_topic_by_exact_name) { + ECF_NAME_THIS_TEST(); + + const nlohmann::json* found = ecf::HelpCatalog::find_topic("summary"); + BOOST_REQUIRE(found != nullptr); + BOOST_CHECK(found->contains("summary")); + + BOOST_CHECK(ecf::HelpCatalog::find_topic("no-such-topic") == nullptr); +} + +BOOST_AUTO_TEST_CASE(test_summary_for_command_and_option) { + ECF_NAME_THIS_TEST(); + + std::optional command_summary = ecf::HelpCatalog::summary_for("abort"); + BOOST_REQUIRE(command_summary.has_value()); + BOOST_CHECK_EQUAL(*command_summary, ecf::HelpCatalog::find_command("abort")->at("summary").get()); + + std::optional option_summary = ecf::HelpCatalog::summary_for("host"); + BOOST_REQUIRE(option_summary.has_value()); + BOOST_CHECK_EQUAL(*option_summary, ecf::HelpCatalog::find_option("host")->at("summary").get()); + + BOOST_CHECK(!ecf::HelpCatalog::summary_for("no-such-name").has_value()); +} + +BOOST_AUTO_TEST_CASE(test_description_for_command_and_option) { + ECF_NAME_THIS_TEST(); + + std::optional command_description = ecf::HelpCatalog::description_for("abort"); + BOOST_REQUIRE(command_description.has_value()); + BOOST_CHECK(!command_description->empty()); + + std::optional option_description = ecf::HelpCatalog::description_for("host"); + BOOST_REQUIRE(option_description.has_value()); + BOOST_CHECK(!option_description->empty()); + + BOOST_CHECK(!ecf::HelpCatalog::description_for("no-such-name").has_value()); +} + +BOOST_AUTO_TEST_CASE(test_description_for_joins_lines_with_newline) { + ECF_NAME_THIS_TEST(); + + // The description holds one line per array element; description_for() joins them with a single + // newline, so the reconstruction equals the elements joined by '\n', verbatim. + const nlohmann::json* entry = ecf::HelpCatalog::find_command("abort"); + BOOST_REQUIRE(entry != nullptr); + + std::string expected; + bool first = true; + for (const auto& line : entry->at("description")) { + if (!first) { + expected += "\n"; + } + first = false; + expected += line.get(); + } + + std::optional actual = ecf::HelpCatalog::description_for("abort"); + BOOST_REQUIRE(actual.has_value()); + BOOST_CHECK_EQUAL(*actual, expected); + + // A blank line between paragraphs is stored as an empty element and reappears as "\n\n". + BOOST_CHECK(actual->find("\n\n") != std::string::npos); +} + +BOOST_AUTO_TEST_SUITE_END() + +BOOST_AUTO_TEST_SUITE_END() diff --git a/libs/client/CMakeLists.txt b/libs/client/CMakeLists.txt index 66a799a71..cbd801de4 100644 --- a/libs/client/CMakeLists.txt +++ b/libs/client/CMakeLists.txt @@ -54,6 +54,7 @@ set(test_srcs # Sources test/TestClient_main.cpp # test entry point test/TestClientOptions.cpp + test/TestHelp.cpp test/TestClientInterface.cpp test/TestClientConfigurations.cpp test/TestClientEnvironment.cpp diff --git a/libs/client/src/ecflow/client/ClientOptions.cpp b/libs/client/src/ecflow/client/ClientOptions.cpp index 1754c4b03..339e1c273 100644 --- a/libs/client/src/ecflow/client/ClientOptions.cpp +++ b/libs/client/src/ecflow/client/ClientOptions.cpp @@ -53,38 +53,16 @@ ClientOptions::ClientOptions() { // Allow the host,port and rid to be overridden by the command line // This allows the jobs, which make other calls to ecflow_client from interfering with each other // clang-format off - desc_->add_options()( - "rid", - po::value()->implicit_value(std::string{}), - "When specified overrides the environment variable ECF_RID. Can only be used for child commands."); - desc_->add_options()( - "port", - po::value()->implicit_value(std::string{}), - "When specified overrides the environment variable ECF_PORT and default port: '3141'"); - desc_->add_options()( - "host", - po::value()->implicit_value(std::string{}), - "When specified overrides the environment variable ECF_HOST and default host: 'localhost'"); - desc_->add_options()( - "user", - po::value()->implicit_value(std::string{}), - "Specifies the user name used to contact the server. Must be used in combination with option --password."); - desc_->add_options()( - "password", - po::value()->implicit_value(std::string{}), - "Specifies the password used to contact the server. Must be used in combination with option --user."); + desc_->add_options()("rid", po::value()->implicit_value(std::string{})); + desc_->add_options()("port", po::value()->implicit_value(std::string{})); + desc_->add_options()("host", po::value()->implicit_value(std::string{})); + desc_->add_options()("user", po::value()->implicit_value(std::string{})); + desc_->add_options()("password", po::value()->implicit_value(std::string{})); #ifdef ECF_OPENSSL - desc_->add_options()( - "ssl", - "Enables the use of SSL when contacting the server.\n" - "When specified overrides the environment variable ECF_SSL."); + desc_->add_options()("ssl", ""); #endif - desc_->add_options()( - "http", - "Enables communication over HTTP between client/server.\n"); - desc_->add_options()( - "https", - "Enables communication over HTTPS between client/server.\n"); + desc_->add_options()("http", ""); + desc_->add_options()("https", ""); // clang-format on } diff --git a/libs/client/src/ecflow/client/Help.cpp b/libs/client/src/ecflow/client/Help.cpp index ffe38c058..0c653352a 100644 --- a/libs/client/src/ecflow/client/Help.cpp +++ b/libs/client/src/ecflow/client/Help.cpp @@ -11,7 +11,9 @@ #include "ecflow/client/Help.hpp" #include +#include +#include "ecflow/base/HelpCatalog.hpp" #include "ecflow/core/Child.hpp" #include "ecflow/core/Ecf.hpp" #include "ecflow/core/Str.hpp" @@ -49,120 +51,72 @@ struct CommandFilter known_options{"add", "debug", "host", "password", "port", "rid", "ssl", "user", "http", "https"}; }; -struct EnvironmentOptionDocs -{ - std::string kind; // "option", "task", "both" - std::string name; - std::string type; - std::string required; - std::string description; -}; +/// +/// @brief Formats a single environment-variable manifest entry as two indented help lines. +/// +/// @details The first line carries the variable name, its type, and its requirement level; a +/// trailing asterisk marks a variable whose value can be overridden by a command-line option. The +/// second line carries the description. The layout matches the documentation renderer in +/// docs/client_api/build.py, so that the CLI help and the generated documentation agree. +/// +/// @param[in] var Manifest entry taken from the "environment_variables" array; must contain the +/// "required", "name", "type", and "description" fields, and may contain +/// "overridable_by". +/// @return The formatted two-line block, terminated by a newline. +/// +std::string format_env_var(const nlohmann::json& var) { + std::string required = var.at("required").get(); + if (var.contains("overridable_by")) { + required += "*"; + } -std::vector known_env_options = { - // clang-format off - { - "both", - "ECF_HOST", - "string", - "mandatory*", - "The main server hostname; default value is 'localhost'" - }, - { - "both", - "ECF_PORT", - "int", - "mandatory*", - "The main server port; default value is '3141'" - }, -#ifdef ECF_OPENSSL - { - "both", - "ECF_SSL", - "any", - "optional*", - "Enable secure communication between client and server." - }, -#endif - { - "both", - "ECF_HOSTFILE", - "string", - "optional", - "File that lists alternate hosts to try, if connection to main host fails" - }, - { - "both", - "ECF_HOSTFILE_POLICY", - "string", - "optional", - "The policy ('task' or 'all') to define which commands consider using alternate hosts." - }, - { - "task", - "ECF_NAME", - "string", - "mandatory", "Full path name to the task" - }, - { - "task", - "ECF_PASS", - "string", - "mandatory", - "The job password (defined by the server, and used to authenticate client requests)" - }, - { - "task", - "ECF_TRYNO", - "int", - "mandatory", - "The run number of the job (defined by the server, and used in job/output file name generation." - }, - { - "task", - "ECF_RID", - "string", - "mandatory", - "The process identifier. Supports identifying zombies and automated killing of running jobs" - }, - { - "task", - "ECF_TIMEOUT", - "int", - "optional", - "Maximum time in *seconds* for client to deliver message to main server; default is 24 hours" - }, - { - "task", - "ECF_DENIED", - "any", - "optional", - "Allows task to exit with an error, upon connection failure, thus avoids ECF_TIMEOUTs wait." - }, - { - "task", - "NO_ECF", - "any", - "optional", - "If set, ecflow_client exits immediately with success; useful to test the scripts without a server" + std::string line = " "; + line += var.at("name").get(); + line += " <"; + line += var.at("type").get(); + line += "> ["; + line += required; + line += "]\n "; + line += var.at("description").get(); + line += "\n"; + return line; +} + +/// +/// @brief Reports whether an environment variable applies to the current build. +/// +/// @details A variable may carry a "requires" field naming the build feature that gates it (for +/// example, ECF_SSL requires ECF_OPENSSL). A gated variable is advertised only when its feature is +/// compiled in, so that a client built without a feature does not describe variables that have no +/// effect. Variables with no "requires" field, or requiring a feature that is present, are enabled. +/// +/// @param[in] var Manifest entry taken from the "environment_variables" array. +/// @return true when the variable applies to the current build, false when a required feature is absent. +/// +bool env_var_enabled([[maybe_unused]] const nlohmann::json& var) { +#ifndef ECF_OPENSSL + if (var.contains("requires") && var.at("requires") == "ECF_OPENSSL") { + return false; } - // clang-format on -}; +#endif + return true; +} -auto make_client_env_description() -> auto { +/// +/// @brief Builds the environment-variable help common to both user and task commands. +/// +/// @details Collects every manifest entry whose "applies_to" field is "both" and appends a note +/// explaining the asterisk convention used for values that a command-line option can override. +/// +/// @return The assembled help text. +/// +std::string make_client_env_description() { std::string help; - help += "The client considers, for both user and child commands, the following environment variables:\n\n"; - - for (const auto& o : known_env_options) { - if (o.kind == "both") { - help += " "; - help += o.name; - help += " <"; - help += o.type; - help += "> ["; - help += o.required; - help += "]\n "; - help += o.description; - help += "\n"; + help += "The client considers, for both user and task commands, the following environment variables:\n\n"; + + for (const auto& var : ecf::HelpCatalog::manifest().at("environment_variables")) { + if (var.at("applies_to").get() == "both" && env_var_enabled(var)) { + help += format_env_var(var); } } @@ -173,21 +127,21 @@ auto make_client_env_description() -> auto { return help; } -auto make_task_env_description() -> auto { +/// +/// @brief Builds the environment-variable help specific to task commands. +/// +/// @details Collects every manifest entry whose "applies_to" field is "task" and appends a note +/// reminding that the mandatory variables are expected to be exported by the scripts. +/// +/// @return The assembled help text. +/// +std::string make_task_env_description() { std::string help; - help += "The following environment variables are used specifically by child commands:\n\n"; - - for (const auto& o : known_env_options) { - if (o.kind == "task") { - help += " "; - help += o.name; - help += " <"; - help += o.type; - help += "> ["; - help += o.required; - help += "]\n "; - help += o.description; - help += "\n"; + help += "The following environment variables are used specifically by task commands:\n\n"; + + for (const auto& var : ecf::HelpCatalog::manifest().at("environment_variables")) { + if (var.at("applies_to").get() == "task" && env_var_enabled(var)) { + help += format_env_var(var); } } @@ -196,9 +150,32 @@ auto make_task_env_description() -> auto { return help; } -std::string client_env_description = make_client_env_description(); +/// +/// @brief Returns the environment-variable help common to user and task commands. +/// +/// @details Built once on first use and cached, mirroring HelpCatalog::manifest()'s own lazy +/// parse: ecflow_client is invoked by a task potentially thousands of times over a suite run, and +/// only a small fraction of those invocations ever request help, so this must not force a JSON +/// parse on every one. +/// +/// @return Reference to the cached text, valid for the remaining lifetime of the process. +/// +const std::string& client_env_description() { + static const std::string instance = make_client_env_description(); + return instance; +} -std::string client_task_env_description = make_task_env_description(); +/// +/// @brief Returns the environment-variable help specific to task commands. +/// +/// @details Built once on first use and cached, for the same reason as client_env_description(). +/// +/// @return Reference to the cached text, valid for the remaining lifetime of the process. +/// +const std::string& client_task_env_description() { + static const std::string instance = make_task_env_description(); + return instance; +} int get_options_max_width(const Help::descriptions_t& options) { size_t vec_size = options.size(); @@ -246,7 +223,7 @@ class Documentation { return "option "; } else if (CommandFilter::is_task_command(name)) { - return "child "; + return "task "; } else if (CommandFilter::is_user_command(name)) { return "user "; @@ -262,7 +239,7 @@ class Documentation { void Documentation::show(std::ostream& os, const std::string& topic) const { // WARNING!! - // This assumes that there are no user/child commands named: 'summary', 'all', 'child', 'user' + // This assumes that there are no user/task commands named: 'summary', 'all', 'task', 'user' // if (topic.empty()) { @@ -274,8 +251,8 @@ void Documentation::show(std::ostream& os, const std::string& topic) const { else if (topic == "summary") { show_all_commands_summary(os, "\nEcflow client commands:\n"); } - else if (topic == "child") { - show_task_commands_summary(os, "\nEcflow child client commands:\n"); + else if (topic == "task") { + show_task_commands_summary(os, "\nEcflow task client commands:\n"); } else if (topic == "user") { show_user_commands_summary(os, "\nEcflow user client commands:\n"); @@ -296,7 +273,7 @@ void Documentation::show_help(std::ostream& os) const { os << "Try:\n\n"; os << " " << Ecf::CLIENT_NAME() << " --help=all # List all commands, verbosely\n"; os << " " << Ecf::CLIENT_NAME() << " --help=summary # One line summary of all commands\n"; - os << " " << Ecf::CLIENT_NAME() << " --help=child # One line summary of child commands\n"; + os << " " << Ecf::CLIENT_NAME() << " --help=task # One line summary of task commands\n"; os << " " << Ecf::CLIENT_NAME() << " --help=user # One line summary of user command\n"; os << " " << Ecf::CLIENT_NAME() << " --help= # Detailed help on each command\n\n"; @@ -306,7 +283,32 @@ void Documentation::show_help(std::ostream& os) const { } void Documentation::show_list_options(std::ostream& os) const { - os << descriptions_ << "\n"; + // The options are shown using a very plain list of all the options, with their description. + + os << "\n Client options:\n\n " << ecf::Version::description() << "\n\n"; + + for (const auto& option : descriptions_.options()) { + std::string description; + if (std::optional text = ecf::HelpCatalog::description_for(option->long_name())) { + description = *text; + } + else { + description = "No description was found for this option.\n" + "Please report this issue to the development team."; + } + + os << " --" << option->long_name() << "\n\n"; + + std::vector lines; + ecf::algorithm::split_fields_at(lines, description, "\n"); + for (const auto& line : lines) { + if (!line.empty()) { + os << " " << line; + } + os << "\n"; + } + os << "\n"; + } } template @@ -344,14 +346,19 @@ 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::algorithm::split_at(lines, option->description(), "\n"); - if (!lines.empty()) { - std::string name = option->long_name(); - os << " " << std::left << std::setw(max_width) << name << " "; - os << Documentation::get_name_kind(name); - os << lines[0] << "\n"; + std::string name = option->long_name(); + + std::string first_line; + if (std::optional summary = ecf::HelpCatalog::summary_for(name)) { + first_line = *summary; + } + else { + first_line = ecf::HelpCatalog::not_provided; } + + os << " " << std::left << std::setw(max_width) << name << " "; + os << Documentation::get_name_kind(name); + os << first_line << "\n"; } os << "\n"; } @@ -391,12 +398,17 @@ void Documentation::show_command_help(std::ostream& os, const std::string& comma os << "-"; } os << "\n\n"; - os << od->description() << "\n\n"; + if (std::optional text = ecf::HelpCatalog::description_for(od->long_name())) { + os << *text << "\n\n"; + } + else { + os << ecf::HelpCatalog::not_provided << "\n\n"; + } if (!CommandFilter::is_option(od->long_name())) { - os << client_env_description; + os << client_env_description(); if (ecf::Child::valid_child_cmd(od->long_name())) { os << "\n"; - os << client_task_env_description; + os << client_task_env_description(); } } } diff --git a/libs/client/test/TestHelp.cpp b/libs/client/test/TestHelp.cpp new file mode 100644 index 000000000..0f3cb3e75 --- /dev/null +++ b/libs/client/test/TestHelp.cpp @@ -0,0 +1,182 @@ +/* + * 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 + +#include "ecflow/base/cts/CtsCmdRegistry.hpp" +#include "ecflow/client/Help.hpp" +#include "ecflow/test/scaffold/Naming.hpp" + +/// +/// @brief Tests the manifest-driven help rendering exposed by the Help class. +/// +/// @details The rendering helpers live in an anonymous namespace inside Help.cpp; they are +/// exercised here through the only public seam, streaming a Help built from the same +/// options_description the client assembles in production. +/// + +namespace { + +/// +/// @brief Builds the option description exactly as ClientOptions assembles it in production. +/// +/// @details Registers every command option through CtsCmdRegistry and then appends the global +/// command-line options, so that the rendered help matches what ecflow_client produces. +/// +/// @return The assembled option description. +/// +boost::program_options::options_description make_description() { + namespace po = boost::program_options; + + po::options_description desc("Client options"); + + CtsCmdRegistry registry; + registry.addAllOptions(desc); + + desc.add_options()("rid", po::value()->implicit_value(std::string{})); + desc.add_options()("port", po::value()->implicit_value(std::string{})); + desc.add_options()("host", po::value()->implicit_value(std::string{})); + desc.add_options()("user", po::value()->implicit_value(std::string{})); + desc.add_options()("password", po::value()->implicit_value(std::string{})); +#ifdef ECF_OPENSSL + desc.add_options()("ssl", ""); +#endif + desc.add_options()("http", ""); + desc.add_options()("https", ""); + + return desc; +} + +/// +/// @brief Renders the help output for the given topic. +/// +/// @details Mirrors `ecflow_client --help=` by streaming a Help built from the +/// production option description. +/// +/// @param[in] topic Help topic, such as "summary", "task", a command name, or an option name. +/// @return The rendered help text. +/// +std::string render(const std::string& topic) { + boost::program_options::options_description desc = make_description(); + std::ostringstream ss; + ss << Help{desc, topic}; + return ss.str(); +} + +/// +/// @brief Reports whether one string occurs within another. +/// +/// @param[in] haystack Text to search. +/// @param[in] needle Substring to look for. +/// @return true when @p needle occurs anywhere in @p haystack, false otherwise. +/// +bool contains(const std::string& haystack, const std::string& needle) { + return haystack.find(needle) != std::string::npos; +} + +// The banner sentences produced by make_client_env_description() and make_task_env_description(). +const std::string client_env_banner = "The client considers, for both user and task commands"; +const std::string task_env_banner = "The following environment variables are used specifically by task commands"; + +} // namespace + +BOOST_AUTO_TEST_SUITE(S_Client) + +BOOST_AUTO_TEST_SUITE(T_Help) + +BOOST_AUTO_TEST_CASE(test_summary_lists_task_and_user_commands) { + ECF_NAME_THIS_TEST(); + + std::string out = render("summary"); + BOOST_CHECK(contains(out, "Ecflow client commands:")); + // abort is a task command, alter a user command; both appear in the full summary. + BOOST_CHECK(contains(out, "abort")); + BOOST_CHECK(contains(out, "alter")); + // get_name_kind() labels the command kinds in the summary column. + BOOST_CHECK(contains(out, "task")); + BOOST_CHECK(contains(out, "user")); +} + +BOOST_AUTO_TEST_CASE(test_task_topic_lists_task_commands) { + ECF_NAME_THIS_TEST(); + + std::string out = render("task"); + BOOST_CHECK(contains(out, "Ecflow task client commands:")); + BOOST_CHECK(contains(out, "abort")); +} + +BOOST_AUTO_TEST_CASE(test_user_topic_lists_user_commands) { + ECF_NAME_THIS_TEST(); + + std::string out = render("user"); + BOOST_CHECK(contains(out, "Ecflow user client commands:")); + BOOST_CHECK(contains(out, "alter")); +} + +BOOST_AUTO_TEST_CASE(test_command_help_for_task_command_appends_both_env_banners) { + ECF_NAME_THIS_TEST(); + + std::string out = render("abort"); + // Heading plus the manifest description. + BOOST_CHECK(contains(out, "abort")); + // A task command gets both the common banner and the task-specific banner. + BOOST_CHECK(contains(out, client_env_banner)); + BOOST_CHECK(contains(out, "ECF_HOST")); + BOOST_CHECK(contains(out, task_env_banner)); + BOOST_CHECK(contains(out, "ECF_NAME")); +} + +BOOST_AUTO_TEST_CASE(test_command_help_for_user_command_omits_task_env_banner) { + ECF_NAME_THIS_TEST(); + + std::string out = render("alter"); + // A user command gets the common banner but not the task-specific one. + BOOST_CHECK(contains(out, client_env_banner)); + BOOST_CHECK(!contains(out, task_env_banner)); +} + +BOOST_AUTO_TEST_CASE(test_command_help_for_option_omits_env_banner) { + ECF_NAME_THIS_TEST(); + + std::string out = render("host"); + // An option is neither a user nor a task command, so no environment banner is appended. + BOOST_CHECK(contains(out, "host")); + BOOST_CHECK(!contains(out, client_env_banner)); + BOOST_CHECK(!contains(out, task_env_banner)); +} + +BOOST_AUTO_TEST_CASE(test_unknown_topic_falls_back_to_command_list) { + ECF_NAME_THIS_TEST(); + + // The renamed-away 'child' topic, and any other unknown name, degrade to the command list. + std::string out = render("child"); + BOOST_CHECK(contains(out, "No matching command found")); +} + +BOOST_AUTO_TEST_CASE(test_env_banner_ssl_visibility_follows_openssl_build) { + ECF_NAME_THIS_TEST(); + + // ECF_SSL carries requires: ECF_OPENSSL in the manifest, so it is advertised in the common + // environment-variable banner only when the client is built with OpenSSL. + std::string out = render("abort"); +#ifdef ECF_OPENSSL + BOOST_CHECK(contains(out, "ECF_SSL")); +#else + BOOST_CHECK(!contains(out, "ECF_SSL")); +#endif +} + +BOOST_AUTO_TEST_SUITE_END() + +BOOST_AUTO_TEST_SUITE_END()