You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
161 lines
4.3 KiB
161 lines
4.3 KiB
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<String>) -> 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<Local> = match metadata.modified() {
|
|
Ok(t) => DateTime::from(t),
|
|
Err(_) => Local::now(),
|
|
};
|
|
|
|
let accessed: DateTime<Local> = match metadata.accessed() {
|
|
Ok(t) => DateTime::from(t),
|
|
Err(_) => Local::now(),
|
|
};
|
|
|
|
let created: DateTime<Local> = 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<Local>) -> String {
|
|
let now: DateTime<Local> = 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<Local>,
|
|
accessed: DateTime<Local>,
|
|
created: DateTime<Local>,
|
|
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");
|
|
}
|
|
}
|