use std::path::Path;
use std::sync::Arc;
use serde::Serialize;
use void_core::support::events::VoidObserver;
use void_core::workspace::stage::{stage_paths, StageOptions};
use crate::context::{build_void_context, void_err_to_cli};
use crate::observer::ProgressObserver;
use crate::output::{run_command, CliError, CliOptions};
#[derive(Debug, Clone, Serialize)]
pub struct AddOutput {
pub staged: Vec<String>,
pub deleted: Vec<String>,
pub count: usize,
}
pub fn run(cwd: &Path, paths: Vec<String>, opts: &CliOptions) -> Result<(), CliError> {
let paths: Vec<String> = paths.into_iter().filter(|p| !p.trim().is_empty()).collect();
run_command("add", opts, |ctx| {
if paths.is_empty() {
return Err(CliError::invalid_args(
"No paths specified. Use `.` to add all files.",
));
}
let void_ctx = build_void_context(cwd)?;
let progress = if ctx.use_json() {
ProgressObserver::new_hidden()
} else {
ProgressObserver::new("Staging files...")
};
let observer = Arc::new(progress);
ctx.progress("Staging files...");
let stage_opts = StageOptions {
ctx: void_ctx,
patterns: paths,
observer: Some(observer.clone() as Arc<dyn VoidObserver>),
};
let result = stage_paths(stage_opts).map_err(void_err_to_cli)?;
observer.finish();
if !ctx.use_json() {
let total_staged = result.staged.len();
let total_deleted = result.deleted.len();
if total_staged == 0 && total_deleted == 0 {
ctx.info("Nothing to stage");
} else {
if total_staged > 0 {
ctx.info(format!("Staged {} file(s)", total_staged));
if ctx.is_verbose() {
for path in &result.staged {
ctx.info(format!(" {}", path));
}
}
}
if total_deleted > 0 {
ctx.info(format!("Deleted {} file(s)", total_deleted));
if ctx.is_verbose() {
for path in &result.deleted {
ctx.info(format!(" {}", path));
}
}
}
}
}
let count = result.staged.len() + result.deleted.len();
Ok(AddOutput {
staged: result.staged,
deleted: result.deleted,
count,
})
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_add_output_serialization() {
let output = AddOutput {
staged: vec!["src/main.rs".to_string(), "src/lib.rs".to_string()],
deleted: vec!["old_file.txt".to_string()],
count: 3,
};
let json = serde_json::to_string(&output).unwrap();
assert!(json.contains("\"staged\""));
assert!(json.contains("\"deleted\""));
assert!(json.contains("\"count\":3"));
assert!(json.contains("src/main.rs"));
assert!(json.contains("old_file.txt"));
}
#[test]
fn test_add_output_empty() {
let output = AddOutput {
staged: vec![],
deleted: vec![],
count: 0,
};
let json = serde_json::to_string(&output).unwrap();
assert!(json.contains("\"staged\":[]"));
assert!(json.contains("\"deleted\":[]"));
assert!(json.contains("\"count\":0"));
}
}