module; #include module cargoxx.lockfile; import std; import cargoxx.util; namespace cargoxx::lockfile { auto Lockfile::nixpkgs_rev() const -> std::optional { for (const auto& p : packages) { if (p.nixpkgs_rev && !p.nixpkgs_rev->empty()) { return p.nixpkgs_rev; } } return std::nullopt; } namespace { using util::Error; using util::ErrorCode; auto err(ErrorCode code, std::string msg, std::filesystem::path path) -> Error { return Error{code, std::move(msg), "", std::move(path), std::nullopt}; } auto extract_string_array(const toml::array& arr, std::string_view field, const std::filesystem::path& path) -> util::Result> { std::vector out; out.reserve(arr.size()); for (const auto& el : arr) { if (auto s = el.value()) { out.push_back(*s); } else { return std::unexpected(err(ErrorCode::ManifestInvalidField, std::format("'{}' must be an array of strings", field), path)); } } return out; } auto parse_package(const toml::table& tbl, const std::filesystem::path& path) -> util::Result { LockfilePackage pkg; if (auto v = tbl["name"].value()) { pkg.name = *v; } else { return std::unexpected( err(ErrorCode::ManifestInvalidField, "lockfile package missing 'name'", path)); } if (auto v = tbl["version"].value()) { pkg.version = *v; } else { return std::unexpected( err(ErrorCode::ManifestInvalidField, "lockfile package missing 'version'", path)); } if (const auto* deps = tbl["dependencies"].as_array()) { auto r = extract_string_array(*deps, "dependencies", path); if (!r) { return std::unexpected(r.error()); } pkg.dependencies = std::move(*r); } if (auto v = tbl["nixpkgs_attr"].value()) { pkg.nixpkgs_attr = *v; } if (auto v = tbl["nixpkgs_rev"].value()) { pkg.nixpkgs_rev = *v; } if (auto v = tbl["linkdb_source"].value()) { pkg.linkdb_source = *v; } return pkg; } } // namespace auto parse(const std::filesystem::path& path) -> util::Result { std::error_code ec; if (!std::filesystem::exists(path, ec) || ec) { return std::unexpected(err(ErrorCode::ManifestNotFound, std::format("lockfile not found: {}", path.string()), path)); } toml::table root; try { root = toml::parse_file(path.string()); } catch (const toml::parse_error& e) { return std::unexpected(err(ErrorCode::ManifestParseError, std::format("toml parse error: {}", e.description()), path)); } Lockfile lock; if (auto v = root["version"].value()) { lock.version = *v; } if (const auto* arr = root["package"].as_array()) { lock.packages.reserve(arr->size()); for (const auto& el : *arr) { const auto* tbl = el.as_table(); if (!tbl) { return std::unexpected(err(ErrorCode::ManifestInvalidField, "[[package]] entries must be tables", path)); } auto p = parse_package(*tbl, path); if (!p) { return std::unexpected(p.error()); } lock.packages.push_back(std::move(*p)); } } return lock; } auto write(const Lockfile& lock, const std::filesystem::path& path) -> util::Result { toml::table root; root.insert_or_assign("version", lock.version); toml::array packages; for (const auto& p : lock.packages) { toml::table tbl; tbl.insert_or_assign("name", p.name); tbl.insert_or_assign("version", p.version); if (!p.dependencies.empty()) { toml::array deps; for (const auto& d : p.dependencies) { deps.push_back(d); } tbl.insert_or_assign("dependencies", std::move(deps)); } if (p.nixpkgs_attr) { tbl.insert_or_assign("nixpkgs_attr", *p.nixpkgs_attr); } if (p.nixpkgs_rev) { tbl.insert_or_assign("nixpkgs_rev", *p.nixpkgs_rev); } if (p.linkdb_source) { tbl.insert_or_assign("linkdb_source", *p.linkdb_source); } packages.push_back(std::move(tbl)); } root.insert_or_assign("package", std::move(packages)); std::ofstream out{path}; if (!out) { return std::unexpected(util::Error{ util::ErrorCode::Internal, std::format("cannot open lockfile for writing: {}", path.string()), "", path, std::nullopt, }); } out << root << '\n'; if (!out) { return std::unexpected(util::Error{ util::ErrorCode::Internal, std::format("write failed: {}", path.string()), "", path, std::nullopt, }); } return {}; } } // namespace cargoxx::lockfile