void-cli 0.0.4

CLI for void — anonymous encrypted source control
//! The `void mv` command implementation.
//!
//! Moves/renames files in the working tree and updates the index.

use std::path::Path;

use serde::Serialize;
use crate::context::{open_repo, void_err_to_cli};
use crate::output::{run_command, CliError, CliOptions};

/// JSON output structure for the mv command.
#[derive(Debug, Clone, Serialize)]
pub struct MvOutput {
    /// Original file path.
    pub from: String,
    /// New file path.
    pub to: String,
}

/// Run the mv command.
///
/// # Arguments
///
/// * `cwd` - Current working directory.
/// * `source` - Source path to move.
/// * `dest` - Destination path.
/// * `opts` - CLI options.
///
/// # Returns
///
/// Returns `Ok(())` on success, or a `CliError` on failure.
pub fn run(cwd: &Path, source: String, dest: String, opts: &CliOptions) -> Result<(), CliError> {
    run_command("mv", opts, |ctx| {
        // Validate source provided
        if source.trim().is_empty() {
            return Err(CliError::invalid_args("No source path specified."));
        }

        // Validate dest provided
        if dest.trim().is_empty() {
            return Err(CliError::invalid_args("No destination path specified."));
        }

        let repo = open_repo(cwd)?;

        ctx.progress("Moving file...");

        let result = repo.mv(&source, &dest).map_err(void_err_to_cli)?;

        // Print human-readable summary if not in JSON mode
        if !ctx.use_json() {
            ctx.info(format!("Renamed {} -> {}", result.from, result.to));
        }

        Ok(MvOutput {
            from: result.from,
            to: result.to,
        })
    })
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_mv_output_serialization() {
        let output = MvOutput {
            from: "old/path.rs".to_string(),
            to: "new/path.rs".to_string(),
        };

        let json = serde_json::to_string(&output).unwrap();
        assert!(json.contains("\"from\""));
        assert!(json.contains("\"to\""));
        assert!(json.contains("old/path.rs"));
        assert!(json.contains("new/path.rs"));
    }
}