#![cfg_attr(target_os = "yggdrasil", feature(yggdrasil_os))] use std::{ fmt, fs::{read_dir, FileType, Metadata}, io, path::Path, }; #[cfg(unix)] use std::os::unix::fs::MetadataExt; #[cfg(target_os = "yggdrasil")] use std::os::yggdrasil::fs::MetadataExt; use clap::Parser; use humansize::{FormatSize, BINARY}; #[derive(Parser)] #[clap(disable_help_flag = true)] pub struct Args { #[arg(short)] long: bool, #[arg(short, long)] human_readable: bool, paths: Vec, } trait DisplayBit { fn display_bit(&self, opts: &Args, f: &mut fmt::Formatter<'_>) -> fmt::Result; fn display_with<'a, 'b>(&'a self, opts: &'b Args) -> DisplayWith<'a, 'b, Self> where Self: Sized, { DisplayWith { item: self, opts } } } trait DisplaySizeBit: Sized { fn display_size_bit(self, opts: &Args, f: &mut fmt::Formatter<'_>) -> fmt::Result; fn display_size_with<'a>(self, opts: &'a Args) -> DisplaySizeWith<'a, Self> { DisplaySizeWith { size: self, opts } } } impl DisplaySizeBit for u64 { fn display_size_bit(self, opts: &Args, f: &mut fmt::Formatter<'_>) -> fmt::Result { if opts.human_readable { fmt::Display::fmt(&self.format_size(BINARY), f) } else { fmt::Display::fmt(&self, f) } } } struct DisplaySizeWith<'a, T> { size: T, opts: &'a Args, } struct DisplayWith<'a, 'b, T: DisplayBit> { item: &'a T, opts: &'b Args, } impl fmt::Display for DisplayWith<'_, '_, T> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.item.display_bit(self.opts, f) } } impl fmt::Display for DisplaySizeWith<'_, T> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.size.display_size_bit(self.opts, f) } } struct Entry { name: String, ty: Option, attrs: Option, } impl DisplayBit for Option { fn display_bit(&self, _opts: &Args, f: &mut fmt::Formatter<'_>) -> fmt::Result { if let Some(ty) = self { if ty.is_dir() { f.write_str("d") } else if ty.is_symlink() { f.write_str("l") } else { f.write_str("-") } } else { f.write_str("?") } } } #[cfg(target_os = "yggdrasil")] impl DisplayBit for Option { fn display_bit(&self, opts: &Args, f: &mut fmt::Formatter<'_>) -> fmt::Result { let Some(attrs) = self else { return write!(f, "--------- {:<8}", "???"); }; let mode = attrs.mode_ext(); write!(f, "{} {:>8}", mode, attrs.len().display_size_with(opts)) } } #[cfg(unix)] impl DisplayBit for Option { fn display_bit(&self, opts: &Args, f: &mut fmt::Formatter<'_>) -> fmt::Result { use sysutils::unix::FileMode; let Some(attrs) = self else { return write!(f, "--------- {:<8}", "???"); }; let mode = FileMode::from(attrs.mode()); write!(f, "{} {:>8}", mode, attrs.len().display_size_with(opts)) } } impl DisplayBit for Entry { fn display_bit(&self, opts: &Args, f: &mut fmt::Formatter<'_>) -> fmt::Result { if opts.long { write!( f, "{}{} {}", self.ty.display_with(opts), self.attrs.display_with(opts), self.name ) } else { f.write_str(&self.name) } } } impl Entry { fn invalid() -> Self { Self { name: "???".to_owned(), ty: None, attrs: None, } } } fn list_directory(path: &Path) -> io::Result> { let mut entries = vec![]; for entry in read_dir(path)? { let Ok(entry) = entry else { entries.push(Entry::invalid()); continue; }; let os_filename = entry.file_name(); let ty = entry.file_type().ok(); let attrs = entry.path().metadata().ok(); entries.push(Entry { name: os_filename.to_string_lossy().to_string(), ty, attrs, }); } Ok(entries) } fn list(opts: &Args, path: &Path) -> io::Result<()> { if path.is_dir() { let entries = list_directory(path)?; for entry in entries { println!("{}", entry.display_with(opts)); } } else { // TODO fetch info println!("{}", path.display()); } Ok(()) } fn list_wrap>(opts: &Args, path: P) { let path = path.as_ref(); match list(opts, path) { Ok(_) => (), Err(e) => { eprintln!("{}: {}", path.display(), e); } } } pub fn main() { let args = Args::parse(); if args.paths.is_empty() { list_wrap(&args, "."); } else { for path in args.paths.iter() { if args.paths.len() > 1 { println!("{}:", path); } list_wrap(&args, path); } } }