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