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:
+142
-19
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user