use hd_cas::ContentHash;
use hd_engine::{Dag, Node, DagDiff};
const RESET: &str = "\x1b[0m";
const DIM: &str = "\x1b[2m";
const GREEN: &str = "\x1b[32m";
const YELLOW: &str = "\x1b[33m";
const RED: &str = "\x1b[31m";
#[allow(dead_code)]
pub fn format_bytes(bytes: u64) -> String {
if bytes < 1024 {
format!("{}B", bytes)
} else if bytes < 1024 * 1024 {
format!("{:.1}KB", bytes as f64 / 1024.0)
} else {
format!("{:.1}MB", bytes as f64 / (1024.0 * 1024.0))
}
}
fn short_hash(hash: &ContentHash) -> String {
hash.to_hex()[..12].to_string()
}
fn truncate(s: &str, max: usize) -> String {
if s.len() <= max {
s.to_string()
} else {
format!("{}...", &s[..max])
}
}
fn node_label(node: &Node, hash: &ContentHash) -> String {
let h = short_hash(hash);
match node {
Node::Env { name, .. } => format!("Env({}) {}", name, h),
Node::Dir { path, .. } => format!("Dir({}) {}", path, h),
Node::File { path, manifest_hash } => {
format!("File({}) {} manifest:{}", path, h, short_hash(manifest_hash))
}
Node::Package { provider, name, version, .. } => {
format!("Pkg({}/{} {}) {}", provider, name, version, h)
}
Node::BuildStep { command, .. } => {
format!("Build({}) {}", truncate(command, 40), h)
}
}
}
fn color_for(hash: &ContentHash, diff: &DagDiff) -> (&'static str, &'static str) {
if diff.added.contains(hash) {
(GREEN, "[NEW]")
} else if diff.changed.contains(hash) {
(YELLOW, "[CHANGED]")
} else if diff.removed.contains(hash) {
(RED, "[REMOVED]")
} else {
(DIM, "[ok]")
}
}
fn walk_tree(dag: &Dag, hash: &ContentHash, depth: usize, diff: Option<&DagDiff>) {
let node = match dag.get(hash) {
Some(n) => n,
None => return,
};
let label_str = node_label(node, hash);
let (color, status) = if let Some(d) = diff {
color_for(hash, d)
} else {
(DIM, "[ok]")
};
let indent = " ".repeat(depth);
println!("{}{}{} {}{}{}",
indent,
color,
label_str,
DIM,
status,
RESET,
);
match node {
Node::Env { children, .. } => {
for child in children {
walk_tree(dag, child, depth + 1, diff);
}
}
Node::Dir { children, .. } => {
for (_name, child_hash) in children {
walk_tree(dag, child_hash, depth + 1, diff);
}
}
Node::BuildStep { input_hashes, .. } => {
for child in input_hashes {
walk_tree(dag, child, depth + 1, diff);
}
}
Node::File { .. } | Node::Package { .. } => {
}
}
}
pub fn render_tree(dag: &Dag, root: &ContentHash) {
walk_tree(dag, root, 0, None);
}
#[allow(dead_code)]
pub fn render_diff(dag: &Dag, old_root: &ContentHash, new_root: &ContentHash) {
let diff = hd_engine::dag_diff(dag, old_root, new_root);
walk_tree(dag, new_root, 0, Some(&diff));
let removed: Vec<ContentHash> = diff.removed.iter().copied().collect();
if !removed.is_empty() {
println!("{}{}--- removed nodes ---{}", DIM, RED, RESET);
for hash in &removed {
if let Some(node) = dag.get(hash) {
let label_str = node_label(node, hash);
println!("{}{} [REMOVED]{}", RED, label_str, RESET);
}
}
}
println!();
println!(
"{}+ {} added{} {}~ {} changed{} {}- {} removed{}",
GREEN, diff.added.len(), RESET,
YELLOW, diff.changed.len(), RESET,
RED, diff.removed.len(), RESET,
);
}