OXYGENHUB
◆ v0.9.0 oxygen / src / cli / artifact.rs
5.2 KiB · Rust · last changed Cylinder C1, first part: artifacts as trees of the repository's chunks, oxy artifact pack/list/show/unpack/verify, kept by gc (refs #108) 7 days ago source blame history raw
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 `=`.
13
fn pairs(list: &[String], what: &str) -> Result<std::collections::BTreeMap<String, String>> {
14
    list.iter()
15
        .map(|p| {
16
            p.split_once('=')
17
                .map(|(k, v)| (k.to_string(), v.to_string()))
18
                .ok_or_else(|| anyhow::anyhow!("{what} {p:?}: write it KEY=VALUE"))
19
        })
20
        .collect()
21
}
22
23
fn summary(k: &cylinder::Kept) -> String {
24
    let a = &k.artifact;
25
    let mut parts = vec![a.kind.as_str().to_string()];
26
    parts.extend(a.os.clone());
27
    parts.extend(a.arch.clone());
28
    format!(
29
        "{} {} {}",
30
        ui::hash(&k.name[..12]),
31
        parts.join(" "),
32
        ui::dim(format!("{} bytes", a.total_size))
33
    )
34
}
35
36
fn as_json(k: &cylinder::Kept, files: usize) -> String {
37
    let a = &k.artifact;
38
    let text = |v: &Option<String>| v.as_deref().map(json::s).unwrap_or_else(|| "null".into());
39
    let map = |m: &std::collections::BTreeMap<String, String>| {
40
        let inner: Vec<String> = m
41
            .iter()
42
            .map(|(k, v)| format!("{}:{}", json::s(k), json::s(v)))
43
            .collect();
44
        format!("{{{}}}", inner.join(","))
45
    };
46
    json::obj([
47
        ("name", json::s(&k.name)),
48
        ("kind", json::s(a.kind.as_str())),
49
        ("arch", text(&a.arch)),
50
        ("os", text(&a.os)),
51
        ("tree", json::s(&a.tree.to_hex())),
52
        ("files", json::n(files as u64)),
53
        ("total_size", json::n(a.total_size)),
54
        ("env", map(&a.env)),
55
        ("labels", map(&a.labels)),
56
    ])
57
}
58
59
#[allow(clippy::too_many_arguments)]
60
pub fn pack(
61
    dir: &Path,
62
    kind: &str,
63
    arch: Option<String>,
64
    os: Option<String>,
65
    entrypoint: Vec<String>,
66
    env: &[String],
67
    labels: &[String],
68
    as_json_out: bool,
69
) -> Result<i32> {
70
    let repo = open()?;
71
    let mut spec = Spec::new(Kind::parse(kind)?);
72
    spec.arch = arch;
73
    spec.os = os;
74
    spec.entrypoint = (!entrypoint.is_empty()).then_some(entrypoint);
75
    spec.env = pairs(env, "env")?;
76
    spec.labels = pairs(labels, "label")?;
77
    let sp = ui::spinner("Packing…");
78
    let k = cylinder::pack(&repo, dir, &spec)?;
79
    sp.clear();
80
    let files = cylinder::tree(&repo, &k.artifact)?.entries.len();
81
    if as_json_out {
82
        println!("{}", as_json(&k, files));
83
    } else {
84
        println!(
85
            "{} {} as {}",
86
            ui::done("Packed"),
87
            ui::path(dir.display()),
88
            summary(&k)
89
        );
90
        println!("{}", ui::kv("name", &k.name));
91
        println!("{}", ui::kv("files", files.to_string()));
92
    }
93
    Ok(0)
94
}
95
96
pub fn list(as_json_out: bool) -> Result<i32> {
97
    let repo = open()?;
98
    let names = cylinder::list(&repo)?;
99
    let kept: Vec<cylinder::Kept> = names
100
        .iter()
101
        .map(|n| cylinder::open(&repo, n))
102
        .collect::<std::result::Result<_, _>>()?;
103
    if as_json_out {
104
        let items: Vec<String> = kept
105
            .iter()
106
            .map(|k| {
107
                let files = cylinder::tree(&repo, &k.artifact)
108
                    .map(|t| t.entries.len())
109
                    .unwrap_or(0);
110
                as_json(k, files)
111
            })
112
            .collect();
113
        println!("[{}]", items.join(","));
114
        return Ok(0);
115
    }
116
    if kept.is_empty() {
117
        println!("No artifacts yet: oxy artifact pack DIR --kind archive");
118
    }
119
    for k in &kept {
120
        println!("{}", summary(k));
121
    }
122
    Ok(0)
123
}
124
125
pub fn show(name: &str, as_json_out: bool) -> Result<i32> {
126
    let repo = open()?;
127
    let k = cylinder::open(&repo, name)?;
128
    let t = cylinder::tree(&repo, &k.artifact)?;
129
    if as_json_out {
130
        println!("{}", as_json(&k, t.entries.len()));
131
        return Ok(0);
132
    }
133
    println!("{}", summary(&k));
134
    println!("{}", ui::kv("name", &k.name));
135
    if let Some(e) = &k.artifact.entrypoint {
136
        println!("{}", ui::kv("entrypoint", e.join(" ")));
137
    }
138
    for (key, v) in &k.artifact.labels {
139
        println!("{}", ui::kv("label", format!("{key}={v}")));
140
    }
141
    for (key, v) in &k.artifact.env {
142
        println!("{}", ui::kv("env", format!("{key}={v}")));
143
    }
144
    for e in &t.entries {
145
        let mode = match e.mode {
146
            cylinder::MODE_EXEC => "x",
147
            cylinder::MODE_LINK => "l",
148
            _ => " ",
149
        };
150
        println!("  {mode} {:>10}  {}", e.size, e.path);
151
    }
152
    Ok(0)
153
}
154
155
pub fn unpack(name: &str, dest: &Path) -> Result<i32> {
156
    let repo = open()?;
157
    let k = cylinder::open(&repo, name)?;
158
    let n = cylinder::unpack(&repo, &k, dest)?;
159
    println!(
160
        "{} {} files of {} into {}",
161
        ui::done("Unpacked"),
162
        n,
163
        ui::hash(&k.name[..12]),
164
        ui::path(dest.display())
165
    );
166
    Ok(0)
167
}
168
169
pub fn verify(name: &str) -> Result<i32> {
170
    let repo = open()?;
171
    let k = cylinder::open(&repo, name)?;
172
    let n = cylinder::verify(&repo, &k)?;
173
    println!(
174
        "{} {}: its name, tree and {} files check out",
175
        ui::done("Verified"),
176
        ui::hash(&k.name[..12]),
177
        n
178
    );
179
    Ok(0)
180
}