From 0c1e82d66b22ba265118b252c798ea5c530386da Mon Sep 17 00:00:00 2001 From: HuntedByTheIRS Date: Wed, 29 Jul 2026 23:52:03 -0400 Subject: [PATCH] fix: review fixes + feat: imports, assertions, merge engine, per-package overrides Review fixes (8 blocking issues): - Extract ParseError to shared error.hpp (ODR fix) - Remove dead package.cpp/package.hpp + tomlplusplus dep - Safe parse_int() helper replacing crash-prone std::stoi - consume_string() now accepts bare numbers and idents - line_at() fixed for post-EOF line numbers - Subcommand validation before file read in CLI - KwService/KwAssert/KwImport added to consume_ident() - root partition promoted to first-class BootBlock field New features: - Imports: imports = [...] with recursive merge resolution - Assertions: assert { "msg" : field op value } in both parsers - Merge engine: resolve_package() with features/config merge + force support - Per-package overrides: /kappa/system/builds/.kap - .gitignore: added vcpkg_installed/ and kappa binary --- .gitignore | 2 + CMakeLists.txt | 5 +- examples/builds/foo.kap | 17 ++++ examples/config.kap | 8 ++ include/kappa/config/merge.hpp | 28 ++++++ include/kappa/dsl/ast.hpp | 8 ++ include/kappa/dsl/error.hpp | 33 ++++++++ include/kappa/dsl/system.hpp | 4 + include/kappa/dsl/token.hpp | 2 + include/kappa/package.hpp | 23 ----- src/cli/diagnostic.cpp | 4 +- src/config/merge.cpp | 62 ++++++++++++++ src/dsl/lexer.cpp | 4 + src/dsl/parser.cpp | 51 +++++++++-- src/dsl/system.cpp | 97 +++++++++++++++++++-- src/main.cpp | 35 +++++--- src/package.cpp | 150 --------------------------------- vcpkg.json | 4 +- 18 files changed, 325 insertions(+), 212 deletions(-) create mode 100644 examples/builds/foo.kap create mode 100644 include/kappa/config/merge.hpp create mode 100644 include/kappa/dsl/error.hpp delete mode 100644 include/kappa/package.hpp create mode 100644 src/config/merge.cpp delete mode 100644 src/package.cpp diff --git a/.gitignore b/.gitignore index 18bf9d3..9f2113e 100644 --- a/.gitignore +++ b/.gitignore @@ -35,4 +35,6 @@ # Build build/ compile_commands.json +vcpkg_installed/ +kappa diff --git a/CMakeLists.txt b/CMakeLists.txt index fe59ac6..9152c0b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,17 +19,14 @@ if(DEFINED ENV{VCPKG_ROOT}) CACHE STRING "vcpkg toolchain") endif() -find_package(tomlplusplus CONFIG REQUIRED) - add_executable(kappa src/main.cpp - src/package.cpp src/paths.cpp src/dsl/lexer.cpp src/dsl/parser.cpp src/dsl/system.cpp src/eval/vars.cpp src/cli/diagnostic.cpp + src/config/merge.cpp ) target_include_directories(kappa PRIVATE include) -target_link_libraries(kappa PRIVATE tomlplusplus::tomlplusplus) diff --git a/examples/builds/foo.kap b/examples/builds/foo.kap new file mode 100644 index 0000000..1f4004a --- /dev/null +++ b/examples/builds/foo.kap @@ -0,0 +1,17 @@ +/* + * Per-package override for foo. + * Lives at /kappa/system/builds/foo.kap + * + * Uses system config syntax — only features/config need to be specified. + * The rest inherits from the package definition and system config. + */ +packages { + foo { + features { + debug = true + } + config { + port = "9090" + } + } +} diff --git a/examples/config.kap b/examples/config.kap index 9e610fe..3a8e86d 100644 --- a/examples/config.kap +++ b/examples/config.kap @@ -2,6 +2,14 @@ * Kappa system configuration. * Lives at /kappa/system/config.kap */ + +imports = [] + +assert { + "efi partition required for UEFI boot" : boot.efi != "" + "root partition must be set" : boot.root != "" +} + system { hostname = "kappa.local" timezone = "America/New_York" diff --git a/include/kappa/config/merge.hpp b/include/kappa/config/merge.hpp new file mode 100644 index 0000000..66af3e1 --- /dev/null +++ b/include/kappa/config/merge.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include "kappa/dsl/ast.hpp" +#include "kappa/dsl/system.hpp" + +#include +#include +#include + +namespace kappa::config { + +struct ResolvedPackage { + dsl::PackageDef original; + std::unordered_map features; + std::unordered_map config; +}; + +ResolvedPackage resolve_package( + const dsl::PackageDef& pkg, + const dsl::SystemBlock& system, + const std::optional& overrides); + +std::unordered_map resolve_config( + const dsl::PackageDef& pkg, + const dsl::SystemBlock& system, + const std::optional& overrides); + +} // namespace kappa::config diff --git a/include/kappa/dsl/ast.hpp b/include/kappa/dsl/ast.hpp index a2cd72a..fdf62ef 100644 --- a/include/kappa/dsl/ast.hpp +++ b/include/kappa/dsl/ast.hpp @@ -52,6 +52,13 @@ struct ServiceInit { std::unordered_map env; }; +struct Assertion { + std::string message; + std::string field; + std::string op; // "==" or "!=" + std::string value; +}; + struct PackageDef { std::string name; std::string version; @@ -65,6 +72,7 @@ struct PackageDef { std::vector patches; std::vector env_entries; std::unordered_map service; + std::vector assertions; std::unordered_set const_keys; Phase prepare; Phase build; diff --git a/include/kappa/dsl/error.hpp b/include/kappa/dsl/error.hpp new file mode 100644 index 0000000..71e681d --- /dev/null +++ b/include/kappa/dsl/error.hpp @@ -0,0 +1,33 @@ +#pragma once + +#include +#include +#include + +namespace kappa::dsl { + +class ParseError : public std::runtime_error { +public: + ParseError(int line, int col, const std::string& msg) + : std::runtime_error(std::format("{}:{}: {}", line, col, msg)) {} +}; + +inline int parse_int(int line, int col, const std::string& lexeme) { + try { + std::size_t pos = 0; + int val = std::stoi(lexeme, &pos); + if (pos != lexeme.size()) { + throw ParseError(line, col, + std::format("expected integer, got '{}'", lexeme)); + } + return val; + } catch (const std::invalid_argument&) { + throw ParseError(line, col, + std::format("expected integer, got '{}'", lexeme)); + } catch (const std::out_of_range&) { + throw ParseError(line, col, + std::format("integer out of range: '{}'", lexeme)); + } +} + +} // namespace kappa::dsl diff --git a/include/kappa/dsl/system.hpp b/include/kappa/dsl/system.hpp index 1022a2e..0afd3b0 100644 --- a/include/kappa/dsl/system.hpp +++ b/include/kappa/dsl/system.hpp @@ -34,6 +34,7 @@ struct BootBlock { std::string init; std::string efi; std::string swap; + std::string root; std::string bootloader; std::unordered_map params; }; @@ -52,13 +53,16 @@ struct ServiceRef { }; struct SystemConfig { + std::vector imports; SystemBlock system; std::vector packages; std::vector services; BootBlock boot; std::vector users; + std::vector assertions; }; SystemConfig parse_system_config(std::string_view source); +SystemConfig resolve_imports(const SystemConfig& cfg, const std::string& base_dir); } // namespace kappa::dsl diff --git a/include/kappa/dsl/token.hpp b/include/kappa/dsl/token.hpp index e3fab7f..ed6f126 100644 --- a/include/kappa/dsl/token.hpp +++ b/include/kappa/dsl/token.hpp @@ -35,6 +35,8 @@ enum class TokenType { KwPatches, KwEnv, KwService, + KwAssert, + KwImport, KwPrepare, KwBuild, KwCheck, diff --git a/include/kappa/package.hpp b/include/kappa/package.hpp deleted file mode 100644 index 3aa3d74..0000000 --- a/include/kappa/package.hpp +++ /dev/null @@ -1,23 +0,0 @@ -#pragma once - -#include -#include -#include - -namespace kappa { - -struct PackageConfig { - std::string name; - std::string version; - std::string description; - std::string source_url; - std::string source_extension; - std::string source_hash; - std::vector dependencies; - std::string build_system; -}; - -PackageConfig parse_package(const std::string& toml_path); -void print_package(std::ostream& os, const PackageConfig& pkg); - -} // namespace kappa diff --git a/src/cli/diagnostic.cpp b/src/cli/diagnostic.cpp index 9799056..c417671 100644 --- a/src/cli/diagnostic.cpp +++ b/src/cli/diagnostic.cpp @@ -17,12 +17,12 @@ static constexpr auto reset = "\033[0m"sv; static std::string_view line_at(std::string_view source, int target_line) { int current = 1; - std::size_t start = 0; + std::size_t start = std::string_view::npos; for (std::size_t i = 0; i < source.size(); ++i) { if (current == target_line) { start = i; break; } if (source[i] == '\n') { ++current; } } - if (current != target_line) { return ""sv; } + if (start == std::string_view::npos) { return ""sv; } auto end = source.find('\n', start); if (end == std::string_view::npos) { end = source.size(); } diff --git a/src/config/merge.cpp b/src/config/merge.cpp new file mode 100644 index 0000000..c75f6b3 --- /dev/null +++ b/src/config/merge.cpp @@ -0,0 +1,62 @@ +#include "kappa/config/merge.hpp" + +namespace kappa::config { + +ResolvedPackage resolve_package( + const dsl::PackageDef& pkg, + const dsl::SystemBlock& system, + const std::optional& overrides) +{ + ResolvedPackage rp; + rp.original = pkg; + + rp.features = pkg.features; + + for (auto& [key, sys_feat] : system.features) { + auto it = rp.features.find(key); + if (it == rp.features.end()) { + rp.features[key] = sys_feat; + } else if (!it->second.force) { + it->second.enabled = sys_feat.enabled; + } + } + + if (overrides) { + for (auto& [key, ovr_feat] : overrides->features) { + auto it = rp.features.find(key); + if (it == rp.features.end()) { + rp.features[key] = ovr_feat; + } else if (!it->second.force) { + it->second.enabled = ovr_feat.enabled; + } + } + } + + rp.config = resolve_config(pkg, system, overrides); + + return rp; +} + +std::unordered_map resolve_config( + const dsl::PackageDef& pkg, + const dsl::SystemBlock& system, + const std::optional& overrides) +{ + std::unordered_map cfg = system.config; + + for (auto& cf : pkg.config_files) { + for (auto& [key, val] : cf.entries) { + cfg.try_emplace(key, val); + } + } + + if (overrides) { + for (auto& [key, val] : overrides->config) { + cfg[key] = val; + } + } + + return cfg; +} + +} // namespace kappa::config diff --git a/src/dsl/lexer.cpp b/src/dsl/lexer.cpp index 6446e3e..fa90a07 100644 --- a/src/dsl/lexer.cpp +++ b/src/dsl/lexer.cpp @@ -20,6 +20,8 @@ static const std::unordered_map keywords = { {"patches", TokenType::KwPatches}, {"env", TokenType::KwEnv}, {"service", TokenType::KwService}, + {"assert", TokenType::KwAssert}, + {"import", TokenType::KwImport}, {"prepare", TokenType::KwPrepare}, {"build", TokenType::KwBuild}, {"check", TokenType::KwCheck}, @@ -53,6 +55,8 @@ std::string_view token_name(TokenType type) { case TokenType::KwPatches: return "patches"; case TokenType::KwEnv: return "env"; case TokenType::KwService: return "service"; + case TokenType::KwAssert: return "assert"; + case TokenType::KwImport: return "import"; case TokenType::KwPrepare: return "prepare"; case TokenType::KwBuild: return "build"; case TokenType::KwCheck: return "check"; diff --git a/src/dsl/parser.cpp b/src/dsl/parser.cpp index d68c22f..2f22288 100644 --- a/src/dsl/parser.cpp +++ b/src/dsl/parser.cpp @@ -1,4 +1,5 @@ #include "kappa/dsl/parser.hpp" +#include "kappa/dsl/error.hpp" #include "kappa/dsl/lexer.hpp" #include @@ -8,12 +9,6 @@ namespace kappa::dsl { -class ParseError : public std::runtime_error { -public: - ParseError(int line, int col, const std::string& msg) - : std::runtime_error(std::format("{}:{}: {}", line, col, msg)) {} -}; - class Parser { public: explicit Parser(std::string_view source) : lexer_(source) { advance(); } @@ -215,7 +210,7 @@ void Parser::parse_body(PackageDef& pkg) { skip_newlines(); while (!at(TokenType::Rbrace) && !at(TokenType::Eof)) { if (at(TokenType::Newline)) { advance(); continue; } - auto init_name = current_.lexeme; // e.g. "runit", "s6" + auto init_name = current_.lexeme; advance(); consume(TokenType::Lbrace); skip_newlines(); @@ -236,7 +231,8 @@ void Parser::parse_body(PackageDef& pkg) { skip_newlines(); while (!at(TokenType::Rbracket) && !at(TokenType::Eof)) { si.ports.push_back( - std::stoi(std::string(current_.lexeme))); + parse_int(current_.line, current_.col, + current_.lexeme)); advance(); skip_newlines(); if (at(TokenType::Comma)) { consume(TokenType::Comma); } @@ -255,6 +251,42 @@ void Parser::parse_body(PackageDef& pkg) { consume(TokenType::Rbrace); break; + case TokenType::KwAssert: + consume(TokenType::KwAssert); + consume(TokenType::Lbrace); + skip_newlines(); + while (!at(TokenType::Rbrace) && !at(TokenType::Eof)) { + if (at(TokenType::Newline)) { advance(); continue; } + Assertion a; + a.message = consume(TokenType::String).lexeme; + consume(TokenType::Ident); // ":" + a.field = current_.lexeme; + advance(); + if (at(TokenType::Equals)) { + advance(); + if (at(TokenType::Equals)) { + a.op = "=="; advance(); + } else { + a.op = "="; + } + } else { + a.op = current_.lexeme; advance(); + } + if (at(TokenType::String)) { + a.value = consume(TokenType::String).lexeme; + } else if (at(TokenType::KwTrue)) { + a.value = "true"; advance(); + } else if (at(TokenType::KwFalse)) { + a.value = "false"; advance(); + } else { + a.value = current_.lexeme; advance(); + } + pkg.assertions.push_back(std::move(a)); + skip_newlines(); + } + consume(TokenType::Rbrace); + break; + case TokenType::KwPrepare: consume(TokenType::KwPrepare); pkg.prepare = parse_phase(); @@ -420,7 +452,8 @@ Patch Parser::parse_patch_item() { } else if (key == "sha256") { p.sha256 = consume(TokenType::String).lexeme; } else if (key == "level") { - p.level = std::stoi(std::string(current_.lexeme)); + p.level = parse_int(current_.line, current_.col, + current_.lexeme); advance(); } skip_newlines(); diff --git a/src/dsl/system.cpp b/src/dsl/system.cpp index 905a490..ed529ac 100644 --- a/src/dsl/system.cpp +++ b/src/dsl/system.cpp @@ -1,17 +1,15 @@ #include "kappa/dsl/system.hpp" +#include "kappa/dsl/error.hpp" #include "kappa/dsl/lexer.hpp" +#include #include +#include +#include #include namespace kappa::dsl { -class ParseError : public std::runtime_error { -public: - ParseError(int line, int col, const std::string& msg) - : std::runtime_error(std::format("{}:{}: {}", line, col, msg)) {} -}; - class SysParser { public: explicit SysParser(std::string_view source) : lexer_(source) { advance(); } @@ -58,6 +56,9 @@ Token SysParser::consume(TokenType type) { std::string SysParser::consume_ident() { if (at(TokenType::Ident) || at(TokenType::KwEnv) || + at(TokenType::KwService) || + at(TokenType::KwAssert) || + at(TokenType::KwImport) || at(TokenType::KwConfig) || at(TokenType::KwFeatures) || at(TokenType::KwVersion) || @@ -87,6 +88,7 @@ std::string SysParser::consume_ident() { std::string SysParser::consume_string() { if (at(TokenType::KwTrue)) { advance(); return "true"; } if (at(TokenType::KwFalse)) { advance(); return "false"; } + if (at(TokenType::Ident)) { auto v = current_.lexeme; advance(); return v; } return consume(TokenType::String).lexeme; } @@ -108,7 +110,50 @@ SystemConfig SysParser::parse() { auto kw = consume_ident(); - if (kw == "system") { parse_system_block(cfg); } + if (kw == "imports") { + consume(TokenType::Equals); + consume(TokenType::Lbracket); + skip_newlines(); + while (!at(TokenType::Rbracket) && !at(TokenType::Eof)) { + cfg.imports.push_back(consume(TokenType::String).lexeme); + skip_newlines(); + if (at(TokenType::Comma)) { consume(TokenType::Comma); } + skip_newlines(); + } + consume(TokenType::Rbracket); + } else if (kw == "assert") { + consume(TokenType::Lbrace); + skip_newlines(); + while (!at(TokenType::Rbrace) && !at(TokenType::Eof)) { + if (at(TokenType::Newline)) { advance(); continue; } + Assertion a; + a.message = consume(TokenType::String).lexeme; + consume(TokenType::Ident); // ":" + a.field = current_.lexeme; advance(); + if (at(TokenType::Equals)) { + advance(); // first = + if (at(TokenType::Equals)) { + a.op = "=="; advance(); // second = + } else { + a.op = "="; + } + } else { + a.op = current_.lexeme; advance(); + } + if (at(TokenType::String)) { + a.value = consume(TokenType::String).lexeme; + } else if (at(TokenType::KwTrue)) { + a.value = "true"; advance(); + } else if (at(TokenType::KwFalse)) { + a.value = "false"; advance(); + } else { + a.value = current_.lexeme; advance(); + } + cfg.assertions.push_back(std::move(a)); + skip_newlines(); + } + consume(TokenType::Rbrace); + } else if (kw == "system") { parse_system_block(cfg); } else if (kw == "packages") { parse_packages_block(cfg); } else if (kw == "services") { parse_services_block(cfg); } else if (kw == "boot") { parse_boot_block(cfg); } @@ -184,7 +229,8 @@ void SysParser::parse_system_block(SystemConfig& cfg) { auto k = consume_ident(); consume(TokenType::Equals); if (k == "keep") { - cfg.system.rollback.keep = std::stoi(std::string(current_.lexeme)); + cfg.system.rollback.keep = parse_int(current_.line, current_.col, + current_.lexeme); advance(); } skip_newlines(); @@ -299,6 +345,7 @@ void SysParser::parse_boot_block(SystemConfig& cfg) { else if (key == "init") { cfg.boot.init = std::move(val); } else if (key == "efi") { cfg.boot.efi = std::move(val); } else if (key == "swap") { cfg.boot.swap = std::move(val); } + else if (key == "root") { cfg.boot.root = std::move(val); } else if (key == "bootloader"){ cfg.boot.bootloader = std::move(val); } else { cfg.boot.params[key] = std::move(val); } skip_newlines(); @@ -355,4 +402,38 @@ SystemConfig parse_system_config(std::string_view source) { return p.parse(); } +static void merge_config(SystemConfig& base, SystemConfig&& imported) { + if (!imported.system.hostname.empty()) { base.system.hostname = std::move(imported.system.hostname); } + if (!imported.system.timezone.empty()) { base.system.timezone = std::move(imported.system.timezone); } + for (auto& e : imported.system.env) { base.system.env.push_back(std::move(e)); } + for (auto& [k, v] : imported.system.config) { base.system.config[k] = std::move(v); } + for (auto& [k, v] : imported.system.features) { base.system.features[k] = std::move(v); } + if (imported.system.rollback.keep > 0) { base.system.rollback.keep = imported.system.rollback.keep; } + for (auto& p : imported.packages) { base.packages.push_back(std::move(p)); } + for (auto& s : imported.services) { base.services.push_back(std::move(s)); } + for (auto& u : imported.users) { base.users.push_back(std::move(u)); } + if (!imported.boot.kernel.empty()) { base.boot.kernel = std::move(imported.boot.kernel); } + if (!imported.boot.init.empty()) { base.boot.init = std::move(imported.boot.init); } + if (!imported.boot.efi.empty()) { base.boot.efi = std::move(imported.boot.efi); } + if (!imported.boot.swap.empty()) { base.boot.swap = std::move(imported.boot.swap); } + if (!imported.boot.root.empty()) { base.boot.root = std::move(imported.boot.root); } + if (!imported.boot.bootloader.empty()) { base.boot.bootloader = std::move(imported.boot.bootloader); } + for (auto& [k, v] : imported.boot.params) { base.boot.params[k] = std::move(v); } +} + +SystemConfig resolve_imports(const SystemConfig& cfg, const std::string& base_dir) { + auto resolved = cfg; + for (auto& import_path : cfg.imports) { + auto full_path = std::filesystem::path(base_dir) / import_path; + std::ifstream in(full_path); + if (!in) { continue; } + std::ostringstream buf; + buf << in.rdbuf(); + auto imported = parse_system_config(buf.str()); + merge_config(resolved, resolve_imports(imported, base_dir)); + } + resolved.imports.clear(); + return resolved; +} + } // namespace kappa::dsl diff --git a/src/main.cpp b/src/main.cpp index 9df2efc..d8ef297 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -48,24 +48,25 @@ static std::string read_file(const char* path) { 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 ": " + try { + 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); - cli::print_error(std::cerr, source, - {path, line, col}, message, - "check the syntax at this location"); - } else { - std::cerr << "error: " << e.what() << '\n'; + cli::print_error(std::cerr, source, + {path, line, col}, message, + "check the syntax at this location"); + return; + } catch (const std::exception&) {} } + std::cerr << "error: " << e.what() << '\n'; } int main(int argc, char* argv[]) { @@ -86,6 +87,16 @@ int main(int argc, char* argv[]) { return 0; } + bool valid_subcommand = (subcommand == "parse-package") + || (subcommand == "parse-config") + || (subcommand == "validate"); + + if (!valid_subcommand) { + std::cerr << "error: unknown subcommand '" << subcommand << "'\n\n"; + print_usage(); + return 1; + } + // Find the file argument (skip flags) const char* file_arg = nullptr; for (int i = 2; i < argc; ++i) { @@ -145,7 +156,5 @@ int main(int argc, char* argv[]) { } } - std::cerr << "error: unknown subcommand '" << subcommand << "'\n\n"; - print_usage(); - return 1; + return 1; // unreachable } diff --git a/src/package.cpp b/src/package.cpp deleted file mode 100644 index c4c7fe2..0000000 --- a/src/package.cpp +++ /dev/null @@ -1,150 +0,0 @@ -#include "kappa/package.hpp" - -#include -#include -#include -#include -#include -#include - -namespace kappa { -namespace { - -using namespace std::string_view_literals; - -static constexpr std::array hash_keys = {"sha512"sv, "sha256"sv, "md5"sv}; - -static constexpr std::array known_extensions = { - "tar.xz"sv, "tar.gz"sv, "tar.zst"sv, "zip"sv, "tar"sv, "zst"sv, "git"sv, "bs2"sv -}; - -static std::string_view find_hash(const toml::table& src) { - for (auto key : hash_keys) { - if (src.contains(key)) { return key; } - } - return ""sv; -} - -static std::string extract_extension(std::string_view url) { - auto path = url.substr(0, url.find_first_of("?#")); - auto filename = path.substr(path.rfind('/') + 1); - - for (auto ext : known_extensions) { - auto dotted = std::string(".") + std::string(ext); - if (filename.ends_with(dotted)) { return std::string(ext); } - } - - auto dot = filename.rfind('.'); - if (dot != std::string_view::npos) { return std::string(filename.substr(dot + 1)); } - - return ""; -} - -static bool is_known_extension(std::string_view ext) { - return std::any_of(known_extensions.begin(), known_extensions.end(), - [ext](auto k) { return ext == k; }); -} - -static std::string interpolate(std::string raw, const toml::table& vars) { - std::string result; - result.reserve(raw.size()); - std::size_t i = 0; - - while (i < raw.size()) { - if (raw[i] == '$' && i + 1 < raw.size() && raw[i + 1] == '{') { - auto end = raw.find('}', i + 2); - if (end == std::string::npos) { - result += raw.substr(i); - break; - } - auto key = raw.substr(i + 2, end - (i + 2)); - result += vars[key].value_or(""); - i = end + 1; - } else { - result += raw[i]; - ++i; - } - } - - return result; -} - -} // namespace - -PackageConfig parse_package(const std::string& toml_path) { - auto tbl = toml::parse_file(toml_path); - PackageConfig pkg; - - if (auto* pkg_tbl = tbl["package"].as_table()) { - pkg.name = (*pkg_tbl)["name"].value_or(""); - pkg.version = (*pkg_tbl)["version"].value_or(""); - pkg.description = (*pkg_tbl)["description"].value_or(""); - } - - if (auto* src = tbl["source"].as_table()) { - auto url_str = (*src)["url"].value_or(""sv); - auto* pkg_tbl = tbl["package"].as_table(); - auto resolved = interpolate(std::string(url_str), - pkg_tbl != nullptr ? *pkg_tbl : toml::table{}); - pkg.source_url = std::move(resolved); - - pkg.source_extension = extract_extension(pkg.source_url); - - auto hash_name = find_hash(*src); - if (!hash_name.empty()) { - pkg.source_hash = std::string(hash_name) + ':' - + std::string((*src)[hash_name].value_or(""sv)); - } - } - - if (auto* deps_tbl = tbl["dependencies"].as_table()) { - if (auto* arr = (*deps_tbl)["deps"].as_array()) { - for (std::size_t i = 0; i < arr->size(); ++i) { - pkg.dependencies.emplace_back((*arr)[i].value_or(""sv)); - } - } - } - - if (auto* bld = tbl["build"].as_table()) { - pkg.build_system = (*bld)["system"].value_or(""sv); - } - - return pkg; -} - -void print_package(std::ostream& os, const PackageConfig& pkg) { - os << "package.name = " << pkg.name << '\n'; - os << "package.version = " << pkg.version << '\n'; - os << "package.description = " << pkg.description << '\n'; - - if (!pkg.source_url.empty()) { - os << "source.url = " << pkg.source_url << '\n'; - } - if (!pkg.source_extension.empty()) { - os << "source.extension = " << pkg.source_extension; - if (!is_known_extension(pkg.source_extension)) { - os << " (unrecognised)"; - } - os << '\n'; - } - if (!pkg.source_hash.empty()) { - os << "source.hash = " << pkg.source_hash << '\n'; - } - - if (!pkg.dependencies.empty()) { - os << "dependencies = ["; - for (std::size_t i = 0; i < pkg.dependencies.size(); ++i) { - if (i > 0) { os << ", "; } - os << '"' << pkg.dependencies[i] << '"'; - } - os << "]\n"; - } - - if (!pkg.build_system.empty()) { - os << "build.system = " << pkg.build_system << '\n'; - } - - os << '\n'; -} - -} // namespace kappa diff --git a/vcpkg.json b/vcpkg.json index 834b1b4..b85dc4f 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -2,7 +2,5 @@ "$schema": "https://raw.githubusercontent.com/microsoft/vcpkg-tool/main/docs/vcpkg.schema.json", "name": "kappa", "version": "0.1.0", - "dependencies": [ - "tomlplusplus" - ] + "dependencies": [] }