diff --git a/toml/find.hpp b/toml/find.hpp index 1f8ed02..169323f 100644 --- a/toml/find.hpp +++ b/toml/find.hpp @@ -3,6 +3,7 @@ #ifndef TOML11_FIND_HPP #define TOML11_FIND_HPP #include "get.hpp" +#include namespace toml { @@ -357,6 +358,408 @@ expect(const Table& t, const toml::key& k, } } +// =========================================================================== +// find_fuzzy + +// --------------------------------------------------------------------------- +// default fuzzy matcher; levenstein distance (all cost is 1) + +struct levenstein_matcher +{ + levenstein_matcher(): tolerance(1) {} + levenstein_matcher(const std::uint32_t tol): tolerance(tol) {} + ~levenstein_matcher() = default; + levenstein_matcher(levenstein_matcher const&) = default; + levenstein_matcher(levenstein_matcher &&) = default; + levenstein_matcher& operator=(levenstein_matcher const&) = default; + levenstein_matcher& operator=(levenstein_matcher &&) = default; + + template + bool operator()(const std::basic_string& lhs, + const std::basic_string& rhs) const + { + return this->distance(lhs, rhs) <= this->tolerance; + } + + template + std::uint32_t distance( + const std::basic_string& lhs, + const std::basic_string& rhs) const + { + // force `lhs.size() <= rhs.size()` + if(lhs.size() > rhs.size()) {return this->distance(rhs, lhs);} + + std::vector matrix(lhs.size() + 1u); + std::iota(matrix.begin(), matrix.end(), 0); + + for(const charT r : rhs) + { + std::uint32_t prev_diag = matrix.front(); + matrix.front() += 1; + + for(std::size_t i=0; i(v, "tablename", FuzzyMatcher); + +template class M, template class V, + typename FuzzyMatcher = levenstein_matcher> +auto find_fuzzy(const basic_value& v, const key& ky, + const FuzzyMatcher match = levenstein_matcher(1)) + -> decltype(find(std::declval&>(), ky)) +{ + try + { + return find(v, ky); + } + catch(const std::out_of_range& oor) + { + const auto& tab = v.as_table(); + for(const auto& kv : tab) + { + if(match(kv.first, ky)) + { + return get(kv.second); + } + } + throw; + } +} +template class M, template class V, + typename FuzzyMatcher = levenstein_matcher> +auto find_fuzzy(basic_value& v, const key& ky, + const FuzzyMatcher match = levenstein_matcher(1)) + -> decltype(find(std::declval&>(), ky)) +{ + try + { + return find(v, ky); + } + catch(const std::out_of_range& oor) + { + auto& tab = v.as_table(); + for(auto& kv : tab) + { + if(match(kv.first, ky)) + { + return get(kv.second); + } + } + throw; + } +} +template class M, template class V, + typename FuzzyMatcher = levenstein_matcher> +auto find_fuzzy(basic_value&& v_, const key& ky, + const FuzzyMatcher match = levenstein_matcher(1)) + -> decltype(find(std::declval&&>(), ky)) +{ + basic_value v = v_; // to re-use later, store it once + try + { + return std::move(find(v, ky)); // pass lref, move later + } + catch(const std::out_of_range& oor) + { + auto& tab = v.as_table(); // because v is used here + for(auto& kv : tab) + { + if(match(kv.first, ky)) + { + return get(std::move(kv.second)); + } + } + throw; + } +} + +// --------------------------------------------------------------------------- +// no-template-argument case (by default, return toml::value). +// toml::find_fuzzy(v, "tablename", FuzzyMatcher); + +template class M, template class V, + typename FuzzyMatcher = levenstein_matcher> +basic_value const& +find_fuzzy(const basic_value& v, const key& ky, + const FuzzyMatcher match = levenstein_matcher(1)) +{ + try + { + return find(v, ky); + } + catch(const std::out_of_range& oor) + { + const auto& tab = v.as_table(); + for(const auto& kv : tab) + { + if(match(kv.first, ky)) + { + return kv.second; + } + } + throw; + } +} +template class M, template class V, + typename FuzzyMatcher = levenstein_matcher> +basic_value& +find_fuzzy(basic_value& v, const key& ky, + const FuzzyMatcher match = levenstein_matcher(1)) +{ + try + { + return find(v, ky); + } + catch(const std::out_of_range& oor) + { + auto& tab = v.as_table(); + for(auto& kv : tab) + { + if(match(kv.first, ky)) + { + return kv.second; + } + } + throw; + } +} +template class M, template class V, + typename FuzzyMatcher = levenstein_matcher> +basic_value&& +find_fuzzy(basic_value&& v_, const key& ky, + const FuzzyMatcher match = levenstein_matcher(1)) +{ + basic_value v = v_; // to re-use later, store it once + try + { + return std::move(find(v, ky)); + } + catch(const std::out_of_range& oor) + { + auto& tab = v.as_table(); + for(auto& kv : tab) + { + if(match(kv.first, ky)) + { + return std::move(kv.second); + } + } + throw; + } +} + +// =========================================================================== +// find(v, k, matcher) +// +// when matcher is passed, check a key that matches exists or not. if it exists, +// suggest that in the error message + +template class M, template class V, + typename FuzzyMatcher> +basic_value const& +find(const basic_value& v, const key& ky, FuzzyMatcher match) +{ + const auto& tab = v.template cast(); + if(tab.count(ky) == 0) + { + for(const auto& kv : tab) + { + if(match(kv.first, ky)) + { + throw std::out_of_range(detail::format_underline(concat_to_string( + "[error] key \"", ky, "\" not found."), { + {std::addressof(detail::get_region(v)), "in this table"}, + {std::addressof(detail::get_region(kv.second)), + "did you mean this?"} + })); + } + } + throw std::out_of_range(detail::format_underline(concat_to_string( + "[error] key \"", ky, "\" not found"), { + {std::addressof(detail::get_region(v)), "in this table"} + })); + } + return tab.at(ky); +} +template class M, template class V, + typename FuzzyMatcher> +basic_value& +find(basic_value& v, const key& ky, FuzzyMatcher match) +{ + auto& tab = v.template cast(); + if(tab.count(ky) == 0) + { + for(const auto& kv : tab) + { + if(match(kv.first, ky)) + { + throw std::out_of_range(detail::format_underline(concat_to_string( + "[error] key \"", ky, "\" not found."), { + {std::addressof(detail::get_region(v)), "in this table"}, + {std::addressof(detail::get_region(kv.second)), + "did you mean this?"} + })); + } + } + throw std::out_of_range(detail::format_underline(concat_to_string( + "[error] key \"", ky, "\" not found"), { + {std::addressof(detail::get_region(v)), "in this table"} + })); + } + return tab.at(ky); +} +template class M, template class V, + typename FuzzyMatcher> +basic_value&& +find(basic_value&& v, const key& ky, FuzzyMatcher match) +{ + auto tab = std::move(v).as_table(); + if(tab.count(ky) == 0) + { + for(const auto& kv : tab) + { + if(match(kv.first, ky)) + { + throw std::out_of_range(detail::format_underline(concat_to_string( + "[error] key \"", ky, "\" not found."), { + {std::addressof(detail::get_region(v)), "in this table"}, + {std::addressof(detail::get_region(kv.second)), + "did you mean this?"} + })); + } + } + throw std::out_of_range(detail::format_underline(concat_to_string( + "[error] key \"", ky, "\" not found"), { + {std::addressof(detail::get_region(v)), "in this table"} + })); + } + return std::move(tab.at(ky)); +} + +// ---------------------------------------------------------------------------- +// find(value, key, fuzzy_matcher); + +template class M, template class V, + typename FuzzyMatcher> +detail::enable_if_t< + detail::negation>::value, + decltype(::toml::get(std::declval const&>()))> +find(const basic_value& v, const key& ky, FuzzyMatcher match) +{ + const auto& tab = v.as_table(); + if(tab.count(ky) == 0) + { + for(const auto& kv : tab) + { + if(match(kv.first, ky)) + { + throw std::out_of_range(detail::format_underline(concat_to_string( + "[error] key \"", ky, "\" not found."), { + {std::addressof(detail::get_region(v)), "in this table"}, + {std::addressof(detail::get_region(kv.second)), + "did you mean this here?"} + })); + } + } + throw std::out_of_range(detail::format_underline(concat_to_string( + "[error] key \"", ky, "\" not found"), { + {std::addressof(detail::get_region(v)), "in this table"} + })); + } + return ::toml::get(tab.at(ky)); +} + +template class M, template class V, + typename FuzzyMatcher> +detail::enable_if_t< + detail::negation>::value, + decltype(::toml::get(std::declval&>()))> +find(basic_value& v, const key& ky, FuzzyMatcher match) +{ + auto& tab = v.as_table(); + if(tab.count(ky) == 0) + { + for(const auto& kv : tab) + { + if(match(kv.first, ky)) + { + throw std::out_of_range(detail::format_underline(concat_to_string( + "[error] key \"", ky, "\" not found."), { + {std::addressof(detail::get_region(v)), "in this table"}, + {std::addressof(detail::get_region(kv.second)), + "did you mean this here?"} + })); + } + } + throw std::out_of_range(detail::format_underline(concat_to_string( + "[error] key \"", ky, "\" not found"), { + {std::addressof(detail::get_region(v)), "in this table"} + })); + } + return ::toml::get(tab.at(ky)); +} + +template class M, template class V, + typename FuzzyMatcher> +detail::enable_if_t< + detail::negation>::value, + decltype(::toml::get(std::declval&&>()))> +find(basic_value&& v, const key& ky, FuzzyMatcher match) +{ + auto tab = v.as_table(); + if(tab.count(ky) == 0) + { + for(const auto& kv : tab) + { + if(match(kv.first, ky)) + { + throw std::out_of_range(detail::format_underline(concat_to_string( + "[error] key \"", ky, "\" not found."), { + {std::addressof(detail::get_region(v)), "in this table"}, + {std::addressof(detail::get_region(kv.second)), + "did you mean this here?"} + })); + } + } + throw std::out_of_range(detail::format_underline(concat_to_string( + "[error] key \"", ky, "\" not found"), { + {std::addressof(detail::get_region(v)), "in this table"} + })); + } + return ::toml::get(std::move(tab.at(ky))); +} } // toml #endif// TOML11_FIND_HPP