215 lines
5.0 KiB
Rust
Raw Normal View History

2023-11-24 13:29:53 +02:00
#![cfg_attr(target_os = "yggdrasil", feature(yggdrasil_os))]
2023-11-14 14:55:13 +02:00
use std::{
fmt,
2023-11-24 13:29:53 +02:00
fs::{read_dir, FileType, Metadata},
2023-11-14 14:55:13 +02:00
io,
path::Path,
};
2023-11-24 13:29:53 +02:00
#[cfg(target_os = "yggdrasil")]
use std::os::yggdrasil::fs::MetadataExt;
#[cfg(unix)]
use std::os::unix::fs::MetadataExt;
2023-11-14 14:55:13 +02:00
use clap::Parser;
use humansize::{FormatSize, BINARY};
#[derive(Parser)]
pub struct Args {
#[arg(short)]
long: bool,
#[arg(short, long)]
human_readable: bool,
paths: Vec<String>,
}
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<T: DisplayBit> fmt::Display for DisplayWith<'_, '_, T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.item.display_bit(self.opts, f)
}
}
impl<T: DisplaySizeBit + Copy> 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<FileType>,
2023-11-24 13:29:53 +02:00
attrs: Option<Metadata>,
2023-11-14 14:55:13 +02:00
}
impl DisplayBit for Option<FileType> {
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("?")
}
}
}
2023-11-24 13:29:53 +02:00
#[cfg(target_os = "yggdrasil")]
impl DisplayBit for Option<Metadata> {
2023-11-14 14:55:13 +02:00
fn display_bit(&self, opts: &Args, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let Some(attrs) = self else {
return write!(f, "--------- {:<8}", "???");
};
2023-11-24 13:29:53 +02:00
let mode = attrs.mode_ext();
write!(f, "{} {:>8}", mode, attrs.len().display_size_with(opts))
}
}
#[cfg(unix)]
impl DisplayBit for Option<Metadata> {
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))
2023-11-14 14:55:13 +02:00
}
}
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<Vec<Entry>> {
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();
2023-11-24 13:29:53 +02:00
let attrs = entry.path().metadata().ok();
2023-11-14 14:55:13 +02:00
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<P: AsRef<Path>>(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);
}
}
}