| 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 |