Program Listing for File TrackedDeck.hpp
↰ Return to documentation for file (src/utils/TrackedDeck.hpp)
#ifndef TRACKEDDECK_HPP
#define TRACKEDDECK_HPP
#include "../pdfs/PDF.hpp"
#include "ParseUtils.hpp"
#include "TOMLUtils.hpp"
#include <algorithm>
#include <cctype>
#include <cstddef>
#include <cstdint>
#include <functional>
#include <map>
#include <optional>
#include <set>
#include <string>
#include <string_view>
#include <toml.hpp>
#include <utility>
#include <vector>
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<std::size_t> prev(b.size() + 1);
std::vector<std::size_t> 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<std::string>& 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 toml::node>
{
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<class T>
[[nodiscard]] auto value(const std::string& path, const bool required = false) const -> std::optional<T>
{
static_cast<void>(atPath(path));
return utils::getTOMLKeyWithError<T>(deck_.get(), path, required);
}
[[nodiscard]] auto initPDF(const std::string& path, const std::string& prefix = "") const -> pdfs::PDF
{
static_cast<void>(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<DeckKeyReport>
{
std::vector<DeckKeyReport> 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<DeckKeyReport>
{
std::vector<DeckKeyReport> 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<const toml::table> deck_;
mutable std::map<std::string, Leaf> leaves_;
mutable std::set<std::string> wanted_;
};
} // namespace utils
#endif // TRACKEDDECK_HPP