//! `oxy artifact`: Cylinder's artifacts (docs/cylinder.md §4.1, step C1): //! a directory packed into a tree of the repository's own chunks, named by //! the BLAKE3 of its canonical encoding. use super::json; use super::open; use super::ui; use anyhow::Result; use cylinder::{Kind, Spec}; use std::path::Path; /// `KEY=VALUE` pairs, refused when one has no `=`. fn pairs(list: &[String], what: &str) -> Result> { list.iter() .map(|p| { p.split_once('=') .map(|(k, v)| (k.to_string(), v.to_string())) .ok_or_else(|| anyhow::anyhow!("{what} {p:?}: write it KEY=VALUE")) }) .collect() } fn summary(k: &cylinder::Kept) -> String { let a = &k.artifact; let mut parts = vec![a.kind.as_str().to_string()]; parts.extend(a.os.clone()); parts.extend(a.arch.clone()); format!( "{} {} {}", ui::hash(&k.name[..12]), parts.join(" "), ui::dim(format!("{} bytes", a.total_size)) ) } fn as_json(k: &cylinder::Kept, files: usize) -> String { let a = &k.artifact; let text = |v: &Option| v.as_deref().map(json::s).unwrap_or_else(|| "null".into()); let map = |m: &std::collections::BTreeMap| { let inner: Vec = m .iter() .map(|(k, v)| format!("{}:{}", json::s(k), json::s(v))) .collect(); format!("{{{}}}", inner.join(",")) }; json::obj([ ("name", json::s(&k.name)), ("kind", json::s(a.kind.as_str())), ("arch", text(&a.arch)), ("os", text(&a.os)), ("tree", json::s(&a.tree.to_hex())), ("files", json::n(files as u64)), ("total_size", json::n(a.total_size)), ("env", map(&a.env)), ("labels", map(&a.labels)), ]) } #[allow(clippy::too_many_arguments)] pub fn pack( dir: &Path, kind: &str, arch: Option, os: Option, entrypoint: Vec, env: &[String], labels: &[String], as_json_out: bool, ) -> Result { let repo = open()?; let mut spec = Spec::new(Kind::parse(kind)?); spec.arch = arch; spec.os = os; spec.entrypoint = (!entrypoint.is_empty()).then_some(entrypoint); spec.env = pairs(env, "env")?; spec.labels = pairs(labels, "label")?; let sp = ui::spinner("Packing…"); let k = cylinder::pack(&repo, dir, &spec)?; sp.clear(); let files = cylinder::tree(&repo, &k.artifact)?.entries.len(); if as_json_out { println!("{}", as_json(&k, files)); } else { println!( "{} {} as {}", ui::done("Packed"), ui::path(dir.display()), summary(&k) ); println!("{}", ui::kv("name", &k.name)); println!("{}", ui::kv("files", files.to_string())); } Ok(0) } pub fn list(as_json_out: bool) -> Result { let repo = open()?; let names = cylinder::list(&repo)?; let kept: Vec = names .iter() .map(|n| cylinder::open(&repo, n)) .collect::>()?; if as_json_out { let items: Vec = kept .iter() .map(|k| { let files = cylinder::tree(&repo, &k.artifact) .map(|t| t.entries.len()) .unwrap_or(0); as_json(k, files) }) .collect(); println!("[{}]", items.join(",")); return Ok(0); } if kept.is_empty() { println!("No artifacts yet: oxy artifact pack DIR --kind archive"); } for k in &kept { println!("{}", summary(k)); } Ok(0) } pub fn show(name: &str, as_json_out: bool) -> Result { let repo = open()?; let k = cylinder::open(&repo, name)?; let t = cylinder::tree(&repo, &k.artifact)?; if as_json_out { println!("{}", as_json(&k, t.entries.len())); return Ok(0); } println!("{}", summary(&k)); println!("{}", ui::kv("name", &k.name)); if let Some(e) = &k.artifact.entrypoint { println!("{}", ui::kv("entrypoint", e.join(" "))); } for (key, v) in &k.artifact.labels { println!("{}", ui::kv("label", format!("{key}={v}"))); } for (key, v) in &k.artifact.env { println!("{}", ui::kv("env", format!("{key}={v}"))); } for e in &t.entries { let mode = match e.mode { cylinder::MODE_EXEC => "x", cylinder::MODE_LINK => "l", _ => " ", }; println!(" {mode} {:>10} {}", e.size, e.path); } Ok(0) } pub fn unpack(name: &str, dest: &Path) -> Result { let repo = open()?; let k = cylinder::open(&repo, name)?; let n = cylinder::unpack(&repo, &k, dest)?; println!( "{} {} files of {} into {}", ui::done("Unpacked"), n, ui::hash(&k.name[..12]), ui::path(dest.display()) ); Ok(0) } pub fn verify(name: &str) -> Result { let repo = open()?; let k = cylinder::open(&repo, name)?; let n = cylinder::verify(&repo, &k)?; println!( "{} {}: its name, tree and {} files check out", ui::done("Verified"), ui::hash(&k.name[..12]), n ); Ok(0) }