Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Scan po adapter #324

Merged
merged 7 commits into from
Sep 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ target_sources(MorpheusApplication
FILE_SET HEADERS
FILES
enums.hpp
scannable.hpp
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#pragma once

#include "morpheus/core/conformance/scan.hpp"
#include "morpheus/core/meta/concepts/scannable.hpp"

#include <boost/any.hpp>
#include <boost/program_options.hpp>

#include <string>
#include <vector>

namespace boost
{

template <class CharType, typename S>
requires morpheus::meta::concepts::Scannable<S, CharType>
void validate(boost::any& v, std::vector<std::basic_string<CharType>> 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>(s, "{}");
if (result)
v = result.value().value();
else
throw po::validation_error(po::validation_error::invalid_option_value);
}

} // namespace boost
1 change: 1 addition & 0 deletions libraries/application/tests/po/adapters/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ add_subdirectory(std)
target_sources(MorpheusApplicationTests
PRIVATE
enum.tests.cpp
scannable.tests.cpp
)
4 changes: 2 additions & 2 deletions libraries/application/tests/po/adapters/boost/asio.tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<int>(cliOptions.size()), cliOptions.data(), HelpDocumentation{}, address);
REQUIRE(!result);
return address.ipAddress;
};
Expand All @@ -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<int>(cliOptions.size()), cliOptions.data(), HelpDocumentation{}, address);
REQUIRE(result);
}
}
Expand Down
1 change: 0 additions & 1 deletion libraries/application/tests/po/adapters/enum.tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
78 changes: 78 additions & 0 deletions libraries/application/tests/po/adapters/scannable.tests.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
#include "morpheus/application/application.hpp"
#include "morpheus/application/po/adapters/scannable.hpp"
#include "morpheus/logging.hpp"

#include <catch2/catch_test_macros.hpp>

#include <array>
#include <compare>
#include <cstdint>
#include <string_view>
#include <tuple>

struct Coordinates
{
double x = 0.0;
double y = 0.0;

auto operator<=>(Coordinates const&) const = default;
};

template <>
struct morpheus::scan_ns::scanner<Coordinates> : morpheus::scan_ns::scanner<std::string>
{
template <typename Context>
auto scan(Coordinates& val, Context& ctx) const -> morpheus::scan_ns::scan_expected<typename Context::iterator>
{
return morpheus::scan_ns::scan<int, double>(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<int>(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<int>(cliOptions.size()), cliOptions.data(), HelpDocumentation{}, location);
REQUIRE(result);
}
}

} // namespace morpheus::application::po
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ concept InsertReturnType = requires
template <typename I, typename T>
concept InsertNodeHandleReturnType = requires
{
requires requires { std::same_as<I, typename T::iterator>; } or requires { std::same_as<I, typename T::insert_return_type>; };
requires requires { requires std::same_as<I, typename T::iterator>; } or requires { requires std::same_as<I, typename T::insert_return_type>; };
};

template <typename I, typename T>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ target_sources(MorpheusCore
enum.hpp
hashable.hpp
satisfies.hpp
scannable.hpp
string.hpp
trait.hpp
)
16 changes: 16 additions & 0 deletions libraries/core/src/morpheus/core/meta/concepts/scannable.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#pragma once

#include "morpheus/core/conformance/scan.hpp"
#include "morpheus/core/meta/detail/scannable.hpp"

#include <type_traits>

namespace morpheus::meta::concepts
{

/// \concept Scannable
/// Verifies a given T is a scannable type.
template <class T, class CharT>
concept Scannable = detail::ScannableWith<std::remove_reference_t<T>, scan_ns::basic_scan_context<CharT>>;

} // namespace morpheus::meta::concepts
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ target_sources(MorpheusCore
FILES
aggregate.hpp
any.hpp
scannable.hpp
)
19 changes: 19 additions & 0 deletions libraries/core/src/morpheus/core/meta/detail/scannable.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#pragma once

#include "morpheus/core/conformance/scan.hpp"

#include <concepts>
#include <type_traits>

namespace morpheus::meta::concepts::detail
{

template <class T, class Context, class Scanner = typename Context::template scanner_type<std::remove_const_t<T>>>
concept ScannableWith =
std::semiregular<Scanner> &&
requires(Scanner& s, const Scanner& cs, T& t, Context& ctx, scan_ns::basic_scan_parse_context<typename Context::char_type> pctx) {
{ s.parse(pctx) } -> std::same_as<scan_ns::scan_expected<typename decltype(pctx)::iterator>>;
{ cs.scan(t, ctx) } -> std::same_as<scan_ns::scan_expected<typename Context::iterator>>;
};

} // namespace morpheus::meta::concepts::detail
2 changes: 2 additions & 0 deletions libraries/core/tests/meta/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,5 @@ target_sources(MorpheusCoreTests
satisfies.tests.cpp
trait.tests.cpp
)

add_subdirectory(concepts)
4 changes: 4 additions & 0 deletions libraries/core/tests/meta/concepts/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
target_sources(MorpheusCoreTests
PUBLIC
scannable.tests.cpp
)
48 changes: 48 additions & 0 deletions libraries/core/tests/meta/concepts/scannable.tests.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#include "morpheus/core/conformance/scan.hpp"
#include "morpheus/core/meta/concepts/scannable.hpp"

#include <catch2/catch_all.hpp>

#include <tuple>

struct ScannableType
{
int first = 0;
double second = 0.0;
};

template <>
struct morpheus::scan_ns::scanner<ScannableType> : morpheus::scan_ns::scanner<std::string>
{
template <typename Context>
auto scan(ScannableType& val, Context& ctx) const -> morpheus::scan_ns::scan_expected<typename Context::iterator>
{
return morpheus::scan_ns::scan<int, double>(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<UnscannableType, scan_ns::basic_scan_context<char>>);
STATIC_REQUIRE(detail::ScannableWith<ScannableType, scan_ns::basic_scan_context<char>>);
}

TEST_CASE("Meta concept scannable_with verifies a given type customises scan_ns::scanner", "[morpheus.meta.concepts.scannable]")
{
STATIC_REQUIRE(!Scannable<UnscannableType, char>);
STATIC_REQUIRE(Scannable<ScannableType, char>);
}

} // namespace morpheus::meta::concepts
Loading