diff --git a/libraries/application/src/morpheus/application/po/adapters/CMakeLists.txt b/libraries/application/src/morpheus/application/po/adapters/CMakeLists.txt index 2dedb984..cb409177 100644 --- a/libraries/application/src/morpheus/application/po/adapters/CMakeLists.txt +++ b/libraries/application/src/morpheus/application/po/adapters/CMakeLists.txt @@ -5,4 +5,5 @@ target_sources(MorpheusApplication FILE_SET HEADERS FILES enums.hpp + scannable.hpp ) diff --git a/libraries/application/src/morpheus/application/po/adapters/scannable.hpp b/libraries/application/src/morpheus/application/po/adapters/scannable.hpp new file mode 100644 index 00000000..c99f5fd1 --- /dev/null +++ b/libraries/application/src/morpheus/application/po/adapters/scannable.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include "morpheus/core/conformance/scan.hpp" +#include "morpheus/core/meta/concepts/scannable.hpp" + +#include +#include + +#include +#include + +namespace boost +{ + +template +requires morpheus::meta::concepts::Scannable +void validate(boost::any& v, std::vector> const& values, S*, int) +{ + namespace po = boost::program_options; + po::validators::check_first_occurrence(v); + auto const& s = po::validators::get_single_string(values); + + auto const result = morpheus::scan_ns::scan(s, "{}"); + if (result) + v = result.value().value(); + else + throw po::validation_error(po::validation_error::invalid_option_value); +} + +} // namespace boost diff --git a/libraries/application/tests/po/adapters/CMakeLists.txt b/libraries/application/tests/po/adapters/CMakeLists.txt index d7de9f88..88c278d1 100644 --- a/libraries/application/tests/po/adapters/CMakeLists.txt +++ b/libraries/application/tests/po/adapters/CMakeLists.txt @@ -4,4 +4,5 @@ add_subdirectory(std) target_sources(MorpheusApplicationTests PRIVATE enum.tests.cpp + scannable.tests.cpp ) diff --git a/libraries/application/tests/po/adapters/boost/asio.tests.cpp b/libraries/application/tests/po/adapters/boost/asio.tests.cpp index 3d8b493f..7232fca7 100644 --- a/libraries/application/tests/po/adapters/boost/asio.tests.cpp +++ b/libraries/application/tests/po/adapters/boost/asio.tests.cpp @@ -40,7 +40,7 @@ TEST_CASE_METHOD(BoostLogFixture, "Test parsing of boost asio address as program { Address address{}; std::array cliOptions = {"dummyProgram.exe", "--address", param.data()}; - auto const result = parseProgramOptions(cliOptions.size(), cliOptions.data(), HelpDocumentation{}, address); + auto const result = parseProgramOptions(static_cast(cliOptions.size()), cliOptions.data(), HelpDocumentation{}, address); REQUIRE(!result); return address.ipAddress; }; @@ -55,7 +55,7 @@ TEST_CASE_METHOD(BoostLogFixture, "Test parsing of boost asio address as program { std::array cliOptions = {"dummyProgram.exe", "--address", "invalid"}; Address address; - auto const result = parseProgramOptions(cliOptions.size(), cliOptions.data(), HelpDocumentation{}, address); + auto const result = parseProgramOptions(static_cast(cliOptions.size()), cliOptions.data(), HelpDocumentation{}, address); REQUIRE(result); } } diff --git a/libraries/application/tests/po/adapters/enum.tests.cpp b/libraries/application/tests/po/adapters/enum.tests.cpp index e8b950c4..c507d22f 100644 --- a/libraries/application/tests/po/adapters/enum.tests.cpp +++ b/libraries/application/tests/po/adapters/enum.tests.cpp @@ -46,7 +46,6 @@ TEST_CASE_METHOD(LoggingFixture, "Test parsing of enums as options", "[morpheus. return preferences.drink; }; - auto const exeLocation = boost::dll::program_location().parent_path(); REQUIRE(getDrink("Coke") == Drink::Coke); REQUIRE(getDrink("Pepsi") == Drink::Pepsi); REQUIRE(getDrink("Tango") == Drink::Tango); diff --git a/libraries/application/tests/po/adapters/scannable.tests.cpp b/libraries/application/tests/po/adapters/scannable.tests.cpp new file mode 100644 index 00000000..64cc2e11 --- /dev/null +++ b/libraries/application/tests/po/adapters/scannable.tests.cpp @@ -0,0 +1,78 @@ +#include "morpheus/application/application.hpp" +#include "morpheus/application/po/adapters/scannable.hpp" +#include "morpheus/logging.hpp" + +#include + +#include +#include +#include +#include +#include + +struct Coordinates +{ + double x = 0.0; + double y = 0.0; + + auto operator<=>(Coordinates const&) const = default; +}; + +template <> +struct morpheus::scan_ns::scanner : morpheus::scan_ns::scanner +{ + template + auto scan(Coordinates& val, Context& ctx) const -> morpheus::scan_ns::scan_expected + { + return morpheus::scan_ns::scan(ctx.range(), "[{}, {}]") + .transform( + [&val](auto const& result) + { + std::tie(val.x, val.y) = result.values(); + return result.begin(); + }); + } +}; + +namespace morpheus::application::po +{ + +struct Location +{ + Coordinates coordinates; + + void addOptions(boost::program_options::options_description& options) + { + namespace po = boost::program_options; + // clang-format off + options.add_options() + ("coordinates", po::value(&coordinates), "The 2-dimensional coordinates of the location."); + // clang-format on + } +}; + +TEST_CASE_METHOD(LoggingFixture, "Test parsing of scannable as options", "[morpheus.application.po.adapters.scannable]") +{ + SECTION("Ensure valid value parse correctly") + { + auto getCoordinates = [](std::string_view param) + { + Location location{}; + std::array cliOptions = {"dummyProgram.exe", "--coordinates", param.data()}; + auto const result = parseProgramOptions(static_cast(cliOptions.size()), cliOptions.data(), HelpDocumentation{}, location); + REQUIRE(!result); + return location.coordinates; + }; + + REQUIRE(getCoordinates("[1, 97]") == Coordinates {1.0, 97.0}); + } + SECTION("Ensure invalid value parse correctly") + { + std::array cliOptions = {"dummyProgram.exe", "--coordinates", "invalid"}; + Location location{}; + auto const result = parseProgramOptions(static_cast(cliOptions.size()), cliOptions.data(), HelpDocumentation{}, location); + REQUIRE(result); + } +} + +} // namespace morpheus::application::po diff --git a/libraries/core/src/morpheus/core/containers/concepts/detail/return_types.hpp b/libraries/core/src/morpheus/core/containers/concepts/detail/return_types.hpp index d09c08b2..844ab8ee 100644 --- a/libraries/core/src/morpheus/core/containers/concepts/detail/return_types.hpp +++ b/libraries/core/src/morpheus/core/containers/concepts/detail/return_types.hpp @@ -15,7 +15,7 @@ concept InsertReturnType = requires template concept InsertNodeHandleReturnType = requires { - requires requires { std::same_as; } or requires { std::same_as; }; + requires requires { requires std::same_as; } or requires { requires std::same_as; }; }; template diff --git a/libraries/core/src/morpheus/core/meta/concepts/CMakeLists.txt b/libraries/core/src/morpheus/core/meta/concepts/CMakeLists.txt index 8e892317..af4f5156 100644 --- a/libraries/core/src/morpheus/core/meta/concepts/CMakeLists.txt +++ b/libraries/core/src/morpheus/core/meta/concepts/CMakeLists.txt @@ -10,6 +10,7 @@ target_sources(MorpheusCore enum.hpp hashable.hpp satisfies.hpp + scannable.hpp string.hpp trait.hpp ) diff --git a/libraries/core/src/morpheus/core/meta/concepts/scannable.hpp b/libraries/core/src/morpheus/core/meta/concepts/scannable.hpp new file mode 100644 index 00000000..e75ea738 --- /dev/null +++ b/libraries/core/src/morpheus/core/meta/concepts/scannable.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include "morpheus/core/conformance/scan.hpp" +#include "morpheus/core/meta/detail/scannable.hpp" + +#include + +namespace morpheus::meta::concepts +{ + +/// \concept Scannable +/// Verifies a given T is a scannable type. +template +concept Scannable = detail::ScannableWith, scan_ns::basic_scan_context>; + +} // namespace morpheus::meta::concepts diff --git a/libraries/core/src/morpheus/core/meta/detail/CMakeLists.txt b/libraries/core/src/morpheus/core/meta/detail/CMakeLists.txt index 99ecea24..cf328d1f 100644 --- a/libraries/core/src/morpheus/core/meta/detail/CMakeLists.txt +++ b/libraries/core/src/morpheus/core/meta/detail/CMakeLists.txt @@ -4,4 +4,5 @@ target_sources(MorpheusCore FILES aggregate.hpp any.hpp + scannable.hpp ) diff --git a/libraries/core/src/morpheus/core/meta/detail/scannable.hpp b/libraries/core/src/morpheus/core/meta/detail/scannable.hpp new file mode 100644 index 00000000..72454e02 --- /dev/null +++ b/libraries/core/src/morpheus/core/meta/detail/scannable.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include "morpheus/core/conformance/scan.hpp" + +#include +#include + +namespace morpheus::meta::concepts::detail +{ + +template >> +concept ScannableWith = + std::semiregular && + requires(Scanner& s, const Scanner& cs, T& t, Context& ctx, scan_ns::basic_scan_parse_context pctx) { + { s.parse(pctx) } -> std::same_as>; + { cs.scan(t, ctx) } -> std::same_as>; + }; + +} // namespace morpheus::meta::concepts::detail diff --git a/libraries/core/tests/meta/CMakeLists.txt b/libraries/core/tests/meta/CMakeLists.txt index 783d6633..655637b8 100644 --- a/libraries/core/tests/meta/CMakeLists.txt +++ b/libraries/core/tests/meta/CMakeLists.txt @@ -10,3 +10,5 @@ target_sources(MorpheusCoreTests satisfies.tests.cpp trait.tests.cpp ) + +add_subdirectory(concepts) diff --git a/libraries/core/tests/meta/concepts/CMakeLists.txt b/libraries/core/tests/meta/concepts/CMakeLists.txt new file mode 100644 index 00000000..7a45b61b --- /dev/null +++ b/libraries/core/tests/meta/concepts/CMakeLists.txt @@ -0,0 +1,4 @@ +target_sources(MorpheusCoreTests + PUBLIC + scannable.tests.cpp +) diff --git a/libraries/core/tests/meta/concepts/scannable.tests.cpp b/libraries/core/tests/meta/concepts/scannable.tests.cpp new file mode 100644 index 00000000..d7a94ed0 --- /dev/null +++ b/libraries/core/tests/meta/concepts/scannable.tests.cpp @@ -0,0 +1,48 @@ +#include "morpheus/core/conformance/scan.hpp" +#include "morpheus/core/meta/concepts/scannable.hpp" + +#include + +#include + +struct ScannableType +{ + int first = 0; + double second = 0.0; +}; + +template <> +struct morpheus::scan_ns::scanner : morpheus::scan_ns::scanner +{ + template + auto scan(ScannableType& val, Context& ctx) const -> morpheus::scan_ns::scan_expected + { + return morpheus::scan_ns::scan(ctx.range(), "[{}, {}]") + .transform( + [&val](auto const& result) + { + std::tie(val.first, val.second) = result.values(); + return result.begin(); + }); + } +}; + + +namespace morpheus::meta::concepts +{ + +class UnscannableType; + +TEST_CASE("Meta concept scannable_with verifies a given type customises scan_ns::scanner", "[morpheus.meta.concepts.scannable_with]") +{ + STATIC_REQUIRE(!detail::ScannableWith>); + STATIC_REQUIRE(detail::ScannableWith>); +} + +TEST_CASE("Meta concept scannable_with verifies a given type customises scan_ns::scanner", "[morpheus.meta.concepts.scannable]") +{ + STATIC_REQUIRE(!Scannable); + STATIC_REQUIRE(Scannable); +} + +} // namespace morpheus::meta::concepts