OXYGENHUB

Coverage / src/cli/artifact.rs 65.6%

105 of 160 lines run, 15 of 36 functions · run #427 on main · run missed

1
//! `oxy artifact`: Cylinder's artifacts (docs/cylinder.md §4.1, step C1):
2
//! a directory packed into a tree of the repository's own chunks, named by
3
//! the BLAKE3 of its canonical encoding.
4
5
use super::json;
6
use super::open;
7
use super::ui;
8
use anyhow::Result;
9
use cylinder::{Kind, Spec};
10
use std::path::Path;
11
12
/// `KEY=VALUE` pairs, refused when one has no `=`.
132×
fn pairs(list: &[String], what: &str) -> Result<std::collections::BTreeMap<String, String>> {
142×
    list.iter()
152×
        .map(|p| {
161×
            p.split_once('=')
171×
                .map(|(k, v)| (k.to_string(), v.to_string()))
181×
                .ok_or_else(|| anyhow::anyhow!("{what} {p:?}: write it KEY=VALUE"))
191×
        })
202×
        .collect()
212×
}
22
230×
fn summary(k: &cylinder::Kept) -> String {
240×
    let a = &k.artifact;
250×
    let mut parts = vec![a.kind.as_str().to_string()];
260×
    parts.extend(a.os.clone());
270×
    parts.extend(a.arch.clone());
280×
    format!(
29
        "{} {} {}",
300×
        ui::hash(&k.name[..12]),
310×
        parts.join(" "),
320×
        ui::dim(format!("{} bytes", a.total_size))
33
    )
340×
}
35
362×
fn as_json(k: &cylinder::Kept, files: usize) -> String {
372×
    let a = &k.artifact;
384×
    let text = |v: &Option<String>| v.as_deref().map(json::s).unwrap_or_else(|| "null".into());
394×
    let map = |m: &std::collections::BTreeMap<String, String>| {
404×
        let inner: Vec<String> = m
414×
            .iter()
424×
            .map(|(k, v)| format!("{}:{}", json::s(k), json::s(v)))
434×
            .collect();
444×
        format!("{{{}}}", inner.join(","))
454×
    };
462×
    json::obj([
472×
        ("name", json::s(&k.name)),
482×
        ("kind", json::s(a.kind.as_str())),
492×
        ("arch", text(&a.arch)),
502×
        ("os", text(&a.os)),
512×
        ("tree", json::s(&a.tree.to_hex())),
522×
        ("files", json::n(files as u64)),
532×
        ("total_size", json::n(a.total_size)),
542×
        ("env", map(&a.env)),
552×
        ("labels", map(&a.labels)),
562×
    ])
572×
}
58
59
#[allow(clippy::too_many_arguments)]
602×
pub fn pack(
612×
    dir: &Path,
622×
    kind: &str,
632×
    arch: Option<String>,
642×
    os: Option<String>,
652×
    entrypoint: Vec<String>,
662×
    env: &[String],
672×
    labels: &[String],
682×
    as_json_out: bool,
692×
) -> Result<i32> {
702×
    let repo = open()?;
712×
    let mut spec = Spec::new(Kind::parse(kind)?);
721×
    spec.arch = arch;
731×
    spec.os = os;
741×
    spec.entrypoint = (!entrypoint.is_empty()).then_some(entrypoint);
751×
    spec.env = pairs(env, "env")?;
761×
    spec.labels = pairs(labels, "label")?;
771×
    let sp = ui::spinner("Packing…");
781×
    let k = cylinder::pack(&repo, dir, &spec)?;
791×
    sp.clear();
801×
    let files = cylinder::tree(&repo, &k.artifact)?.entries.len();
811×
    if as_json_out {
821×
        println!("{}", as_json(&k, files));
831×
    } else {
840×
        println!(
850×
            "{} {} as {}",
860×
            ui::done("Packed"),
870×
            ui::path(dir.display()),
880×
            summary(&k)
890×
        );
900×
        println!("{}", ui::kv("name", &k.name));
910×
        println!("{}", ui::kv("files", files.to_string()));
920×
    }
931×
    Ok(0)
942×
}
95
961×
pub fn list(as_json_out: bool) -> Result<i32> {
971×
    let repo = open()?;
981×
    let names = cylinder::list(&repo)?;
991×
    let kept: Vec<cylinder::Kept> = names
1001×
        .iter()
1011×
        .map(|n| cylinder::open(&repo, n))
1021×
        .collect::<std::result::Result<_, _>>()?;
1031×
    if as_json_out {
1041×
        let items: Vec<String> = kept
1051×
            .iter()
1061×
            .map(|k| {
1071×
                let files = cylinder::tree(&repo, &k.artifact)
1081×
                    .map(|t| t.entries.len())
1091×
                    .unwrap_or(0);
1101×
                as_json(k, files)
1111×
            })
1121×
            .collect();
1131×
        println!("[{}]", items.join(","));
1141×
        return Ok(0);
1150×
    }
1160×
    if kept.is_empty() {
1170×
        println!("No artifacts yet: oxy artifact pack DIR --kind archive");
1180×
    }
1190×
    for k in &kept {
1200×
        println!("{}", summary(k));
1210×
    }
1220×
    Ok(0)
1231×
}
124
1250×
pub fn show(name: &str, as_json_out: bool) -> Result<i32> {
1260×
    let repo = open()?;
1270×
    let k = cylinder::open(&repo, name)?;
1280×
    let t = cylinder::tree(&repo, &k.artifact)?;
1290×
    if as_json_out {
1300×
        println!("{}", as_json(&k, t.entries.len()));
1310×
        return Ok(0);
1320×
    }
1330×
    println!("{}", summary(&k));
1340×
    println!("{}", ui::kv("name", &k.name));
1350×
    if let Some(e) = &k.artifact.entrypoint {
1360×
        println!("{}", ui::kv("entrypoint", e.join(" ")));
1370×
    }
1380×
    for (key, v) in &k.artifact.labels {
1390×
        println!("{}", ui::kv("label", format!("{key}={v}")));
1400×
    }
1410×
    for (key, v) in &k.artifact.env {
1420×
        println!("{}", ui::kv("env", format!("{key}={v}")));
1430×
    }
1440×
    for e in &t.entries {
1450×
        let mode = match e.mode {
1460×
            cylinder::MODE_EXEC => "x",
1470×
            cylinder::MODE_LINK => "l",
1480×
            _ => " ",
149
        };
1500×
        println!("  {mode} {:>10}  {}", e.size, e.path);
151
    }
1520×
    Ok(0)
1530×
}
154
1551×
pub fn unpack(name: &str, dest: &Path) -> Result<i32> {
1561×
    let repo = open()?;
1571×
    let k = cylinder::open(&repo, name)?;
1581×
    let n = cylinder::unpack(&repo, &k, dest)?;
1591×
    println!(
160
        "{} {} files of {} into {}",
1611×
        ui::done("Unpacked"),
162
        n,
1631×
        ui::hash(&k.name[..12]),
1641×
        ui::path(dest.display())
165
    );
1661×
    Ok(0)
1671×
}
168
1691×
pub fn verify(name: &str) -> Result<i32> {
1701×
    let repo = open()?;
1711×
    let k = cylinder::open(&repo, name)?;
1721×
    let n = cylinder::verify(&repo, &k)?;
1731×
    println!(
174
        "{} {}: its name, tree and {} files check out",
1751×
        ui::done("Verified"),
1761×
        ui::hash(&k.name[..12]),
177
        n
178
    );
1791×
    Ok(0)
1801×
}