Uses: neo/sqlite3 | Uses: neo/sqlite3 | ||||
Uses: neo/fun | Uses: neo/fun | ||||
Uses: semver/semver | Uses: semver/semver | ||||
Uses: vob/semester | |||||
Uses: pubgrub/pubgrub | Uses: pubgrub/pubgrub | ||||
Uses: vob/json5 | Uses: vob/json5 |
"neo/fun", | "neo/fun", | ||||
"semver/semver", | "semver/semver", | ||||
"pubgrub/pubgrub", | "pubgrub/pubgrub", | ||||
"vob/json5" | |||||
"vob/json5", | |||||
"vob/semester", | |||||
] | ] | ||||
} | } |
Depends: neo-fun 0.1.0 | Depends: neo-fun 0.1.0 | ||||
Depends: semver 0.2.1 | Depends: semver 0.2.1 | ||||
Depends: pubgrub 0.2.0 | Depends: pubgrub 0.2.0 | ||||
Depends: json5 0.1.2 | |||||
Depends: vob-json5 0.1.5-dev | |||||
Depends: semester 0.1.0 | |||||
Test-Driver: Catch-Main | |||||
Test-Driver: Catch-Main |
"neo-fun": "0.1.0", | "neo-fun": "0.1.0", | ||||
"semver": "0.2.1", | "semver": "0.2.1", | ||||
"pubgrub": "0.2.0", | "pubgrub": "0.2.0", | ||||
"json5": "0.1.2" | |||||
"vob-json5": "0.1.5-dev", | |||||
"semester": "*" | |||||
}, | }, | ||||
"test_driver": "Catch-Main" | "test_driver": "Catch-Main" | ||||
} | } |
#include <dds/dym.hpp> | #include <dds/dym.hpp> | ||||
#include <dds/error/errors.hpp> | #include <dds/error/errors.hpp> | ||||
#include <dds/util/algo.hpp> | #include <dds/util/algo.hpp> | ||||
#include <dds/util/json5_read.hpp> | |||||
#include <libman/parse.hpp> | #include <libman/parse.hpp> | ||||
#include <json5/parse_data.hpp> | #include <json5/parse_data.hpp> | ||||
#include <range/v3/view/transform.hpp> | #include <range/v3/view/transform.hpp> | ||||
#include <semester/decomp.hpp> | |||||
#include <spdlog/spdlog.h> | #include <spdlog/spdlog.h> | ||||
using namespace dds; | using namespace dds; | ||||
} | } | ||||
library_manifest lib; | library_manifest lib; | ||||
using namespace json_read::ops; | |||||
json_read::decompose( // | |||||
data.as_object(), | |||||
object(key("name", require_string(put_into{lib.name}, "`name` must be a string")), | |||||
key("uses", | |||||
array_each{require_string( | |||||
[&](auto&& uses) { | |||||
lib.uses.push_back(lm::split_usage_string(uses.as_string())); | |||||
return json_read::accept_t{}; | |||||
}, | |||||
"All `uses` items must be strings")}), | |||||
key("links", | |||||
array_each{require_string( | |||||
[&](auto&& links) { | |||||
lib.links.push_back(lm::split_usage_string(links.as_string())); | |||||
return json_read::accept_t{}; | |||||
}, | |||||
"All `links` items must be strings")}))); | |||||
using namespace semester::decompose_ops; | |||||
auto res = semester::decompose( // | |||||
data, | |||||
try_seq{require_type<json5::data::mapping_type>{ | |||||
"The root of the library manifest must be an object (mapping)"}, | |||||
mapping{ | |||||
if_key{"name", | |||||
require_type<std::string>{"`name` must be a string"}, | |||||
put_into{lib.name}}, | |||||
if_key{"uses", | |||||
require_type<json5::data::array_type>{ | |||||
"`uses` must be an array of usage requirements"}, | |||||
for_each{ | |||||
require_type<std::string>{"`uses` elements must be strings"}, | |||||
[&](auto&& uses) { | |||||
lib.uses.push_back(lm::split_usage_string(uses.as_string())); | |||||
return semester::dc_accept; | |||||
}, | |||||
}}, | |||||
if_key{"links", | |||||
require_type<json5::data::array_type>{ | |||||
"`links` must be an array of usage requirements"}, | |||||
for_each{ | |||||
require_type<std::string>{"`links` elements must be strings"}, | |||||
[&](auto&& links) { | |||||
lib.links.push_back(lm::split_usage_string(links.as_string())); | |||||
return semester::dc_accept; | |||||
}, | |||||
}}, | |||||
}}); | |||||
auto rej = std::get_if<semester::dc_reject_t>(&res); | |||||
if (rej) { | |||||
throw_user_error<errc::invalid_lib_manifest>(rej->message); | |||||
} | |||||
// using namespace json_read::ops; | |||||
// json_read::decompose( // | |||||
// data.as_object(), | |||||
// object(key("name", require_string(put_into{lib.name}, "`name` must be a string")), | |||||
// key("uses", | |||||
// array_each{require_string( | |||||
// [&](auto&& uses) { | |||||
// lib.uses.push_back(lm::split_usage_string(uses.as_string())); | |||||
// return json_read::accept_t{}; | |||||
// }, | |||||
// "All `uses` items must be strings")}), | |||||
// key("links", | |||||
// array_each{require_string( | |||||
// [&](auto&& links) { | |||||
// lib.links.push_back(lm::split_usage_string(links.as_string())); | |||||
// return json_read::accept_t{}; | |||||
// }, | |||||
// "All `links` items must be strings")}))); | |||||
if (lib.name.empty()) { | if (lib.name.empty()) { | ||||
throw_user_error<errc::invalid_lib_manifest>( | throw_user_error<errc::invalid_lib_manifest>( |
#include <dds/dym.hpp> | #include <dds/dym.hpp> | ||||
#include <dds/error/errors.hpp> | #include <dds/error/errors.hpp> | ||||
#include <dds/util/json5_read.hpp> | |||||
#include <dds/util/string.hpp> | #include <dds/util/string.hpp> | ||||
#include <libman/parse.hpp> | #include <libman/parse.hpp> | ||||
#include <range/v3/view/split.hpp> | #include <range/v3/view/split.hpp> | ||||
#include <range/v3/view/split_when.hpp> | #include <range/v3/view/split_when.hpp> | ||||
#include <range/v3/view/transform.hpp> | #include <range/v3/view/transform.hpp> | ||||
#include <semester/decomp.hpp> | |||||
#include <spdlog/spdlog.h> | #include <spdlog/spdlog.h> | ||||
#include <json5/parse_data.hpp> | #include <json5/parse_data.hpp> | ||||
throw_user_error<errc::invalid_pkg_manifest>("Root value must be an object"); | throw_user_error<errc::invalid_pkg_manifest>("Root value must be an object"); | ||||
} | } | ||||
const auto& obj = data.as_object(); | |||||
package_manifest ret; | package_manifest ret; | ||||
using namespace json_read::ops; | |||||
json_read::decompose( | |||||
obj, | |||||
object( | |||||
key("name", require_string(put_into{ret.pkg_id.name}, "`name` must be a string")), | |||||
key("namespace", | |||||
require_string(put_into{ret.namespace_}, "`namespace` must be a string")), | |||||
ignore_key{"$schema"}, | |||||
key("version", | |||||
require_string( | |||||
using namespace semester::decompose_ops; | |||||
auto res = semester::decompose( // | |||||
data, | |||||
try_seq{ | |||||
require_type<json5::data::mapping_type>{ | |||||
"The root of a package manifest must be an object (mapping)"}, | |||||
mapping{ | |||||
if_key{"$schema", just_accept}, | |||||
if_key{ | |||||
"name", | |||||
require_type<std::string>{"`name` must be a string"}, | |||||
put_into{ret.pkg_id.name}, | |||||
}, | |||||
if_key{ | |||||
"namespace", | |||||
require_type<std::string>{"`namespace` must be a string"}, | |||||
put_into{ret.namespace_}, | |||||
}, | |||||
if_key{ | |||||
"version", | |||||
require_type<std::string>{"`version` must be a string"}, | |||||
[&](auto&& version_str_) { | [&](auto&& version_str_) { | ||||
auto& version = version_str_.as_string(); | auto& version = version_str_.as_string(); | ||||
ret.pkg_id.version = semver::version::parse(version); | ret.pkg_id.version = semver::version::parse(version); | ||||
return json_read::accept_t{}; | |||||
return semester::dc_accept; | |||||
}, | }, | ||||
"`version` must be a string")), | |||||
key("depends", object([&](auto key, auto&& range_str_) { | |||||
auto pkg_name = std::string(key); | |||||
if (!range_str_.is_string()) { | |||||
throw_user_error<errc::invalid_pkg_manifest>( | |||||
"Dependency for '{}' must be a range string", pkg_name); | |||||
} | |||||
try { | |||||
auto rng = semver::range::parse_restricted(range_str_.as_string()); | |||||
dependency dep{std::string(pkg_name), {rng.low(), rng.high()}}; | |||||
ret.dependencies.push_back(std::move(dep)); | |||||
} catch (const semver::invalid_range&) { | |||||
throw_user_error<errc::invalid_version_range_string>( | |||||
"Invalid version range string '{}' in dependency declaration for '{}'", | |||||
range_str_.as_string(), | |||||
pkg_name); | |||||
} | |||||
return json_read::accept_t{}; | |||||
})), | |||||
key("test_driver", | |||||
require_string( | |||||
[&](auto&& test_driver_str_) { | |||||
auto& test_driver = test_driver_str_.as_string(); | |||||
if (test_driver == "Catch-Main") { | |||||
ret.test_driver = test_lib::catch_main; | |||||
} else if (test_driver == "Catch") { | |||||
ret.test_driver = test_lib::catch_; | |||||
} else { | |||||
auto dym = *did_you_mean(test_driver, {"Catch-Main", "Catch"}); | |||||
throw_user_error<errc::unknown_test_driver>( | |||||
"Unknown 'test_driver' '{}' (Did you mean '{}'?)", | |||||
test_driver, | |||||
dym); | |||||
}, | |||||
if_key{ | |||||
"depends", | |||||
require_type<json5::data::mapping_type>{ | |||||
"`depends` must be a mapping between package names and version ranges"}, | |||||
mapping{[&](auto pkg_name, auto&& range_str_) { | |||||
if (!range_str_.is_string()) { | |||||
throw_user_error<errc::invalid_pkg_manifest>( | |||||
"Dependency for '{}' must be a range string", pkg_name); | |||||
} | } | ||||
return json_read::accept_t{}; | |||||
}, | |||||
"`test_driver` must be a valid test driver name string")), | |||||
reject_key)); | |||||
try { | |||||
auto rng = semver::range::parse_restricted(range_str_.as_string()); | |||||
dependency dep{std::string(pkg_name), {rng.low(), rng.high()}}; | |||||
ret.dependencies.push_back(std::move(dep)); | |||||
} catch (const semver::invalid_range&) { | |||||
throw_user_error<errc::invalid_version_range_string>( | |||||
"Invalid version range string '{}' in dependency declaration for " | |||||
"'{}'", | |||||
range_str_.as_string(), | |||||
pkg_name); | |||||
} | |||||
return semester::dc_accept; | |||||
}}, | |||||
}, | |||||
if_key{"test_driver", | |||||
require_type<std::string>{"`test_driver` must be a string"}, | |||||
[&](auto&& test_driver_str_) { | |||||
auto& test_driver = test_driver_str_.as_string(); | |||||
if (test_driver == "Catch-Main") { | |||||
ret.test_driver = test_lib::catch_main; | |||||
} else if (test_driver == "Catch") { | |||||
ret.test_driver = test_lib::catch_; | |||||
} else { | |||||
auto dym = *did_you_mean(test_driver, {"Catch-Main", "Catch"}); | |||||
throw_user_error<errc::unknown_test_driver>( | |||||
"Unknown 'test_driver' '{}' (Did you mean '{}'?)", | |||||
test_driver, | |||||
dym); | |||||
} | |||||
return semester::dc_accept; | |||||
}}, | |||||
[&](auto key, auto&&) { | |||||
return semester::dc_reject_t{ | |||||
fmt::format("Unknown key `{}` in package manifest", key)}; | |||||
}}}); | |||||
auto rej = std::get_if<semester::dc_reject_t>(&res); | |||||
if (rej) { | |||||
throw_user_error<errc::invalid_pkg_manifest>(rej->message); | |||||
} | |||||
if (ret.pkg_id.name.empty()) { | if (ret.pkg_id.name.empty()) { | ||||
throw_user_error<errc::invalid_pkg_manifest>("The 'name' field is required."); | throw_user_error<errc::invalid_pkg_manifest>("The 'name' field is required."); |
#pragma once | |||||
#include <json5/data.hpp> | |||||
#include <tuple> | |||||
#include <variant> | |||||
namespace dds { | |||||
namespace json_read { | |||||
struct reject_t { | |||||
std::string message; | |||||
}; | |||||
struct accept_t {}; | |||||
struct pass_t {}; | |||||
using result_var = std::variant<reject_t, accept_t, pass_t>; | |||||
inline namespace ops { | |||||
struct reject { | |||||
std::string_view message; | |||||
result_var operator()(const json5::data&) const noexcept { | |||||
return reject_t{std::string(message)}; | |||||
} | |||||
}; | |||||
template <typename... Handlers> | |||||
struct then { | |||||
std::tuple<Handlers...> _hs; | |||||
explicit then(Handlers... hs) | |||||
: _hs(std::move(hs)...) {} | |||||
result_var _handle(const json5::data&) noexcept { return pass_t{}; } | |||||
template <typename Head, typename... Tail> | |||||
result_var _handle(const json5::data& dat, Head&& h, Tail&&... tail) { | |||||
result_var res = h(dat); | |||||
if (!std::holds_alternative<pass_t>(res)) { | |||||
return res; | |||||
} | |||||
return _handle(dat, tail...); | |||||
} | |||||
result_var operator()(const json5::data& dat) { | |||||
return std::apply([&](auto&&... hs) { return _handle(dat, hs...); }, _hs); | |||||
} | |||||
}; | |||||
template <typename... KeyHandlers> | |||||
struct object { | |||||
std::tuple<KeyHandlers...> _keys; | |||||
explicit object(KeyHandlers... ks) | |||||
: _keys(ks...) {} | |||||
result_var _handle(std::string_view, const json5::data&) { return pass_t{}; } | |||||
template <typename Head, typename... Tail> | |||||
result_var _handle(std::string_view key, const json5::data& dat, Head cur, Tail... ts) { | |||||
result_var current = cur(key, dat); | |||||
if (std::holds_alternative<pass_t>(current)) { | |||||
return _handle(key, dat, ts...); | |||||
} | |||||
return current; | |||||
} | |||||
result_var operator()(const json5::data& dat) { | |||||
if (!dat.is_object()) { | |||||
return pass_t{}; | |||||
} | |||||
for (const auto& [key, val] : dat.as_object()) { | |||||
result_var res | |||||
= std::apply([&](auto... ks) { return _handle(key, val, ks...); }, _keys); | |||||
if (std::holds_alternative<accept_t>(res)) { | |||||
continue; | |||||
} | |||||
if (std::holds_alternative<reject_t>(res)) { | |||||
return res; | |||||
} | |||||
} | |||||
return accept_t{}; | |||||
} | |||||
}; | |||||
template <typename Handler> | |||||
struct array_each { | |||||
Handler _hs; | |||||
result_var operator()(const json5::data& arr) { | |||||
if (!arr.is_array()) { | |||||
return pass_t{}; | |||||
} | |||||
for (const auto& elem : arr.as_array()) { | |||||
result_var res = _hs(elem); | |||||
if (std::holds_alternative<reject_t>(res)) { | |||||
return res; | |||||
} | |||||
} | |||||
return accept_t{}; | |||||
} | |||||
}; | |||||
template <typename Handler> | |||||
array_each(Handler) -> array_each<Handler>; | |||||
template <typename Handler> | |||||
struct key { | |||||
std::string_view _key; | |||||
Handler _handle; | |||||
key(std::string_view k, Handler h) | |||||
: _key(k) | |||||
, _handle(h) {} | |||||
result_var operator()(std::string_view key, const json5::data& dat) { | |||||
if (key == _key) { | |||||
return _handle(dat); | |||||
} | |||||
return pass_t{}; | |||||
} | |||||
}; | |||||
inline struct reject_key_t { | |||||
result_var operator()(std::string_view key, const json5::data&) const noexcept { | |||||
return reject_t{"The key `" + std::string(key) + "` is invalid"}; | |||||
} | |||||
} reject_key; | |||||
struct ignore_key { | |||||
std::string_view key; | |||||
result_var operator()(std::string_view key, const json5::data&) const noexcept { | |||||
if (key == this->key) { | |||||
return accept_t{}; | |||||
} | |||||
return pass_t{}; | |||||
} | |||||
}; | |||||
template <typename T, typename Handler> | |||||
struct accept_type { | |||||
Handler _handle; | |||||
result_var operator()(const json5::data& d) { | |||||
if (!d.is<T>()) { | |||||
return pass_t{}; | |||||
} | |||||
return _handle(d); | |||||
} | |||||
}; | |||||
template <typename T, typename Handler> | |||||
auto accept(Handler h) { | |||||
return accept_type<T, Handler>{h}; | |||||
} | |||||
template <typename H> | |||||
auto if_string(H h) { | |||||
return accept<std::string>(h); | |||||
} | |||||
template <typename H> | |||||
auto require_string(H h, std::string_view msg) { | |||||
return then(if_string(h), reject{msg}); | |||||
} | |||||
template <typename T> | |||||
struct put_into { | |||||
T& _dest; | |||||
result_var operator()(const json5::data& d) { | |||||
_dest = d.as<T>(); | |||||
return accept_t{}; | |||||
} | |||||
}; | |||||
template <typename T> | |||||
put_into(T) -> put_into<T>; | |||||
} // namespace ops | |||||
template <typename Handler> | |||||
auto decompose(const json5::data& dat, Handler&& h) { | |||||
result_var res = h(dat); | |||||
if (std::holds_alternative<reject_t>(res)) { | |||||
throw std::runtime_error(std::get<reject_t>(res).message); | |||||
} | |||||
} | |||||
} // namespace json_read | |||||
} // namespace dds |
# Range-v3 0.10.0 contains an accidental conversion warning | # Range-v3 0.10.0 contains an accidental conversion warning | ||||
Flags: -D SPDLOG_COMPILED_LIB -Werror=return-type -Wno-conversion | Flags: -D SPDLOG_COMPILED_LIB -Werror=return-type -Wno-conversion | ||||
C++-Flags: -fconcepts | C++-Flags: -fconcepts | ||||
Link-Flags: -static-libgcc -static-libstdc++ | |||||
# Debug: True | |||||
Optimize: True | |||||
# Link-Flags: -static-libgcc -static-libstdc++ | |||||
Debug: True | |||||
#Optimize: True | |||||
Compiler-Launcher: ccache | Compiler-Launcher: ccache |