use std::str::FromStr;
use super::version::Version;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct LockedPackage {
pub name: String,
pub version: Version,
pub checksum: String,
pub dependencies: Vec<String>,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct Lockfile {
pub packages: Vec<LockedPackage>,
}
impl Lockfile {
pub fn new() -> Self {
Self::default()
}
pub fn add_package(&mut self, pkg: LockedPackage) {
if let Some(pos) = self.packages.iter().position(|p| p.name == pkg.name) {
self.packages[pos] = pkg;
} else {
self.packages.push(pkg);
}
}
pub fn get_package(&self, name: &str) -> Option<&LockedPackage> {
self.packages.iter().find(|p| p.name == name)
}
pub fn parse(content: &str) -> Result<Self, String> {
let mut lockfile = Self::new();
let mut cur_name = String::new();
let mut cur_ver = None;
let mut cur_checksum = String::new();
let mut cur_deps = Vec::new();
let mut in_pkg = false;
for line in content.lines() {
let trimmed = line.trim();
if trimmed.is_empty() || trimmed.starts_with('#') {
continue;
}
if trimmed == "[[package]]" {
if let (true, Some(v)) = (in_pkg && !cur_name.is_empty(), cur_ver.take()) {
lockfile.add_package(LockedPackage {
name: cur_name.clone(),
version: v,
checksum: cur_checksum.clone(),
dependencies: std::mem::take(&mut cur_deps),
});
}
in_pkg = true;
cur_name.clear();
cur_ver = None;
cur_checksum.clear();
cur_deps.clear();
continue;
}
if !in_pkg {
continue;
}
let Some((k, v_raw)) = trimmed.split_once('=') else {
continue;
};
let k = k.trim();
let v = v_raw.trim().trim_matches('"').trim_matches('\'');
match k {
"name" => cur_name = v.to_string(),
"version" => cur_ver = Some(Version::from_str(v)?),
"checksum" => cur_checksum = v.to_string(),
"dependencies" => {
let inner = v.trim_matches('[').trim_matches(']');
cur_deps = inner
.split(',')
.map(|s| s.trim().trim_matches('"').trim_matches('\'').to_string())
.filter(|s| !s.is_empty())
.collect();
}
_ => {}
}
}
if let (true, Some(v)) = (in_pkg && !cur_name.is_empty(), cur_ver) {
lockfile.add_package(LockedPackage {
name: cur_name,
version: v,
checksum: cur_checksum,
dependencies: cur_deps,
});
}
Ok(lockfile)
}
pub fn serialize(&self) -> String {
let mut out = String::new();
out.push_str("# This file is automatically generated by Neyuki Package Manager.\n");
out.push_str("# Manual edits may be overwritten.\n\n");
for pkg in &self.packages {
out.push_str("[[package]]\n");
out.push_str(&format!("name = \"{}\"\n", pkg.name));
out.push_str(&format!("version = \"{}\"\n", pkg.version));
out.push_str(&format!("checksum = \"{}\"\n", pkg.checksum));
let deps_quoted: Vec<String> = pkg
.dependencies
.iter()
.map(|d| format!("\"{}\"", d))
.collect();
out.push_str(&format!("dependencies = [{}]\n\n", deps_quoted.join(", ")));
}
out
}
}