#include <dds/build/builder.hpp> | #include <dds/build/builder.hpp> | ||||
#include <dds/catalog/catalog.hpp> | #include <dds/catalog/catalog.hpp> | ||||
#include <dds/catalog/get.hpp> | #include <dds/catalog/get.hpp> | ||||
#include <dds/dym.hpp> | |||||
#include <dds/error/errors.hpp> | #include <dds/error/errors.hpp> | ||||
#include <dds/repo/repo.hpp> | #include <dds/repo/repo.hpp> | ||||
#include <dds/source/dist.hpp> | #include <dds/source/dist.hpp> | ||||
int run() { | int run() { | ||||
auto cat = cat_path.open(); | auto cat = cat_path.open(); | ||||
for (const auto& req : requirements.Get()) { | for (const auto& req : requirements.Get()) { | ||||
auto id = dds::package_id::parse(req); | |||||
auto info = cat.get(id); | |||||
auto id = dds::package_id::parse(req); | |||||
dds::dym_target dym; | |||||
auto info = cat.get(id); | |||||
if (!info) { | if (!info) { | ||||
dds::throw_user_error<dds::errc::no_such_catalog_package>( | dds::throw_user_error<dds::errc::no_such_catalog_package>( | ||||
"No package in the catalog matched the ID '{}'", req); | |||||
"No package in the catalog matched the ID '{}'.{}", | |||||
req, | |||||
dym.sentence_suffix()); | |||||
} | } | ||||
auto tsd = dds::get_package_sdist(*info); | auto tsd = dds::get_package_sdist(*info); | ||||
auto out_path = out.Get(); | auto out_path = out.Get(); |
#include "./catalog.hpp" | #include "./catalog.hpp" | ||||
#include <dds/dym.hpp> | |||||
#include <dds/error/errors.hpp> | #include <dds/error/errors.hpp> | ||||
#include <dds/solve/solve.hpp> | #include <dds/solve/solve.hpp> | ||||
std::optional<std::string>, | std::optional<std::string>, | ||||
std::string>(st); | std::string>(st); | ||||
if (!opt_tup) { | if (!opt_tup) { | ||||
dym_target::fill([&] { | |||||
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 std::nullopt; | return std::nullopt; | ||||
} | } | ||||
const auto& [pkg_id, name, version, git_url, git_ref, lm_name, lm_namespace, description] | const auto& [pkg_id, name, version, git_url, git_ref, lm_name, lm_namespace, description] |
#include <dds/dym.hpp> | |||||
#include <range/v3/algorithm/min_element.hpp> | |||||
#include <range/v3/view/cartesian_product.hpp> | |||||
#include <range/v3/view/iota.hpp> | |||||
#include <cassert> | |||||
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; | |||||
const auto empty_row = std::vector<std::size_t>(n_columns, 0); | |||||
std::vector<std::vector<std::size_t>> matrix(n_rows, empty_row); | |||||
auto row_iter = ranges::views::iota(1u, n_rows); | |||||
auto col_iter = ranges::views::iota(1u, n_columns); | |||||
for (auto n : col_iter) { | |||||
matrix[0][n] = n; | |||||
} | |||||
for (auto n : row_iter) { | |||||
matrix[n][0] = n; | |||||
} | |||||
auto prod = ranges::views::cartesian_product(row_iter, col_iter); | |||||
for (auto [row, col] : prod) { | |||||
auto cost = a[col - 1] == b[row - 1] ? 0 : 1; | |||||
auto t1 = matrix[row - 1][col] + 1; | |||||
auto t2 = matrix[row][col - 1] + 1; | |||||
auto t3 = matrix[row - 1][col - 1] + cost; | |||||
auto arr = std::array{t1, t2, t3}; | |||||
matrix[row][col] = *ranges::min_element(arr); | |||||
} | |||||
return matrix.back().back(); | |||||
} |
#pragma once | |||||
#include <range/v3/algorithm/min_element.hpp> | |||||
#include <range/v3/view/all.hpp> | |||||
#include <optional> | |||||
#include <string> | |||||
#include <string_view> | |||||
namespace dds { | |||||
std::size_t lev_edit_distance(std::string_view a, std::string_view b) 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; } | |||||
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) { | |||||
return lev_edit_distance(candidate, given); | |||||
}); | |||||
if (cand == ranges::end(strings)) { | |||||
return std::nullopt; | |||||
} else { | |||||
return std::string(*cand); | |||||
} | |||||
} | |||||
inline std::optional<std::string> | |||||
did_you_mean(std::string_view given, std::initializer_list<std::string_view> strings) noexcept { | |||||
return did_you_mean(given, ranges::views::all(strings)); | |||||
} | |||||
} // namespace dds |
#include "./dym.hpp" | |||||
#include <catch2/catch.hpp> | |||||
TEST_CASE("Basic string distance") { | |||||
CHECK(dds::lev_edit_distance("a", "a") == 0); | |||||
CHECK(dds::lev_edit_distance("a", "b") == 1); | |||||
CHECK(dds::lev_edit_distance("aa", "a") == 1); | |||||
} | |||||
TEST_CASE("Find the 'did-you-mean' candidate") { | |||||
auto cand = dds::did_you_mean("food", {"foo", "bar"}); | |||||
CHECK(cand == "foo"); | |||||
cand = dds::did_you_mean("eatable", {"edible", "tangible"}); | |||||
CHECK(cand == "edible"); | |||||
} |