1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-28 11:37:44 +00:00

ls: use lscolors crate

This commit is contained in:
Terts Diepraam 2021-04-21 16:58:37 +02:00
parent 0ea35f3fbc
commit 34a824af71
3 changed files with 116 additions and 169 deletions

View file

@ -16,17 +16,14 @@ path = "src/ls.rs"
[dependencies]
clap = "2.33"
lazy_static = "1.0.1"
number_prefix = "0.4"
term_grid = "0.1.5"
termsize = "0.1.6"
time = "0.1.40"
unicode-width = "0.1.5"
globset = "0.4.6"
lscolors = { version="0.7.1", features=["ansi_term"] }
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["entries", "fs"] }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
[target.'cfg(unix)'.dependencies]
atty = "0.2"
[[bin]]

View file

@ -7,9 +7,6 @@
// spell-checker:ignore (ToDO) cpio svgz webm somegroup nlink rmvb xspf
#[cfg(unix)]
#[macro_use]
extern crate lazy_static;
#[macro_use]
extern crate uucore;
@ -18,10 +15,9 @@ mod version_cmp;
use clap::{App, Arg};
use globset::{self, Glob, GlobSet, GlobSetBuilder};
use lscolors::LsColors;
use number_prefix::NumberPrefix;
use quoting_style::{escape_name, QuotingStyle};
#[cfg(unix)]
use std::collections::HashMap;
use std::fs;
use std::fs::{DirEntry, FileType, Metadata};
#[cfg(unix)]
@ -41,7 +37,7 @@ use time::{strftime, Timespec};
#[cfg(unix)]
use unicode_width::UnicodeWidthStr;
#[cfg(unix)]
use uucore::libc::{mode_t, S_ISGID, S_ISUID, S_ISVTX, S_IWOTH, S_IXGRP, S_IXOTH, S_IXUSR};
use uucore::libc::{mode_t, S_IXGRP, S_IXOTH, S_IXUSR};
static VERSION: &str = env!("CARGO_PKG_VERSION");
static ABOUT: &str = "
@ -54,30 +50,6 @@ fn get_usage() -> String {
format!("{0} [OPTION]... [FILE]...", executable!())
}
#[cfg(unix)]
static DEFAULT_COLORS: &str = "rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=00:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.jpg=01;35:*.jpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=00;36:*.au=00;36:*.flac=00;36:*.m4a=00;36:*.mid=00;36:*.midi=00;36:*.mka=00;36:*.mp3=00;36:*.mpc=00;36:*.ogg=00;36:*.ra=00;36:*.wav=00;36:*.oga=00;36:*.opus=00;36:*.spx=00;36:*.xspf=00;36:";
#[cfg(unix)]
lazy_static! {
static ref LS_COLORS: String =
std::env::var("LS_COLORS").unwrap_or_else(|_| DEFAULT_COLORS.to_string());
static ref COLOR_MAP: HashMap<&'static str, &'static str> = {
let codes = LS_COLORS.split(':');
let mut map = HashMap::new();
for c in codes {
let p: Vec<_> = c.splitn(2, '=').collect();
if p.len() == 2 {
map.insert(p[0], p[1]);
}
}
map
};
static ref RESET_CODE: &'static str = COLOR_MAP.get("rs").unwrap_or(&"0");
static ref LEFT_CODE: &'static str = COLOR_MAP.get("lc").unwrap_or(&"\x1b[");
static ref RIGHT_CODE: &'static str = COLOR_MAP.get("rc").unwrap_or(&"m");
static ref END_CODE: &'static str = COLOR_MAP.get("ec").unwrap_or(&"");
}
pub mod options {
pub mod format {
pub static ONELINE: &str = "1";
@ -212,8 +184,7 @@ struct Config {
time: Time,
#[cfg(unix)]
inode: bool,
#[cfg(unix)]
color: bool,
color: Option<LsColors>,
long: LongFormat,
width: Option<u16>,
quoting_style: QuotingStyle,
@ -337,8 +308,7 @@ impl Config {
Time::Modification
};
#[cfg(unix)]
let color = match options.value_of(options::COLOR) {
let needs_color = match options.value_of(options::COLOR) {
None => options.is_present(options::COLOR),
Some(val) => match val {
"" | "always" | "yes" | "force" => true,
@ -347,6 +317,12 @@ impl Config {
},
};
let color = if needs_color {
Some(LsColors::from_env().unwrap_or_default())
} else {
None
};
let size_format = if options.is_present(options::size::HUMAN_READABLE) {
SizeFormat::Binary
} else if options.is_present(options::size::SI) {
@ -520,7 +496,6 @@ impl Config {
size_format,
directory: options.is_present(options::DIRECTORY),
time,
#[cfg(unix)]
color,
#[cfg(unix)]
inode: options.is_present(options::INODE),
@ -1470,64 +1445,44 @@ fn get_file_name(name: &Path, strip: Option<&Path>) -> String {
name.to_string_lossy().into_owned()
}
#[cfg(not(unix))]
fn display_file_name(
path: &Path,
strip: Option<&Path>,
metadata: &Metadata,
config: &Config,
) -> Cell {
let mut name = escape_name(get_file_name(path, strip), &config.quoting_style);
let file_type = metadata.file_type();
// #[cfg(not(unix))]
// fn display_file_name(
// path: &Path,
// strip: Option<&Path>,
// metadata: &Metadata,
// config: &Config,
// ) -> Cell {
// let mut name = escape_name(get_file_name(path, strip), &config.quoting_style);
// let file_type = metadata.file_type();
match config.indicator_style {
IndicatorStyle::Classify | IndicatorStyle::FileType => {
if file_type.is_dir() {
name.push('/');
}
if file_type.is_symlink() {
name.push('@');
}
}
IndicatorStyle::Slash => {
if file_type.is_dir() {
name.push('/');
}
}
_ => (),
};
// match config.indicator_style {
// IndicatorStyle::Classify | IndicatorStyle::FileType => {
// if file_type.is_dir() {
// name.push('/');
// }
// if file_type.is_symlink() {
// name.push('@');
// }
// }
// IndicatorStyle::Slash => {
// if file_type.is_dir() {
// name.push('/');
// }
// }
// _ => (),
// };
if config.format == Format::Long && metadata.file_type().is_symlink() {
if let Ok(target) = path.read_link() {
// We don't bother updating width here because it's not used for long listings
let target_name = target.to_string_lossy().to_string();
name.push_str(" -> ");
name.push_str(&target_name);
}
}
// if config.format == Format::Long && metadata.file_type().is_symlink() {
// if let Ok(target) = path.read_link() {
// // We don't bother updating width here because it's not used for long listings
// let target_name = target.to_string_lossy().to_string();
// name.push_str(" -> ");
// name.push_str(&target_name);
// }
// }
name.into()
}
#[cfg(unix)]
fn color_name(name: String, typ: &str) -> String {
let mut typ = typ;
if !COLOR_MAP.contains_key(typ) {
if typ == "or" {
typ = "ln";
} else if typ == "mi" {
typ = "fi";
}
};
if let Some(code) = COLOR_MAP.get(typ) {
format!(
"{}{}{}{}{}{}{}{}",
*LEFT_CODE, code, *RIGHT_CODE, name, *END_CODE, *LEFT_CODE, *RESET_CODE, *RIGHT_CODE,
)
} else {
name
}
}
// name.into()
// }
#[cfg(unix)]
macro_rules! has {
@ -1537,6 +1492,40 @@ macro_rules! has {
}
#[cfg(unix)]
fn classify_file(md: &Metadata) -> Option<char> {
let file_type = md.file_type();
if file_type.is_dir() {
Some('/')
} else if file_type.is_symlink() {
Some('@')
} else if file_type.is_socket() {
Some('=')
} else if file_type.is_fifo() {
Some('|')
} else if file_type.is_file() {
let mode = md.mode() as mode_t;
if has!(mode, S_IXUSR | S_IXGRP | S_IXOTH) {
Some('*')
} else {
None
}
} else {
None
}
}
#[cfg(not(unix))]
fn classify_file(md: &Metadata) -> Option<char> {
let file_type = md.file_type();
if file_type.is_dir() {
Some('/')
} else if file_type.is_symlink() {
Some('@')
} else {
None
}
}
#[allow(clippy::cognitive_complexity)]
fn display_file_name(
path: &Path,
@ -1545,65 +1534,18 @@ fn display_file_name(
config: &Config,
) -> Cell {
let mut name = escape_name(get_file_name(path, strip), &config.quoting_style);
#[cfg(unix)]
if config.format != Format::Long && config.inode {
name = get_inode(metadata) + " " + &name;
}
let mut width = UnicodeWidthStr::width(&*name);
let ext;
if config.color || config.indicator_style != IndicatorStyle::None {
let file_type = metadata.file_type();
if let Some(ls_colors) = &config.color {
name = color_name(&ls_colors, path, name, metadata).to_string();
}
let (code, sym) = if file_type.is_dir() {
("di", Some('/'))
} else if file_type.is_symlink() {
if path.exists() {
("ln", Some('@'))
} else {
("or", Some('@'))
}
} else if file_type.is_socket() {
("so", Some('='))
} else if file_type.is_fifo() {
("pi", Some('|'))
} else if file_type.is_block_device() {
("bd", None)
} else if file_type.is_char_device() {
("cd", None)
} else if file_type.is_file() {
let mode = metadata.mode() as mode_t;
let sym = if has!(mode, S_IXUSR | S_IXGRP | S_IXOTH) {
Some('*')
} else {
None
};
if has!(mode, S_ISUID) {
("su", sym)
} else if has!(mode, S_ISGID) {
("sg", sym)
} else if has!(mode, S_ISVTX) && has!(mode, S_IWOTH) {
("tw", sym)
} else if has!(mode, S_ISVTX) {
("st", sym)
} else if has!(mode, S_IWOTH) {
("ow", sym)
} else if has!(mode, S_IXUSR | S_IXGRP | S_IXOTH) {
("ex", sym)
} else if metadata.nlink() > 1 {
("mh", sym)
} else if let Some(e) = path.extension() {
ext = format!("*.{}", e.to_string_lossy());
(ext.as_str(), None)
} else {
("fi", None)
}
} else {
("", None)
};
if config.color {
name = color_name(name, code);
}
if config.indicator_style != IndicatorStyle::None {
let sym = classify_file(metadata);
let char_opt = match config.indicator_style {
IndicatorStyle::Classify => sym,
@ -1626,23 +1568,32 @@ fn display_file_name(
if let Some(c) = char_opt {
name.push(c);
width += 1;
}
}
if config.format == Format::Long && metadata.file_type().is_symlink() {
if let Ok(target) = path.read_link() {
// We don't bother updating width here because it's not used for long listings
let code = if target.exists() { "fi" } else { "mi" };
let target_name = color_name(target.to_string_lossy().to_string(), code);
// We don't bother updating width here because it's not used for long
let mut target_name = target.to_string_lossy().to_string();
if let Some(ls_colors) = &config.color {
target_name = color_name(&ls_colors, &target, target_name, metadata);
}
name.push_str(" -> ");
name.push_str(&target_name);
}
}
Cell {
contents: name,
width,
name.into()
}
fn color_name(ls_colors: &LsColors, path: &Path, name: String, md: &Metadata) -> String {
match ls_colors.style_for_path_with_metadata(path, Some(&md)) {
Some(style) => {
dbg!(style);
style.to_ansi_term_style().paint(name).to_string()
}
None => dbg!(name),
}
}

View file

@ -621,20 +621,27 @@ fn test_ls_recursive() {
result.stdout_contains(&"a\\b:\nb");
}
#[cfg(unix)]
#[test]
fn test_ls_ls_color() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
at.mkdir("a");
at.mkdir("a/nested_dir");
let nested_dir = Path::new("a")
.join("nested_dir")
.to_string_lossy()
.to_string();
at.mkdir(&nested_dir);
at.mkdir("z");
at.touch(&at.plus_as_string("a/nested_file"));
let nested_file = Path::new("a")
.join("nested_file")
.to_string_lossy()
.to_string();
at.touch(&nested_file);
at.touch("test-color");
let a_with_colors = "\x1b[01;34ma\x1b[0m";
let z_with_colors = "\x1b[01;34mz\x1b[0m";
let nested_dir_with_colors = "\x1b[01;34mnested_dir\x1b[0m";
let a_with_colors = "\x1b[1;34ma\x1b[0m";
let z_with_colors = "\x1b[1;34mz\x1b[0m";
let nested_dir_with_colors = "\x1b[1;34mnested_dir\x1b[0m";
// Color is disabled by default
let result = scene.ucmd().succeeds();
@ -670,14 +677,6 @@ fn test_ls_ls_color() {
.succeeds()
.stdout_contains(nested_dir_with_colors);
// Color has no effect
scene
.ucmd()
.arg("--color=always")
.arg("a/nested_file")
.succeeds()
.stdout_contains("a/nested_file\n");
// No output
scene
.ucmd()