#include <dds/dym.hpp> | #include <dds/dym.hpp> | ||||
#include <dds/error/errors.hpp> | #include <dds/error/errors.hpp> | ||||
#include <dds/error/nonesuch.hpp> | |||||
#include <dds/pkg/db.hpp> | #include <dds/pkg/db.hpp> | ||||
#include <dds/pkg/get/get.hpp> | #include <dds/pkg/get/get.hpp> | ||||
#include <dds/util/http/pool.hpp> | #include <dds/util/http/pool.hpp> | ||||
static int _pkg_get(const options& opts) { | static int _pkg_get(const options& opts) { | ||||
auto cat = opts.open_pkg_db(); | auto cat = opts.open_pkg_db(); | ||||
for (const auto& item : opts.pkg.get.pkgs) { | for (const auto& item : opts.pkg.get.pkgs) { | ||||
auto id = pkg_id::parse(item); | |||||
dds::dym_target dym; | |||||
auto info = cat.get(id); | |||||
if (!info) { | |||||
dds::throw_user_error<dds::errc::no_such_catalog_package>( | |||||
"No package in the database matched the ID '{}'.{}", item, dym.sentence_suffix()); | |||||
} | |||||
auto tsd = get_package_sdist(*info); | |||||
auto id = pkg_id::parse(item); | |||||
auto info = *cat.get(id); | |||||
auto tsd = get_package_sdist(info); | |||||
auto dest = opts.out_path.value_or(fs::current_path()) / id.to_string(); | auto dest = opts.out_path.value_or(fs::current_path()) / id.to_string(); | ||||
dds_log(info, "Create sdist at {}", dest.string()); | dds_log(info, "Create sdist at {}", dest.string()); | ||||
fs::remove_all(dest); | fs::remove_all(dest); | ||||
dds_log(error, "Error accessing the package database: {}", e.message); | dds_log(error, "Error accessing the package database: {}", e.message); | ||||
return 1; | return 1; | ||||
}, | }, | ||||
[](e_nonesuch nonesuch) -> int { | |||||
nonesuch.log_error("There is no entry in the package database for '{}'."); | |||||
write_error_marker("pkg-get-no-pkg-id-listing"); | |||||
return 1; | |||||
}, | |||||
[&](dds::e_system_error_exc e, dds::network_origin conn) { | [&](dds::e_system_error_exc e, dds::network_origin conn) { | ||||
dds_log(error, | dds_log(error, | ||||
"Error opening connection to [{}:{}]: {}", | "Error opening connection to [{}:{}]: {}", |
#include <dds/dym.hpp> | #include <dds/dym.hpp> | ||||
#include <dds/error/errors.hpp> | #include <dds/error/errors.hpp> | ||||
#include <dds/error/nonesuch.hpp> | |||||
#include <dds/pkg/remote.hpp> | #include <dds/pkg/remote.hpp> | ||||
#include <dds/util/http/pool.hpp> | #include <dds/util/http/pool.hpp> | ||||
#include <dds/util/log.hpp> | #include <dds/util/log.hpp> | ||||
e.message); | e.message); | ||||
return 1; | return 1; | ||||
}, | }, | ||||
[](matchv<pkg_repo_subcommand::remove>, | |||||
user_error<errc::no_catalog_remote_info>, | |||||
e_remote_name reponame, | |||||
dds::e_did_you_mean dym) { | |||||
dds_log(error, | |||||
"Cannot delete remote '{}', as no such remote repository is locally " | |||||
"registered by that name.", | |||||
reponame.value); | |||||
dym.log_as_error(); | |||||
[](matchv<pkg_repo_subcommand::remove>, e_nonesuch missing) { | |||||
missing.log_error( | |||||
"Cannot delete remote '{}', as no such remote repository is locally registered by " | |||||
"that name."); | |||||
write_error_marker("repo-rm-no-such-repo"); | write_error_marker("repo-rm-no-such-repo"); | ||||
return 1; | return 1; | ||||
}); | }); |
using namespace dds; | using namespace dds; | ||||
thread_local dym_target* dym_target::_tls_current = nullptr; | |||||
std::size_t dds::lev_edit_distance(std::string_view a, std::string_view b) noexcept { | std::size_t dds::lev_edit_distance(std::string_view a, std::string_view b) noexcept { | ||||
const auto n_rows = b.size() + 1; | const auto n_rows = b.size() + 1; | ||||
const auto n_columns = a.size() + 1; | const auto n_columns = a.size() + 1; | ||||
return matrix.back().back(); | return matrix.back().back(); | ||||
} | } | ||||
void dds::e_did_you_mean::log_as_error() const noexcept { | |||||
if (value) { | |||||
dds_log(error, " (Did you mean \"{}\"?)", *value); | |||||
} | |||||
} |
std::size_t lev_edit_distance(std::string_view a, std::string_view b) noexcept; | std::size_t lev_edit_distance(std::string_view a, std::string_view b) noexcept; | ||||
struct e_did_you_mean { | |||||
std::optional<std::string> value; | |||||
void log_as_error() const noexcept; | |||||
}; | |||||
class dym_target { | |||||
std::optional<std::string> _candidate; | |||||
dym_target* _tls_prev = nullptr; | |||||
static thread_local dym_target* _tls_current; | |||||
public: | |||||
dym_target() | |||||
: _tls_prev(_tls_current) { | |||||
_tls_current = this; | |||||
} | |||||
dym_target(const dym_target&) = delete; | |||||
~dym_target() { _tls_current = _tls_prev; } | |||||
template <typename Func> | |||||
static void fill(Func&& fn) noexcept { | |||||
if (_tls_current) { | |||||
_tls_current->_candidate = fn(); | |||||
} | |||||
} | |||||
auto& candidate() const noexcept { return _candidate; } | |||||
auto e_value() const noexcept { return e_did_you_mean{_candidate}; } | |||||
std::string sentence_suffix() const noexcept { | |||||
if (_candidate) { | |||||
return " (Did you mean '" + *_candidate + "'?)"; | |||||
} | |||||
return ""; | |||||
} | |||||
}; | |||||
template <typename Range> | template <typename Range> | ||||
std::optional<std::string> did_you_mean(std::string_view given, Range&& strings) noexcept { | std::optional<std::string> did_you_mean(std::string_view given, Range&& strings) noexcept { | ||||
auto cand = ranges::min_element(strings, ranges::less{}, [&](std::string_view candidate) { | auto cand = ranges::min_element(strings, ranges::less{}, [&](std::string_view candidate) { | ||||
return did_you_mean(given, ranges::views::all(strings)); | return did_you_mean(given, ranges::views::all(strings)); | ||||
} | } | ||||
template <typename Range> | |||||
e_did_you_mean calc_e_did_you_mean(std::string_view given, Range&& strings) noexcept { | |||||
return {did_you_mean(given, strings)}; | |||||
} | |||||
inline e_did_you_mean calc_e_did_you_mean(std::string_view given, | |||||
std::initializer_list<std::string_view> il) noexcept { | |||||
return calc_e_did_you_mean(given, ranges::views::all(il)); | |||||
} | |||||
} // namespace dds | } // namespace dds |
#include "./nonesuch.hpp" | |||||
#include <dds/util/log.hpp> | |||||
using namespace dds; | |||||
void e_nonesuch::log_error(std::string_view fmt) const noexcept { | |||||
dds_log(error, fmt, given); | |||||
if (nearest) { | |||||
dds_log(error, " (Did you mean '{}'?)", *nearest); | |||||
} | |||||
} |
#pragma once | |||||
#include <optional> | |||||
#include <string> | |||||
namespace dds { | |||||
struct e_nonesuch { | |||||
std::string given; | |||||
std::optional<std::string> nearest; | |||||
e_nonesuch(std::string_view gn, std::optional<std::string> nr) noexcept | |||||
: given{gn} | |||||
, nearest{nr} {} | |||||
void log_error(std::string_view fmt) const noexcept; | |||||
}; | |||||
} // namespace dds |
#pragma once | |||||
#include <boost/leaf/on_error.hpp> | |||||
/** | |||||
* @brief Generate a callable object that returns the given expression. | |||||
* | |||||
* Use this as a parameter to leaf's error-loading APIs. | |||||
*/ | |||||
#define DDS_E_ARG(...) ([&] { return __VA_ARGS__; }) | |||||
/** | |||||
* @brief Generate a leaf::on_error object that loads the given expression into the currently | |||||
* in-flight error if the current scope is exitted via exception or a bad result<> | |||||
*/ | |||||
#define DDS_E_SCOPE(...) \ | |||||
auto NEO_CONCAT(_err_info_, __LINE__) = boost::leaf::on_error(DDS_E_ARG(__VA_ARGS__)) |
#pragma once | |||||
#include "./result_fwd.hpp" | |||||
#include <boost/leaf/error.hpp> | |||||
#include <boost/leaf/result.hpp> | |||||
namespace dds { | |||||
using boost::leaf::new_error; | |||||
} // namespace dds |
#pragma once | |||||
namespace boost::leaf { | |||||
template <typename T> | |||||
class result; | |||||
} // namespace boost::leaf | |||||
namespace dds { | |||||
using boost::leaf::result; | |||||
} // namespace dds |
#include <dds/dym.hpp> | #include <dds/dym.hpp> | ||||
#include <dds/error/errors.hpp> | #include <dds/error/errors.hpp> | ||||
#include <dds/error/nonesuch.hpp> | |||||
#include <dds/solve/solve.hpp> | #include <dds/solve/solve.hpp> | ||||
#include <dds/util/log.hpp> | #include <dds/util/log.hpp> | ||||
#include <dds/util/paths.hpp> | #include <dds/util/paths.hpp> | ||||
do_store_pkg(_db, _stmt_cache, pkg); | do_store_pkg(_db, _stmt_cache, pkg); | ||||
} | } | ||||
std::optional<pkg_listing> pkg_db::get(const pkg_id& pk_id) const noexcept { | |||||
result<pkg_listing> pkg_db::get(const pkg_id& pk_id) const noexcept { | |||||
auto ver_str = pk_id.version.to_string(); | auto ver_str = pk_id.version.to_string(); | ||||
dds_log(trace, "Lookup package {}@{}", pk_id.name, ver_str); | dds_log(trace, "Lookup package {}@{}", pk_id.name, ver_str); | ||||
auto& st = _stmt_cache(R"( | auto& st = _stmt_cache(R"( | ||||
st.bindings() = std::forward_as_tuple(pk_id.name, ver_str); | st.bindings() = std::forward_as_tuple(pk_id.name, ver_str); | ||||
auto ec = st.step(std::nothrow); | auto ec = st.step(std::nothrow); | ||||
if (ec == nsql::errc::done) { | if (ec == nsql::errc::done) { | ||||
dym_target::fill([&] { | |||||
return new_error([&] { | |||||
auto all_ids = this->all(); | auto all_ids = this->all(); | ||||
auto id_strings | auto id_strings | ||||
= ranges::views::transform(all_ids, [&](auto id) { return id.to_string(); }); | = ranges::views::transform(all_ids, [&](auto id) { return id.to_string(); }); | ||||
return did_you_mean(pk_id.to_string(), id_strings); | |||||
return e_nonesuch{pk_id.to_string(), did_you_mean(pk_id.to_string(), id_strings)}; | |||||
}); | }); | ||||
return std::nullopt; | |||||
} | } | ||||
neo_assert_always(invariant, | neo_assert_always(invariant, | ||||
ec == nsql::errc::row, | ec == nsql::errc::row, |
#include "./listing.hpp" | #include "./listing.hpp" | ||||
#include <dds/error/result.hpp> | |||||
#include <dds/util/fs.hpp> | #include <dds/util/fs.hpp> | ||||
#include <neo/sqlite3/database.hpp> | #include <neo/sqlite3/database.hpp> | ||||
static fs::path default_path() noexcept; | static fs::path default_path() noexcept; | ||||
void store(const pkg_listing& info); | |||||
std::optional<pkg_listing> get(const pkg_id& id) const noexcept; | |||||
void store(const pkg_listing& info); | |||||
result<pkg_listing> get(const pkg_id& id) const noexcept; | |||||
std::vector<pkg_id> all() const noexcept; | std::vector<pkg_id> all() const noexcept; | ||||
std::vector<pkg_id> by_name(std::string_view sv) const noexcept; | std::vector<pkg_id> by_name(std::string_view sv) const noexcept; |
void dds::get_all(const std::vector<pkg_id>& pkgs, pkg_cache& repo, const pkg_db& cat) { | void dds::get_all(const std::vector<pkg_id>& pkgs, pkg_cache& repo, const pkg_db& cat) { | ||||
std::mutex repo_mut; | std::mutex repo_mut; | ||||
auto absent_pkg_infos = pkgs // | |||||
auto absent_pkg_infos | |||||
= pkgs // | |||||
| ranges::views::filter([&](auto pk) { | | ranges::views::filter([&](auto pk) { | ||||
std::scoped_lock lk{repo_mut}; | |||||
return !repo.find(pk); | |||||
}) | |||||
std::scoped_lock lk{repo_mut}; | |||||
return !repo.find(pk); | |||||
}) | |||||
| ranges::views::transform([&](auto id) { | | ranges::views::transform([&](auto id) { | ||||
auto info = cat.get(id); | |||||
neo_assert(invariant, | |||||
info.has_value(), | |||||
"No database entry for package id?", | |||||
id.to_string()); | |||||
return *info; | |||||
}); | |||||
auto info = cat.get(id); | |||||
neo_assert(invariant, !!info, "No database entry for package id?", id.to_string()); | |||||
return *info; | |||||
}); | |||||
auto okay = parallel_run(absent_pkg_infos, 8, [&](pkg_listing inf) { | auto okay = parallel_run(absent_pkg_infos, 8, [&](pkg_listing inf) { | ||||
dds_log(info, "Download package: {}", inf.ident.to_string()); | dds_log(info, "Download package: {}", inf.ident.to_string()); |
#include <dds/dym.hpp> | #include <dds/dym.hpp> | ||||
#include <dds/error/errors.hpp> | #include <dds/error/errors.hpp> | ||||
#include <dds/error/nonesuch.hpp> | |||||
#include <dds/pkg/db.hpp> | #include <dds/pkg/db.hpp> | ||||
#include <dds/temp.hpp> | #include <dds/temp.hpp> | ||||
#include <dds/util/http/pool.hpp> | #include <dds/util/http/pool.hpp> | ||||
get_rowid_st.bindings()[1] = name; | get_rowid_st.bindings()[1] = name; | ||||
auto row = neo::sqlite3::unpack_single_opt<std::int64_t>(get_rowid_st); | auto row = neo::sqlite3::unpack_single_opt<std::int64_t>(get_rowid_st); | ||||
if (!row) { | if (!row) { | ||||
auto calc_dym = [&] { | |||||
auto all_st = db.prepare("SELECT name FROM dds_pkg_remotes"); | |||||
auto tups = neo::sqlite3::iter_tuples<std::string>(all_st); | |||||
auto names = tups | ranges::views::transform([](auto&& tup) { | |||||
auto&& [n] = tup; | |||||
return n; | |||||
}) | |||||
| ranges::to_vector; | |||||
return calc_e_did_you_mean(name, names); | |||||
}; | |||||
BOOST_LEAF_THROW_EXCEPTION(make_user_error<errc::no_catalog_remote_info>( | |||||
"There is no remote with name '{}'", name), | |||||
DDS_E_ARG(e_remote_name{std::string(name)}), | |||||
calc_dym); | |||||
BOOST_LEAF_THROW_EXCEPTION( // | |||||
make_user_error<errc::no_catalog_remote_info>("There is no remote with name '{}'", | |||||
name), | |||||
[&] { | |||||
auto all_st = db.prepare("SELECT name FROM dds_pkg_remotes"); | |||||
auto tups = neo::sqlite3::iter_tuples<std::string>(all_st); | |||||
auto names = tups | ranges::views::transform([](auto&& tup) { | |||||
auto&& [n] = tup; | |||||
return n; | |||||
}) | |||||
| ranges::to_vector; | |||||
return e_nonesuch{name, did_you_mean(name, names)}; | |||||
}); | |||||
} | } | ||||
auto [rowid] = *row; | auto [rowid] = *row; | ||||
neo::sqlite3::exec(db.prepare("DELETE FROM dds_pkg_remotes WHERE remote_id = ?"), rowid); | neo::sqlite3::exec(db.prepare("DELETE FROM dds_pkg_remotes WHERE remote_id = ?"), rowid); |
#pragma once | #pragma once | ||||
#include <dds/error/on_error.hpp> | |||||
#include <dds/error/result.hpp> | |||||
#include <boost/leaf/handle_error.hpp> | #include <boost/leaf/handle_error.hpp> | ||||
#include <boost/leaf/on_error.hpp> | #include <boost/leaf/on_error.hpp> | ||||
#include <boost/leaf/result.hpp> | #include <boost/leaf/result.hpp> | ||||
std::filesystem::path path; | std::filesystem::path path; | ||||
}; | }; | ||||
struct e_error_marker { | |||||
std::string_view value; | |||||
}; | |||||
struct e_parse_error { | |||||
std::string value; | |||||
}; | |||||
/** | /** | ||||
* @brief Capture currently in-flight special exceptions as new error object. Works around a bug in | * @brief Capture currently in-flight special exceptions as new error object. Works around a bug in | ||||
* Boost.LEAF when catching std::system error. | * Boost.LEAF when catching std::system error. | ||||
*/ | */ | ||||
[[noreturn]] void capture_exception(); | [[noreturn]] void capture_exception(); | ||||
#define DDS_E_ARG(...) ([&] { return __VA_ARGS__; }) | |||||
void write_error_marker(std::string_view error) noexcept; | void write_error_marker(std::string_view error) noexcept; | ||||
/** | |||||
* @brief Generate a leaf::on_error object that loads the given expression into the currently | |||||
* in-flight error if the current scope is exitted via exception or a bad result<> | |||||
*/ | |||||
#define DDS_E_SCOPE(...) \ | |||||
auto NEO_CONCAT(_err_info_, __LINE__) = boost::leaf::on_error(DDS_E_ARG(__VA_ARGS__)) | |||||
} // namespace dds | } // namespace dds |