sysutils: ls colors

This commit is contained in:
2025-03-01 18:40:24 +02:00
parent dfa74e5c87
commit c069982ed9
+129 -6
View File
@@ -6,7 +6,7 @@ use std::{
ffi::OsString,
fmt,
fs::{read_dir, FileType, Metadata},
io,
io::{self, stdout, IsTerminal},
path::{Path, PathBuf},
process::ExitCode,
time::SystemTime,
@@ -24,6 +24,8 @@ use libutil::fmt::FormatSize;
#[derive(Parser)]
#[clap(disable_help_flag = true)]
pub struct Args {
#[arg(long, default_value_t = true)]
color: bool,
#[arg(short)]
long: bool,
#[arg(short)]
@@ -64,6 +66,12 @@ trait MetadataImpl {
fn mtime(&self) -> SystemTime;
}
trait FileTypeImpl {
fn is_directory(&self) -> bool;
fn is_block_device(&self) -> bool;
fn is_char_device(&self) -> bool;
}
impl DisplaySizeBit for u64 {
fn display_size_bit(self, opts: &Args, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if opts.human_readable {
@@ -106,7 +114,11 @@ struct Entry {
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() {
if ty.is_char_device() {
f.write_str("c")
} else if ty.is_block_device() {
f.write_str("b")
} else if ty.is_directory() {
f.write_str("d")
} else if ty.is_symlink() {
f.write_str("l")
@@ -119,6 +131,40 @@ impl DisplayBit for Option<FileType> {
}
}
#[cfg(any(target_os = "yggdrasil", rust_analyzer))]
impl FileTypeImpl for FileType {
fn is_directory(&self) -> bool {
FileType::is_dir(self)
}
fn is_char_device(&self) -> bool {
use std::os::yggdrasil::fs::FileTypeExt;
FileTypeExt::is_char_device(self)
}
fn is_block_device(&self) -> bool {
use std::os::yggdrasil::fs::FileTypeExt;
FileTypeExt::is_block_device(self)
}
}
#[cfg(any(unix, rust_analyzer))]
impl FileTypeImpl for FileType {
fn is_directory(&self) -> bool {
FileType::is_dir(self)
}
fn is_char_device(&self) -> bool {
use std::os::unix::fs::FileTypeExt;
FileTypeExt::is_char_device(self)
}
fn is_block_device(&self) -> bool {
use std::os::unix::fs::FileTypeExt;
FileTypeExt::is_block_device(self)
}
}
#[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 {
@@ -176,6 +222,47 @@ impl DisplayBit for UnixFileMode {
}
}
impl Entry {
pub fn is_symlink(&self) -> bool {
self.ty.map_or(false, |d| d.is_symlink())
}
pub fn is_directory(&self) -> bool {
self.ty.map_or(false, |d| d.is_dir())
}
}
#[cfg(any(unix, rust_analyzer))]
impl Entry {
pub fn is_device(&self) -> bool {
self.ty
.map_or(false, |d| d.is_block_device() || d.is_char_device())
}
pub fn is_executable(&self) -> bool {
self.attrs
.as_ref()
.map(MetadataImpl::mode)
.map_or(false, |d| d.0 & 0o100 != 0)
}
}
#[cfg(any(target_os = "yggdrasil", rust_analyzer))]
impl Entry {
pub fn is_device(&self) -> bool {
self.ty.map_or(false, |d| d.is_block_device() || d.is_char_device())
}
pub fn is_executable(&self) -> bool {
self.attrs
.as_ref()
.map(MetadataImpl::mode)
.map_or(false, |d| {
d.contains(std::os::yggdrasil::io::RawFileMode::USER_EXEC)
})
}
}
#[cfg(any(unix, rust_analyzer))]
impl MetadataImpl for Metadata {
type Mode = UnixFileMode;
@@ -272,6 +359,34 @@ impl DisplayBit for Option<Metadata> {
}
}
fn display_filename(f: &mut fmt::Formatter<'_>, entry: &Entry, color: bool) -> fmt::Result {
if !color {
return f.write_str(&entry.name);
}
let color = if entry.is_device() {
// Yellow
Some(3)
} else if entry.is_symlink() {
// Purple
Some(5)
} else if entry.is_directory() {
// Cyan
Some(6)
} else if entry.is_executable() {
// Green
Some(2)
} else {
None
};
if let Some(color) = color {
write!(f, "\x1B[3{color}m{}\x1B[0m", &entry.name)
} else {
f.write_str(&entry.name)
}
}
impl DisplayBit for Entry {
fn display_bit(&self, opts: &Args, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// let ino = self.attrs.as_ref().and_then(<Metadata as MetadataExt>::inode);
@@ -279,12 +394,13 @@ impl DisplayBit for Entry {
if opts.long {
write!(
f,
"{}{} {}",
"{}{} ",
self.ty.display_with(opts),
self.attrs.display_with(opts),
self.name
)?;
display_filename(f, self, opts.color)?;
if let Some(target) = self.target.as_ref() {
write!(f, " -> {}", target)?;
}
@@ -302,7 +418,10 @@ impl DisplayBit for Entry {
write!(f, "{:<8} ", "---")?;
}
}
f.write_str(&self.name)
display_filename(f, self, opts.color)?;
Ok(())
}
}
}
@@ -431,7 +550,11 @@ fn run(opts: &Args) -> Vec<Result<(), io::Error>> {
}
pub fn main() -> ExitCode {
let args = Args::parse();
let mut args = Args::parse();
if !stdout().is_terminal() {
args.color = false;
}
let results = run(&args);
let code = match results.iter().any(|e| e.is_err()) {