feat: system config DSL, services, CLI with rustc diagnostics

- System config parser (system/packages/services/boot/users blocks)
- Boot block: kernel, init, efi, swap, bootloader, root partition
- System-wide features, rollback config, swap partition
- Service block: per-init variants (runit, s6, etc.), system init selection via boot.init
- Feature-gated dependencies, config values accept numbers/bools/strings
- CLI: parse-package, parse-config, validate subcommands
- Rustc-style error diagnostics with source context, carets, help text
- Diagnostic module with ANSI color output
This commit is contained in:
2026-07-29 22:50:39 -04:00
parent a8b53681fc
commit d5f7d397ed
12 changed files with 850 additions and 19 deletions
+142 -19
View File
@@ -1,28 +1,151 @@
#include "kappa/package.hpp"
#include "kappa/cli/diagnostic.hpp"
#include "kappa/dsl/parser.hpp"
#include "kappa/dsl/system.hpp"
#include <toml++/toml.hpp>
#include <cstdlib>
#include <exception>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <sstream>
#include <string_view>
int main(int argc, char* argv[]) {
std::filesystem::path toml_path = (argc > 1) ? argv[1] : "package.toml";
using namespace std::string_view_literals;
using namespace kappa;
if (!std::filesystem::exists(toml_path)) {
std::cerr << "Error: '" << toml_path << "' not found\n";
return 1;
static constexpr auto version = "kappa 0.1.0"sv;
static void print_usage() {
std::cout << R"(kappa — a declarative source-based package manager
Usage:
kappa <subcommand> [options] <file>
Subcommands:
parse-package <file> Parse and validate a package definition (.kap)
parse-config <file> Parse and validate a system configuration
validate <file> Validate any kappa file (package or config)
Options:
-h, --help Show this help message
-V, --version Show version information
)";
}
static bool is_flag(std::string_view arg) {
return arg == "-h" || arg == "--help" || arg == "-V" || arg == "--version";
}
static std::string read_file(const char* path) {
std::ifstream in(path);
if (!in) {
std::cerr << "error: cannot open '" << path << "'\n";
std::exit(1);
}
std::ostringstream buf;
buf << in.rdbuf();
return buf.str();
}
try {
auto pkg = kappa::parse_package(toml_path.native());
kappa::print_package(std::cout, pkg);
return 0;
} catch (const toml::parse_error& e) {
std::cerr << "Parse error:\n" << e << '\n';
return 1;
} catch (const std::exception& e) {
std::cerr << "Error: " << e.what() << '\n';
return 1;
static void handle_parse_error(const char* path,
std::string_view source,
const std::runtime_error& e) {
// ParseError format: "line:col: message"
auto msg = std::string_view(e.what());
auto first_colon = msg.find(':');
auto second_colon = msg.find(':', first_colon + 1);
if (first_colon != std::string_view::npos &&
second_colon != std::string_view::npos) {
int line = std::stoi(std::string(msg.substr(0, first_colon)));
int col = std::stoi(std::string(
msg.substr(first_colon + 1, second_colon - first_colon - 1)));
auto message = msg.substr(second_colon + 2); // skip ": "
cli::print_error(std::cerr, source,
{path, line, col}, message,
"check the syntax at this location");
} else {
std::cerr << "error: " << e.what() << '\n';
}
}
int main(int argc, char* argv[]) {
if (argc < 2) {
std::cerr << "kappa: missing subcommand\n\n";
print_usage();
return 1;
}
auto subcommand = std::string_view(argv[1]);
if (subcommand == "-h" || subcommand == "--help") {
print_usage();
return 0;
}
if (subcommand == "-V" || subcommand == "--version") {
std::cout << version << '\n';
return 0;
}
// Find the file argument (skip flags)
const char* file_arg = nullptr;
for (int i = 2; i < argc; ++i) {
if (!is_flag(argv[i])) {
file_arg = argv[i];
break;
}
}
if (file_arg == nullptr) {
std::cerr << "error: no input file specified\n";
return 1;
}
auto source = read_file(file_arg);
if (subcommand == "parse-package") {
try {
auto pkg = dsl::parse(source);
std::cout << "package \"" << pkg.name << "\" " << pkg.version
<< " — valid\n";
return 0;
} catch (const std::runtime_error& e) {
handle_parse_error(file_arg, source, e);
return 1;
}
}
if (subcommand == "parse-config") {
try {
auto cfg = dsl::parse_system_config(source);
std::cout << "system config — valid ("
<< cfg.packages.size() << " packages, "
<< cfg.services.size() << " services, "
<< cfg.users.size() << " users)\n";
return 0;
} catch (const std::runtime_error& e) {
handle_parse_error(file_arg, source, e);
return 1;
}
}
if (subcommand == "validate") {
try {
dsl::parse(source);
std::cout << file_arg << ": valid package definition\n";
return 0;
} catch (const std::runtime_error&) {
try {
dsl::parse_system_config(source);
std::cout << file_arg << ": valid system configuration\n";
return 0;
} catch (const std::runtime_error& e) {
handle_parse_error(file_arg, source, e);
return 1;
}
}
}
std::cerr << "error: unknown subcommand '" << subcommand << "'\n\n";
print_usage();
return 1;
}