瀏覽代碼

Overhaul catalog import to use semester, disable repo transforms for now.

default_compile_flags
vector-of-bool 4 年之前
父節點
當前提交
4212131ab0
共有 12 個文件被更改,包括 481 次插入328 次删除
  1. +4
    -0
      src/dds.main.cpp
  2. +22
    -174
      src/dds/catalog/catalog.cpp
  3. +3
    -31
      src/dds/catalog/catalog.hpp
  4. +3
    -0
      src/dds/catalog/catalog.test.cpp
  5. +17
    -123
      src/dds/catalog/get.cpp
  6. +241
    -0
      src/dds/catalog/import.cpp
  7. +9
    -0
      src/dds/catalog/import.hpp
  8. +79
    -0
      src/dds/catalog/import.test.cpp
  9. +27
    -0
      src/dds/catalog/package_info.hpp
  10. +18
    -0
      src/dds/catalog/remote/git.cpp
  11. +21
    -0
      src/dds/catalog/remote/git.hpp
  12. +37
    -0
      src/dds/util/fs_transform.hpp

+ 4
- 0
src/dds.main.cpp 查看文件

@@ -328,6 +328,10 @@ struct cli_catalog {
}
}

void print_remote_info(std::monostate) {
std::cout << "THIS ENTRY IS MISSING REMOTE INFORMATION!\n";
}

int run() {
auto pk_id = dds::package_id::parse(ident.Get());
auto cat = cat_path.open();

+ 22
- 174
src/dds/catalog/catalog.cpp 查看文件

@@ -1,9 +1,12 @@
#include "./catalog.hpp"

#include "./import.hpp"

#include <dds/dym.hpp>
#include <dds/error/errors.hpp>
#include <dds/solve/solve.hpp>

#include <neo/assert.hpp>
#include <neo/sqlite3/exec.hpp>
#include <neo/sqlite3/iter_tuples.hpp>
#include <neo/sqlite3/single.hpp>
@@ -131,96 +134,6 @@ std::vector<dds::glob> parse_glob_list(const nlohmann::json& data, std::string_v
return ret;
}

std::optional<dds::repo_transform::copy_move> parse_copy_move_transform(nlohmann::json copy) {
if (copy.is_null()) {
return std::nullopt;
}

check_json(copy.is_object(), "'transform[.]/{copy,move}' must be an object");

auto from = copy["from"];
auto to = copy["to"];
check_json(from.is_string(),
"'transform[.]/{copy,move}/from' must be present and must be a string");
check_json(to.is_string(),
"'transform[.]/{copy,move}/to' must be present and must be a string");

dds::repo_transform::copy_move operation;
operation.from = fs::path(std::string(from));
operation.to = fs::path(std::string(to));
if (operation.from.is_absolute()) {
throw_user_error<errc::invalid_catalog_json>(
"The 'from' filepath for a copy/move operation [{}] is an absolute path. These paths "
"*must* be relative paths only.",
operation.from.string());
}
if (operation.to.is_absolute()) {
throw_user_error<errc::invalid_catalog_json>(
"The 'to' filepath for a copy/move operation [{}] is an absolute path. These paths "
"*must* be relative paths only.",
operation.to.string());
}
operation.include = parse_glob_list(copy["include"], "transform[.]/{copy,move}/include");
operation.exclude = parse_glob_list(copy["exclude"], "transform[.]/{copy,move}/exclude");

auto strip_comps = copy["strip_components"];
if (!strip_comps.is_null()) {
check_json(strip_comps.is_number() || int(strip_comps) < 0,
"transform[.]/{copy,move}/strip_components must be a positive integer");
operation.strip_components = int(strip_comps);
}

return operation;
}

dds::repo_transform parse_transform(nlohmann::json data) {
assert(data.is_object());

dds::repo_transform transform;
transform.copy = parse_copy_move_transform(data["copy"]);
transform.move = parse_copy_move_transform(data["move"]);
return transform;
}

nlohmann::json transform_to_json(const dds::repo_transform::copy_move& tr) {
auto obj = nlohmann::json::object();
obj["from"] = tr.from.string();
obj["to"] = tr.to.string();
obj["include"] = ranges::views::all(tr.include) | ranges::views::transform(&dds::glob::string);
obj["exclude"] = ranges::views::all(tr.exclude) | ranges::views::transform(&dds::glob::string);
return obj;
}

nlohmann::json transform_to_json(const struct dds::repo_transform::remove& rm) {
auto obj = nlohmann::json::object();
obj["path"] = rm.path.string();
obj["only_matching"]
= ranges::views::all(rm.only_matching) | ranges::views::transform(&dds::glob::string);
return obj;
}

nlohmann::json transform_to_json(const dds::repo_transform& tr) {
auto obj = nlohmann::json::object();
if (tr.copy) {
obj["copy"] = transform_to_json(*tr.copy);
}
if (tr.move) {
obj["move"] = transform_to_json(*tr.move);
}
if (tr.remove) {
obj["remove"] = transform_to_json(*tr.remove);
}
return obj;
}

std::string transform_to_json(const std::vector<dds::repo_transform>& trs) {
auto arr = nlohmann::json::array();
for (auto& tr : trs) {
arr.push_back(transform_to_json(tr));
}
return to_string(arr);
}

} // namespace

catalog catalog::open(const std::string& db_path) {
@@ -243,6 +156,15 @@ catalog catalog::open(const std::string& db_path) {
catalog::catalog(sqlite3::database db)
: _db(std::move(db)) {}

void catalog::_store_pkg(const package_info& pkg, std::monostate) {
neo_assert_always(
invariant,
false,
"There was an attempt to insert a package listing into the database where that package "
"listing does not have a remote listing. If you see this message, it is a dds bug.",
pkg.ident.to_string());
}

void catalog::_store_pkg(const package_info& pkg, const git_remote_listing& git) {
auto lm_usage = git.auto_lib.value_or(lm::usage{});
sqlite3::exec( //
@@ -275,8 +197,9 @@ void catalog::_store_pkg(const package_info& pkg, const git_remote_listing& git)
git.ref,
lm_usage.name,
lm_usage.namespace_,
pkg.description,
transform_to_json(pkg.transforms)));
pkg.description
//, transform_to_json(pkg.transforms))
));
}

void catalog::store(const package_info& pkg) {
@@ -378,9 +301,10 @@ std::optional<package_info> catalog::get(const package_id& pk_id) const noexcept
check_json(tr_json.is_array(),
fmt::format("Database record for {} has an invalid 'repo_transform' field",
pkg_id));
for (const auto& el : tr_json) {
info.transforms.push_back(parse_transform(el));
}
/// XXX:
// for (const auto& el : tr_json) {
// info.transforms.push_back(parse_transform(el));
// }
}
return info;
}
@@ -436,86 +360,10 @@ std::vector<dependency> catalog::dependencies_of(const package_id& pkg) const no
}

void catalog::import_json_str(std::string_view content) {
using nlohmann::json;

auto root = json::parse(content);
check_json(root.is_object(), "Root of JSON must be an object (key-value mapping)");

auto version = root["version"];
check_json(version.is_number_integer(), "/version must be an integral value");
check_json(version <= 1, "/version is too new. We don't know how to parse this.");

auto packages = root["packages"];
check_json(packages.is_object(), "/packages must be an object");
auto pkgs = parse_packages_json(content);

sqlite3::transaction_guard tr{_db};

for (const auto& [pkg_name_, versions_map] : packages.items()) {
std::string pkg_name = pkg_name_;
check_json(versions_map.is_object(),
fmt::format("/packages/{} must be an object", pkg_name));

for (const auto& [version_, pkg_info] : versions_map.items()) {
auto version = semver::version::parse(version_);
check_json(pkg_info.is_object(),
fmt::format("/packages/{}/{} must be an object", pkg_name, version_));

package_info info{{pkg_name, version}, {}, {}, {}, {}};
auto deps = pkg_info["depends"];

if (!deps.is_null()) {
check_json(deps.is_object(),
fmt::format("/packages/{}/{}/depends must be an object",
pkg_name,
version_));

for (const auto& [dep_name, dep_version] : deps.items()) {
check_json(dep_version.is_string(),
fmt::format("/packages/{}/{}/depends/{} must be a string",
pkg_name,
version_,
dep_name));
auto range = semver::range::parse(std::string(dep_version));
info.deps.push_back({
std::string(dep_name),
{range.low(), range.high()},
});
}
}

auto git_remote = pkg_info["git"];
if (!git_remote.is_null()) {
check_json(git_remote.is_object(), "`git` must be an object");
std::string url = git_remote["url"];
std::string ref = git_remote["ref"];
auto lm_usage = git_remote["auto-lib"];
std::optional<lm::usage> autolib;
if (!lm_usage.is_null()) {
autolib = lm::split_usage_string(std::string(lm_usage));
}
info.remote = git_remote_listing{url, ref, autolib};
} else {
throw_user_error<errc::no_catalog_remote_info>("No remote info for /packages/{}/{}",
pkg_name,
version_);
}

auto transforms = pkg_info["transform"];
if (!transforms.is_null()) {
check_json(transforms.is_array(), "`transform` must be an array of objects");
for (nlohmann::json const& el : transforms) {
check_json(el.is_object(), "Each element of `transform` must be an object");
info.transforms.emplace_back(parse_transform(el));
}
}

auto desc_ = pkg_info["description"];
if (!desc_.is_null()) {
check_json(desc_.is_string(), "`description` must be a string");
info.description = desc_;
}

store(info);
}
for (const auto& pkg : pkgs) {
store(pkg);
}
}

+ 3
- 31
src/dds/catalog/catalog.hpp 查看文件

@@ -1,11 +1,12 @@
#pragma once

#include <dds/catalog/git.hpp>
#include <dds/deps.hpp>
#include <dds/package/id.hpp>
#include <dds/util/fs.hpp>
#include <dds/util/glob.hpp>

#include "./package_info.hpp"

#include <neo/sqlite3/database.hpp>
#include <neo/sqlite3/statement.hpp>
#include <neo/sqlite3/statement_cache.hpp>
@@ -17,36 +18,6 @@

namespace dds {

struct repo_transform {
struct copy_move {
fs::path from;
fs::path to;
int strip_components = 0;
std::vector<dds::glob> include;
std::vector<dds::glob> exclude;
};

struct remove {
fs::path path;

std::vector<dds::glob> only_matching;
};

std::optional<copy_move> copy;
std::optional<copy_move> move;
std::optional<remove> remove;
};

struct package_info {
package_id ident;
std::vector<dependency> deps;
std::string description;

std::variant<git_remote_listing> remote;

std::vector<repo_transform> transforms;
};

class catalog {
neo::sqlite3::database _db;
mutable neo::sqlite3::statement_cache _stmt_cache{_db};
@@ -55,6 +26,7 @@ class catalog {
catalog(const catalog&) = delete;

void _store_pkg(const package_info&, const git_remote_listing&);
void _store_pkg(const package_info&, std::monostate);

public:
catalog(catalog&&) = default;

+ 3
- 0
src/dds/catalog/catalog.test.cpp 查看文件

@@ -20,6 +20,7 @@ TEST_CASE_METHOD(catalog_test_case, "Store a simple package") {
{},
"example",
dds::git_remote_listing{"http://example.com", "master", std::nullopt},
{},
});

auto pkgs = db.by_name("foo");
@@ -39,6 +40,7 @@ TEST_CASE_METHOD(catalog_test_case, "Store a simple package") {
{},
"example",
dds::git_remote_listing{"http://example.com", "develop", std::nullopt},
{},
}));
// The previous pkg_id is still a valid lookup key
info = db.get(pkgs[0]);
@@ -55,6 +57,7 @@ TEST_CASE_METHOD(catalog_test_case, "Package requirements") {
},
"example",
dds::git_remote_listing{"http://example.com", "master", std::nullopt},
{},
});
auto pkgs = db.by_name("foo");
REQUIRE(pkgs.size() == 1);

+ 17
- 123
src/dds/catalog/get.cpp 查看文件

@@ -2,8 +2,8 @@

#include <dds/catalog/catalog.hpp>
#include <dds/error/errors.hpp>
#include <dds/proc.hpp>

#include <neo/assert.hpp>
#include <nlohmann/json.hpp>
#include <range/v3/algorithm/all_of.hpp>
#include <range/v3/algorithm/any_of.hpp>
@@ -15,129 +15,27 @@ using namespace dds;

namespace {

enum operation { move, copy };
void apply_copy(const dds::repo_transform::copy_move& copy, path_ref root, operation op) {
auto copy_src = fs::weakly_canonical(root / copy.from);
auto copy_dest = fs::weakly_canonical(root / copy.to);
if (fs::relative(copy_src, root).generic_string().find("../") == 0) {
throw std::runtime_error(
fmt::format("A copy_src ends up copying from outside the root. (Relative path was "
"[{}], resolved path was [{}])",
copy.from.string(),
copy_src.string()));
}
if (fs::relative(copy_dest, root).generic_string().find("../") == 0) {
throw std::runtime_error(
fmt::format("A copy_dest ends up copying from outside the root. (Relative path was "
"[{}], resolved path was [{}])",
copy.from.string(),
copy_dest.string()));
}

if (fs::is_regular_file(copy_src)) {
// Just copying a single file? Okay.
if (op == move) {
safe_rename(copy_src, copy_dest);
} else {
fs::copy_file(copy_src, copy_dest, fs::copy_options::overwrite_existing);
}
return;
}

auto f_iter = fs::recursive_directory_iterator(copy_src);
for (auto item : f_iter) {
auto relpath = fs::relative(item, copy_src);
auto matches_glob = [&](auto glob) { return glob.match(relpath.string()); };
auto included = ranges::all_of(copy.include, matches_glob);
auto excluded = ranges::any_of(copy.exclude, matches_glob);
if (!included || excluded) {
continue;
}

auto n_components = ranges::distance(relpath);
if (n_components <= copy.strip_components) {
continue;
}

auto it = relpath.begin();
std::advance(it, copy.strip_components);
relpath = ranges::accumulate(it, relpath.end(), fs::path(), std::divides<>());

auto dest = copy_dest / relpath;
fs::create_directories(dest.parent_path());
if (item.is_directory()) {
fs::create_directories(dest);
} else {
if (op == move) {
safe_rename(item, dest);
} else {
fs::copy_file(item, dest, fs::copy_options::overwrite_existing);
}
}
}
}

void apply_remove(const struct dds::repo_transform::remove& rm, path_ref root) {
const auto item = fs::weakly_canonical(root / rm.path);
if (fs::relative(item, root).generic_string().find("../") == 0) {
throw std::runtime_error(fmt::format(
"A 'remove' ends up removing files from outside the root. (Relative path was "
"[{}], resolved path was [{}])",
rm.path.string(),
item.string()));
}

if (!rm.only_matching.empty()) {
if (!fs::is_directory(item)) {
throw std::runtime_error(
fmt::format("A 'remove' item has an 'only_matching' pattern list, but the named "
"path is not a directory [{}]",
item.string()));
}
for (auto glob : rm.only_matching) {
for (auto rm_item : glob.scan_from(item)) {
fs::remove_all(rm_item);
}
}
} else {
fs::remove_all(item);
}

if (fs::is_directory(item)) {
}
}

void apply_transform(const dds::repo_transform& transform, path_ref root) {
if (transform.copy) {
apply_copy(*transform.copy, root, copy);
}
if (transform.move) {
apply_copy(*transform.move, root, move);
}
if (transform.remove) {
apply_remove(*transform.remove, root);
}
temporary_sdist do_pull_sdist(const package_info& listing, std::monostate) {
neo_assert_always(
invariant,
false,
"A package listing in the catalog has no defined remote from which to pull. This "
"shouldn't happen in normal usage. This will occur if the database has been "
"manually altered, or if DDS has a bug.",
listing.ident.to_string());
}

temporary_sdist do_pull_sdist(const package_info& listing, const git_remote_listing& git) {
auto tmpdir = dds::temporary_dir::create();
using namespace std::literals;

spdlog::info("Cloning Git repository: {} [{}] ...", git.url, git.ref);
auto command = {"git"s,
"clone"s,
"--depth=1"s,
"--branch"s,
git.ref,
git.url,
tmpdir.path().generic_string()};
auto git_res = run_proc(command);
if (!git_res.okay()) {
throw_external_error<errc::git_clone_failure>(
"Git clone operation failed [Git command: {}] [Exitted {}]:\n{}",
quote_command(command),
git_res.retc,
git_res.output);
}
git.clone(tmpdir.path());

/// XXX:
// for (const auto& tr : listing.transforms) {
// tr.apply_to(tmpdir.path());
// }

spdlog::info("Create sdist from clone ...");
if (git.auto_lib.has_value()) {
spdlog::info("Generating library data automatically");
@@ -157,10 +55,6 @@ temporary_sdist do_pull_sdist(const package_info& listing, const git_remote_list
lib_strm << nlohmann::to_string(lib_json);
}

for (const auto& tr : listing.transforms) {
apply_transform(tr, tmpdir.path());
}

sdist_params params;
params.project_dir = tmpdir.path();
auto sd_tmp_dir = dds::temporary_dir::create();

+ 241
- 0
src/dds/catalog/import.cpp 查看文件

@@ -0,0 +1,241 @@
#include "./import.hpp"

#include <dds/error/errors.hpp>

#include <json5/parse_data.hpp>
#include <neo/assert.hpp>
#include <semester/decomp.hpp>
#include <spdlog/fmt/fmt.h>

#include <optional>

using namespace dds;

template <typename... Args>
struct any_key {
semester::try_seq<Args...> _seq;
std::string_view& _key;

any_key(std::string_view& key_var, Args&&... args)
: _seq(NEO_FWD(args)...)
, _key{key_var} {}

template <typename Data>
semester::dc_result_t operator()(std::string_view key, Data&& dat) const {
_key = key;
return _seq.invoke(dat);
}
};

template <typename... Args>
any_key(std::string_view, Args&&...) -> any_key<Args&&...>;

namespace {

semester::dc_result_t reject(std::string s) { return semester::dc_reject_t{s}; }
semester::dc_result_t pass = semester::dc_pass;
semester::dc_result_t accept = semester::dc_accept;
using require_obj = semester::require_type<json5::data::mapping_type>;

auto reject_unknown_key(std::string_view path) {
return [path = std::string(path)](auto key, auto&&) { //
return reject(fmt::format("{}: unknown key '{}'", path, key));
};
};

std::vector<dependency> parse_deps_json_v1(const json5::data& deps, std::string_view path) {
std::vector<dependency> acc_deps;
std::string_view dep_name;
std::string_view dep_version_range_str;

using namespace semester::decompose_ops;
auto result = semester::decompose( //
deps,
mapping{any_key{
dep_name,
[&](auto&& range_str) {
if (!range_str.is_string()) {
throw_user_error<
errc::invalid_catalog_json>("{}/{} should be a string version range",
path,
dep_name);
}
try {
auto rng = semver::range::parse_restricted(range_str.as_string());
acc_deps.push_back(dependency{std::string{dep_name}, {rng.low(), rng.high()}});
return accept;
} catch (const semver::invalid_range&) {
throw_user_error<errc::invalid_version_range_string>(
"Invalid version range string '{}' at {}/{}",
range_str.as_string(),
path,
dep_name);
}
},
}});

neo_assert(invariant,
std::holds_alternative<semester::dc_accept_t>(result),
"Parsing dependency object did not accept??");

return acc_deps;
}

package_info parse_pkg_json_v1(std::string_view name,
semver::version version,
std::string_view path,
const json5::data& pkg) {
using namespace semester::decompose_ops;
package_info ret;
ret.ident = package_id{std::string{name}, version};

auto result = semester::decompose( //
pkg,
mapping{if_key{"description",
require_type<std::string>{
fmt::format("{}/description should be a string", path)},
put_into{ret.description}},
if_key{"depends",
require_obj{fmt::format("{}/depends must be a JSON object", path)},
[&](auto&& dep_obj) {
ret.deps = parse_deps_json_v1(dep_obj, fmt::format("{}/depends", path));
return accept;
}},
if_key{
"git",
require_obj{fmt::format("{}/git must be a JSON object", path)},
[&](auto&& git_obj) {
git_remote_listing git_remote;

auto r = semester::decompose(
git_obj,
mapping{
if_key{"url", put_into{git_remote.url}},
if_key{"ref", put_into{git_remote.ref}},
if_key{"auto-lib",
require_type<std::string>{
fmt::format("{}/git/auto-lib must be a string", path)},
[&](auto&& al) {
git_remote.auto_lib
= lm::split_usage_string(al.as_string());
return accept;
}},
reject_unknown_key(std::string(path) + "/git"),
});

if (git_remote.url.empty() || git_remote.ref.empty()) {
throw_user_error<errc::invalid_catalog_json>(
"{}/git requires both 'url' and 'ref' non-empty string properties",
path);
}

ret.remote = git_remote;
return r;
},
},
reject_unknown_key(path)});

if (std::holds_alternative<std::monostate>(ret.remote)) {
throw_user_error<
errc::invalid_catalog_json>("{}: Requires a remote listing (e.g. a 'git' proprety).",
path);
}
return ret;
}

std::vector<package_info> parse_json_v1(const json5::data& data) {
using namespace semester::decompose_ops;
auto packages_it = data.as_object().find("packages");
if (packages_it == data.as_object().end() || !packages_it->second.is_object()) {
throw_user_error<errc::invalid_catalog_json>(
"Root JSON object requires a 'packages' property");
}

std::vector<package_info> acc_pkgs;

std::string_view pkg_name;
std::string_view pkg_version_str;

auto result = semester::decompose(
data,
mapping{
// Ignore the "version" key at this level
if_key{"version", just_accept},
if_key{
"packages",
mapping{any_key{
pkg_name,
[&](auto&& entry) {
if (!entry.is_object()) {
return reject(
fmt::format("/packages/{} must be a JSON object", pkg_name));
}
return pass;
},
mapping{any_key{
pkg_version_str,
[&](auto&& pkg_def) {
semver::version version;
try {
version = semver::version::parse(pkg_version_str);
} catch (const semver::invalid_version& e) {
throw_user_error<errc::invalid_catalog_json>(
"/packages/{} version string '{}' is invalid: {}",
pkg_name,
pkg_version_str,
e.what());
}
if (!pkg_def.is_object()) {
return reject(fmt::format("/packages/{}/{} must be a JSON object"));
}
auto pkg = parse_pkg_json_v1(pkg_name,
version,
fmt::format("/packages/{}/{}",
pkg_name,
pkg_version_str),
pkg_def);
acc_pkgs.emplace_back(std::move(pkg));
return accept;
},
}},
}},
},
reject_unknown_key("/"),
});

auto rej = std::get_if<semester::dc_reject_t>(&result);
if (rej) {
throw_user_error<errc::invalid_catalog_json>(rej->message);
}
return acc_pkgs;
}

} // namespace

std::vector<package_info> dds::parse_packages_json(std::string_view content) {
json5::data data;
try {
data = json5::parse_data(content);
} catch (const json5::parse_error& e) {
throw_user_error<errc::invalid_catalog_json>("JSON5 syntax error: {}", e.what());
}

if (!data.is_object()) {
throw_user_error<errc::invalid_catalog_json>("Root of import JSON must be a JSON object");
}

auto& data_obj = data.as_object();
auto version_it = data_obj.find("version");
if (version_it == data_obj.end() || !version_it->second.is_number()) {
throw_user_error<errc::invalid_catalog_json>(
"Root JSON import requires a 'version' property");
}

double version = version_it->second.as_number();

if (version == 1.0) {
return parse_json_v1(data);
} else {
throw_user_error<errc::invalid_catalog_json>("Unknown catalog JSON version '{}'", version);
}
}

+ 9
- 0
src/dds/catalog/import.hpp 查看文件

@@ -0,0 +1,9 @@
#pragma once

#include "./package_info.hpp"

namespace dds {

std::vector<package_info> parse_packages_json(std::string_view);

} // namespace dds

+ 79
- 0
src/dds/catalog/import.test.cpp 查看文件

@@ -0,0 +1,79 @@
#include "./import.hpp"

#include <dds/error/errors.hpp>

#include <catch2/catch.hpp>

TEST_CASE("An empty import is okay") {
// An empty JSON with no packages in it
auto pkgs = dds::parse_packages_json("{version: 1, packages: {}}");
CHECK(pkgs.empty());
}

TEST_CASE("Valid/invalid package JSON5") {
std::string_view bads[] = {
// Invalid JSON:
"",
// Should be an object
"[]",
// Missing keys
"{}",
// Missing "packages"
"{version: 1}",
// Bad version
"{version: 1.7, packages: {}}",
"{version: [], packages: {}}",
"{version: null, packages: {}}",
// 'packages' should be an object
"{version: 1, packages: []}",
"{version: 1, packages: null}",
"{version: 1, packages: 4}",
"{version: 1, packages: 'lol'}",
// Objects in 'packages' should be objects
"{version:1, packages:{foo:null}}",
"{version:1, packages:{foo:[]}}",
"{version:1, packages:{foo:9}}",
"{version:1, packages:{foo:'lol'}}",
// Objects in 'packages' shuold have version strings
"{version:1, packages:{foo:{'lol':{}}}}",
"{version:1, packages:{foo:{'1.2':{}}}}",
};

for (auto bad : bads) {
INFO("Bad: " << bad);
CHECK_THROWS_AS(dds::parse_packages_json(bad),
dds::user_error<dds::errc::invalid_catalog_json>);
}

std::string_view goods[] = {
// Basic empty:
"{version:1, packages:{}}",
// No versions for 'foo' is weird, but okay
"{version:1, packages:{foo:{}}}",
};
for (auto good : goods) {
INFO("Parse: " << good);
CHECK_NOTHROW(dds::parse_packages_json(good));
}
}

TEST_CASE("Check a single object") {
// An empty JSON with no packages in it
auto pkgs = dds::parse_packages_json(R"({
version: 1,
packages: {
foo: {
'1.2.3': {
git: {
url: 'foo',
ref: 'fasdf'
}
}
}
}
})");
CHECK(pkgs.size() == 1);
CHECK(pkgs[0].ident.name == "foo");
CHECK(pkgs[0].ident.to_string() == "foo@1.2.3");
CHECK(std::holds_alternative<dds::git_remote_listing>(pkgs[0].remote));
}

+ 27
- 0
src/dds/catalog/package_info.hpp 查看文件

@@ -0,0 +1,27 @@
#pragma once

#include "./remote/git.hpp"

#include <dds/deps.hpp>
#include <dds/package/id.hpp>
#include <dds/util/fs_transform.hpp>
#include <dds/util/glob.hpp>

#include <optional>
#include <string>
#include <variant>
#include <vector>

namespace dds {

struct package_info {
package_id ident;
std::vector<dependency> deps;
std::string description;

std::variant<std::monostate, git_remote_listing> remote;

std::vector<fs_transformation> transforms;
};

} // namespace dds

+ 18
- 0
src/dds/catalog/remote/git.cpp 查看文件

@@ -0,0 +1,18 @@
#include "./git.hpp"

#include <dds/error/errors.hpp>
#include <dds/proc.hpp>

void dds::git_remote_listing::clone(dds::path_ref dest) const {
fs::remove_all(dest);
using namespace std::literals;
auto command = {"git"s, "clone"s, "--depth=1"s, "--branch"s, ref, url, dest.generic_string()};
auto git_res = run_proc(command);
if (!git_res.okay()) {
throw_external_error<errc::git_clone_failure>(
"Git clone operation failed [Git command: {}] [Exitted {}]:\n{}",
quote_command(command),
git_res.retc,
git_res.output);
}
}

+ 21
- 0
src/dds/catalog/remote/git.hpp 查看文件

@@ -0,0 +1,21 @@
#pragma once

#include <dds/catalog/get.hpp>
#include <dds/util/fs.hpp>

#include <libman/package.hpp>

#include <optional>
#include <string>

namespace dds {

struct git_remote_listing {
std::string url;
std::string ref;
std::optional<lm::usage> auto_lib;

void clone(path_ref path) const;
};

} // namespace dds

+ 37
- 0
src/dds/util/fs_transform.hpp 查看文件

@@ -0,0 +1,37 @@
#pragma once

#include "./fs.hpp"
#include "./glob.hpp"

#include <optional>
#include <variant>

namespace dds {

class fs_transformation {
struct copy_move_base {
fs::path from;
fs::path to;

int strip_components = 0;
std::vector<dds::glob> include;
std::vector<dds::glob> exclude;
};

struct copy : copy_move_base {};
struct move : copy_move_base {};

struct remove {
fs::path path;

std::vector<dds::glob> only_matching;
};

std::optional<struct copy> copy;
std::optional<struct move> move;
std::optional<remove> remove;

void apply_to(path_ref root) const;
};

} // namespace dds

Loading…
取消
儲存