2024-12-04 20:44:17 +02:00
|
|
|
#![cfg_attr(target_os = "yggdrasil", feature(yggdrasil_os, rustc_private))]
|
|
|
|
#![feature(let_chains, decl_macro)]
|
2023-11-24 13:29:53 +02:00
|
|
|
|
2023-11-14 14:55:13 +02:00
|
|
|
use std::{
|
2024-12-21 00:00:00 +02:00
|
|
|
cmp::Ordering, fmt, fs::{read_dir, FileType, Metadata}, io, path::{Path, PathBuf}, process::ExitCode
|
2023-11-14 14:55:13 +02:00
|
|
|
};
|
|
|
|
|
2023-11-24 13:29:53 +02:00
|
|
|
#[cfg(unix)]
|
|
|
|
use std::os::unix::fs::MetadataExt;
|
2024-01-22 14:41:09 +02:00
|
|
|
#[cfg(target_os = "yggdrasil")]
|
|
|
|
use std::os::yggdrasil::fs::MetadataExt;
|
2023-11-24 13:29:53 +02:00
|
|
|
|
2023-11-14 14:55:13 +02:00
|
|
|
use clap::Parser;
|
2024-01-22 14:41:09 +02:00
|
|
|
use humansize::{FormatSize, BINARY};
|
2023-11-14 14:55:13 +02:00
|
|
|
|
|
|
|
#[derive(Parser)]
|
2024-01-22 14:41:09 +02:00
|
|
|
#[clap(disable_help_flag = true)]
|
2023-11-14 14:55:13 +02:00
|
|
|
pub struct Args {
|
|
|
|
#[arg(short)]
|
|
|
|
long: bool,
|
2024-12-04 19:22:08 +02:00
|
|
|
#[arg(short)]
|
|
|
|
inodes: bool,
|
2023-11-14 14:55:13 +02:00
|
|
|
#[arg(short, long)]
|
|
|
|
human_readable: bool,
|
|
|
|
|
2024-12-04 18:28:27 +02:00
|
|
|
paths: Vec<PathBuf>,
|
2023-11-14 14:55:13 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
2024-01-28 18:23:04 +02:00
|
|
|
fn display_size_with(self, opts: &Args) -> DisplaySizeWith<'_, Self> {
|
2023-11-14 14:55:13 +02:00
|
|
|
DisplaySizeWith { size: self, opts }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-12-04 20:44:17 +02:00
|
|
|
trait MetadataImpl {
|
|
|
|
type Mode: DisplayBit;
|
|
|
|
|
|
|
|
fn size(&self) -> u64;
|
|
|
|
fn inode(&self) -> Option<u32>;
|
|
|
|
fn mode(&self) -> Self::Mode;
|
|
|
|
}
|
|
|
|
|
2023-11-14 14:55:13 +02:00
|
|
|
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,
|
2024-01-22 14:41:09 +02:00
|
|
|
opts: &'a Args,
|
2023-11-14 14:55:13 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
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,
|
2024-08-02 17:04:47 +03:00
|
|
|
target: Option<String>,
|
2023-11-14 14:55:13 +02:00
|
|
|
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("?")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-12-04 20:44:17 +02:00
|
|
|
#[cfg(any(target_os = "yggdrasil", rust_analyzer))]
|
|
|
|
impl DisplayBit for std::os::yggdrasil::io::RawFileMode {
|
|
|
|
fn display_bit(&self, _opts: &Args, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
|
|
write!(f, "{self}")
|
|
|
|
}
|
|
|
|
}
|
2024-12-04 19:22:08 +02:00
|
|
|
|
2024-12-04 20:44:17 +02:00
|
|
|
#[cfg(any(target_os = "yggdrasil", rust_analyzer))]
|
|
|
|
impl MetadataImpl for Metadata {
|
|
|
|
type Mode = std::os::yggdrasil::io::RawFileMode;
|
2023-11-14 14:55:13 +02:00
|
|
|
|
2024-12-04 20:44:17 +02:00
|
|
|
fn size(&self) -> u64 {
|
|
|
|
self.len()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn mode(&self) -> Self::Mode {
|
|
|
|
self.mode_ext()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn inode(&self) -> Option<u32> {
|
|
|
|
MetadataExt::inode(self)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[cfg(any(unix, rust_analyzer))]
|
|
|
|
struct UnixFileMode(u32);
|
|
|
|
|
|
|
|
#[cfg(any(unix, rust_analyzer))]
|
|
|
|
impl DisplayBit for UnixFileMode {
|
|
|
|
fn display_bit(&self, _opts: &Args, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
|
|
use std::fmt::Write;
|
|
|
|
|
|
|
|
macro display_bit($self:expr, $letter:expr, $bit:expr) {
|
|
|
|
if $self.0 & (1 << $bit) != 0 {
|
|
|
|
$letter
|
2024-12-04 19:22:08 +02:00
|
|
|
} else {
|
2024-12-04 20:44:17 +02:00
|
|
|
'-'
|
2024-12-04 19:22:08 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-12-04 20:44:17 +02:00
|
|
|
for i in (0..3).rev() {
|
|
|
|
let r = i * 3 + 2;
|
|
|
|
let w = i * 3 + 1;
|
|
|
|
let x = i * 3;
|
|
|
|
f.write_char(display_bit!(self, 'r', r))?;
|
|
|
|
f.write_char(display_bit!(self, 'w', w))?;
|
|
|
|
f.write_char(display_bit!(self, 'x', x))?;
|
|
|
|
}
|
|
|
|
|
2024-12-04 19:22:08 +02:00
|
|
|
Ok(())
|
2023-11-24 13:29:53 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-12-04 20:44:17 +02:00
|
|
|
#[cfg(any(unix, rust_analyzer))]
|
|
|
|
impl MetadataImpl for Metadata {
|
|
|
|
type Mode = UnixFileMode;
|
|
|
|
|
|
|
|
fn size(&self) -> u64 {
|
|
|
|
self.len()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn inode(&self) -> Option<u32> {
|
|
|
|
self.ino().try_into().ok()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn mode(&self) -> Self::Mode {
|
|
|
|
UnixFileMode(MetadataExt::mode(self))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-11-24 13:29:53 +02:00
|
|
|
impl DisplayBit for Option<Metadata> {
|
|
|
|
fn display_bit(&self, opts: &Args, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
2024-12-04 20:44:17 +02:00
|
|
|
let this = self.as_ref();
|
|
|
|
let ino = this.and_then(MetadataImpl::inode);
|
|
|
|
let mode = this.map(MetadataImpl::mode);
|
|
|
|
let size = this.map(MetadataImpl::size);
|
2023-11-24 13:29:53 +02:00
|
|
|
|
2024-12-04 20:44:17 +02:00
|
|
|
if let Some(mode) = mode {
|
|
|
|
mode.display_bit(opts, f)?;
|
|
|
|
} else {
|
|
|
|
write!(f, "---------")?;
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(size) = size {
|
|
|
|
write!(f, " {:>12}", size.display_size_with(opts))?;
|
|
|
|
} else {
|
|
|
|
write!(f, " {:<12}", "???")?;
|
|
|
|
}
|
|
|
|
|
|
|
|
if opts.inodes {
|
|
|
|
if let Some(ino) = ino {
|
|
|
|
write!(f, " {ino:>8}")?;
|
|
|
|
} else {
|
|
|
|
write!(f, " {:<8}", "---")?;
|
|
|
|
}
|
|
|
|
}
|
2023-11-24 13:29:53 +02:00
|
|
|
|
2024-12-04 20:44:17 +02:00
|
|
|
Ok(())
|
2023-11-14 14:55:13 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl DisplayBit for Entry {
|
|
|
|
fn display_bit(&self, opts: &Args, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
2024-12-04 20:44:17 +02:00
|
|
|
// let ino = self.attrs.as_ref().and_then(<Metadata as MetadataExt>::inode);
|
2024-12-04 19:22:08 +02:00
|
|
|
|
2023-11-14 14:55:13 +02:00
|
|
|
if opts.long {
|
|
|
|
write!(
|
|
|
|
f,
|
|
|
|
"{}{} {}",
|
|
|
|
self.ty.display_with(opts),
|
|
|
|
self.attrs.display_with(opts),
|
|
|
|
self.name
|
2024-08-02 17:04:47 +03:00
|
|
|
)?;
|
|
|
|
|
|
|
|
if let Some(target) = self.target.as_ref() {
|
|
|
|
write!(f, " -> {}", target)?;
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
2023-11-14 14:55:13 +02:00
|
|
|
} else {
|
2024-12-04 20:44:17 +02:00
|
|
|
let ino = self.attrs.as_ref().and_then(<Metadata as MetadataImpl>::inode);
|
2024-12-04 19:22:08 +02:00
|
|
|
if opts.inodes {
|
|
|
|
if let Some(ino) = ino {
|
|
|
|
write!(f, "{ino:<8} ")?;
|
|
|
|
} else {
|
|
|
|
write!(f, "{:<8} ", "---")?;
|
|
|
|
}
|
|
|
|
}
|
2023-11-14 14:55:13 +02:00
|
|
|
f.write_str(&self.name)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Entry {
|
|
|
|
fn invalid() -> Self {
|
|
|
|
Self {
|
|
|
|
name: "???".to_owned(),
|
2024-08-02 17:04:47 +03:00
|
|
|
target: None,
|
2023-11-14 14:55:13 +02:00
|
|
|
ty: None,
|
|
|
|
attrs: None,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-12-21 00:00:00 +02:00
|
|
|
fn sort_dirs_first(a: &Entry, b: &Entry) -> Ordering {
|
|
|
|
let a_is_dir = a.ty.map(|t| t.is_dir()).unwrap_or(false);
|
|
|
|
let b_is_dir = b.ty.map(|t| t.is_dir()).unwrap_or(false);
|
|
|
|
|
|
|
|
let by_type = Ord::cmp(&(b_is_dir as usize), &(a_is_dir as usize));
|
|
|
|
let by_name = Ord::cmp(&a.name, &b.name);
|
|
|
|
|
|
|
|
by_type.then(by_name)
|
|
|
|
}
|
|
|
|
|
2023-11-14 14:55:13 +02:00
|
|
|
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();
|
2024-08-02 17:04:47 +03:00
|
|
|
let attrs = entry.path().symlink_metadata().ok();
|
|
|
|
|
|
|
|
let target = if let Some(attrs) = attrs.as_ref()
|
|
|
|
&& attrs.is_symlink()
|
|
|
|
{
|
|
|
|
Some(match entry.path().read_link() {
|
|
|
|
Ok(res) => res.to_string_lossy().to_string(),
|
|
|
|
Err(_) => "???".into(),
|
|
|
|
})
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
};
|
2023-11-14 14:55:13 +02:00
|
|
|
|
|
|
|
entries.push(Entry {
|
|
|
|
name: os_filename.to_string_lossy().to_string(),
|
2024-08-02 17:04:47 +03:00
|
|
|
target,
|
2023-11-14 14:55:13 +02:00
|
|
|
ty,
|
|
|
|
attrs,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2024-12-21 00:00:00 +02:00
|
|
|
entries.sort_by(sort_dirs_first);
|
2024-12-04 18:28:27 +02:00
|
|
|
|
2023-11-14 14:55:13 +02:00
|
|
|
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));
|
|
|
|
}
|
2024-12-04 18:28:27 +02:00
|
|
|
|
|
|
|
Ok(())
|
2023-11-14 14:55:13 +02:00
|
|
|
} else {
|
2024-12-04 18:28:27 +02:00
|
|
|
let attrs = path.symlink_metadata()?;
|
2023-11-14 14:55:13 +02:00
|
|
|
|
2024-12-04 18:28:27 +02:00
|
|
|
let target = attrs.is_symlink().then(|| match path.read_link() {
|
|
|
|
Ok(res) => res.display().to_string(),
|
|
|
|
Err(_) => "???".into(),
|
|
|
|
});
|
2023-11-14 14:55:13 +02:00
|
|
|
|
2024-12-04 18:28:27 +02:00
|
|
|
let entry = Entry {
|
|
|
|
name: path.display().to_string(),
|
|
|
|
ty: Some(attrs.file_type()),
|
|
|
|
attrs: Some(attrs),
|
|
|
|
target,
|
|
|
|
};
|
2023-11-14 14:55:13 +02:00
|
|
|
|
2024-12-04 18:28:27 +02:00
|
|
|
println!("{}", entry.display_with(opts));
|
|
|
|
|
|
|
|
Ok(())
|
2023-11-14 14:55:13 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-12-04 18:28:27 +02:00
|
|
|
fn run_inner(opts: &Args, paths: &[PathBuf], print_names: bool) -> Vec<Result<(), io::Error>> {
|
|
|
|
let mut results = vec![];
|
|
|
|
for path in paths {
|
|
|
|
if print_names {
|
|
|
|
println!("{}: ", path.display());
|
|
|
|
}
|
|
|
|
results.push(list(opts, path).inspect_err(|error| {
|
|
|
|
eprintln!("{}: {error}", path.display());
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
results
|
|
|
|
}
|
2023-11-14 14:55:13 +02:00
|
|
|
|
2024-12-04 18:28:27 +02:00
|
|
|
fn run(opts: &Args) -> Vec<Result<(), io::Error>> {
|
|
|
|
if opts.paths.is_empty() {
|
|
|
|
run_inner(opts, &[".".into()], false)
|
2023-11-14 14:55:13 +02:00
|
|
|
} else {
|
2024-12-04 18:28:27 +02:00
|
|
|
run_inner(opts, &opts.paths, opts.paths.len() > 1)
|
2023-11-14 14:55:13 +02:00
|
|
|
}
|
|
|
|
}
|
2024-12-04 18:28:27 +02:00
|
|
|
|
|
|
|
pub fn main() -> ExitCode {
|
|
|
|
let args = Args::parse();
|
|
|
|
|
|
|
|
let results = run(&args);
|
|
|
|
let code = match results.iter().any(|e| e.is_err()) {
|
|
|
|
false => ExitCode::SUCCESS,
|
2024-12-04 19:22:08 +02:00
|
|
|
true => ExitCode::FAILURE,
|
2024-12-04 18:28:27 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
code
|
|
|
|
}
|