sysutils: ls colors
This commit is contained in:
@@ -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()) {
|
||||
|
||||
Reference in New Issue
Block a user