Browse Source

Use semester for JSON processing

default_compile_flags
vector-of-bool 4 years ago
parent
commit
bb3c1b0bc1
8 changed files with 135 additions and 276 deletions
  1. +1
    -0
      library.dds
  2. +2
    -1
      library.jsonc
  3. +3
    -2
      package.dds
  4. +2
    -1
      package.jsonc
  5. +53
    -19
      src/dds/library/manifest.cpp
  6. +71
    -51
      src/dds/package/manifest.cpp
  7. +0
    -199
      src/dds/util/json5_read.hpp
  8. +3
    -3
      tools/gcc-9.dds

+ 1
- 0
library.dds View File

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

+ 2
- 1
library.jsonc View File

"neo/fun", "neo/fun",
"semver/semver", "semver/semver",
"pubgrub/pubgrub", "pubgrub/pubgrub",
"vob/json5"
"vob/json5",
"vob/semester",
] ]
} }

+ 3
- 2
package.dds View File

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

+ 2
- 1
package.jsonc View File

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

+ 53
- 19
src/dds/library/manifest.cpp View File

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

+ 71
- 51
src/dds/package/manifest.cpp View File



#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.");

+ 0
- 199
src/dds/util/json5_read.hpp View File

#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

+ 3
- 3
tools/gcc-9.dds View File

# 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

Loading…
Cancel
Save