wlk 0.1.0

File-centric, event-sourced version control system with implicit branching
Documentation
use anyhow::Result;
use colored::*;
use indicatif::{ProgressBar, ProgressStyle};
use sha2::{Digest, Sha256};
use std::fs;
use std::path::Path;
use std::time::Duration;

/// Check if color output should be used
pub fn use_color() -> bool {
    atty::is(atty::Stream::Stdout)
}

/// Create a spinner for long operations
pub fn create_spinner(message: &str) -> ProgressBar {
    let pb = ProgressBar::new_spinner();
    pb.set_style(
        ProgressStyle::default_spinner()
            .template("{spinner:.green} {msg}")
            .unwrap()
            .tick_chars("⠁⠂⠄⡀⢀⠠⠐⠈ "),
    );
    pb.set_message(message.to_string());
    pb.enable_steady_tick(Duration::from_millis(100));
    pb
}

/// Compute SHA256 hash of file content
pub fn hash_file_content(content: &str) -> String {
    let mut hasher = Sha256::new();
    hasher.update(content.as_bytes());
    format!("{:x}", hasher.finalize())
}

/// Check if file content has changed by comparing hashes
pub fn file_has_changed(path: &Path, expected_content: &str) -> Result<bool> {
    if !path.exists() {
        return Ok(true);
    }

    let current_content = fs::read_to_string(path)?;
    Ok(hash_file_content(&current_content) != hash_file_content(expected_content))
}

/// Format file size in human-readable format
pub fn format_size(bytes: u64) -> String {
    const UNITS: &[&str] = &["B", "KB", "MB", "GB", "TB"];
    let mut size = bytes as f64;
    let mut unit_idx = 0;

    while size >= 1024.0 && unit_idx < UNITS.len() - 1 {
        size /= 1024.0;
        unit_idx += 1;
    }

    if unit_idx == 0 {
        format!("{} {}", bytes, UNITS[0])
    } else {
        format!("{:.2} {}", size, UNITS[unit_idx])
    }
}

/// Print success message
pub fn success(message: &str) {
    if use_color() {
        println!("{} {}", "".green().bold(), message);
    } else {
        println!("{}", message);
    }
}

/// Print error message
pub fn error(message: &str) {
    if use_color() {
        eprintln!("{} {}", "".red().bold(), message);
    } else {
        eprintln!("{}", message);
    }
}

/// Print warning message
pub fn warning(message: &str) {
    if use_color() {
        println!("{} {}", "".yellow().bold(), message);
    } else {
        println!("{}", message);
    }
}

/// Print info message
pub fn info(message: &str) {
    if use_color() {
        println!("{} {}", "".blue().bold(), message);
    } else {
        println!("{}", message);
    }
}

/// Suggest a command to the user
pub fn suggest_command(message: &str, command: &str) {
    if use_color() {
        println!("{}", message);
        println!("  Try: {}", command.cyan().bold());
    } else {
        println!("{}", message);
        println!("  Try: {}", command);
    }
}

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

    #[test]
    fn test_hash_file_content() {
        let content = "test content";
        let hash1 = hash_file_content(content);
        let hash2 = hash_file_content(content);
        assert_eq!(hash1, hash2);

        let different = hash_file_content("different content");
        assert_ne!(hash1, different);
    }

    #[test]
    fn test_format_size() {
        assert_eq!(format_size(100), "100 B");
        assert_eq!(format_size(1024), "1.00 KB");
        assert_eq!(format_size(1024 * 1024), "1.00 MB");
        assert_eq!(format_size(1024 * 1024 * 1024), "1.00 GB");
    }
}