From ebf55750261ef2ec8e86df73f2164750ce086a7f Mon Sep 17 00:00:00 2001 From: Yogesh9000 <96904418+Yogesh9000@users.noreply.github.com> Date: Mon, 30 Sep 2024 14:15:35 +0530 Subject: [PATCH] Add an API to reset/unset options and expose it at CLI level (#1640) --- cmake/f3dOptions.cmake | 11 ++++++- library/private/options_tools.h.in | 25 ++++++++++++++++ library/public/options.h.in | 18 ++++++++++++ library/src/options.cxx | 25 ++++++++++++++++ library/testing/TestSDKOptions.cxx | 47 ++++++++++++++++++++++++++++++ 5 files changed, 125 insertions(+), 1 deletion(-) diff --git a/cmake/f3dOptions.cmake b/cmake/f3dOptions.cmake index 605f97d9b9..e7c5e5924c 100644 --- a/cmake/f3dOptions.cmake +++ b/cmake/f3dOptions.cmake @@ -74,6 +74,8 @@ function (f3d_generate_options) list(JOIN _options_string_setter ";\n else " _options_string_setter) list(JOIN _options_string_getter ";\n else " _options_string_getter) list(JOIN _options_lister ",\n " _options_lister) + list(JOIN _options_is_optional ";\n else " _options_is_optional) + list(JOIN _options_reset ";\n else " _options_reset) configure_file( "${_f3d_generate_options_INPUT_PUBLIC_HEADER}" @@ -133,12 +135,17 @@ function(_parse_json_option _top_json) if(_default_value_error STREQUAL "NOTFOUND") # Use default_value - string(APPEND _options_struct "${_option_indent} ${_option_actual_type} ${_member_name} = ${_option_default_value_start}${_option_default_value}${_option_default_value_end};\n") + set(_optional_default_value_initialize "${_option_default_value_start}${_option_default_value}${_option_default_value_end}") + string(APPEND _options_struct "${_option_indent} ${_option_actual_type} ${_member_name} = ${_optional_default_value_initialize};\n") set(_optional_getter "") + list(APPEND _options_is_optional "if (name == \"${_option_name}\") return false") + list(APPEND _options_reset "if (name == \"${_option_name}\") opt.${_option_name} = ${_optional_default_value_initialize}") else() # No default_value, it is an std::optional string(APPEND _options_struct "${_option_indent} std::optional<${_option_actual_type}> ${_member_name};\n") set(_optional_getter ".value()") + list(APPEND _options_is_optional "if (name == \"${_option_name}\") return true") + list(APPEND _options_reset "if (name == \"${_option_name}\") opt.${_option_name}.reset()") endif() list(APPEND _options_setter "if (name == \"${_option_name}\") opt.${_option_name} = std::get<${_option_variant_type}>(value)") @@ -168,4 +175,6 @@ function(_parse_json_option _top_json) set(_options_string_setter ${_options_string_setter} PARENT_SCOPE) set(_options_string_getter ${_options_string_getter} PARENT_SCOPE) set(_options_lister ${_options_lister} PARENT_SCOPE) + set(_options_is_optional ${_options_is_optional} PARENT_SCOPE) + set(_options_reset ${_options_reset} PARENT_SCOPE) endfunction() diff --git a/library/private/options_tools.h.in b/library/private/options_tools.h.in index c82444b887..221dc674b9 100644 --- a/library/private/options_tools.h.in +++ b/library/private/options_tools.h.in @@ -348,6 +348,31 @@ std::string getAsString(const options& opt, const std::string& name) throw options::no_value_exception("Trying to get " + name + " before it was set"); } } + +//---------------------------------------------------------------------------- +/** + * Generated method, see `options::isOptional` + */ +bool isOptional(const std::string& name) +{ + // clang-format off + ${_options_is_optional}; + // clang-format on + else throw options::inexistent_exception("Option " + name + " does not exist"); +} + +//---------------------------------------------------------------------------- +/** + * Generated method, see `options::reset` + */ +void reset(options& opt, const std::string& name) +{ + // clang-format off + ${_options_reset}; + // clang-format on + else throw options::inexistent_exception("Option " + name + " does not exist"); +} + } // option_tools } // f3d #endif // f3d_options_tools_h diff --git a/library/public/options.h.in b/library/public/options.h.in index 13dad70f5a..ab600d749b 100644 --- a/library/public/options.h.in +++ b/library/public/options.h.in @@ -114,6 +114,24 @@ public: */ std::pair getClosestOption(const std::string& option) const; + /** + * Returns true if the option is optional else returns false. + * Throws an options::inexistent_exception if option does not exist. + */ + bool isOptional(const std::string& option) const; + + /** + * Resets the option to default value. + * Throws an options::inexistent_exception if option does not exist. + */ + void reset(const std::string& name); + + /** + * Unset the option if it is optional else throws options::incompatible_exception. + * Throws an options::inexistent_exception if option does not exist. + */ + void removeValue(const std::string& name); + /** * Templated parsing method used internally to parse strings. * Implemented for: diff --git a/library/src/options.cxx b/library/src/options.cxx index d39e14b7ff..23174d6799 100644 --- a/library/src/options.cxx +++ b/library/src/options.cxx @@ -136,6 +136,31 @@ std::pair options::getClosestOption(const std::string return ret; } +//---------------------------------------------------------------------------- +bool options::isOptional(const std::string& option) const +{ + return options_tools::isOptional(option); +} + +//---------------------------------------------------------------------------- +void options::reset(const std::string& name) +{ + options_tools::reset(*this, name); +} + +//---------------------------------------------------------------------------- +void options::removeValue(const std::string& name) +{ + if (this->isOptional(name)) + { + this->reset(name); + } + else + { + throw options::incompatible_exception("Option " + name + " is not not optional"); + } +} + //---------------------------------------------------------------------------- template T options::parse(const std::string& str) diff --git a/library/testing/TestSDKOptions.cxx b/library/testing/TestSDKOptions.cxx index 6d2213aed4..162e8196ee 100644 --- a/library/testing/TestSDKOptions.cxx +++ b/library/testing/TestSDKOptions.cxx @@ -195,5 +195,52 @@ int TestSDKOptions(int argc, char* argv[]) test.expect("no_value_exception exception on getAsString", [&]() { opt.getAsString("scene.animation.time"); }); + f3d::options opt6{}; + + // Test isOptional optional values + test("isOptional with optional value", opt6.isOptional("model.scivis.array_name")); + test("isOptional with optional value", opt6.isOptional("model.scivis.range")); + + // Test isOptional non-optional values + test("isOptional with non-optional value", opt6.isOptional("model.scivis.cells") == false); + test("isOptional with non-optional value", opt6.isOptional("model.scivis.enable") == false); + + // Test isOptional non-existent options + test.expect( + "isOptional with non-existent option", [&]() { opt6.isOptional("dummy"); }); + + f3d::options opt7{}; + + // Test reset non-optional values + opt7.model.color.opacity = 0.5; + opt7.reset("model.color.opacity"); + test("reset non-optional values", opt7.model.color.opacity == 1.0); + + // Test reset optional values + opt7.model.scivis.array_name = "dummy"; + opt7.reset("model.scivis.array_name"); + test.expect( + "reset non-optional values", [&]() { opt7.get("model.scivis.array_name"); }); + + // Test reset non-existent option + test.expect( + "reset with non-existent option", [&]() { opt7.reset("dummy"); }); + + f3d::options opt8{}; + + // Test removeValue optional values + opt8.model.scivis.array_name = "dummy"; + opt8.removeValue("model.scivis.array_name"); + test.expect( + "removeValue optional values", [&]() { opt8.get("model.scivis.array_name"); }); + + // Test removeValue non-optional values + test.expect( + "removeValue non-optional values", [&]() { opt8.removeValue("model.scivis.cells"); }); + + // Test removeValue non-optional values + test.expect( + "removeValue non-existent option", [&]() { opt8.removeValue("dummy"); }); + return EXIT_SUCCESS; }