use std::path::Path;
use owo_colors::OwoColorize;
use serde_json::Value;
use crate::client::CellosClient;
use crate::exit::{CtlError, CtlResult};
use crate::model::Formation;
pub async fn run(client: &CellosClient, path: &Path) -> CtlResult<()> {
let yaml = std::fs::read_to_string(path)
.map_err(|e| CtlError::usage(format!("read {}: {e}", path.display())))?;
let local: Value = serde_yaml::from_str(&yaml)?;
let local_name = extract_name(&local).ok_or_else(|| {
CtlError::validation(format!(
"{}: cannot find formation name (`name` or `metadata.name`)",
path.display()
))
})?;
let remote: Formation = match client
.get_json(&format!("/v1/formations/{}", urlencode(&local_name)))
.await
{
Ok(f) => f,
Err(e) if e.status == Some(404) => {
println!("(formation/{local_name} not found on server — would create)");
for line in serde_yaml::to_string(&local).unwrap_or_default().lines() {
println!("{}", format!("+ {line}").green());
}
return Ok(());
}
Err(e) => return Err(e),
};
let local_pretty = serde_json::to_string_pretty(&local).unwrap_or_default();
let remote_pretty = serde_json::to_string_pretty(&remote).unwrap_or_default();
print_unified_diff(&remote_pretty, &local_pretty);
Ok(())
}
fn extract_name(v: &Value) -> Option<String> {
if let Some(n) = v.get("name").and_then(|x| x.as_str()) {
return Some(n.to_string());
}
v.get("metadata")
.and_then(|m| m.get("name"))
.and_then(|n| n.as_str())
.map(|s| s.to_string())
}
fn print_unified_diff(a: &str, b: &str) {
println!("--- server (current)");
println!("+++ local (proposed)");
println!(
"# (cellctl diff is a shallow line comparison; for fine-grained review use `diff -u`)"
);
let a_lines: Vec<&str> = a.lines().collect();
let b_lines: Vec<&str> = b.lines().collect();
let n = a_lines.len().max(b_lines.len());
for i in 0..n {
let al = a_lines.get(i).copied();
let bl = b_lines.get(i).copied();
match (al, bl) {
(Some(x), Some(y)) if x == y => println!(" {x}"),
(Some(x), Some(y)) => {
println!("{}", format!("- {x}").red());
println!("{}", format!("+ {y}").green());
}
(Some(x), None) => println!("{}", format!("- {x}").red()),
(None, Some(y)) => println!("{}", format!("+ {y}").green()),
(None, None) => {}
}
}
}
fn urlencode(s: &str) -> String {
percent_encoding::utf8_percent_encode(s, percent_encoding::NON_ALPHANUMERIC).collect()
}