Files
kappa/src/tools/format.cpp
T
huntedbytheirs 4f1ce17ec8 feat: add repos DSL, index format, and kappa index command
- Add repos { } block to system config with named repos, channels,
  mirrors, and priority

- Add index "name" { } file format for package indexes

- Add kappa index <dir> subcommand that scans .kap files and
  generates index.kap

- parse_index() and build_index() in DSL module

- format_index() roundtrip support in tools module

- validate and format subcommands handle all three formats
  (package, config, index) with automatic detection

- Backward compatible: remotes = [...] still works unchanged
2026-08-04 13:48:25 -04:00

385 lines
13 KiB
C++

#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) {
const char* op;
switch (e.mode) {
case dsl::EnvMode::Soft: op = " ?= "; break;
case dsl::EnvMode::Append: op = " += "; break;
default: op = " = "; break;
}
os << Indent(d + 1) << e.key << op
<< '"' << 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; }
std::vector<std::string> keys;
for (auto& [k, _] : feats) keys.push_back(k);
std::sort(keys.begin(), keys.end());
os << Indent(d) << "features {\n";
for (auto& k : keys) {
auto& f = feats.at(k);
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.conflicts.empty()) {
os << Indent(d) << "conflicts = [";
for (std::size_t i = 0; i < pkg.conflicts.size(); ++i) {
if (i > 0) { os << ", "; }
os << '"' << pkg.conflicts[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.services.empty()) {
for (auto& ns : pkg.services) {
if (ns.name != "main") {
os << Indent(d) << "service " << ns.name << " {\n";
} else {
os << Indent(d) << "service {\n";
}
if (!ns.exec.empty()) { os << Indent(d + 1) << "exec = \"" << ns.exec << "\"\n"; }
if (!ns.type.empty()) { os << Indent(d + 1) << "type = \"" << ns.type << "\"\n"; }
if (!ns.user.empty()) { os << Indent(d + 1) << "user = \"" << ns.user << "\"\n"; }
if (!ns.ports.empty()) {
os << Indent(d + 1) << "ports = [";
for (std::size_t i = 0; i < ns.ports.size(); ++i) {
if (i > 0) { os << ", "; }
os << ns.ports[i];
}
os << "]\n";
}
if (!ns.description.empty()) { os << Indent(d + 1) << "description = \"" << ns.description << "\"\n"; }
if (!ns.after.empty()) { os << Indent(d + 1) << "after = \"" << ns.after << "\"\n"; }
if (!ns.restart.empty()) { os << Indent(d + 1) << "restart = \"" << ns.restart << "\"\n"; }
if (!ns.working_dir.empty()) { os << Indent(d + 1) << "working_dir = \"" << ns.working_dir << "\"\n"; }
for (auto& [k, v] : ns.env) {
os << Indent(d + 1) << k << " = \"" << v << "\"\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);
write_phase(os, d, "uninstall", pkg.uninstall);
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.remotes.empty()) {
os << "remotes = [\n";
for (auto& r : cfg.remotes) {
os << " \"" << r << "\",\n";
}
os << "]\n\n";
}
if (!cfg.repos.empty()) {
os << "repos {\n";
for (auto& repo : cfg.repos) {
os << " " << repo.name << " {\n";
os << " url = \"" << repo.url << "\"\n";
if (!repo.channels.empty()) {
os << " channels = [";
for (std::size_t i = 0; i < repo.channels.size(); ++i) {
if (i > 0) { os << ", "; }
os << '"' << repo.channels[i] << '"';
}
os << "]\n";
}
if (!repo.mirrors.empty()) {
os << " mirrors = [\n";
for (auto& m : repo.mirrors) {
os << " \"" << m << "\",\n";
}
os << " ]\n";
}
if (repo.priority != 50) {
os << " priority = " << repo.priority << "\n";
}
os << " }\n";
}
os << "}\n\n";
}
if (!cfg.assertions.empty()) {
os << "assert {\n";
for (auto& a : cfg.assertions) {
os << " \"" << a.message << "\" : "
<< a.field << " " << a.op
<< " \"" << a.value << '"' << "\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";
std::vector<std::string> cfg_keys;
for (auto& [k, _] : s.config) cfg_keys.push_back(k);
std::sort(cfg_keys.begin(), cfg_keys.end());
for (auto& k : cfg_keys) {
os << " " << k << " = \"" << s.config.at(k) << "\"\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";
std::vector<std::string> pcfg_keys;
for (auto& [k, _] : p.config) pcfg_keys.push_back(k);
std::sort(pcfg_keys.begin(), pcfg_keys.end());
for (auto& k : pcfg_keys) {
os << " " << k << " = " << p.config.at(k) << "\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";
}
if (!cfg.groups.empty()) {
os << "groups {\n";
for (auto& g : cfg.groups) {
os << " " << g.name;
if (g.gid < 0) {
os << " {}\n";
} else {
os << " {\n gid = " << g.gid << "\n }\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";
}
}
void format_index(std::ostream& os, const dsl::IndexDef& idx) {
os << "index \"" << idx.name << "\" {\n";
for (auto& pkg : idx.packages) {
os << " " << pkg.name << " { version = \"" << pkg.version << "\" }\n";
}
os << "}\n";
}
} // namespace kappa::tools