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