🎯 Рекомендуемые коллекции
Балансированные коллекции примеров кода из различных категорий, которые вы можете исследовать
Примеры Файловых Операций Web Rust
Примеры файловых операций Web Rust включая чтение/запись текстовых файлов, копирование/перемещение, обход директорий и валидацию файлов
💻 Чтение/Запись Текстовых Файлов rust
🟢 simple
⭐⭐
Чтение и запись текстовых файлов с различными опциями кодирования используя Rust std::fs и 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(())
}
💻 Копирование/Перемещение Файлов rust
🟡 intermediate
⭐⭐⭐
Копирование и перемещение файлов с отслеживанием прогресса и обработкой ошибок
⏱️ 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(())
}
💻 Обход Директорий rust
🔴 complex
⭐⭐⭐⭐
Обход директорий используя WalkDir, glob patterns и файловую эксплорацию
⏱️ 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)
}
}