Skip to content

Commit

Permalink
Scan po adapter (#324)
Browse files Browse the repository at this point in the history
* Add scannable concepts

* Compile fixes

* Enable scannable types for use with program options

* Type fix

* Access the scan result value, not the expected
  • Loading branch information
Twon authored Sep 22, 2024
1 parent 7b4fa97 commit b048ae5
Show file tree
Hide file tree
Showing 14 changed files with 204 additions and 4 deletions.
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

0 comments on commit b048ae5

Please sign in to comment.