| @@ -0,0 +1,15 @@ | |||
| { | |||
| "$schema": "./res/library-schema.json", | |||
| "name": "dds", | |||
| "uses": [ | |||
| "spdlog/spdlog", | |||
| "Microsoft/wil", | |||
| "range-v3/range-v3", | |||
| "nlohmann/json", | |||
| "neo/sqlite3", | |||
| "neo/fun", | |||
| "semver/semver", | |||
| "pubgrub/pubgrub", | |||
| "vob/json5" | |||
| ] | |||
| } | |||
| @@ -0,0 +1,34 @@ | |||
| { | |||
| "type": "object", | |||
| "description": "DDS Library Manifest", | |||
| "additionalProperties": false, | |||
| "patternProperties": { | |||
| "^\\$": {} | |||
| }, | |||
| "required": [ | |||
| "name" | |||
| ], | |||
| "properties": { | |||
| "name": { | |||
| "type": "string", | |||
| "description": "The name of the library within the package.", | |||
| "pattern": "^[A-z][A-z0-9_]*((\\.|-)[A-z0-9_]+)*$" | |||
| }, | |||
| "uses": { | |||
| "type": "array", | |||
| "items": { | |||
| "type": "string", | |||
| "description": "A library that is used by this library. Should be of the form `namespace/name`.", | |||
| "pattern": "^[A-z][A-z0-9_]*((\\.|-)[A-z0-9_]+)*/[A-z][A-z0-9_]*((\\.|-)[A-z0-9_]+)*$" | |||
| } | |||
| }, | |||
| "links": { | |||
| "type": "array", | |||
| "items": { | |||
| "type": "string", | |||
| "description": "A library that is linked to this library. Should be of the form `namespace/name`.", | |||
| "pattern": "^[A-z][A-z0-9_]*((\\.|-)[A-z0-9_]+)*/[A-z][A-z0-9_]*((\\.|-)[A-z0-9_]+)*$" | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -13,13 +13,13 @@ | |||
| "properties": { | |||
| "name": { | |||
| "type": "string", | |||
| "description": "The name of the package. Must be a valid Semantic Version string.", | |||
| "description": "The name of the package", | |||
| "pattern": "^[a-z][a-z0-9_]*((\\.|-)[a-z0-9_]+)*$" | |||
| }, | |||
| "version": { | |||
| "type": "string", | |||
| "pattern": "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$", | |||
| "description": "The version of the package. Required", | |||
| "description": "The version of the package. Must be a valid Semantic Version string.", | |||
| "default": "0.1.0" | |||
| }, | |||
| "namespace": { | |||
| @@ -41,6 +41,8 @@ std::string error_url_suffix(dds::errc ec) noexcept { | |||
| return "sdist-ident-mismatch.html"; | |||
| case errc::corrupted_build_db: | |||
| return "corrupted-build-db.html"; | |||
| case errc::invalid_lib_manifest: | |||
| return "invalid-lib-manifest.html"; | |||
| case errc::invalid_pkg_manifest: | |||
| return "invalid-pkg-manifest.html"; | |||
| case errc::invalid_version_range_string: | |||
| @@ -141,6 +143,11 @@ modified by a newer version of dds? | |||
| The catalog database schema doesn't match what dds expects. This indicates that | |||
| the database file has been modified in a way that dds cannot automatically fix | |||
| and handle. | |||
| )"; | |||
| case errc::invalid_lib_manifest: | |||
| return R"( | |||
| A library manifest is malformed Refer to the documentation and above error | |||
| message for more details. | |||
| )"; | |||
| case errc::invalid_pkg_manifest: | |||
| return R"( | |||
| @@ -273,6 +280,8 @@ std::string_view dds::default_error_string(dds::errc ec) noexcept { | |||
| "that was expected of it"; | |||
| case errc::corrupted_build_db: | |||
| return "The build database file is corrupted"; | |||
| case errc::invalid_lib_manifest: | |||
| return "The library manifest is invalid"; | |||
| case errc::invalid_pkg_manifest: | |||
| return "The package manifest is invalid"; | |||
| case errc::invalid_version_range_string: | |||
| @@ -280,17 +289,14 @@ std::string_view dds::default_error_string(dds::errc ec) noexcept { | |||
| "`dds` bug. Please report it.)"; | |||
| case errc::invalid_version_string: | |||
| return "Attempted to parse an invalid version string. <- (Seeing this text is a `dds` " | |||
| "bug. " | |||
| "Please report it.)"; | |||
| "bug. Please report it.)"; | |||
| case errc::invalid_config_key: | |||
| return "Found an invalid configuration key. <- (Seeing this text is a `dds` bug. " | |||
| "Please " | |||
| "report it.)"; | |||
| "Please report it.)"; | |||
| case errc::invalid_lib_filesystem: | |||
| case errc::invalid_pkg_filesystem: | |||
| return "The filesystem structure of the package/library is invalid. <- (Seeing this " | |||
| "text " | |||
| "is a `dds` bug. Please report it.)"; | |||
| "text is a `dds` bug. Please report it.)"; | |||
| case errc::invalid_pkg_id: | |||
| return "A package identifier is invalid <- (Seeing this text is a `dds` bug. Please " | |||
| "report it.)"; | |||
| @@ -299,8 +305,7 @@ std::string_view dds::default_error_string(dds::errc ec) noexcept { | |||
| "it.)"; | |||
| case errc::sdist_exists: | |||
| return "The source ditsribution already exists at the destination <- (Seeing this " | |||
| "text is " | |||
| "a `dds` bug. Please report it.)"; | |||
| "text is a `dds` bug. Please report it.)"; | |||
| case errc::unknown_test_driver: | |||
| return "The specified Test-Driver is not known to `dds`"; | |||
| case errc::dependency_resolve_failure: | |||
| @@ -29,6 +29,7 @@ enum class errc { | |||
| corrupted_build_db, | |||
| invalid_lib_manifest, | |||
| invalid_pkg_manifest, | |||
| invalid_version_range_string, | |||
| invalid_version_string, | |||
| @@ -1,16 +1,21 @@ | |||
| #include "./manifest.hpp" | |||
| #include <dds/dym.hpp> | |||
| #include <dds/error/errors.hpp> | |||
| #include <dds/util/algo.hpp> | |||
| #include <range/v3/view/transform.hpp> | |||
| #include <dds/util/json5_read.hpp> | |||
| #include <libman/parse.hpp> | |||
| #include <spdlog/fmt/fmt.h> | |||
| #include <json5/parse_data.hpp> | |||
| #include <range/v3/view/transform.hpp> | |||
| #include <spdlog/spdlog.h> | |||
| using namespace dds; | |||
| library_manifest library_manifest::load_from_file(const fs::path& fpath) { | |||
| library_manifest library_manifest::load_from_dds_file(path_ref fpath) { | |||
| spdlog::warn( | |||
| "Using deprecated library.dds parsing (on file {}). This will be removed soon. Migrate!", | |||
| fpath.string()); | |||
| auto kvs = lm::parse_file(fpath); | |||
| library_manifest ret; | |||
| ret.name = fpath.parent_path().filename().string(); | |||
| @@ -27,3 +32,73 @@ library_manifest library_manifest::load_from_file(const fs::path& fpath) { | |||
| extend(ret.links, ranges::views::transform(links_strings, lm::split_usage_string)); | |||
| return ret; | |||
| } | |||
| library_manifest library_manifest::load_from_file(path_ref fpath) { | |||
| auto content = slurp_file(fpath); | |||
| auto data = json5::parse_data(content); | |||
| if (!data.is_object()) { | |||
| throw_user_error<errc::invalid_lib_manifest>("Root value must be an object"); | |||
| } | |||
| 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")}))); | |||
| if (lib.name.empty()) { | |||
| throw_user_error<errc::invalid_lib_manifest>( | |||
| "The 'name' field is required (Reading library manifest [{}])", fpath.string()); | |||
| } | |||
| return lib; | |||
| } | |||
| std::optional<fs::path> library_manifest::find_in_directory(path_ref dirpath) { | |||
| auto fnames = { | |||
| "library.json5", | |||
| "library.jsonc", | |||
| "library.json", | |||
| }; | |||
| for (auto c : fnames) { | |||
| auto cand = dirpath / c; | |||
| if (fs::is_regular_file(cand)) { | |||
| return cand; | |||
| } | |||
| } | |||
| auto dds_file = dirpath / "library.dds"; | |||
| if (fs::is_regular_file(dds_file)) { | |||
| return dds_file; | |||
| } | |||
| return std::nullopt; | |||
| } | |||
| std::optional<library_manifest> library_manifest::load_from_directory(path_ref dirpath) { | |||
| auto found = find_in_directory(dirpath); | |||
| if (!found.has_value()) { | |||
| return std::nullopt; | |||
| } | |||
| if (found->extension() == ".dds") { | |||
| return load_from_dds_file(*found); | |||
| } else { | |||
| return load_from_file(*found); | |||
| } | |||
| } | |||
| @@ -24,7 +24,16 @@ struct library_manifest { | |||
| /** | |||
| * Load the library manifest from an existing file | |||
| */ | |||
| static library_manifest load_from_file(const fs::path&); | |||
| static library_manifest load_from_file(path_ref); | |||
| static library_manifest load_from_dds_file(path_ref); | |||
| /** | |||
| * Find a library manifest within a directory. This will search for a few | |||
| * file candidates and return the result from the first matching. If none | |||
| * match, it will return nullopt. | |||
| */ | |||
| static std::optional<fs::path> find_in_directory(path_ref); | |||
| static std::optional<library_manifest> load_from_directory(path_ref); | |||
| }; | |||
| } // namespace dds | |||
| @@ -55,10 +55,14 @@ library_root library_root::from_directory(path_ref lib_dir) { | |||
| auto sources = collect_pf_sources(lib_dir); | |||
| library_manifest man; | |||
| man.name = lib_dir.filename().string(); | |||
| auto man_path = lib_dir / "library.dds"; | |||
| if (fs::is_regular_file(man_path)) { | |||
| man = library_manifest::load_from_file(man_path); | |||
| man.name = lib_dir.filename().string(); | |||
| auto found = library_manifest::find_in_directory(lib_dir); | |||
| if (found) { | |||
| if (found->extension() == ".dds") { | |||
| man = library_manifest::load_from_dds_file(*found); | |||
| } else { | |||
| man = library_manifest::load_from_file(*found); | |||
| } | |||
| } | |||
| auto lib = library_root(lib_dir, std::move(sources), std::move(man)); | |||
| @@ -81,8 +81,8 @@ package_manifest package_manifest::load_from_file(const fs::path& fpath) { | |||
| const auto& obj = data.as_object(); | |||
| package_manifest ret; | |||
| using namespace j5_read::ops; | |||
| j5_read::destructure( | |||
| 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")), | |||
| @@ -94,7 +94,7 @@ package_manifest package_manifest::load_from_file(const fs::path& fpath) { | |||
| [&](auto&& version_str_) { | |||
| auto& version = version_str_.as_string(); | |||
| ret.pkg_id.version = semver::version::parse(version); | |||
| return j5_read::accept_t{}; | |||
| return json_read::accept_t{}; | |||
| }, | |||
| "`version` must be a string")), | |||
| key("depends", object([&](auto key, auto&& range_str_) { | |||
| @@ -113,7 +113,7 @@ package_manifest package_manifest::load_from_file(const fs::path& fpath) { | |||
| range_str_.as_string(), | |||
| pkg_name); | |||
| } | |||
| return j5_read::accept_t{}; | |||
| return json_read::accept_t{}; | |||
| })), | |||
| key("test_driver", | |||
| require_string( | |||
| @@ -130,7 +130,7 @@ package_manifest package_manifest::load_from_file(const fs::path& fpath) { | |||
| test_driver, | |||
| dym); | |||
| } | |||
| return j5_read::accept_t{}; | |||
| return json_read::accept_t{}; | |||
| }, | |||
| "`test_driver` must be a valid test driver name string")), | |||
| reject_key)); | |||
| @@ -7,7 +7,7 @@ | |||
| namespace dds { | |||
| namespace j5_read { | |||
| namespace json_read { | |||
| struct reject_t { | |||
| std::string message; | |||
| @@ -51,9 +51,6 @@ struct then { | |||
| } | |||
| }; | |||
| template <typename... Hs> | |||
| then(Hs...) -> then<Hs...>; | |||
| template <typename... KeyHandlers> | |||
| struct object { | |||
| std::tuple<KeyHandlers...> _keys; | |||
| @@ -92,8 +89,26 @@ struct object { | |||
| } | |||
| }; | |||
| template <typename... Ks> | |||
| object(Ks...) -> object<Ks...>; | |||
| 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 { | |||
| @@ -172,13 +187,13 @@ put_into(T) -> put_into<T>; | |||
| } // namespace ops | |||
| template <typename Handler> | |||
| auto destructure(const json5::data& dat, Handler&& h) { | |||
| 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 j5_read | |||
| } // namespace json_read | |||
| } // namespace dds | |||