@@ -7,5 +7,6 @@ Uses: nlohmann/json | |||
Uses: neo/sqlite3 | |||
Uses: neo/fun | |||
Uses: semver/semver | |||
Uses: vob/semester | |||
Uses: pubgrub/pubgrub | |||
Uses: vob/json5 |
@@ -10,6 +10,7 @@ | |||
"neo/fun", | |||
"semver/semver", | |||
"pubgrub/pubgrub", | |||
"vob/json5" | |||
"vob/json5", | |||
"vob/semester", | |||
] | |||
} |
@@ -10,6 +10,7 @@ Depends: neo-sqlite3 0.2.2 | |||
Depends: neo-fun 0.1.0 | |||
Depends: semver 0.2.1 | |||
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 |
@@ -12,7 +12,8 @@ | |||
"neo-fun": "0.1.0", | |||
"semver": "0.2.1", | |||
"pubgrub": "0.2.0", | |||
"json5": "0.1.2" | |||
"vob-json5": "0.1.5-dev", | |||
"semester": "*" | |||
}, | |||
"test_driver": "Catch-Main" | |||
} |
@@ -3,11 +3,11 @@ | |||
#include <dds/dym.hpp> | |||
#include <dds/error/errors.hpp> | |||
#include <dds/util/algo.hpp> | |||
#include <dds/util/json5_read.hpp> | |||
#include <libman/parse.hpp> | |||
#include <json5/parse_data.hpp> | |||
#include <range/v3/view/transform.hpp> | |||
#include <semester/decomp.hpp> | |||
#include <spdlog/spdlog.h> | |||
using namespace dds; | |||
@@ -42,24 +42,58 @@ library_manifest library_manifest::load_from_file(path_ref fpath) { | |||
} | |||
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()) { | |||
throw_user_error<errc::invalid_lib_manifest>( |
@@ -2,13 +2,13 @@ | |||
#include <dds/dym.hpp> | |||
#include <dds/error/errors.hpp> | |||
#include <dds/util/json5_read.hpp> | |||
#include <dds/util/string.hpp> | |||
#include <libman/parse.hpp> | |||
#include <range/v3/view/split.hpp> | |||
#include <range/v3/view/split_when.hpp> | |||
#include <range/v3/view/transform.hpp> | |||
#include <semester/decomp.hpp> | |||
#include <spdlog/spdlog.h> | |||
#include <json5/parse_data.hpp> | |||
@@ -78,62 +78,82 @@ package_manifest package_manifest::load_from_file(const fs::path& fpath) { | |||
throw_user_error<errc::invalid_pkg_manifest>("Root value must be an object"); | |||
} | |||
const auto& obj = data.as_object(); | |||
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 = version_str_.as_string(); | |||
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()) { | |||
throw_user_error<errc::invalid_pkg_manifest>("The 'name' field is required."); |
@@ -1,199 +0,0 @@ | |||
#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 |
@@ -5,7 +5,7 @@ C++-Compiler: g++-9 | |||
# Range-v3 0.10.0 contains an accidental conversion warning | |||
Flags: -D SPDLOG_COMPILED_LIB -Werror=return-type -Wno-conversion | |||
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 |