use std::{ fs::{metadata, Permissions}, path::PathBuf, str::FromStr, }; use chrono::{DateTime, Datelike, Local}; use crate::error::ShellError; use super::{Builtin, BuiltinConfig}; pub struct Ls; impl Builtin for Ls { fn execute(&mut self, _: &mut BuiltinConfig, _: Vec) -> Result<(), ShellError> { let dir = std::env::current_dir().unwrap_or(PathBuf::from_str("/").unwrap()); let entries = match std::fs::read_dir(dir) { Ok(e) => e, Err(e) => return Err(ShellError::ExecuteFailure(e.to_string())), }; //for entry in entries.by_ref().into_iter() {} println!( "{} | dir | size | modified | accessed | created | readonly |", right_padding(" filename", 20) ); for entry in entries { let Ok(entry) = entry else { eprintln!("Couldn't get directory entry"); continue; }; let metadata = match metadata(entry.path()) { Ok(m) => m, Err(_) => { continue; } }; let file_name = entry.file_name().to_string_lossy().to_string(); let modified: DateTime = match metadata.modified() { Ok(t) => DateTime::from(t), Err(_) => Local::now(), }; let accessed: DateTime = match metadata.accessed() { Ok(t) => DateTime::from(t), Err(_) => Local::now(), }; let created: DateTime = match metadata.created() { Ok(t) => DateTime::from(t), Err(_) => Local::now(), }; let permissions = metadata.permissions(); let mut file_type = "unknown"; if metadata.file_type().is_dir() { file_type = "dir" } else if metadata.file_type().is_file() { file_type = "file" } else if metadata.file_type().is_symlink() { file_type = "link" } println!( "{}", format_line( 20, &file_name, file_type, metadata.len(), modified, accessed, created, permissions ) ); } Ok(()) } } fn format_filesize(filesize: u64) -> String { if filesize > 9_999 && filesize <= 999_999 { format!("{}KB", filesize / 1_000) } else if filesize > 999_999 && filesize <= 999_999_999 { format!("{}MB", filesize / 1_000_000) } else if filesize > 999_999_999 { format!("{}GB", filesize / 1_000_000_000) } else { format!("{}B", filesize) } } fn format_date(date: DateTime) -> String { let now: DateTime = Local::now(); if date.day() != now.day() || date.month() != now.month() || date.year() != now.year() { date.format("%F").to_string() } else { date.format("%T").to_string() } } fn format_line( max_name_len: usize, file_name: &str, file_type: &str, file_size: u64, modified: DateTime, accessed: DateTime, created: DateTime, permissions: Permissions, ) -> String { format!( "{} | {:4} | {:6} | {:<10} | {:<10} | {:<10} | {:<5} |", right_padding(file_name, max_name_len), file_type, format_filesize(file_size), format_date(modified), format_date(accessed), format_date(created), permissions.readonly().to_string(), ) } fn right_padding(s: &str, max: usize) -> String { let mut tmp = String::from_str(s).unwrap(); for _ in tmp.len()..max { tmp.push(' '); } tmp } #[cfg(test)] mod tests { use super::*; #[test] fn test_format_filesize_bytes() { assert_eq!(format_filesize(6969), "6969B"); } #[test] fn test_format_filesize_kilobytes() { assert_eq!(format_filesize(69420), "69KB"); } #[test] fn test_format_filesize_megabytes() { assert_eq!(format_filesize(69420420), "69MB"); } #[test] fn test_format_filesize_gigabytes() { assert_eq!(format_filesize(69420420420), "69GB"); } }