@@ -2,6 +2,7 @@ | |||
#include <dds/dym.hpp> | |||
#include <dds/error/errors.hpp> | |||
#include <dds/error/nonesuch.hpp> | |||
#include <dds/pkg/db.hpp> | |||
#include <dds/pkg/get/get.hpp> | |||
#include <dds/util/http/pool.hpp> | |||
@@ -16,14 +17,9 @@ namespace dds::cli::cmd { | |||
static int _pkg_get(const options& opts) { | |||
auto cat = opts.open_pkg_db(); | |||
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(); | |||
dds_log(info, "Create sdist at {}", dest.string()); | |||
fs::remove_all(dest); | |||
@@ -59,6 +55,11 @@ int pkg_get(const options& opts) { | |||
dds_log(error, "Error accessing the package database: {}", e.message); | |||
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_log(error, | |||
"Error opening connection to [{}:{}]: {}", |
@@ -4,6 +4,7 @@ | |||
#include <dds/dym.hpp> | |||
#include <dds/error/errors.hpp> | |||
#include <dds/error/nonesuch.hpp> | |||
#include <dds/pkg/remote.hpp> | |||
#include <dds/util/http/pool.hpp> | |||
#include <dds/util/log.hpp> | |||
@@ -59,15 +60,10 @@ int dds::cli::cmd::handle_pkg_repo_remote_errors(std::function<int()> fn) { | |||
e.message); | |||
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"); | |||
return 1; | |||
}); |
@@ -11,8 +11,6 @@ | |||
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 { | |||
const auto n_rows = b.size() + 1; | |||
const auto n_columns = a.size() + 1; | |||
@@ -46,9 +44,3 @@ std::size_t dds::lev_edit_distance(std::string_view a, std::string_view b) noexc | |||
return matrix.back().back(); | |||
} | |||
void dds::e_did_you_mean::log_as_error() const noexcept { | |||
if (value) { | |||
dds_log(error, " (Did you mean \"{}\"?)", *value); | |||
} | |||
} |
@@ -11,44 +11,6 @@ namespace dds { | |||
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> | |||
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) { | |||
@@ -66,14 +28,4 @@ did_you_mean(std::string_view given, std::initializer_list<std::string_view> str | |||
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 |
@@ -0,0 +1,12 @@ | |||
#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); | |||
} | |||
} |
@@ -0,0 +1,19 @@ | |||
#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 |
@@ -0,0 +1,17 @@ | |||
#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__)) |
@@ -0,0 +1,12 @@ | |||
#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 |
@@ -0,0 +1,14 @@ | |||
#pragma once | |||
namespace boost::leaf { | |||
template <typename T> | |||
class result; | |||
} // namespace boost::leaf | |||
namespace dds { | |||
using boost::leaf::result; | |||
} // namespace dds |
@@ -2,6 +2,7 @@ | |||
#include <dds/dym.hpp> | |||
#include <dds/error/errors.hpp> | |||
#include <dds/error/nonesuch.hpp> | |||
#include <dds/solve/solve.hpp> | |||
#include <dds/util/log.hpp> | |||
#include <dds/util/paths.hpp> | |||
@@ -258,7 +259,7 @@ void pkg_db::store(const pkg_listing& 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(); | |||
dds_log(trace, "Lookup package {}@{}", pk_id.name, ver_str); | |||
auto& st = _stmt_cache(R"( | |||
@@ -276,13 +277,12 @@ std::optional<pkg_listing> pkg_db::get(const pkg_id& pk_id) const noexcept { | |||
st.bindings() = std::forward_as_tuple(pk_id.name, ver_str); | |||
auto ec = st.step(std::nothrow); | |||
if (ec == nsql::errc::done) { | |||
dym_target::fill([&] { | |||
return new_error([&] { | |||
auto all_ids = this->all(); | |||
auto id_strings | |||
= 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, | |||
ec == nsql::errc::row, |
@@ -2,6 +2,7 @@ | |||
#include "./listing.hpp" | |||
#include <dds/error/result.hpp> | |||
#include <dds/util/fs.hpp> | |||
#include <neo/sqlite3/database.hpp> | |||
@@ -32,8 +33,8 @@ public: | |||
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> by_name(std::string_view sv) const noexcept; |
@@ -46,19 +46,17 @@ temporary_sdist dds::get_package_sdist(const pkg_listing& pkg) { | |||
void dds::get_all(const std::vector<pkg_id>& pkgs, pkg_cache& repo, const pkg_db& cat) { | |||
std::mutex repo_mut; | |||
auto absent_pkg_infos = pkgs // | |||
auto absent_pkg_infos | |||
= pkgs // | |||
| 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) { | |||
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) { | |||
dds_log(info, "Download package: {}", inf.ident.to_string()); |
@@ -2,6 +2,7 @@ | |||
#include <dds/dym.hpp> | |||
#include <dds/error/errors.hpp> | |||
#include <dds/error/nonesuch.hpp> | |||
#include <dds/pkg/db.hpp> | |||
#include <dds/temp.hpp> | |||
#include <dds/util/http/pool.hpp> | |||
@@ -224,20 +225,19 @@ void dds::remove_remote(pkg_db& pkdb, std::string_view name) { | |||
get_rowid_st.bindings()[1] = name; | |||
auto row = neo::sqlite3::unpack_single_opt<std::int64_t>(get_rowid_st); | |||
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; | |||
neo::sqlite3::exec(db.prepare("DELETE FROM dds_pkg_remotes WHERE remote_id = ?"), rowid); |
@@ -1,5 +1,8 @@ | |||
#pragma once | |||
#include <dds/error/on_error.hpp> | |||
#include <dds/error/result.hpp> | |||
#include <boost/leaf/handle_error.hpp> | |||
#include <boost/leaf/on_error.hpp> | |||
#include <boost/leaf/result.hpp> | |||
@@ -55,29 +58,12 @@ struct e_missing_file { | |||
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 | |||
* Boost.LEAF when catching std::system error. | |||
*/ | |||
[[noreturn]] void capture_exception(); | |||
#define DDS_E_ARG(...) ([&] { return __VA_ARGS__; }) | |||
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 |