Browse Source

Refactors of did-you-mean to simplify

default_compile_flags
vector-of-bool 3 years ago
parent
commit
3d3eb26460
14 changed files with 121 additions and 121 deletions
  1. +9
    -8
      src/dds/cli/cmd/pkg_get.cpp
  2. +5
    -9
      src/dds/cli/cmd/pkg_repo_err_handle.cpp
  3. +0
    -8
      src/dds/dym.cpp
  4. +0
    -48
      src/dds/dym.hpp
  5. +12
    -0
      src/dds/error/nonesuch.cpp
  6. +19
    -0
      src/dds/error/nonesuch.hpp
  7. +17
    -0
      src/dds/error/on_error.hpp
  8. +12
    -0
      src/dds/error/result.hpp
  9. +14
    -0
      src/dds/error/result_fwd.hpp
  10. +4
    -4
      src/dds/pkg/db.cpp
  11. +3
    -2
      src/dds/pkg/db.hpp
  12. +9
    -11
      src/dds/pkg/get/get.cpp
  13. +14
    -14
      src/dds/pkg/remote.cpp
  14. +3
    -17
      src/dds/util/result.hpp

+ 9
- 8
src/dds/cli/cmd/pkg_get.cpp View File

@@ -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 [{}:{}]: {}",

+ 5
- 9
src/dds/cli/cmd/pkg_repo_err_handle.cpp View File

@@ -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;
});

+ 0
- 8
src/dds/dym.cpp View File

@@ -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);
}
}

+ 0
- 48
src/dds/dym.hpp View File

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

+ 12
- 0
src/dds/error/nonesuch.cpp View File

@@ -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);
}
}

+ 19
- 0
src/dds/error/nonesuch.hpp View File

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

+ 17
- 0
src/dds/error/on_error.hpp View File

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

+ 12
- 0
src/dds/error/result.hpp View File

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

+ 14
- 0
src/dds/error/result_fwd.hpp View File

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

+ 4
- 4
src/dds/pkg/db.cpp View File

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

+ 3
- 2
src/dds/pkg/db.hpp View File

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

+ 9
- 11
src/dds/pkg/get/get.cpp View File

@@ -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());

+ 14
- 14
src/dds/pkg/remote.cpp View File

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

+ 3
- 17
src/dds/util/result.hpp View File

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

Loading…
Cancel
Save