feat: dependency resolver (Phase 1) + formatter + doctor + assertion evaluation
Resolver (Phase 1): - resolve::resolve() takes SystemConfig + Registry → BuildPlan - Three-layer feature/config merge (uses config::resolve_package) - Feature-gated dependencies: deps with feature=X skipped if feature disabled - Topological sort via Kahn's algorithm (BFS on in-degree) - Cycle detection, missing package warnings - CLI: kappa resolve <config> [<package>] Formatter: - format_package() + format_config() → canonical output - Consistent 4-space indent, canonical declaration order Doctor: - check_package() + check_config() → warnings for common issues - Missing fields, empty configs, root shell, feature warnings Assertion evaluation: - evaluate_assertions() resolves dotted field paths - Supports == and != operators for config validation - kappa validate now runs assertion checks Review fixes: - plan.steps.empty() exit code corrected to 0 - Removed unused <unordered_set> include - Eliminated duplicate merge logic (uses config::resolve_package)
This commit is contained in:
@@ -0,0 +1,296 @@
|
||||
#include "kappa/tools/format.hpp"
|
||||
|
||||
namespace kappa::tools {
|
||||
|
||||
namespace {
|
||||
|
||||
struct Indent {
|
||||
int n = 0;
|
||||
Indent(int i) : n(i) {}
|
||||
friend std::ostream& operator<<(std::ostream& os, const Indent& in) {
|
||||
for (int i = 0; i < in.n; ++i) { os << " "; }
|
||||
return os;
|
||||
}
|
||||
};
|
||||
|
||||
static void write_env(std::ostream& os, int d,
|
||||
const std::vector<dsl::EnvEntry>& entries) {
|
||||
if (entries.empty()) { return; }
|
||||
os << Indent(d) << "env {\n";
|
||||
for (auto& e : entries) {
|
||||
os << Indent(d + 1) << e.key << (e.soft ? " ?= " : " = ")
|
||||
<< '"' << e.value << "\"\n";
|
||||
}
|
||||
os << Indent(d) << "}\n";
|
||||
}
|
||||
|
||||
static void write_features(std::ostream& os, int d,
|
||||
const std::unordered_map<std::string, dsl::FeatureDef>& feats) {
|
||||
if (feats.empty()) { return; }
|
||||
os << Indent(d) << "features {\n";
|
||||
for (auto& [k, f] : feats) {
|
||||
if (f.flag.empty() && !f.force) {
|
||||
os << Indent(d + 1) << k << " = " << (f.enabled ? "true" : "false") << "\n";
|
||||
} else {
|
||||
os << Indent(d + 1) << k << " = { enabled = "
|
||||
<< (f.enabled ? "true" : "false");
|
||||
if (f.force) { os << ", force = true"; }
|
||||
if (!f.flag.empty()) { os << ", flag = \"" << f.flag << "\""; }
|
||||
os << " }\n";
|
||||
}
|
||||
}
|
||||
os << Indent(d) << "}\n";
|
||||
}
|
||||
|
||||
static void write_phase(std::ostream& os, int d, const char* name,
|
||||
const dsl::Phase& phase) {
|
||||
if (phase.commands.empty()) { return; }
|
||||
os << Indent(d) << name << " {\n";
|
||||
for (auto& cmd : phase.commands) {
|
||||
os << Indent(d + 1) << cmd << "\n";
|
||||
}
|
||||
os << Indent(d) << "}\n";
|
||||
}
|
||||
|
||||
static void write_boot_block(std::ostream& os, int d, const dsl::BootBlock& boot) {
|
||||
os << Indent(d) << "boot {\n";
|
||||
auto w = [&](const char* k, const std::string& v) {
|
||||
if (!v.empty()) { os << Indent(d + 1) << k << " = \"" << v << "\"\n"; }
|
||||
};
|
||||
w("kernel", boot.kernel);
|
||||
w("init", boot.init);
|
||||
w("efi", boot.efi);
|
||||
w("swap", boot.swap);
|
||||
w("root", boot.root);
|
||||
w("bootloader", boot.bootloader);
|
||||
for (auto& [k, v] : boot.params) {
|
||||
os << Indent(d + 1) << k << " = \"" << v << "\"\n";
|
||||
}
|
||||
os << Indent(d) << "}\n";
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void format_package(std::ostream& os, const dsl::PackageDef& pkg) {
|
||||
os << "package \"" << pkg.name << "\" {\n";
|
||||
int d = 1;
|
||||
|
||||
if (!pkg.version.empty()) {
|
||||
auto c = pkg.const_keys.contains("version") ? "const " : "";
|
||||
os << Indent(d) << c << "version = \"" << pkg.version << "\"\n";
|
||||
}
|
||||
if (!pkg.source.empty()) {
|
||||
auto c = pkg.const_keys.contains("source") ? "const " : "";
|
||||
os << Indent(d) << c << "source = \"" << pkg.source << "\"\n";
|
||||
}
|
||||
if (!pkg.license.empty()) {
|
||||
os << Indent(d) << "license = \"" << pkg.license << "\"\n";
|
||||
}
|
||||
|
||||
if (!pkg.provides.empty()) {
|
||||
os << Indent(d) << "provides = [";
|
||||
for (std::size_t i = 0; i < pkg.provides.size(); ++i) {
|
||||
if (i > 0) { os << ", "; }
|
||||
os << '"' << pkg.provides[i] << '"';
|
||||
}
|
||||
os << "]\n";
|
||||
}
|
||||
|
||||
if (!pkg.outputs.empty()) {
|
||||
os << Indent(d) << "outputs = [";
|
||||
for (std::size_t i = 0; i < pkg.outputs.size(); ++i) {
|
||||
if (i > 0) { os << ", "; }
|
||||
os << '"' << pkg.outputs[i] << '"';
|
||||
}
|
||||
os << "]\n";
|
||||
}
|
||||
|
||||
if (!pkg.patches.empty()) {
|
||||
os << Indent(d) << "patches = [\n";
|
||||
for (auto& p : pkg.patches) {
|
||||
if (p.sha256.empty()) {
|
||||
os << Indent(d + 1) << '"' << p.url << "\",\n";
|
||||
} else {
|
||||
os << Indent(d + 1) << "{ url = \"" << p.url
|
||||
<< "\", sha256 = \"" << p.sha256
|
||||
<< "\", level = " << p.level << " },\n";
|
||||
}
|
||||
}
|
||||
os << Indent(d) << "]\n";
|
||||
}
|
||||
|
||||
if (!pkg.depends.empty()) {
|
||||
os << Indent(d) << "depends = [\n";
|
||||
for (auto& dep : pkg.depends) {
|
||||
os << Indent(d + 1);
|
||||
if (dep.version.empty() && dep.output.empty() && dep.feature.empty()) {
|
||||
os << '"' << dep.name << '"';
|
||||
} else {
|
||||
os << "{ name = \"" << dep.name << '"';
|
||||
if (!dep.version.empty()) { os << ", version = \"" << dep.version << '"'; }
|
||||
if (!dep.output.empty()) { os << ", output = \"" << dep.output << '"'; }
|
||||
if (!dep.feature.empty()) { os << ", feature = \"" << dep.feature << '"'; }
|
||||
os << " }";
|
||||
}
|
||||
os << ",\n";
|
||||
}
|
||||
os << Indent(d) << "]\n";
|
||||
}
|
||||
|
||||
write_features(os, d, pkg.features);
|
||||
write_env(os, d, pkg.env_entries);
|
||||
|
||||
for (auto& cf : pkg.config_files) {
|
||||
os << Indent(d) << "config {\n";
|
||||
os << Indent(d + 1) << "file \"" << cf.path
|
||||
<< "\" mode = \"" << cf.mode << "\" {\n";
|
||||
for (auto& [k, v] : cf.entries) {
|
||||
os << Indent(d + 2) << k << " = " << v << "\n";
|
||||
}
|
||||
os << Indent(d + 1) << "}\n";
|
||||
os << Indent(d) << "}\n";
|
||||
}
|
||||
|
||||
if (!pkg.service.empty()) {
|
||||
os << Indent(d) << "service {\n";
|
||||
for (auto& [init, si] : pkg.service) {
|
||||
os << Indent(d + 1) << init << " {\n";
|
||||
if (!si.exec.empty()) { os << Indent(d + 2) << "exec = \"" << si.exec << "\"\n"; }
|
||||
if (!si.type.empty()) { os << Indent(d + 2) << "type = \"" << si.type << "\"\n"; }
|
||||
if (!si.user.empty()) { os << Indent(d + 2) << "user = \"" << si.user << "\"\n"; }
|
||||
if (!si.ports.empty()) {
|
||||
os << Indent(d + 2) << "ports = [";
|
||||
for (std::size_t i = 0; i < si.ports.size(); ++i) {
|
||||
if (i > 0) { os << ", "; }
|
||||
os << si.ports[i];
|
||||
}
|
||||
os << "]\n";
|
||||
}
|
||||
for (auto& [k, v] : si.env) {
|
||||
os << Indent(d + 2) << k << " = \"" << v << "\"\n";
|
||||
}
|
||||
os << Indent(d + 1) << "}\n";
|
||||
}
|
||||
os << Indent(d) << "}\n";
|
||||
}
|
||||
|
||||
if (!pkg.assertions.empty()) {
|
||||
os << Indent(d) << "assert {\n";
|
||||
for (auto& a : pkg.assertions) {
|
||||
os << Indent(d + 1) << '"' << a.message << "\" : "
|
||||
<< a.field << " " << a.op;
|
||||
if (!a.value.empty()) { os << " \"" << a.value << '"'; }
|
||||
os << "\n";
|
||||
}
|
||||
os << Indent(d) << "}\n";
|
||||
}
|
||||
|
||||
write_phase(os, d, "prepare", pkg.prepare);
|
||||
write_phase(os, d, "build", pkg.build);
|
||||
write_phase(os, d, "check", pkg.check);
|
||||
write_phase(os, d, "install", pkg.install);
|
||||
|
||||
os << "}\n";
|
||||
}
|
||||
|
||||
void format_config(std::ostream& os, const dsl::SystemConfig& cfg) {
|
||||
if (!cfg.imports.empty()) {
|
||||
os << "imports = [";
|
||||
for (std::size_t i = 0; i < cfg.imports.size(); ++i) {
|
||||
if (i > 0) { os << ", "; }
|
||||
os << '"' << cfg.imports[i] << '"';
|
||||
}
|
||||
os << "]\n\n";
|
||||
}
|
||||
|
||||
if (!cfg.assertions.empty()) {
|
||||
os << "assert {\n";
|
||||
for (auto& a : cfg.assertions) {
|
||||
os << " \"" << a.message << "\" : "
|
||||
<< a.field << " " << a.op;
|
||||
if (!a.value.empty()) { os << " \"" << a.value << '"'; }
|
||||
os << "\n";
|
||||
}
|
||||
os << "}\n\n";
|
||||
}
|
||||
|
||||
auto& s = cfg.system;
|
||||
os << "system {\n";
|
||||
if (!s.hostname.empty()) { os << " hostname = \"" << s.hostname << "\"\n"; }
|
||||
if (!s.timezone.empty()) { os << " timezone = \"" << s.timezone << "\"\n"; }
|
||||
write_features(os, 1, s.features);
|
||||
write_env(os, 1, s.env);
|
||||
if (!s.config.empty()) {
|
||||
os << " config {\n";
|
||||
for (auto& [k, v] : s.config) {
|
||||
os << " " << k << " = \"" << v << "\"\n";
|
||||
}
|
||||
os << " }\n";
|
||||
}
|
||||
if (s.rollback.keep > 0) {
|
||||
os << " rollback {\n keep = " << s.rollback.keep << "\n }\n";
|
||||
}
|
||||
os << "}\n\n";
|
||||
|
||||
if (!cfg.packages.empty()) {
|
||||
os << "packages {\n";
|
||||
for (auto& p : cfg.packages) {
|
||||
os << " " << p.name;
|
||||
if (p.version.empty() && p.features.empty() && p.config.empty()) {
|
||||
os << " {}\n";
|
||||
continue;
|
||||
}
|
||||
os << " {\n";
|
||||
if (!p.version.empty()) { os << " version = \"" << p.version << "\"\n"; }
|
||||
write_features(os, 2, p.features);
|
||||
if (!p.config.empty()) {
|
||||
os << " config {\n";
|
||||
for (auto& [k, v] : p.config) {
|
||||
os << " " << k << " = " << v << "\n";
|
||||
}
|
||||
os << " }\n";
|
||||
}
|
||||
os << " }\n";
|
||||
}
|
||||
os << "}\n\n";
|
||||
}
|
||||
|
||||
if (!cfg.services.empty()) {
|
||||
os << "services {\n";
|
||||
for (auto& svc : cfg.services) {
|
||||
os << " " << svc.name << " {\n";
|
||||
os << " enable = " << (svc.enable ? "true" : "false") << "\n";
|
||||
for (auto& [k, v] : svc.config) {
|
||||
os << " " << k << " = " << v << "\n";
|
||||
}
|
||||
os << " }\n";
|
||||
}
|
||||
os << "}\n\n";
|
||||
}
|
||||
|
||||
write_boot_block(os, 0, cfg.boot);
|
||||
os << "\n";
|
||||
|
||||
if (!cfg.users.empty()) {
|
||||
os << "users {\n";
|
||||
for (auto& u : cfg.users) {
|
||||
os << " " << u.name << " {\n";
|
||||
if (!u.shell.empty()) { os << " shell = \"" << u.shell << "\"\n"; }
|
||||
if (!u.groups.empty()) {
|
||||
os << " groups = [";
|
||||
for (std::size_t i = 0; i < u.groups.size(); ++i) {
|
||||
if (i > 0) { os << ", "; }
|
||||
os << '"' << u.groups[i] << '"';
|
||||
}
|
||||
os << "]\n";
|
||||
}
|
||||
for (auto& [k, v] : u.extra) {
|
||||
os << " " << k << " = \"" << v << "\"\n";
|
||||
}
|
||||
os << " }\n";
|
||||
}
|
||||
os << "}\n";
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace kappa::tools
|
||||
Reference in New Issue
Block a user