use std::collections::BTreeMap;
use std::path::Path;
use anyhow::Result;
use serde::Deserialize;
use serde::Serialize;
use toml::Table;
use nargo_parse::*;
#[derive(Clone, Debug)]
pub struct Lockfile {
#[allow(dead_code)]
pub version: i64,
packages_cache: BTreeMap<String, LockEntry>,
}
impl Lockfile {
pub fn new() -> Self {
Self {
version: 0,
packages_cache: BTreeMap::default(),
}
}
pub fn load_or_init(path: &Path) -> Result<Self> {
if !path.exists() {
return Ok(Self::new());
}
let mut s: BTreeMap<String, toml::Value> = toml::from_str(&std::fs::read_to_string(path)?)?;
let packages = match s.remove("packages").unwrap_or(toml::Value::Array(vec![])) {
toml::Value::Array(packages) => packages
.into_iter()
.map(|v| {
v.try_into().map_err(|e| {
anyhow::anyhow!("failed to parse lockfile package entry {e:?}")
})
})
.collect::<Result<Vec<LockEntry>>>()?,
_ => anyhow::bail!("malformed lockfile, packages must be an array: {path:?}"),
};
let mut packages_cache = BTreeMap::default();
for entry in packages {
let entry_identifier = entry.identifier();
if packages_cache.contains_key(&entry_identifier) {
println!(
"WARNING: lockfile contains a duplicate entry for {}:{}",
entry.git, entry.tag
);
}
packages_cache.insert(entry_identifier, entry);
}
let version = match s.get("version").ok_or(anyhow::anyhow!(
"malformed lockfile, does not contain version"
))? {
toml::Value::Integer(version) => *version,
_ => anyhow::bail!("malformed lockfile, version must be an integer: {path:?}"),
};
if version != 0 {
anyhow::bail!(
"bad version number, only version 0 is supported by this version of nrpm: {path:?}"
);
}
Ok(Self {
version,
packages_cache,
})
}
pub fn entries(&self) -> impl Iterator<Item = &LockEntry> {
self.packages_cache.values()
}
pub fn is_empty(&self) -> bool {
self.packages_cache.is_empty()
}
pub fn entry(&self, identifier: &str) -> Option<LockEntry> {
self.packages_cache
.get(identifier)
.and_then(|entry| Some(entry.clone()))
}
pub fn save(&self, path: &Path) -> Result<()> {
let mut out = BTreeMap::<String, toml::Value>::default();
out.insert("version".into(), toml::Value::Integer(0));
out.insert(
"packages".into(),
toml::Value::Array(
self.packages_cache
.iter()
.map(|(_, val)| Table::try_from(val).map_err(|e| anyhow::anyhow!(e)))
.collect::<Result<Vec<_>>>()?
.into_iter()
.map(|v| toml::Value::Table(v))
.collect::<Vec<_>>(),
),
);
let str = toml::to_string_pretty(&out)?;
std::fs::write(path, str)?;
Ok(())
}
pub fn upsert(&mut self, dep: Dependency, path: &Path) -> Result<()> {
if !path.is_absolute() {
anyhow::bail!("lockfile paths must be absolute");
}
let hash = nrpm_tarball::hash_dir(path)?;
if let Some(git) = &dep.git
&& let Some(tag) = &dep.tag
{
self.packages_cache.insert(
dep.identifier()?,
LockEntry {
git: git.clone(),
tag: tag.clone(),
blake3: hash.to_string(),
},
);
}
Ok(())
}
pub fn remove(&mut self, identifier: &str) {
self.packages_cache.remove(identifier);
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct LockEntry {
pub git: String,
pub tag: String,
pub blake3: String, }
impl LockEntry {
pub fn identifier(&self) -> String {
format!("{}@{}", self.git, self.tag)
}
}