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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|