Skip to content

Commit

Permalink
Release 5.12.1
Browse files Browse the repository at this point in the history
  • Loading branch information
marcosbento authored Mar 7, 2024
2 parents 7b70172 + f63dc75 commit 5625415
Show file tree
Hide file tree
Showing 12 changed files with 220 additions and 67 deletions.
152 changes: 101 additions & 51 deletions Base/src/ecflow/base/ClientOptionsParser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,63 @@ bool is_valid_path(const std::string& path) {
return !path.empty() && path[0] == '/';
}

} // namespace
void parse_option(ClientOptionsParser::option_t& option,
ClientOptionsParser::option_set_t& processed_options,
ClientOptionsParser::arguments_set_t& args) {
if (auto found = args[0].find('='); found == std::string::npos) {
// In case form 1) is used, we discard the '--<option>'
args.erase(args.begin());
// store the 'arg1'
option.value.push_back(args.front());
option.original_tokens.push_back(args.front());
}
else {
// Otherwise, form 2) is used, and the first positional value must be
// taken by tokenizing the option '--<option>=<arg1>'
std::string arg = args[0].substr(found + 1);
option.value.push_back(arg);
option.original_tokens.push_back(arg);
}
// Discard the option at the front
args.erase(args.begin());
}

ClientOptionsParser::option_set_t ClientOptionsParser::operator()(ClientOptionsParser::arguments_set_t& args) {
option_set_t processed_options;
template <typename PREDICATE>
void parse_positional_arguments(
ClientOptionsParser::option_t& option,
ClientOptionsParser::option_set_t& processed_options,
ClientOptionsParser::arguments_set_t& args,
size_t maximum_positional_args,
PREDICATE predicate = [](const std::string&) { return true; }) {

// Collect up to N positional arguments, that satisfy the predicate
for (size_t i = 0; i < maximum_positional_args && !args.empty(); ++i) {
// Take each of the positional values as an option value
std::string& arg = args.front();
// Once we find the first argument that doesn't satify the predicate,
// we stop collecting arguments
if (!predicate(arg)) {
break;
}
option.value.push_back(arg);
option.original_tokens.push_back(arg);

// Remove the argument value
args.erase(args.begin());
}
}

void parse_positional_arguments(ClientOptionsParser::option_t& option,
ClientOptionsParser::option_set_t& processed_options,
ClientOptionsParser::arguments_set_t& args,
size_t maximum_positional_args) {

// Collect up to N positional arguments
parse_positional_arguments(
option, processed_options, args, maximum_positional_args, [](const std::string&) { return true; });
}

void parse_alter(ClientOptionsParser::option_set_t& processed_options, ClientOptionsParser::arguments_set_t& args) {

// *** Important! ***
// This custom handler is needed to ensure that the "--alter" option
Expand All @@ -36,59 +89,56 @@ ClientOptionsParser::option_set_t ClientOptionsParser::operator()(ClientOptionsP
// 1) --alter arg1 arg2 arg3 (arg4) path [path [path [...]]]
// 2) --alter=arg1 arg2 arg3 (arg4) path [path [path [...]]]

if (ecf::algorithm::starts_with(args[0], "--alter")) {
ClientOptionsParser::option_t alter{std::string{"alter"}, {}};

option_t alter{std::string{"alter"}, {}};
parse_option(alter, processed_options, args);

if (auto found = args[0].find('='); found == std::string::npos) {
// In case form 1) is used, we discard the '--alter'
args.erase(args.begin());
// store the 'arg1'
alter.value.push_back(args.front());
alter.original_tokens.push_back(args.front());
}
else {
// Otherwise, form 2) is used, and the first positional value must be
// taken by tokenizing the option '--alter=arg1'
std::string arg = args[0].substr(found + 1);
alter.value.push_back(arg);
alter.original_tokens.push_back(arg);
}
// Discard the option at the front
args.erase(args.begin());
// Collect up to 4 positional arguments, that are not paths
parse_positional_arguments(
alter, processed_options, args, 4, [](const std::string& arg) { return !is_valid_path(arg); });

// Collect (non path) positional arguments
const size_t maximum_positional_args = 4;
for (size_t i = 0; i < maximum_positional_args && !args.empty(); ++i) {
// Take each of the positional values as an option value
std::string& arg = args.front();
// Once we find the first path argument we stop collecting arguments
if (is_valid_path(arg)) {
break;
}
alter.value.push_back(arg);
alter.original_tokens.push_back(arg);

// Remove the argument value
args.erase(args.begin());
}
// Collect remaining positional arguments, that are paths
parse_positional_arguments(
alter, processed_options, args, std::numeric_limits<size_t>::max(), [](const std::string& arg) {
return is_valid_path(arg);
});

// Collect only paths
for (; !args.empty();) {
// Take each of the positional values as an option value
std::string& arg = args.front();
// Once we find a (non path) argument, we stop collections arguments
if (!is_valid_path(arg)) {
break;
}
alter.value.push_back(arg);
alter.original_tokens.push_back(arg);

// Remove the argument value
args.erase(args.begin());
}
processed_options.push_back(alter);
}

void parse_label(ClientOptionsParser::option_set_t& processed_options, ClientOptionsParser::arguments_set_t& args) {

// *** Important! ***
// This custom handler is needed to ensure that the "--alter" option
// special value parameters are handled correctly. For example,
// values starting with -, such as "-j64".
//
// The custom handling will consider that 2 positional values (not
// to be confused with positional arguments) are provided with the
// --label option, as per one of the following forms:
// 1) --label arg1 arg2
// 2) --label=arg1 arg2

ClientOptionsParser::option_t label{std::string{"label"}, {}};

parse_option(label, processed_options, args);

processed_options.push_back(alter);
// Collect 1 positional argument (i.e. the label value)
parse_positional_arguments(label, processed_options, args, 1);

processed_options.push_back(label);
}

} // namespace

ClientOptionsParser::option_set_t ClientOptionsParser::operator()(ClientOptionsParser::arguments_set_t& args) {
option_set_t processed_options;

if (ecf::algorithm::starts_with(args[0], "--alter")) {
parse_alter(processed_options, args);
}
else if (ecf::algorithm::starts_with(args[0], "--label")) {
parse_label(processed_options, args);
}
return processed_options;
}
Expand Down
4 changes: 3 additions & 1 deletion Base/src/ecflow/base/stc/SStringCmd.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ bool SStringCmd::handle_server_response(ServerReply& server_reply, Cmd_ptr cts_c
if (debug)
cout << " SStringCmd::handle_server_response str.size()= " << str_.size() << "\n";
if (server_reply.cli())
std::cout << str_ << "\n";
// The following uses std::endl to ensure the output is flushed.
// This is necessary when called from the Python API, otherwise the output cannot be captured systematically.
std::cout << str_ << std::endl;
else
server_reply.set_string(str_);
return true;
Expand Down
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ endif()
find_package( ecbuild 3.4 REQUIRED HINTS ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/../ecbuild ) # Before project()

# this will generate variables, see ACore/ecflow_version.h.in
project( ecflow LANGUAGES CXX VERSION 5.12.0 )
project( ecflow LANGUAGES CXX VERSION 5.12.1 )

set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake)

Expand Down
70 changes: 70 additions & 0 deletions Client/test/TestClientOptions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

#include <boost/test/unit_test.hpp>

#include "ecflow/base/cts/task/LabelCmd.hpp"
#include "ecflow/base/cts/user/AlterCmd.hpp"
#include "ecflow/client/ClientEnvironment.hpp"
#include "ecflow/client/ClientInvoker.hpp"
Expand All @@ -21,6 +22,27 @@
/// \brief Tests the capabilities of ClientOptions
///

template <typename REQUIRE>
void test_label(const CommandLine& cl, REQUIRE check) {
std::cout << "Testing command line: " << cl.original() << std::endl;

ClientOptions options;
ClientEnvironment environment(false);
// Setup environment with some defaults
environment.set_child_path("/path/to/child");
environment.set_child_password("password");
try {
auto base_command = options.parse(cl, &environment);
auto derived_command = dynamic_cast<LabelCmd*>(base_command.get());

BOOST_REQUIRE(derived_command);
check(*derived_command, environment);
}
catch (boost::program_options::unknown_option& e) {
BOOST_FAIL(std::string("Unexpected exception caught: ") + e.what());
}
}

template <typename REQUIRE>
void test_alter(const CommandLine& cl, REQUIRE check) {
std::cout << "Testing command line: " << cl.original() << std::endl;
Expand Down Expand Up @@ -724,6 +746,54 @@ BOOST_AUTO_TEST_CASE(test_is_able_handle_alter_delete) {
} // paths
}

BOOST_AUTO_TEST_CASE(test_is_able_to_handle_label) {
{
auto cl = CommandLine::make_command_line("ecflow_client",
"--label=name",
"some value with spaces");
test_label(cl, [&](const LabelCmd& command, const ClientEnvironment& env) {
BOOST_REQUIRE_EQUAL(command.name(), "name");
BOOST_REQUIRE_EQUAL(command.label(), "some value with spaces");
});
}
{
auto cl = CommandLine::make_command_line("ecflow_client",
"--label=name",
R"(some "quoted" value)");
test_label(cl, [&](const LabelCmd& command, const ClientEnvironment& env) {
BOOST_REQUIRE_EQUAL(command.name(), "name");
BOOST_REQUIRE_EQUAL(command.label(), R"(some "quoted" value)");
});
}
{
auto cl = CommandLine::make_command_line("ecflow_client",
"--label=name",
"-j64");
test_label(cl, [&](const LabelCmd& command, const ClientEnvironment& env) {
BOOST_REQUIRE_EQUAL(command.name(), "name");
BOOST_REQUIRE_EQUAL(command.label(), "-j64");
});
}
{
auto cl = CommandLine::make_command_line("ecflow_client",
"--label=name",
"--long-option");
test_label(cl, [&](const LabelCmd& command, const ClientEnvironment& env) {
BOOST_REQUIRE_EQUAL(command.name(), "name");
BOOST_REQUIRE_EQUAL(command.label(), "--long-option");
});
}
{
auto cl = CommandLine::make_command_line("ecflow_client",
"--label=name",
"--option=value");
test_label(cl, [&](const LabelCmd& command, const ClientEnvironment& env) {
BOOST_REQUIRE_EQUAL(command.name(), "name");
BOOST_REQUIRE_EQUAL(command.label(), "--option=value");
});
}
}

BOOST_AUTO_TEST_SUITE_END()

BOOST_AUTO_TEST_SUITE_END()
2 changes: 1 addition & 1 deletion Pyext/ecflow/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,6 @@
The ecFlow python module
"""

__version__ = '5.12.0'
__version__ = '5.12.1'

# http://stackoverflow.com/questions/13040646/how-do-i-create-documentation-with-pydoc
7 changes: 4 additions & 3 deletions Pyext/src/ecflow/python/ClientDoc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -680,13 +680,14 @@ const char* ClientDoc::ping() {
}

const char* ClientDoc::stats() {
return "Prints the `ecflow_server`_ statistics to standard out\n::\n\n"
" void stats()\n"
return "Returns the `ecflow_server`_ statistics as a string\n::\n\n"
" string stats()\n"
"\nUsage:\n\n"
".. code-block:: python\n\n"
" try:\n"
" ci = Client() # use default host(ECF_HOST) & port(ECF_PORT)\n"
" ci.stats()\n"
" stats = ci.stats()\n"
" print(stats)\n"
" except RuntimeError, e:\n"
" print(str(e))\n";
}
Expand Down
9 changes: 6 additions & 3 deletions Pyext/src/ecflow/python/ExportClient.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -130,9 +130,12 @@ class CliSetter {
private:
ClientInvoker* _self;
};
void stats(ClientInvoker* self) {
CliSetter setter(self);
const std::string& stats(ClientInvoker* self) {
self->stats();
// The statistics are printed to stdout for backward compatibility with previous python client versions
// TODO: remove printing to stdout in the next release
std::cout << self->server_reply().get_string() << std::endl;
return self->server_reply().get_string();
}
void stats_reset(ClientInvoker* self) {
self->stats_reset();
Expand Down Expand Up @@ -574,7 +577,7 @@ void export_Client() {
.def("free_all_dep", &free_all_dep, ClientDoc::free_all_dep())
.def("free_all_dep", &free_all_dep1)
.def("ping", &ClientInvoker::pingServer, ClientDoc::ping())
.def("stats", &stats, ClientDoc::stats())
.def("stats", &stats, return_value_policy<copy_const_reference>(), ClientDoc::stats())
.def("stats_reset", &stats_reset, ClientDoc::stats_reset())
.def("get_file",
&get_file,
Expand Down
6 changes: 4 additions & 2 deletions Pyext/test/py_s_TestClientApi.py
Original file line number Diff line number Diff line change
Expand Up @@ -534,12 +534,14 @@ def test_client_free_dep(ci):

def test_client_stats(ci):
print_test(ci,"test_client_stats")
ci.stats() # writes to standard out
stats = ci.stats()
assert "statistics" in s, "Expected statistics in the response"

def test_client_stats_reset(ci):
print_test(ci,"test_client_stats_reset")
ci.stats_reset()
ci.stats() # should produce no output, where we measure requests
statis = ci.stats()
assert "statistics" in s, "Expected statistics in the response"

def test_client_debug_server_on_off(ci):
print_test(ci,"test_client_debug_server_on_off")
Expand Down
2 changes: 1 addition & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@


def get_ecflow_version():
version = "5.12.0"
version = "5.12.1"
ecflow_version = version.split(".")
print("Extracted ecflow version '" + str(ecflow_version))
return ecflow_version
Expand Down
9 changes: 5 additions & 4 deletions docs/python_api/Client.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2259,21 +2259,22 @@ Usage:
sort_attributes( (Client)arg1, (list)paths, (str)attribute_name [, (bool)recursive=True]) -> None
.. py:method:: Client.stats( (Client)arg1) -> None :
.. py:method:: Client.stats( (Client)arg1) -> str :
:module: ecflow
Prints the :term:`ecflow_server` statistics to standard out
Returns the :term:`ecflow_server` statistics as a string
::
void stats()
string stats()
Usage:
.. code-block:: python
try:
ci = Client() # use default host(ECF_HOST) & port(ECF_PORT)
ci.stats()
stats = ci.stats()
print(stats)
except RuntimeError, e:
print(str(e))
Expand Down
1 change: 1 addition & 0 deletions docs/release_notes/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ Release notes
.. toctree::
:maxdepth: 1

version_5.12.1
version_5.12
version_5.11.4
version_5.11.3
Expand Down
Loading

0 comments on commit 5625415

Please sign in to comment.