use super::Entry;
use super::datetime::civil_from_days;
use super::error::Error;
use std::os::unix::fs::MetadataExt;
pub fn format_entry(entry: &Entry) -> Result<String, Error> {
match &entry.metadata {
Some(metadata) => Ok(format!(
"{} {} {} {}",
format_permissions(metadata),
metadata.len(),
format_modified(metadata)?,
entry.name
)),
None => Ok(entry.name.clone()),
}
}
pub fn format_permissions(metadata: &std::fs::Metadata) -> String {
let file_type_char = if metadata.is_dir() {
'd'
} else if metadata.is_symlink() {
'l'
} else {
'-'
};
fn permission_text(r: bool, w: bool, x: bool) -> String {
format!(
"{}{}{}",
if r { 'r' } else { '-' },
if w { 'w' } else { '-' },
if x { 'x' } else { '-' }
)
}
fn has_permission(mode: u32, mask: u32) -> bool {
mode & mask != 0
}
let mode = metadata.mode();
format!(
"{}{}{}{}",
file_type_char,
permission_text(
has_permission(mode, 0o400),
has_permission(mode, 0o200),
has_permission(mode, 0o100),
),
permission_text(
has_permission(mode, 0o040),
has_permission(mode, 0o020),
has_permission(mode, 0o010),
),
permission_text(
has_permission(mode, 0o004),
has_permission(mode, 0o002),
has_permission(mode, 0o001),
),
)
}
pub fn format_modified(metadata: &std::fs::Metadata) -> Result<String, Error> {
let total_seconds = metadata
.modified()?
.duration_since(std::time::UNIX_EPOCH)?
.as_secs();
let days_since_epoch = total_seconds / 86_400;
let seconds_of_day = total_seconds % 86_400;
let (year, month, day) = civil_from_days(days_since_epoch);
let hour = seconds_of_day / 3600;
let minute = (seconds_of_day % 3600) / 60;
Ok(format!(
"{year:04}-{month:02}-{day:02} {hour:02}:{minute:02}"
))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_support::temp_dir;
use std::fs;
use std::os::unix::fs::PermissionsExt;
use std::time::{Duration, UNIX_EPOCH};
#[test]
fn regular_file_permissions_are_formatted() {
let dir = temp_dir("perm-file");
let path = dir.join("file.txt");
fs::write(&path, b"").expect("failed to create file.txt");
fs::set_permissions(&path, fs::Permissions::from_mode(0o644))
.expect("failed to set permissions");
let metadata = fs::symlink_metadata(&path).expect("failed to read metadata");
assert_eq!(format_permissions(&metadata), "-rw-r--r--");
fs::remove_dir_all(&dir).expect("failed to clean up temp dir");
}
#[test]
fn directory_permissions_start_with_d() {
let dir = temp_dir("perm-dir");
let metadata = fs::symlink_metadata(&dir).expect("failed to read metadata");
assert_eq!(&format_permissions(&metadata)[..1], "d");
fs::remove_dir_all(&dir).expect("failed to clean up temp dir");
}
#[test]
fn format_modified_formats_utc_date_and_time() {
let dir = temp_dir("mtime");
let path = dir.join("file.txt");
let file = fs::File::create(&path).expect("failed to create file.txt");
let mtime = UNIX_EPOCH + Duration::from_secs(1_788_048_000);
file.set_times(fs::FileTimes::new().set_modified(mtime))
.expect("failed to set mtime");
drop(file);
let metadata = fs::symlink_metadata(&path).expect("failed to read metadata");
assert_eq!(
format_modified(&metadata).expect("format_modified should succeed"),
"2026-08-30 00:00"
);
fs::remove_dir_all(&dir).expect("failed to clean up temp dir");
}
}