.. _program_listing_file_src_utils_TrackedDeck.hpp: Program Listing for File TrackedDeck.hpp ======================================== |exhale_lsh| :ref:`Return to documentation for file ` (``src/utils/TrackedDeck.hpp``) .. |exhale_lsh| unicode:: U+021B0 .. UPWARDS ARROW WITH TIP LEFTWARDS .. code-block:: cpp #ifndef TRACKEDDECK_HPP #define TRACKEDDECK_HPP #include "../pdfs/PDF.hpp" #include "ParseUtils.hpp" #include "TOMLUtils.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include namespace utils { struct DeckKeyReport { // NOLINTBEGIN(readability-identifier-naming) -- trailing underscore matches this project's own member-variable convention (see yields::YieldChannelDescriptor's identical public-struct-fields-with-trailing-underscore precedent) std::string path_; std::uint32_t line_ = 0; std::uint32_t column_ = 0; std::string hint_; std::string reason_; // NOLINTEND(readability-identifier-naming) }; namespace trackeddeck_detail { inline auto canonicalPath(const std::string_view path) -> std::string { const toml::path parsed{ path }; return parsed ? parsed.str() : std::string(path); } inline auto childPath(const std::string& prefix, const std::string_view key) -> std::string { const bool bare = !key.empty() && std::ranges::all_of(key, [](const unsigned char c) { return (std::isalnum(c) != 0) || c == '_' || c == '-'; }); std::string text = bare ? std::string(key) : std::string("\""); if (!bare) { for (const char c : key) { if (c == '"' || c == '\\') { text += '\\'; } text += c; } text += '"'; } return prefix.empty() ? text : prefix + "." + text; } inline auto isUnder(const std::string_view path, const std::string_view prefix) -> bool { if (!path.starts_with(prefix)) { return false; } return path.size() == prefix.size() || path[prefix.size()] == '.' || path[prefix.size()] == '['; } inline auto lastSegment(const std::string_view path) -> std::string { const auto dot = path.rfind('.'); return std::string(dot == std::string_view::npos ? path : path.substr(dot + 1)); } inline auto editDistance(const std::string_view a, const std::string_view b) -> std::size_t { std::vector prev(b.size() + 1); std::vector cur(b.size() + 1); for (std::size_t j = 0; j <= b.size(); ++j) { prev[j] = j; } for (std::size_t i = 1; i <= a.size(); ++i) { cur[0] = i; for (std::size_t j = 1; j <= b.size(); ++j) { const std::size_t sub = prev[j - 1] + (a[i - 1] == b[j - 1] ? 0 : 1); cur[j] = std::min({ prev[j] + 1, cur[j - 1] + 1, sub }); } std::swap(prev, cur); } return prev[b.size()]; } inline auto suggestKey(const std::string& unusedPath, const std::set& wanted) -> std::string { const std::string leaf = lastSegment(unusedPath); const auto same = std::ranges::find_if(wanted, [&leaf](const std::string& w) { return lastSegment(w) == leaf; }); if (same != wanted.end()) { return "did you mean '" + *same + "'?"; } constexpr std::size_t maxDistance = 2; constexpr std::size_t minLength = 4; if (leaf.size() < minLength) { return {}; } const std::string* best = nullptr; std::size_t bestDist = maxDistance + 1; for (const std::string& w : wanted) { const std::size_t d = editDistance(leaf, lastSegment(w)); if (d < bestDist) { bestDist = d; best = &w; } } return best == nullptr ? std::string() : "did you mean '" + *best + "'?"; } } // namespace trackeddeck_detail class TrackedDeck { public: explicit TrackedDeck(const toml::table& deck) : deck_(std::cref(deck)) { collectLeaves(deck, std::string()); } [[nodiscard]] auto atPath(const std::string_view path) const -> toml::node_view { const auto node = deck_.get().at_path(path); const std::string key = trackeddeck_detail::canonicalPath(path); if (!node) { wanted_.insert(key); } else if (!node.is_table()) { markLeafOrAncestor(key); } return node; } template [[nodiscard]] auto value(const std::string& path, const bool required = false) const -> std::optional { static_cast(atPath(path)); return utils::getTOMLKeyWithError(deck_.get(), path, required); } [[nodiscard]] auto initPDF(const std::string& path, const std::string& prefix = "") const -> pdfs::PDF { static_cast(atPath(path)); return utils::initPDFFromKey(deck_.get(), path, prefix); } void markUsedAll(const std::string_view prefix) const { const std::string p = trackeddeck_detail::canonicalPath(prefix); for (auto& [path, leaf] : leaves_) { if (trackeddeck_detail::isUnder(path, p)) { leaf.used_ = true; } } } void markIgnored(const std::string_view prefix, const std::string_view reason) const { const std::string p = trackeddeck_detail::canonicalPath(prefix); for (auto& [path, leaf] : leaves_) { if (!leaf.used_ && leaf.reason_.empty() && trackeddeck_detail::isUnder(path, p)) { leaf.reason_ = std::string(reason); } } } [[nodiscard]] auto unused() const -> std::vector { std::vector result; for (const auto& [path, leaf] : leaves_) { if (leaf.used_ || !leaf.reason_.empty()) { continue; } result.push_back({ path, leaf.line_, leaf.column_, trackeddeck_detail::suggestKey(path, wanted_), std::string() }); } return result; } [[nodiscard]] auto ignored() const -> std::vector { std::vector result; for (const auto& [path, leaf] : leaves_) { if (leaf.used_ || leaf.reason_.empty()) { continue; } result.push_back({ path, leaf.line_, leaf.column_, std::string(), leaf.reason_ }); } return result; } private: struct Leaf { // NOLINTBEGIN(readability-identifier-naming) -- trailing underscore matches this project's own member-variable convention, as for DeckKeyReport above bool used_ = false; std::string reason_; std::uint32_t line_ = 0; std::uint32_t column_ = 0; // NOLINTEND(readability-identifier-naming) }; void collectLeaves(const toml::table& table, const std::string& prefix) { for (const auto& [key, node] : table) { const std::string path = trackeddeck_detail::childPath(prefix, key.str()); if (const auto* sub = node.as_table()) { collectLeaves(*sub, path); continue; } Leaf leaf; leaf.line_ = node.source().begin.line; leaf.column_ = node.source().begin.column; leaves_.emplace(path, leaf); } } void markLeafOrAncestor(std::string key) const { while (!key.empty()) { const auto it = leaves_.find(key); if (it != leaves_.end()) { it->second.used_ = true; return; } const auto cut = key.find_last_of(".["); if (cut == std::string::npos) { return; } key.resize(cut); } } std::reference_wrapper deck_; mutable std::map leaves_; mutable std::set wanted_; }; } // namespace utils #endif // TRACKEDDECK_HPP