Implemented checking of keys in conditions

toml-checker
Duncan Ogilvie 2 years ago
parent 77f4bf7da0
commit 9f9934e9a5

@ -62,25 +62,37 @@ static void get_optional(const TomlBasicValue &v, const toml::key &ky, T &destin
}
}
// TODO: construct this from a helper class with state so all the checking can be done implicitly
class TomlChecker {
const TomlBasicValue &m_v;
tsl::ordered_set<toml::key> m_visited;
bool m_checked = false;
tsl::ordered_set<toml::key> m_conditionVisited;
public:
TomlChecker(const TomlBasicValue &v, const toml::key &ky) : m_v(toml::find(v, ky)) {}
TomlChecker(const TomlBasicValue &v) : m_v(v) {}
TomlChecker(const TomlChecker &) = delete;
~TomlChecker() noexcept(false) {
if (!m_checked) {
throw std::runtime_error("TomlChecker::check() not called");
~TomlChecker() noexcept(false) {}
// TOOD: check if the condition is valid during the parsing stage to print better errors!
template <typename T>
void optional(const toml::key &ky, Condition<T> &destination) {
get_optional(m_v, ky, destination);
for (const auto &itr : destination) {
if (!itr.first.empty()) {
m_conditionVisited.emplace(itr.first);
}
}
visit(ky);
}
template <typename T>
void optional(const toml::key &ky, T &destination) {
get_optional(m_v, ky, destination);
// TODO: this currently doesn't allow you to get an optional map<string, X>
if (m_v.contains(ky)) {
destination = toml::find<T>(m_v, ky);
}
visit(ky);
}
@ -92,13 +104,48 @@ class TomlChecker {
void visit(const toml::key &ky) { m_visited.insert(ky); }
std::string format_unknown_key(const toml::key &ky, const TomlBasicValue &value) {
auto loc = value.location();
auto line_number_str = std::to_string(loc.line());
auto line_width = line_number_str.length();
auto line_str = loc.line_str();
std::ostringstream oss;
oss << "[error] Unknown key: " << ky << '\n';
oss << " --> " << loc.file_name() << '\n';
oss << std::string(line_width + 2, ' ') << "|\n";
oss << ' ' << line_number_str << " | " << line_str << '\n';
oss << std::string(line_width + 2, ' ') << '|';
auto key_start = line_str.find_last_of(ky, loc.column());
if (key_start != std::string::npos) {
oss << std::string(key_start - ky.length() + 2, ' ') << std::string(ky.length(), '~');
}
oss << '\n';
return oss.str();
}
void check() {
m_checked = true;
for (const auto &itr : m_v.as_table()) {
const auto &ky = itr.first;
if (m_visited.count(ky) == 0) {
// TODO: nice error messages
throw std::runtime_error("Unknown key '" + ky + "'");
if (m_conditionVisited.count(ky)) {
// TODO: check if condition (ky) exists
for (const auto &jtr : itr.second.as_table()) {
if (m_visited.count(jtr.first) == 0) {
throw std::runtime_error(format_unknown_key(jtr.first, jtr.second));
}
}
} else if (m_visited.count(ky) == 0) {
if (itr.second.is_table()) {
for (const auto &jtr : itr.second.as_table()) {
if (m_visited.count(jtr.first) == 0) {
throw std::runtime_error(format_unknown_key(jtr.first, jtr.second));
}
}
}
throw std::runtime_error(format_unknown_key(ky, itr.second));
}
}
}
@ -126,6 +173,30 @@ Project::Project(const Project *parent, const std::string &path, bool build) {
get_optional(cmake, "allow-in-tree", allow_in_tree);
}
} else {
// Reasonable default conditions (you can override these if you desire)
if (parent == nullptr) {
conditions["windows"] = R"cmake(WIN32)cmake";
conditions["macos"] = R"cmake(CMAKE_SYSTEM_NAME MATCHES "Darwin")cmake";
conditions["unix"] = R"cmake(UNIX)cmake";
conditions["bsd"] = R"cmake(CMAKE_SYSTEM_NAME MATCHES "BSD")cmake";
conditions["linux"] = conditions["lunix"] = R"cmake(CMAKE_SYSTEM_NAME MATCHES "Linux")cmake";
conditions["gcc"] = R"cmake(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_C_COMPILER_ID STREQUAL "GNU")cmake";
conditions["clang"] = R"cmake(CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR CMAKE_C_COMPILER_ID MATCHES "Clang")cmake";
conditions["msvc"] = R"cmake(MSVC)cmake";
} else {
conditions = parent->conditions;
}
if (toml.contains("conditions")) {
auto conds = toml::find<decltype(conditions)>(toml, "conditions");
for (const auto &cond : conds) {
conditions[cond.first] = cond.second;
}
}
// TODO: make TomlCheckerFactory
// .check() only once (at the end)
if (toml.contains("cmake")) {
const auto &cmake = toml::find(toml, "cmake");
cmake_version = toml::find(cmake, "version").as_string();
@ -165,7 +236,6 @@ Project::Project(const Project *parent, const std::string &path, bool build) {
TomlChecker sub(itr.second);
sub.optional("condition", subdir.condition);
sub.optional("condition", subdir.condition);
sub.optional("cmake-before", subdir.cmake_before);
sub.optional("cmake-after", subdir.cmake_after);
sub.optional("include-before", subdir.include_before);
@ -409,27 +479,6 @@ Project::Project(const Project *parent, const std::string &path, bool build) {
v.required("packages", vcpkg.packages);
v.check();
}
// Reasonable default conditions (you can override these if you desire)
if (parent == nullptr) {
conditions["windows"] = R"cmake(WIN32)cmake";
conditions["macos"] = R"cmake(CMAKE_SYSTEM_NAME MATCHES "Darwin")cmake";
conditions["unix"] = R"cmake(UNIX)cmake";
conditions["bsd"] = R"cmake(CMAKE_SYSTEM_NAME MATCHES "BSD")cmake";
conditions["linux"] = conditions["lunix"] = R"cmake(CMAKE_SYSTEM_NAME MATCHES "Linux")cmake";
conditions["gcc"] = R"cmake(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_C_COMPILER_ID STREQUAL "GNU")cmake";
conditions["clang"] = R"cmake(CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR CMAKE_C_COMPILER_ID MATCHES "Clang")cmake";
conditions["msvc"] = R"cmake(MSVC)cmake";
} else {
conditions = parent->conditions;
}
if (toml.contains("conditions")) {
auto conds = toml::find<decltype(conditions)>(toml, "conditions");
for (const auto &cond : conds) {
conditions[cond.first] = cond.second;
}
}
}
}

Loading…
Cancel
Save