Exemples d'Opérations sur Fichiers Web Rust

Exemples d'opérations sur fichiers Web Rust incluant lecture/écriture de fichiers texte, copier/déplacer, parcours de répertoires et validation de fichiers

💻 Lecture/Écriture de Fichiers Texte rust

🟢 simple ⭐⭐

Lire et écrire des fichiers texte avec diverses options de codification en utilisant Rust std::fs et std::io

⏱️ 20 min 🏷️ rust, web, file operations
Prerequisites: Basic Rust, File I/O, std::fs
// Web Rust Text File Read/Write Examples
// Using Rust std::fs and std::io for file operations

use std::fs::{self, File, OpenOptions};
use std::io::{self, BufRead, BufReader, BufWriter, Read, Write};
use std::path::{Path, PathBuf};
use std::collections::HashMap;

// 1. Reading Text Files

/// Read entire text file content
fn read_text_file(file_path: &str) -> io::Result<String> {
    fs::read_to_string(file_path)
}

/// Read text file with specific encoding (UTF-8 validation)
fn read_text_file_with_encoding(file_path: &str) -> io::Result<String> {
    let content = fs::read_to_string(file_path)?;
    // Rust strings are UTF-8 by default, invalid sequences will cause error
    Ok(content)
}

/// Read text file line by line
fn read_text_file_line_by_line(file_path: &str) -> io::Result<Vec<String>> {
    let file = File::open(file_path)?;
    let reader = BufReader::new(file);

    let mut lines = Vec::new();
    for line in reader.lines() {
        lines.push(line?);
    }

    Ok(lines)
}

/// Read text file in chunks
fn read_text_file_in_chunks(file_path: &str, chunk_size: usize) -> io::Result<Vec<String>> {
    let file = File::open(file_path)?;
    let mut reader = BufReader::new(file);

    let mut chunks = Vec::new();
    loop {
        let mut buffer = vec![0u8; chunk_size];
        let n = reader.read(&mut buffer)?;

        if n == 0 {
            break;
        }

        buffer.truncate(n);
        chunks.push(String::from_utf8_lossy(&buffer).to_string());
    }

    Ok(chunks)
}

/// Read text file using buffered I/O
fn read_text_file_with_buffer(file_path: &str) -> io::Result<String> {
    let file = File::open(file_path)?;
    let mut reader = BufReader::new(file);

    let mut content = String::new();
    reader.read_to_string(&mut content)?;

    Ok(content)
}

// 2. Writing Text Files

/// Write text content to a file
fn write_text_file(file_path: &str, content: &str) -> io::Result<()> {
    fs::write(file_path, content)
}

/// Write a list of lines to a text file
fn write_text_lines(file_path: &str, lines: &[String]) -> io::Result<()> {
    let file = File::create(file_path)?;
    let mut writer = BufWriter::new(file);

    for line in lines {
        writeln!(writer, "{}", line)?;
    }

    writer.flush()
}

/// Write text file with permissions (Unix-like systems)
#[cfg(unix)]
fn write_text_file_with_permissions(file_path: &str, content: &str, permissions: u32) -> io::Result<()> {
    use std::os::unix::fs::PermissionsExt;

    fs::write(file_path, content)?;

    let mut perms = fs::metadata(file_path)?.permissions();
    perms.set_mode(permissions);
    fs::set_permissions(file_path, perms)?;

    Ok(())
}

/// Append text content to an existing file
fn append_to_text_file(file_path: &str, content: &str) -> io::Result<()> {
    let mut file = OpenOptions::new()
        .append(true)
        .create(true)
        .open(file_path)?;

    writeln!(file, "{}", content)
}

/// Write text file atomically (using temp file)
fn write_text_file_atomic(file_path: &str, content: &str) -> io::Result<()> {
    let temp_path = format!("{}.tmp", file_path);

    // Write to temporary file
    fs::write(&temp_path, content)?;

    // Rename atomically
    fs::rename(&temp_path, file_path)?;

    Ok(())
}

// 3. File Info Helper

/// Get file size in bytes
fn get_file_size(file_path: &str) -> io::Result<u64> {
    let metadata = fs::metadata(file_path)?;
    Ok(metadata.len())
}

/// Get file extension
fn get_file_extension(file_path: &str) -> &str {
    Path::new(file_path)
        .extension()
        .and_then(|s| s.to_str())
        .unwrap_or("")
}

/// Get file name without extension
fn get_file_name_without_extension(file_path: &str) -> String {
    Path::new(file_path)
        .file_stem()
        .and_then(|s| s.to_str())
        .unwrap_or("")
        .to_string()
}

/// Format file size in human-readable format
fn format_file_size(bytes: u64) -> String {
    const KB: u64 = 1024;
    const MB: u64 = KB * 1024;
    const GB: u64 = MB * 1024;
    const TB: u64 = GB * 1024;

    if bytes >= TB {
        format!("{:.2} TB", bytes as f64 / TB as f64)
    } else if bytes >= GB {
        format!("{:.2} GB", bytes as f64 / GB as f64)
    } else if bytes >= MB {
        format!("{:.2} MB", bytes as f64 / MB as f64)
    } else if bytes >= KB {
        format!("{:.2} KB", bytes as f64 / KB as f64)
    } else {
        format!("{} B", bytes)
    }
}

/// Get detailed information about a file
fn get_file_details(file_path: &str) -> io::Result<HashMap<String, String>> {
    let metadata = fs::metadata(file_path)?;
    let path = Path::new(file_path);
    let absolute_path = fs::canonicalize(file_path)?;

    let mut details = HashMap::new();
    details.insert("name".to_string(), path.file_name()
        .and_then(|s| s.to_str())
        .unwrap_or("")
        .to_string());
    details.insert("size".to_string(), metadata.len().to_string());
    details.insert("readable_size".to_string(), format_file_size(metadata.len()));
    details.insert("extension".to_string(), get_file_extension(file_path).to_string());
    details.insert("modified_time".to_string(), format!("{:?}", metadata.modified()));
    details.insert("is_file".to_string(), metadata.is_file().to_string());
    details.insert("is_dir".to_string(), metadata.is_dir().to_string());
    details.insert("absolute_path".to_string(), absolute_path.to_str()
        .unwrap_or("")
        .to_string());

    Ok(details)
}

// 4. File Validation

/// Check if a file exists
fn file_exists(file_path: &str) -> bool {
    Path::new(file_path).exists()
}

/// Check if path is a file
fn is_file(file_path: &str) -> bool {
    Path::new(file_path).is_file()
}

/// Check if path is a directory
fn is_directory(dir_path: &str) -> bool {
    Path::new(dir_path).is_dir()
}

/// Validate file properties
fn validate_file(file_path: &str, max_size_mb: u64) -> io::Result<HashMap<String, serde_json::Value>> {
    let mut result = HashMap::new();
    result.insert("valid".to_string(), serde_json::Value::Bool(true));
    result.insert("errors".to_string(), serde_json::Value::Array(vec![]));

    if !file_exists(file_path) {
        result.insert("valid".to_string(), serde_json::Value::Bool(false));
        result.get_mut("errors").unwrap()
            .as_array_mut().unwrap()
            .push(serde_json::json!("File does not exist"));
        return Ok(result);
    }

    if !is_file(file_path) {
        result.insert("valid".to_string(), serde_json::Value::Bool(false));
        result.get_mut("errors").unwrap()
            .as_array_mut().unwrap()
            .push(serde_json::json!("Path is not a file"));
        return Ok(result);
    }

    if max_size_mb > 0 {
        let size = get_file_size(file_path)?;
        let size_mb = size / (1024 * 1024);

        if size_mb > max_size_mb {
            result.insert("valid".to_string(), serde_json::Value::Bool(false));
            result.get_mut("errors").unwrap()
                .as_array_mut().unwrap()
                .push(serde_json::json!(format!(
                    "File size ({:.2}MB) exceeds limit ({}MB)",
                    size_mb, max_size_mb
                )));
        }
    }

    Ok(result)
}

// 5. Batch File Operations

/// Read multiple text files
fn read_multiple_files(file_paths: &[String]) -> HashMap<String, String> {
    let mut results = HashMap::new();

    for file_path in file_paths {
        if let Ok(content) = read_text_file(file_path) {
            results.insert(file_path.clone(), content);
        }
    }

    results
}

/// Write to multiple files
fn batch_write_files(file_data_map: &HashMap<String, String>) -> (Vec<String>, Vec<String>) {
    let mut successful = Vec::new();
    let mut failed = Vec::new();

    for (file_path, content) in file_data_map {
        match write_text_file(file_path, content) {
            Ok(_) => successful.push(file_path.clone()),
            Err(_) => failed.push(file_path.clone()),
        }
    }

    (successful, failed)
}

/// Search for text in multiple files
fn search_in_files(
    file_paths: &[String],
    search_text: &str,
    case_sensitive: bool,
) -> HashMap<String, Vec<usize>> {
    let mut results = HashMap::new();

    for file_path in file_paths {
        if let Ok(lines) = read_text_file_line_by_line(file_path) {
            let mut matching_lines = Vec::new();

            for (i, line) in lines.iter().enumerate() {
                let search_line = if case_sensitive {
                    line.clone()
                } else {
                    line.to_lowercase()
                };

                let search_term = if case_sensitive {
                    search_text.to_string()
                } else {
                    search_text.to_lowercase()
                };

                if search_line.contains(&search_term) {
                    matching_lines.push(i + 1);
                }
            }

            if !matching_lines.is_empty() {
                results.insert(file_path.clone(), matching_lines);
            }
        }
    }

    results
}

// 6. File Encoding Utilities

/// Detect file encoding (simplified)
fn detect_file_encoding(file_path: &str) -> io::Result<String> {
    let content = fs::read(file_path)?;

    // Check for UTF-8 BOM
    if content.len() >= 3 && content[0] == 0xEF && content[1] == 0xBB && content[2] == 0xBF {
        return Ok("UTF-8 with BOM".to_string());
    }

    // Try to parse as UTF-8
    if std::str::from_utf8(&content).is_ok() {
        return Ok("UTF-8".to_string());
    }

    Ok("ASCII/Unknown".to_string())
}

/// Convert file encoding (simplified - currently no-op)
fn convert_file_encoding(
    input_path: &str,
    output_path: &str,
    _from_encoding: &str,
    _to_encoding: &str,
) -> io::Result<()> {
    let content = fs::read_to_string(input_path)?;
    fs::write(output_path, content)?;
    Ok(())
}

// Usage Examples
fn main() -> io::Result<()> {
    println!("=== Web Rust Text File Read/Write Examples ===\n");

    // 1. Read text file
    println!("--- 1. Read Text File ---");
    match read_text_file("example.txt") {
        Ok(content) => println!("Content length: {} characters", content.len()),
        Err(e) => println!("Error: {}", e),
    }

    // 2. Read line by line
    println!("\n--- 2. Read Line by Line ---");
    match read_text_file_line_by_line("example.txt") {
        Ok(lines) => {
            println!("Number of lines: {}", lines.len());
            let display_count = std::cmp::min(3, lines.len());
            println!("First {} lines: {:?}", display_count, &lines[..display_count]);
        }
        Err(e) => println!("Error: {}", e),
    }

    // 3. Write text file
    println!("\n--- 3. Write Text File ---");
    match write_text_file("output.txt", "Hello, World!\nThis is a new file.") {
        Ok(_) => println!("File written successfully"),
        Err(e) => println!("Error: {}", e),
    }

    // 4. Write lines
    println!("\n--- 4. Write Lines ---");
    let lines_to_write = vec!["Line 1".to_string(), "Line 2".to_string(), "Line 3".to_string()];
    match write_text_lines("lines.txt", &lines_to_write) {
        Ok(_) => println!("Lines file written successfully"),
        Err(e) => println!("Error: {}", e),
    }

    // 5. Get file details
    println!("\n--- 5. File Details ---");
    match get_file_details("example.txt") {
        Ok(details) => {
            println!("Name: {}", details.get("name").unwrap_or(&"?".to_string()));
            println!("Size: {}", details.get("readable_size").unwrap_or(&"?".to_string()));
            println!("Extension: {}", details.get("extension").unwrap_or(&"?".to_string()));
        }
        Err(e) => println!("Error: {}", e),
    }

    // 6. File validation
    println!("\n--- 6. File Validation ---");
    match validate_file("example.txt", 1) {
        Ok(validation) => {
            println!("Valid: {}", validation.get("valid").unwrap_or(&serde_json::Value::Bool(false)));
        }
        Err(e) => println!("Error: {}", e),
    }

    println!("\n=== All Text File Operations Completed ===");
    Ok(())
}

💻 Copier/Déplacer des Fichiers rust

🟡 intermediate ⭐⭐⭐

Copier et déplacer des fichiers avec suivi de progression et gestion des erreurs

⏱️ 25 min 🏷️ rust, web, file operations
Prerequisites: Intermediate Rust, std::io, std::fs
// Web Rust File Copy/Move Examples
// File copy and move operations with progress tracking

use std::fs::{self, File};
use std::io::{self, Read, Write, BufReader, BufWriter};
use std::path::{Path, PathBuf};

// 1. File Copy Operations

/// Copy file from source to destination
fn copy_file(source_path: &str, destination_path: &str) -> io::Result<()> {
    // Ensure destination directory exists
    if let Some(parent) = Path::new(destination_path).parent() {
        fs::create_dir_all(parent)?;
    }

    fs::copy(source_path, destination_path)?;
    Ok(())
}

/// Copy file using a buffer
fn copy_file_with_buffer(
    source_path: &str,
    destination_path: &str,
    buffer_size: usize,
) -> io::Result<()> {
    let source_file = File::open(source_path)?;
    let mut reader = BufReader::with_capacity(buffer_size, source_file);

    // Ensure destination directory exists
    if let Some(parent) = Path::new(destination_path).parent() {
        fs::create_dir_all(parent)?;
    }

    let destination_file = File::create(destination_path)?;
    let mut writer = BufWriter::with_capacity(buffer_size, destination_file);

    let mut buffer = vec![0u8; buffer_size];
    loop {
        let n = reader.read(&mut buffer)?;
        if n == 0 {
            break;
        }
        writer.write_all(&buffer[..n])?;
    }

    writer.flush()?;
    Ok(())
}

/// Copy file with progress tracking
type ProgressCallback = fn(written: u64, total: u64);

fn copy_file_with_progress(
    source_path: &str,
    destination_path: &str,
    progress_callback: Option<ProgressCallback>,
) -> io::Result<()> {
    let source_file = File::open(source_path)?;
    let total_size = source_file.metadata()?.len();
    let mut reader = BufReader::new(source_file);

    // Ensure destination directory exists
    if let Some(parent) = Path::new(destination_path).parent() {
        fs::create_dir_all(parent)?;
    }

    let destination_file = File::create(destination_path)?;
    let mut writer = BufWriter::new(destination_file);

    let mut written: u64 = 0;
    const BUFFER_SIZE: usize = 32 * 1024; // 32KB
    let mut buffer = vec![0u8; BUFFER_SIZE];

    loop {
        let n = reader.read(&mut buffer)?;
        if n == 0 {
            break;
        }

        writer.write_all(&buffer[..n])?;
        written += n as u64;

        if let Some(callback) = progress_callback {
            callback(written, total_size);
        }
    }

    writer.flush()?;

    // Copy permissions
    let source_metadata = fs::metadata(source_path)?;
    let destination_metadata = destination_file.metadata()?;
    let mut permissions = destination_metadata.permissions();
    permissions.set_readonly(source_metadata.permissions().readonly());
    fs::set_permissions(destination_path, permissions)?;

    Ok(())
}

// 2. File Move Operations

/// Move file from source to destination
fn move_file(source_path: &str, destination_path: &str) -> io::Result<()> {
    // Try rename first (fastest if on same filesystem)
    if fs::rename(source_path, destination_path).is_ok() {
        return Ok(());
    }

    // If rename fails, copy and delete
    copy_file(source_path, destination_path)?;
    fs::remove_file(source_path)?;
    Ok(())
}

/// Move file atomically
fn move_file_atomic(source_path: &str, destination_path: &str) -> io::Result<()> {
    // Ensure destination directory exists
    if let Some(parent) = Path::new(destination_path).parent() {
        fs::create_dir_all(parent)?;
    }

    fs::rename(source_path, destination_path)
}

// 3. Directory Operations

/// Copy entire directory recursively
fn copy_directory(source_dir: &str, destination_dir: &str) -> io::Result<()> {
    let source_metadata = fs::metadata(source_dir)?;

    // Create destination directory
    fs::create_dir_all(destination_dir)?;
    fs::set_permissions(destination_dir, source_metadata.permissions())?;

    // Read source directory
    let entries = fs::read_dir(source_dir)?;

    for entry in entries {
        let entry = entry?;
        let source_path = entry.path();
        let file_name = source_path.file_name()
            .and_then(|n| n.to_str())
            .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "Invalid filename"))?;
        let dest_path = Path::new(destination_dir).join(file_name);

        if entry.path().is_dir() {
            // Recursively copy subdirectory
            copy_directory(source_path.to_str()
                .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "Invalid path"))?,
                dest_path.to_str()
                .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "Invalid path"))?)?;
        } else {
            // Copy file
            copy_file(source_path.to_str()
                .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "Invalid path"))?,
                dest_path.to_str()
                .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "Invalid path"))?)?;
        }
    }

    Ok(())
}

/// Copy directory with progress tracking
fn copy_directory_with_progress(
    source_dir: &str,
    destination_dir: &str,
    progress_callback: Option<fn(current: usize, total: usize, filename: &str)>,
) -> io::Result<()> {
    // Count total files first
    let mut total_files = 0;
    count_files(source_dir, &mut total_files)?;

    let mut copied_files = 0;

    // Copy files
    copy_directory_recursive(source_dir, destination_dir, &mut copied_files, total_files, progress_callback)
}

fn count_files(dir: &str, count: &mut usize) -> io::Result<()> {
    let entries = fs::read_dir(dir)?;
    for entry in entries {
        let entry = entry?;
        if entry.path().is_dir() {
            let path = entry.path().to_str()
                .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "Invalid path"))?;
            count_files(path, count)?;
        } else {
            *count += 1;
        }
    }
    Ok(())
}

fn copy_directory_recursive(
    source_dir: &str,
    destination_dir: &str,
    copied_files: &mut usize,
    total_files: usize,
    progress_callback: Option<fn(usize, usize, &str)>,
) -> io::Result<()> {
    let source_metadata = fs::metadata(source_dir)?;
    fs::create_dir_all(destination_dir)?;
    fs::set_permissions(destination_dir, source_metadata.permissions())?;

    let entries = fs::read_dir(source_dir)?;

    for entry in entries {
        let entry = entry?;
        let source_path = entry.path();
        let file_name = source_path.file_name()
            .and_then(|n| n.to_str())
            .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "Invalid filename"))?;
        let dest_path = Path::new(destination_dir).join(file_name);

        if source_path.is_dir() {
            copy_directory_recursive(
                source_path.to_str()
                    .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "Invalid path"))?,
                dest_path.to_str()
                    .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "Invalid path"))?,
                copied_files,
                total_files,
                progress_callback,
            )?;
        } else {
            copy_file(
                source_path.to_str()
                    .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "Invalid path"))?,
                dest_path.to_str()
                    .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "Invalid path"))?,
            )?;

            *copied_files += 1;
            if let Some(callback) = progress_callback {
                callback(*copied_files, total_files, file_name);
            }
        }
    }

    Ok(())
}

/// Move directory from source to destination
fn move_directory(source_dir: &str, destination_dir: &str) -> io::Result<()> {
    // Try atomic rename first
    if fs::rename(source_dir, destination_dir).is_ok() {
        return Ok(());
    }

    // If rename fails, copy and delete
    copy_directory(source_dir, destination_dir)?;
    fs::remove_dir_all(source_dir)?;
    Ok(())
}

// 4. Batch Operations

/// Rename files in a directory using a pattern
fn batch_rename_files<F>(directory: &str, rename_func: F) -> io::Result<(Vec<String>, Vec<String>)>
where
    F: Fn(&str) -> String,
{
    let mut renamed = Vec::new();
    let mut failed = Vec::new();

    let entries = fs::read_dir(directory)?;

    for entry in entries {
        let entry = entry?;
        if entry.path().is_dir() {
            continue;
        }

        let old_name = entry.file_name().to_string_lossy().to_string();
        let old_path = Path::new(directory).join(&old_name);
        let new_name = rename_func(&old_name);
        let new_path = Path::new(directory).join(&new_name);

        match fs::rename(&old_path, &new_path) {
            Ok(_) => renamed.push(format!("{} -> {}", old_path.display(), new_path.display())),
            Err(_) => failed.push(old_path.to_string_lossy().to_string()),
        }
    }

    Ok((renamed, failed))
}

// 5. Safe File Operations

/// Safe copy with validation
fn safe_copy(source_path: &str, destination_path: &str) -> io::Result<()> {
    // Validate source
    if !Path::new(source_path).exists() {
        return Err(io::Error::new(io::ErrorKind::NotFound, "Source file does not exist"));
    }

    // Check if destination exists
    if Path::new(destination_path).exists() {
        return Err(io::Error::new(io::ErrorKind::AlreadyExists, "Destination file already exists"));
    }

    copy_file(source_path, destination_path)
}

/// Safe move with validation
fn safe_move(source_path: &str, destination_path: &str) -> io::Result<()> {
    // Validate source
    if !Path::new(source_path).exists() {
        return Err(io::Error::new(io::ErrorKind::NotFound, "Source file does not exist"));
    }

    // Check if destination exists
    if Path::new(destination_path).exists() {
        return Err(io::Error::new(io::ErrorKind::AlreadyExists, "Destination file already exists"));
    }

    // Ensure destination directory exists
    if let Some(parent) = Path::new(destination_path).parent() {
        fs::create_dir_all(parent)?;
    }

    move_file(source_path, destination_path)
}

// 6. Backup and Restore

/// Create backup of files/directories
fn create_backup(source_paths: &[String], backup_dir: &str, use_timestamp: bool) -> io::Result<String> {
    let backup_path = if use_timestamp {
        let timestamp = chrono::Local::now().format("%Y%m%d_%H%M%S");
        Path::new(backup_dir).join(format!("backup_{}", timestamp))
    } else {
        Path::new(backup_dir).join("backup")
    };

    fs::create_dir_all(&backup_path)?;

    for source in source_paths {
        let dest = backup_path.join(
            Path::new(source)
                .file_name()
                .and_then(|n| n.to_str())
                .unwrap_or("unknown")
        );

        let metadata = fs::metadata(source)?;
        if metadata.is_dir() {
            copy_directory(source, dest.to_str()
                .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "Invalid path"))?)?;
        } else {
            copy_file(source, dest.to_str()
                .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "Invalid path"))?)?;
        }
    }

    Ok(backup_path.to_str()
        .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "Invalid path"))?
        .to_string())
}

/// Restore files from backup directory
fn restore_from_backup(backup_dir: &str, target_dir: &str) -> io::Result<()> {
    copy_directory(backup_dir, target_dir)
}

// Usage Examples
fn main() -> io::Result<()> {
    println!("=== Web Rust File Copy/Move Examples ===\n");

    // 1. Copy file
    println!("--- 1. Copy File ---");
    match copy_file("source.txt", "copy.txt") {
        Ok(_) => println!("File copied successfully"),
        Err(e) => println!("Error: {}", e),
    }

    // 2. Copy with progress
    println!("\n--- 2. Copy with Progress ---");
    match copy_file_with_progress("source.txt", "copy2.txt", Some(|written, total| {
        println!("Progress: {}/{} bytes ({:.1}%)", written, total, (written as f64 / total as f64) * 100.0);
    })) {
        Ok(_) => println!("File copied with progress successfully"),
        Err(e) => println!("Error: {}", e),
    }

    // 3. Move file
    println!("\n--- 3. Move File ---");
    match move_file("old_name.txt", "new_name.txt") {
        Ok(_) => println!("File moved successfully"),
        Err(e) => println!("Error: {}", e),
    }

    // 4. Batch rename
    println!("\n--- 4. Batch Rename ---");
    match batch_rename_files(".", |name| {
        name.replace(".txt", ".bak")
    }) {
        Ok((renamed, failed)) => {
            println!("Renamed {} files", renamed.len());
            println!("Failed {} files", failed.len());
        }
        Err(e) => println!("Error: {}", e),
    }

    // 5. Backup
    println!("\n--- 5. Backup ---");
    match create_backup(&["important_file.txt".to_string(), "important_dir".to_string()], "backups", true) {
        Ok(backup_path) => println!("Backup created at: {}", backup_path),
        Err(e) => println!("Error: {}", e),
    }

    println!("\n=== All File Copy/Move Examples Completed ===");
    Ok(())
}

💻 Parcours de Répertoire rust

🔴 complex ⭐⭐⭐⭐

Parcourir les répertoires en utilisant WalkDir, les motifs glob et l'exploration de fichiers

⏱️ 30 min 🏷️ rust, web, file operations, directory
Prerequisites: Advanced Rust, std::fs, std::path
// Web Rust Directory Traversal Examples
// Using walkdir and glob patterns for directory operations

use std::collections::{HashMap, HashSet};
use std::fs::{self, File};
use std::io;
use std::path::{Path, PathBuf};
use std::ffi::OsStr;

// 1. Directory Listing

/// Represents contents of a directory
#[derive(Debug)]
struct DirectoryContents {
    directory: String,
    files: Vec<String>,
    directories: Vec<String>,
}

/// List contents of a directory (non-recursive)
fn list_directory_contents(directory: &str, recursive: bool) -> io::Result<DirectoryContents> {
    let mut result = DirectoryContents {
        directory: directory.to_string(),
        files: Vec::new(),
        directories: Vec::new(),
    };

    if recursive {
        list_directory_recursive(directory, &mut result)?;
    } else {
        let entries = fs::read_dir(directory)?;
        for entry in entries {
            let entry = entry?;
            let path = entry.path();
            let path_str = path.to_string_lossy().to_string();

            if path.is_dir() {
                result.directories.push(path_str);
            } else {
                result.files.push(path_str);
            }
        }
    }

    Ok(result)
}

fn list_directory_recursive(directory: &str, result: &mut DirectoryContents) -> io::Result<()> {
    let entries = fs::read_dir(directory)?;
    for entry in entries {
        let entry = entry?;
        let path = entry.path();
        let path_str = path.to_string_lossy().to_string();

        if path.is_dir() && path_str != result.directory {
            result.directories.push(path_str.clone());
            list_directory_recursive(&path_str, result)?;
        } else if !path.is_dir() {
            result.files.push(path_str);
        }
    }
    Ok(())
}

/// TreeNode represents a node in the directory tree
#[derive(Debug)]
struct TreeNode {
    name: String,
    path: String,
    is_dir: bool,
    children: Vec<TreeNode>,
}

/// List directory as a tree structure
fn list_directory_tree(directory: &str, max_depth: usize) -> io::Result<TreeNode> {
    build_tree(directory, 0, max_depth)
}

fn build_tree(path: &str, depth: usize, max_depth: usize) -> io::Result<TreeNode> {
    let path_obj = Path::new(path);
    let metadata = fs::metadata(path)?;

    let mut node = TreeNode {
        name: path_obj.file_name()
            .and_then(|n| n.to_str())
            .unwrap_or("")
            .to_string(),
        path: path.to_string(),
        is_dir: metadata.is_dir(),
        children: Vec::new(),
    };

    if metadata.is_dir() && (max_depth == 0 || depth < max_depth) {
        let mut entries: Vec<_> = fs::read_dir(path)?
            .filter_map(|e| e.ok())
            .collect();

        // Sort by name
        entries.sort_by_key(|e| e.file_name());

        for entry in entries {
            let child_path = entry.path().to_string_lossy().to_string();
            match build_tree(&child_path, depth + 1, max_depth) {
                Ok(child) => node.children.push(child),
                Err(_) => continue,
            }
        }
    }

    Ok(node)
}

/// Print directory tree structure
fn print_directory_tree(tree: &TreeNode, indent: usize) {
    let prefix = "  ".repeat(indent);
    let icon = if tree.is_dir { "📁" } else { "📄" };
    println!("{}{} {}", prefix, icon, tree.name);

    for child in &tree.children {
        print_directory_tree(child, indent + 1);
    }
}

// 2. File Pattern Matching

/// Find files matching a pattern
fn find_files_by_pattern(directory: &str, pattern: &str) -> io::Result<Vec<String>> {
    let mut matches = Vec::new();

    fn walk(dir: &Path, pattern: &str, matches: &mut Vec<String>) -> io::Result<()> {
        let entries = fs::read_dir(dir)?;
        for entry in entries {
            let entry = entry?;
            let path = entry.path();

            if path.is_dir() {
                walk(&path, pattern, matches)?;
            } else {
                let file_name = path.file_name()
                    .and_then(|n| n.to_str())
                    .unwrap_or("");

                if pattern_matches(pattern, file_name) {
                    matches.push(path.to_string_lossy().to_string());
                }
            }
        }
        Ok(())
    }

    walk(Path::new(directory), pattern, &mut matches)?;
    Ok(matches)
}

/// Simple pattern matching (supports * and ?)
fn pattern_matches(pattern: &str, text: &str) -> bool {
    let pattern_chars: Vec<char> = pattern.chars().collect();
    let text_chars: Vec<char> = text.chars().collect();

    fn match_helper(p: &[char], t: &[char]) -> bool {
        if p.is_empty() {
            return t.is_empty();
        }

        if p[0] == '*' {
            // Try matching * with 0 or more characters
            for i in 0..=t.len() {
                if match_helper(&p[1..], &t[i..]) {
                    return true;
                }
            }
            return false;
        }

        if t.is_empty() {
            return false;
        }

        if p[0] == '?' || p[0] == t[0] {
            return match_helper(&p[1..], &t[1..]);
        }

        false
    }

    match_helper(&pattern_chars, &text_chars)
}

/// Find files with specific extension
fn find_files_by_extension(directory: &str, extension: &str) -> io::Result<Vec<String>> {
    let ext = if extension.starts_with('.') {
        extension.to_string()
    } else {
        format!(".{}", extension)
    };
    find_files_by_pattern(directory, &format!("*{}", ext))
}

/// Find files by name pattern
fn find_files_by_name(directory: &str, name_pattern: &str) -> io::Result<Vec<String>> {
    find_files_by_pattern(directory, name_pattern)
}

// 3. Directory Statistics

/// Represents statistics about a directory
#[derive(Debug)]
struct DirectoryStats {
    total_files: u64,
    total_directories: u64,
    total_size: u64,
    extension_counts: HashMap<String, usize>,
    largest_files: Vec<FileEntry>,
}

/// Represents a file entry with size
#[derive(Debug, Clone)]
struct FileEntry {
    path: String,
    size: u64,
}

/// Get statistics about a directory
fn get_directory_statistics(directory: &str) -> io::Result<DirectoryStats> {
    let mut stats = DirectoryStats {
        total_files: 0,
        total_directories: 0,
        total_size: 0,
        extension_counts: HashMap::new(),
        largest_files: Vec::new(),
    };

    fn walk(dir: &Path, stats: &mut DirectoryStats) -> io::Result<()> {
        let entries = fs::read_dir(dir)?;
        for entry in entries {
            let entry = entry?;
            let path = entry.path();
            let metadata = entry.metadata()?;

            if metadata.is_dir() {
                stats.total_directories += 1;
                walk(&path, stats)?;
            } else {
                stats.total_files += 1;
                stats.total_size += metadata.len();

                // Track extensions
                if let Some(ext) = path.extension() {
                    let ext_str = ext.to_string_lossy().to_lowercase();
                    *stats.extension_counts.entry(ext_str).or_insert(0) += 1;
                }

                // Track largest files
                stats.largest_files.push(FileEntry {
                    path: path.to_string_lossy().to_string(),
                    size: metadata.len(),
                });
            }
        }
        Ok(())
    }

    walk(Path::new(directory), &mut stats)?;

    // Sort largest files and keep top 10
    stats.largest_files.sort_by(|a, b| b.size.cmp(&a.size));
    stats.largest_files.truncate(10);

    Ok(stats)
}

// 4. File Search in Directory

/// Search for text in files within a directory
fn search_in_directory(
    directory: &str,
    search_text: &str,
    file_pattern: &str,
) -> io::Result<HashMap<String, Vec<usize>>> {
    let mut results = HashMap::new();

    let files = find_files_by_pattern(directory, file_pattern)?;
    for file_path in files {
        match search_in_file(&file_path, search_text) {
            Ok(matching_lines) if !matching_lines.is_empty() => {
                results.insert(file_path, matching_lines);
            }
            _ => {}
        }
    }

    Ok(results)
}

fn search_in_file(file_path: &str, search_text: &str) -> io::Result<Vec<usize>> {
    let content = fs::read_to_string(file_path)?;
    let lines: Vec<&str> = content.lines().collect();

    let mut matching_lines = Vec::new();
    for (i, line) in lines.iter().enumerate() {
        if line.contains(search_text) {
            matching_lines.push(i + 1);
        }
    }

    Ok(matching_lines)
}

// 5. Directory Comparison

/// Represents comparison results
#[derive(Debug)]
struct DirectoryComparison {
    only_in_dir1: Vec<String>,
    only_in_dir2: Vec<String>,
    different: Vec<String>,
    same: Vec<String>,
}

/// Compare two directories and find differences
fn compare_directories(dir1: &str, dir2: &str) -> io::Result<DirectoryComparison> {
    let mut result = DirectoryComparison {
        only_in_dir1: Vec::new(),
        only_in_dir2: Vec::new(),
        different: Vec::new(),
        same: Vec::new(),
    };

    let mut files1: HashMap<String, u64> = HashMap::new();
    let mut files2: HashMap<String, u64> = HashMap::new();

    // Walk dir1
    walk_files(Path::new(dir1), dir1, &mut files1)?;

    // Walk dir2
    walk_files(Path::new(dir2), dir2, &mut files2)?;

    // Find files only in dir1
    for path in files1.keys() {
        if !files2.contains_key(path) {
            result.only_in_dir1.push(path.clone());
        }
    }

    // Find files only in dir2
    for path in files2.keys() {
        if !files1.contains_key(path) {
            result.only_in_dir2.push(path.clone());
        }
    }

    // Compare common files
    for (path, size1) in &files1 {
        if let Some(size2) = files2.get(path) {
            if size1 == size2 {
                result.same.push(path.clone());
            } else {
                result.different.push(path.clone());
            }
        }
    }

    Ok(result)
}

fn walk_files(dir: &Path, base: &str, files: &mut HashMap<String, u64>) -> io::Result<()> {
    let entries = fs::read_dir(dir)?;
    for entry in entries {
        let entry = entry?;
        let path = entry.path();

        if path.is_dir() {
            walk_files(&path, base, files)?;
        } else {
            let rel_path = path.strip_prefix(base)?
                .to_string_lossy()
                .replace('\\", "/");
            let size = entry.metadata()?.len();
            files.insert(rel_path, size);
        }
    }
    Ok(())
}

// 6. Directory Cleanup

/// Cleanup directory by removing files matching patterns
fn cleanup_directory(directory: &str, dry_run: bool, patterns: Option<Vec<String>>) -> (Vec<String>, Vec<String>) {
    let mut deleted = Vec::new();
    let mut failed = Vec::new();

    let patterns = patterns.unwrap_or_else(|| vec![
        "*.tmp".to_string(),
        "*.log".to_string(),
        "*.bak".to_string(),
        "*~".to_string(),
    ]);

    let entries = match fs::read_dir(directory) {
        Ok(e) => e,
        Err(_) => return (deleted, failed),
    };

    for entry in entries.filter_map(|e| e.ok()) {
        if entry.path().is_dir() {
            continue;
        }

        let file_name = entry.file_name().to_string_lossy().to_string();
        let full_path = entry.path();

        // Check if matches any pattern
        let matches = patterns.iter().any(|pattern| {
            pattern_matches(pattern, &file_name)
        });

        if matches {
            if dry_run {
                println!("Would delete: {}", full_path.display());
                deleted.push(full_path.to_string_lossy().to_string());
            } else {
                match fs::remove_file(&full_path) {
                    Ok(_) => deleted.push(full_path.to_string_lossy().to_string()),
                    Err(_) => failed.push(full_path.to_string_lossy().to_string()),
                }
            }
        }
    }

    (deleted, failed)
}

/// Remove empty directories
fn remove_empty_directories(directory: &str, recursive: bool) -> usize {
    let mut removed_count = 0;

    if recursive {
        remove_empty_recursive(directory, &mut removed_count);
    }

    // Check and remove the root directory if it's empty
    if let Ok(mut entries) = fs::read_dir(directory) {
        if entries.next().is_none() {
            if fs::remove_dir(directory).is_ok() {
                removed_count += 1;
                println!("Removed empty directory: {}", directory);
            }
        }
    }

    removed_count
}

fn remove_empty_recursive(dir: &str, count: &mut usize) {
    if let Ok(entries) = fs::read_dir(dir) {
        for entry in entries.filter_map(|e| e.ok()) {
            if entry.path().is_dir() {
                let path = entry.path().to_string_lossy().to_string();
                remove_empty_recursive(&path, count);
            }
        }
    }

    // Check if directory is now empty
    if let Ok(mut entries) = fs::read_dir(dir) {
        if entries.next().is_none() {
            if fs::remove_dir(dir).is_ok() {
                *count += 1;
                println!("Removed empty directory: {}", dir);
            }
        }
    }
}

// Usage Examples
fn main() -> io::Result<()> {
    println!("=== Web Rust Directory Traversal Examples ===\n");

    // 1. List directory contents
    println!("--- 1. List Directory Contents ---");
    match list_directory_contents(".", false) {
        Ok(contents) => {
            println!("Files: {}", contents.files.len());
            println!("Directories: {}", contents.directories.len());
        }
        Err(e) => println!("Error: {}", e),
    }

    // 2. Directory tree
    println!("\n--- 2. Directory Tree ---");
    match list_directory_tree(".", 2) {
        Ok(tree) => print_directory_tree(&tree, 0),
        Err(e) => println!("Error: {}", e),
    }

    // 3. Find files by pattern
    println!("\n--- 3. Find Files by Pattern ---");
    match find_files_by_extension(".", "txt") {
        Ok(txt_files) => println!("Text files: {}", txt_files.len()),
        Err(e) => println!("Error: {}", e),
    }

    // 4. Directory statistics
    println!("\n--- 4. Directory Statistics ---");
    match get_directory_statistics(".") {
        Ok(stats) => {
            println!("Total files: {}", stats.total_files);
            println!("Total size: {}", format_file_size(stats.total_size));
            println!("Extensions: {:?}", stats.extension_counts);
        }
        Err(e) => println!("Error: {}", e),
    }

    // 5. Search in directory
    println!("\n--- 5. Search in Directory ---");
    match search_in_directory(".", "TODO", "*.txt") {
        Ok(results) => println!("Files containing 'TODO': {}", results.len()),
        Err(e) => println!("Error: {}", e),
    }

    // 6. Compare directories
    println!("\n--- 6. Compare Directories ---");
    match compare_directories("dir1", "dir2") {
        Ok(comparison) => {
            println!("Only in dir1: {}", comparison.only_in_dir1.len());
            println!("Only in dir2: {}", comparison.only_in_dir2.len());
            println!("Different: {}", comparison.different.len());
        }
        Err(e) => println!("Error: {}", e),
    }

    // 7. Cleanup
    println!("\n--- 7. Cleanup ---");
    let (deleted, _) = cleanup_directory(".", true, Some(vec!["*.tmp".to_string()]));
    println!("Would delete {} files", deleted.len());

    println!("\n=== All Directory Traversal Examples Completed ===");
    Ok(())
}

fn format_file_size(bytes: u64) -> String {
    const KB: u64 = 1024;
    const MB: u64 = KB * 1024;
    const GB: u64 = MB * 1024;

    if bytes >= GB {
        format!("{:.2} GB", bytes as f64 / GB as f64)
    } else if bytes >= MB {
        format!("{:.2} MB", bytes as f64 / MB as f64)
    } else if bytes >= KB {
        format!("{:.2} KB", bytes as f64 / KB as f64)
    } else {
        format!("{} B", bytes)
    }
}