use crate::{DarEntry, EntryKind};
fn type_letter(kind: EntryKind) -> char {
match kind {
EntryKind::File | EntryKind::Hardlink => 'r',
EntryKind::Directory => 'd',
EntryKind::Symlink => 'l',
EntryKind::NamedPipe => 'p',
EntryKind::Socket => 's',
EntryKind::CharDevice => 'c',
EntryKind::BlockDevice => 'b',
EntryKind::Unknown(_) => '-',
}
}
fn perm_string(mode: u16) -> String {
const RWX: [char; 3] = ['r', 'w', 'x'];
let mut s: [char; 9] = ['-'; 9];
for (i, slot) in s.iter_mut().enumerate() {
if mode & (1 << (8 - i)) != 0 {
*slot = RWX[i % 3];
}
}
if mode & 0o4000 != 0 {
s[2] = if s[2] == 'x' { 's' } else { 'S' };
}
if mode & 0o2000 != 0 {
s[5] = if s[5] == 'x' { 's' } else { 'S' };
}
if mode & 0o1000 != 0 {
s[8] = if s[8] == 'x' { 't' } else { 'T' };
}
s.iter().collect()
}
fn escape_name(name: &str) -> String {
let mut out = String::with_capacity(name.len());
for c in name.chars() {
match c {
'\\' => out.push_str("\\\\"),
'|' => out.push_str("\\|"),
c if (c as u32) < 0x20 || c as u32 == 0x7f => {
const HEX: [u8; 16] = *b"0123456789abcdef";
let b = c as u32;
out.push('\\');
out.push('x');
out.push(HEX[((b >> 4) & 0xf) as usize] as char);
out.push(HEX[(b & 0xf) as usize] as char);
}
c => out.push(c),
}
}
out
}
pub(crate) fn line(entry: &DarEntry) -> String {
let t = type_letter(entry.kind);
let mode = format!("{t}/{t}{}", perm_string(entry.mode));
let mut name = escape_name(&entry.path_lossy());
if let Some(target) = &entry.symlink_target {
name.push_str(" -> ");
name.push_str(&escape_name(&String::from_utf8_lossy(target)));
}
let ctime = entry.ctime.unwrap_or(0);
format!(
"0|{name}|0|{mode}|{uid}|{gid}|{size}|{atime}|{mtime}|{ctime}|0",
uid = entry.uid,
gid = entry.gid,
size = entry.size,
atime = entry.atime,
mtime = entry.mtime,
)
}