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