mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-29 03:57:44 +00:00
Merge branch 'main' into feat-refactor-expr
This commit is contained in:
commit
17f2b830d8
31 changed files with 1279 additions and 641 deletions
4
.github/workflows/freebsd.yml
vendored
4
.github/workflows/freebsd.yml
vendored
|
@ -39,7 +39,7 @@ jobs:
|
||||||
- name: Run sccache-cache
|
- name: Run sccache-cache
|
||||||
uses: mozilla-actions/sccache-action@v0.0.3
|
uses: mozilla-actions/sccache-action@v0.0.3
|
||||||
- name: Prepare, build and test
|
- name: Prepare, build and test
|
||||||
uses: vmactions/freebsd-vm@v1.0.2
|
uses: vmactions/freebsd-vm@v1.0.5
|
||||||
with:
|
with:
|
||||||
usesh: true
|
usesh: true
|
||||||
sync: rsync
|
sync: rsync
|
||||||
|
@ -131,7 +131,7 @@ jobs:
|
||||||
- name: Run sccache-cache
|
- name: Run sccache-cache
|
||||||
uses: mozilla-actions/sccache-action@v0.0.3
|
uses: mozilla-actions/sccache-action@v0.0.3
|
||||||
- name: Prepare, build and test
|
- name: Prepare, build and test
|
||||||
uses: vmactions/freebsd-vm@v1.0.2
|
uses: vmactions/freebsd-vm@v1.0.5
|
||||||
with:
|
with:
|
||||||
usesh: true
|
usesh: true
|
||||||
sync: rsync
|
sync: rsync
|
||||||
|
|
2
.github/workflows/fuzzing.yml
vendored
2
.github/workflows/fuzzing.yml
vendored
|
@ -46,6 +46,8 @@ jobs:
|
||||||
- { name: fuzz_date, should_pass: false }
|
- { name: fuzz_date, should_pass: false }
|
||||||
- { name: fuzz_expr, should_pass: true }
|
- { name: fuzz_expr, should_pass: true }
|
||||||
- { name: fuzz_printf, should_pass: false }
|
- { name: fuzz_printf, should_pass: false }
|
||||||
|
- { name: fuzz_echo, should_pass: false }
|
||||||
|
- { name: fuzz_seq, should_pass: false }
|
||||||
- { name: fuzz_parse_glob, should_pass: true }
|
- { name: fuzz_parse_glob, should_pass: true }
|
||||||
- { name: fuzz_parse_size, should_pass: true }
|
- { name: fuzz_parse_size, should_pass: true }
|
||||||
- { name: fuzz_parse_time, should_pass: true }
|
- { name: fuzz_parse_time, should_pass: true }
|
||||||
|
|
9
Cargo.lock
generated
9
Cargo.lock
generated
|
@ -1409,9 +1409,9 @@ checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
version = "1.18.0"
|
version = "1.19.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
|
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "onig"
|
name = "onig"
|
||||||
|
@ -2618,6 +2618,7 @@ dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"clap",
|
"clap",
|
||||||
"glob",
|
"glob",
|
||||||
|
"hostname",
|
||||||
"lscolors",
|
"lscolors",
|
||||||
"number_prefix",
|
"number_prefix",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
|
@ -3518,9 +3519,9 @@ checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "xattr"
|
name = "xattr"
|
||||||
version = "1.0.1"
|
version = "1.1.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f4686009f71ff3e5c4dbcf1a282d0a44db3f021ba69350cd42086b3e5f1c6985"
|
checksum = "fbc6ab6ec1907d1a901cdbcd2bd4cb9e7d64ce5c9739cbb97d3c391acd8c7fae"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
|
@ -284,11 +284,12 @@ fundu = "2.0.0"
|
||||||
gcd = "2.3"
|
gcd = "2.3"
|
||||||
glob = "0.3.1"
|
glob = "0.3.1"
|
||||||
half = "2.3"
|
half = "2.3"
|
||||||
|
hostname = "0.3"
|
||||||
indicatif = "0.17"
|
indicatif = "0.17"
|
||||||
itertools = "0.12.0"
|
itertools = "0.12.0"
|
||||||
libc = "0.2.150"
|
libc = "0.2.150"
|
||||||
lscolors = { version = "0.16.0", default-features = false, features = [
|
lscolors = { version = "0.16.0", default-features = false, features = [
|
||||||
"nu-ansi-term",
|
"gnu_legacy",
|
||||||
] }
|
] }
|
||||||
memchr = "2"
|
memchr = "2"
|
||||||
memmap2 = "0.9"
|
memmap2 = "0.9"
|
||||||
|
@ -298,7 +299,7 @@ notify = { version = "=6.0.1", features = ["macos_kqueue"] }
|
||||||
num-bigint = "0.4.4"
|
num-bigint = "0.4.4"
|
||||||
num-traits = "0.2.17"
|
num-traits = "0.2.17"
|
||||||
number_prefix = "0.4"
|
number_prefix = "0.4"
|
||||||
once_cell = "1.18.0"
|
once_cell = "1.19.0"
|
||||||
onig = { version = "~6.4", default-features = false }
|
onig = { version = "~6.4", default-features = false }
|
||||||
parse_datetime = "0.5.0"
|
parse_datetime = "0.5.0"
|
||||||
phf = "0.11.2"
|
phf = "0.11.2"
|
||||||
|
@ -329,7 +330,7 @@ utf-8 = "0.7.6"
|
||||||
walkdir = "2.4"
|
walkdir = "2.4"
|
||||||
winapi-util = "0.1.6"
|
winapi-util = "0.1.6"
|
||||||
windows-sys = { version = "0.48.0", default-features = false }
|
windows-sys = { version = "0.48.0", default-features = false }
|
||||||
xattr = "1.0.1"
|
xattr = "1.1.1"
|
||||||
zip = { version = "0.6.6", default-features = false, features = ["deflate"] }
|
zip = { version = "0.6.6", default-features = false, features = ["deflate"] }
|
||||||
|
|
||||||
hex = "0.4.3"
|
hex = "0.4.3"
|
||||||
|
|
|
@ -17,7 +17,8 @@ uu_date = { path = "../src/uu/date/" }
|
||||||
uu_test = { path = "../src/uu/test/" }
|
uu_test = { path = "../src/uu/test/" }
|
||||||
uu_expr = { path = "../src/uu/expr/" }
|
uu_expr = { path = "../src/uu/expr/" }
|
||||||
uu_printf = { path = "../src/uu/printf/" }
|
uu_printf = { path = "../src/uu/printf/" }
|
||||||
|
uu_echo = { path = "../src/uu/echo/" }
|
||||||
|
uu_seq = { path = "../src/uu/seq/" }
|
||||||
|
|
||||||
# Prevent this from interfering with workspaces
|
# Prevent this from interfering with workspaces
|
||||||
[workspace]
|
[workspace]
|
||||||
|
@ -35,6 +36,18 @@ path = "fuzz_targets/fuzz_printf.rs"
|
||||||
test = false
|
test = false
|
||||||
doc = false
|
doc = false
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "fuzz_echo"
|
||||||
|
path = "fuzz_targets/fuzz_echo.rs"
|
||||||
|
test = false
|
||||||
|
doc = false
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "fuzz_seq"
|
||||||
|
path = "fuzz_targets/fuzz_seq.rs"
|
||||||
|
test = false
|
||||||
|
doc = false
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "fuzz_expr"
|
name = "fuzz_expr"
|
||||||
path = "fuzz_targets/fuzz_expr.rs"
|
path = "fuzz_targets/fuzz_expr.rs"
|
||||||
|
|
89
fuzz/fuzz_targets/fuzz_echo.rs
Normal file
89
fuzz/fuzz_targets/fuzz_echo.rs
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
#![no_main]
|
||||||
|
use libfuzzer_sys::fuzz_target;
|
||||||
|
use uu_echo::uumain;
|
||||||
|
|
||||||
|
use rand::prelude::SliceRandom;
|
||||||
|
use rand::Rng;
|
||||||
|
use std::ffi::OsString;
|
||||||
|
|
||||||
|
mod fuzz_common;
|
||||||
|
use crate::fuzz_common::CommandResult;
|
||||||
|
use crate::fuzz_common::{
|
||||||
|
compare_result, generate_and_run_uumain, generate_random_string, run_gnu_cmd,
|
||||||
|
};
|
||||||
|
|
||||||
|
static CMD_PATH: &str = "echo";
|
||||||
|
|
||||||
|
fn generate_echo() -> String {
|
||||||
|
let mut rng = rand::thread_rng();
|
||||||
|
let mut echo_str = String::new();
|
||||||
|
|
||||||
|
// Randomly decide whether to include options
|
||||||
|
let include_n = rng.gen_bool(0.1); // 10% chance
|
||||||
|
let include_e = rng.gen_bool(0.1); // 10% chance
|
||||||
|
let include_E = rng.gen_bool(0.1); // 10% chance
|
||||||
|
|
||||||
|
if include_n {
|
||||||
|
echo_str.push_str("-n ");
|
||||||
|
}
|
||||||
|
if include_e {
|
||||||
|
echo_str.push_str("-e ");
|
||||||
|
}
|
||||||
|
if include_E {
|
||||||
|
echo_str.push_str("-E ");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a random string
|
||||||
|
echo_str.push_str(&generate_random_string(rng.gen_range(1..=10)));
|
||||||
|
|
||||||
|
// Include escape sequences if -e is enabled
|
||||||
|
if include_e {
|
||||||
|
// Add a 10% chance of including an escape sequence
|
||||||
|
if rng.gen_bool(0.1) {
|
||||||
|
echo_str.push_str(&generate_escape_sequence(&mut rng));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
echo_str
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_escape_sequence(rng: &mut impl Rng) -> String {
|
||||||
|
let escape_sequences = [
|
||||||
|
"\\\\", "\\a", "\\b", "\\c", "\\e", "\\f", "\\n", "\\r", "\\t", "\\v", "\\0NNN", "\\xHH",
|
||||||
|
];
|
||||||
|
// \0NNN and \xHH need more work
|
||||||
|
escape_sequences.choose(rng).unwrap().to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
fuzz_target!(|_data: &[u8]| {
|
||||||
|
let echo_input = generate_echo();
|
||||||
|
let mut args = vec![OsString::from("echo")];
|
||||||
|
args.extend(echo_input.split_whitespace().map(OsString::from));
|
||||||
|
let rust_result = generate_and_run_uumain(&args, uumain);
|
||||||
|
|
||||||
|
let gnu_result = match run_gnu_cmd(CMD_PATH, &args[1..], false) {
|
||||||
|
Ok(result) => result,
|
||||||
|
Err(error_result) => {
|
||||||
|
eprintln!("Failed to run GNU command:");
|
||||||
|
eprintln!("Stderr: {}", error_result.stderr);
|
||||||
|
eprintln!("Exit Code: {}", error_result.exit_code);
|
||||||
|
CommandResult {
|
||||||
|
stdout: String::new(),
|
||||||
|
stderr: error_result.stderr,
|
||||||
|
exit_code: error_result.exit_code,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
compare_result(
|
||||||
|
"echo",
|
||||||
|
&format!("{:?}", &args[1..]),
|
||||||
|
&rust_result.stdout,
|
||||||
|
&gnu_result.stdout,
|
||||||
|
&rust_result.stderr,
|
||||||
|
&gnu_result.stderr,
|
||||||
|
rust_result.exit_code,
|
||||||
|
gnu_result.exit_code,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
});
|
78
fuzz/fuzz_targets/fuzz_seq.rs
Normal file
78
fuzz/fuzz_targets/fuzz_seq.rs
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
// This file is part of the uutils coreutils package.
|
||||||
|
//
|
||||||
|
// For the full copyright and license information, please view the LICENSE
|
||||||
|
// file that was distributed with this source code.
|
||||||
|
// spell-checker:ignore parens
|
||||||
|
|
||||||
|
#![no_main]
|
||||||
|
use libfuzzer_sys::fuzz_target;
|
||||||
|
use uu_seq::uumain;
|
||||||
|
|
||||||
|
use rand::Rng;
|
||||||
|
use std::ffi::OsString;
|
||||||
|
|
||||||
|
mod fuzz_common;
|
||||||
|
use crate::fuzz_common::CommandResult;
|
||||||
|
use crate::fuzz_common::{
|
||||||
|
compare_result, generate_and_run_uumain, generate_random_string, run_gnu_cmd,
|
||||||
|
};
|
||||||
|
static CMD_PATH: &str = "seq";
|
||||||
|
|
||||||
|
fn generate_seq() -> String {
|
||||||
|
let mut rng = rand::thread_rng();
|
||||||
|
|
||||||
|
// Generate 1 to 3 numbers for seq arguments
|
||||||
|
let arg_count = rng.gen_range(1..=3);
|
||||||
|
let mut args = Vec::new();
|
||||||
|
|
||||||
|
for _ in 0..arg_count {
|
||||||
|
if rng.gen_ratio(1, 100) {
|
||||||
|
// 1% chance to add a random string
|
||||||
|
args.push(generate_random_string(rng.gen_range(1..=10)));
|
||||||
|
} else {
|
||||||
|
// 99% chance to add a numeric value
|
||||||
|
match rng.gen_range(0..=3) {
|
||||||
|
0 => args.push(rng.gen_range(-10000..=10000).to_string()), // Large or small integers
|
||||||
|
1 => args.push(rng.gen_range(-100.0..100.0).to_string()), // Floating-point numbers
|
||||||
|
2 => args.push(rng.gen_range(-100..0).to_string()), // Negative integers
|
||||||
|
_ => args.push(rng.gen_range(1..=100).to_string()), // Regular integers
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
args.join(" ")
|
||||||
|
}
|
||||||
|
|
||||||
|
fuzz_target!(|_data: &[u8]| {
|
||||||
|
let seq = generate_seq();
|
||||||
|
let mut args = vec![OsString::from("seq")];
|
||||||
|
args.extend(seq.split_whitespace().map(OsString::from));
|
||||||
|
|
||||||
|
let rust_result = generate_and_run_uumain(&args, uumain);
|
||||||
|
|
||||||
|
let gnu_result = match run_gnu_cmd(CMD_PATH, &args[1..], false) {
|
||||||
|
Ok(result) => result,
|
||||||
|
Err(error_result) => {
|
||||||
|
eprintln!("Failed to run GNU command:");
|
||||||
|
eprintln!("Stderr: {}", error_result.stderr);
|
||||||
|
eprintln!("Exit Code: {}", error_result.exit_code);
|
||||||
|
CommandResult {
|
||||||
|
stdout: String::new(),
|
||||||
|
stderr: error_result.stderr,
|
||||||
|
exit_code: error_result.exit_code,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
compare_result(
|
||||||
|
"seq",
|
||||||
|
&format!("{:?}", &args[1..]),
|
||||||
|
&rust_result.stdout,
|
||||||
|
&gnu_result.stdout,
|
||||||
|
&rust_result.stderr,
|
||||||
|
&gnu_result.stderr,
|
||||||
|
rust_result.exit_code,
|
||||||
|
gnu_result.exit_code,
|
||||||
|
false, // Set to true if you want to fail on stderr diff
|
||||||
|
);
|
||||||
|
});
|
|
@ -16,7 +16,7 @@ path = "src/dircolors.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
clap = { workspace = true }
|
clap = { workspace = true }
|
||||||
uucore = { workspace = true }
|
uucore = { workspace = true, features = ["colors"] }
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "dircolors"
|
name = "dircolors"
|
||||||
|
|
|
@ -1,225 +0,0 @@
|
||||||
// This file is part of the uutils coreutils package.
|
|
||||||
//
|
|
||||||
// For the full copyright and license information, please view the LICENSE
|
|
||||||
// file that was distributed with this source code.
|
|
||||||
// spell-checker:ignore (ToDO) EIGHTBIT ETERM MULTIHARDLINK cpio dtterm jfbterm konsole kterm mlterm rmvb rxvt stat'able svgz tmux webm xspf COLORTERM tzst avif tzst mjpg mjpeg webp dpkg rpmnew rpmorig rpmsave
|
|
||||||
|
|
||||||
pub const INTERNAL_DB: &str = r#"# Configuration file for dircolors, a utility to help you set the
|
|
||||||
# LS_COLORS environment variable used by GNU ls with the --color option.
|
|
||||||
# Copyright (C) 1996-2022 Free Software Foundation, Inc.
|
|
||||||
# Copying and distribution of this file, with or without modification,
|
|
||||||
# are permitted provided the copyright notice and this notice are preserved.
|
|
||||||
# The keywords COLOR, OPTIONS, and EIGHTBIT (honored by the
|
|
||||||
# slackware version of dircolors) are recognized but ignored.
|
|
||||||
# Global config options can be specified before TERM or COLORTERM entries
|
|
||||||
# Below are TERM or COLORTERM entries, which can be glob patterns, which
|
|
||||||
# restrict following config to systems with matching environment variables.
|
|
||||||
COLORTERM ?*
|
|
||||||
TERM Eterm
|
|
||||||
TERM ansi
|
|
||||||
TERM *color*
|
|
||||||
TERM con[0-9]*x[0-9]*
|
|
||||||
TERM cons25
|
|
||||||
TERM console
|
|
||||||
TERM cygwin
|
|
||||||
TERM *direct*
|
|
||||||
TERM dtterm
|
|
||||||
TERM gnome
|
|
||||||
TERM hurd
|
|
||||||
TERM jfbterm
|
|
||||||
TERM konsole
|
|
||||||
TERM kterm
|
|
||||||
TERM linux
|
|
||||||
TERM linux-c
|
|
||||||
TERM mlterm
|
|
||||||
TERM putty
|
|
||||||
TERM rxvt*
|
|
||||||
TERM screen*
|
|
||||||
TERM st
|
|
||||||
TERM terminator
|
|
||||||
TERM tmux*
|
|
||||||
TERM vt100
|
|
||||||
TERM xterm*
|
|
||||||
# Below are the color init strings for the basic file types.
|
|
||||||
# One can use codes for 256 or more colors supported by modern terminals.
|
|
||||||
# The default color codes use the capabilities of an 8 color terminal
|
|
||||||
# with some additional attributes as per the following codes:
|
|
||||||
# Attribute codes:
|
|
||||||
# 00=none 01=bold 04=underscore 05=blink 07=reverse 08=concealed
|
|
||||||
# Text color codes:
|
|
||||||
# 30=black 31=red 32=green 33=yellow 34=blue 35=magenta 36=cyan 37=white
|
|
||||||
# Background color codes:
|
|
||||||
# 40=black 41=red 42=green 43=yellow 44=blue 45=magenta 46=cyan 47=white
|
|
||||||
#NORMAL 00 # no color code at all
|
|
||||||
#FILE 00 # regular file: use no color at all
|
|
||||||
RESET 0 # reset to "normal" color
|
|
||||||
DIR 01;34 # directory
|
|
||||||
LINK 01;36 # symbolic link. (If you set this to 'target' instead of a
|
|
||||||
# numerical value, the color is as for the file pointed to.)
|
|
||||||
MULTIHARDLINK 00 # regular file with more than one link
|
|
||||||
FIFO 40;33 # pipe
|
|
||||||
SOCK 01;35 # socket
|
|
||||||
DOOR 01;35 # door
|
|
||||||
BLK 40;33;01 # block device driver
|
|
||||||
CHR 40;33;01 # character device driver
|
|
||||||
ORPHAN 40;31;01 # symlink to nonexistent file, or non-stat'able file ...
|
|
||||||
MISSING 00 # ... and the files they point to
|
|
||||||
SETUID 37;41 # file that is setuid (u+s)
|
|
||||||
SETGID 30;43 # file that is setgid (g+s)
|
|
||||||
CAPABILITY 00 # file with capability (very expensive to lookup)
|
|
||||||
STICKY_OTHER_WRITABLE 30;42 # dir that is sticky and other-writable (+t,o+w)
|
|
||||||
OTHER_WRITABLE 34;42 # dir that is other-writable (o+w) and not sticky
|
|
||||||
STICKY 37;44 # dir with the sticky bit set (+t) and not other-writable
|
|
||||||
# This is for files with execute permission:
|
|
||||||
EXEC 01;32
|
|
||||||
# List any file extensions like '.gz' or '.tar' that you would like ls
|
|
||||||
# to color below. Put the extension, a space, and the color init string.
|
|
||||||
# (and any comments you want to add after a '#')
|
|
||||||
# If you use DOS-style suffixes, you may want to uncomment the following:
|
|
||||||
#.cmd 01;32 # executables (bright green)
|
|
||||||
#.exe 01;32
|
|
||||||
#.com 01;32
|
|
||||||
#.btm 01;32
|
|
||||||
#.bat 01;32
|
|
||||||
# Or if you want to color scripts even if they do not have the
|
|
||||||
# executable bit actually set.
|
|
||||||
#.sh 01;32
|
|
||||||
#.csh 01;32
|
|
||||||
# archives or compressed (bright red)
|
|
||||||
.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
|
|
||||||
.dz 01;31
|
|
||||||
.gz 01;31
|
|
||||||
.lrz 01;31
|
|
||||||
.lz 01;31
|
|
||||||
.lzo 01;31
|
|
||||||
.xz 01;31
|
|
||||||
.zst 01;31
|
|
||||||
.tzst 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
|
|
||||||
.wim 01;31
|
|
||||||
.swm 01;31
|
|
||||||
.dwm 01;31
|
|
||||||
.esd 01;31
|
|
||||||
# image formats
|
|
||||||
.avif 01;35
|
|
||||||
.jpg 01;35
|
|
||||||
.jpeg 01;35
|
|
||||||
.mjpg 01;35
|
|
||||||
.mjpeg 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
|
|
||||||
.webp 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
|
|
||||||
# https://wiki.xiph.org/MIME_Types_and_File_Extensions
|
|
||||||
.ogv 01;35
|
|
||||||
.ogx 01;35
|
|
||||||
# audio formats
|
|
||||||
.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
|
|
||||||
# https://wiki.xiph.org/MIME_Types_and_File_Extensions
|
|
||||||
.oga 00;36
|
|
||||||
.opus 00;36
|
|
||||||
.spx 00;36
|
|
||||||
.xspf 00;36
|
|
||||||
# backup files
|
|
||||||
*~ 00;90
|
|
||||||
*# 00;90
|
|
||||||
.bak 00;90
|
|
||||||
.old 00;90
|
|
||||||
.orig 00;90
|
|
||||||
.part 00;90
|
|
||||||
.rej 00;90
|
|
||||||
.swp 00;90
|
|
||||||
.tmp 00;90
|
|
||||||
.dpkg-dist 00;90
|
|
||||||
.dpkg-old 00;90
|
|
||||||
.ucf-dist 00;90
|
|
||||||
.ucf-new 00;90
|
|
||||||
.ucf-old 00;90
|
|
||||||
.rpmnew 00;90
|
|
||||||
.rpmorig 00;90
|
|
||||||
.rpmsave 00;90
|
|
||||||
# Subsequent TERM or COLORTERM entries, can be used to add / override
|
|
||||||
# config specific to those matching environment variables."#;
|
|
|
@ -8,10 +8,12 @@
|
||||||
use std::borrow::Borrow;
|
use std::borrow::Borrow;
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
|
//use std::io::IsTerminal;
|
||||||
use std::io::{BufRead, BufReader};
|
use std::io::{BufRead, BufReader};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use clap::{crate_version, Arg, ArgAction, Command};
|
use clap::{crate_version, Arg, ArgAction, Command};
|
||||||
|
use uucore::colors::{FILE_ATTRIBUTE_CODES, FILE_COLORS, FILE_TYPES, TERMS};
|
||||||
use uucore::display::Quotable;
|
use uucore::display::Quotable;
|
||||||
use uucore::error::{UResult, USimpleError, UUsageError};
|
use uucore::error::{UResult, USimpleError, UUsageError};
|
||||||
use uucore::{help_about, help_section, help_usage};
|
use uucore::{help_about, help_section, help_usage};
|
||||||
|
@ -28,9 +30,6 @@ const USAGE: &str = help_usage!("dircolors.md");
|
||||||
const ABOUT: &str = help_about!("dircolors.md");
|
const ABOUT: &str = help_about!("dircolors.md");
|
||||||
const AFTER_HELP: &str = help_section!("after help", "dircolors.md");
|
const AFTER_HELP: &str = help_section!("after help", "dircolors.md");
|
||||||
|
|
||||||
mod colors;
|
|
||||||
use self::colors::INTERNAL_DB;
|
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Debug)]
|
#[derive(PartialEq, Eq, Debug)]
|
||||||
pub enum OutputFmt {
|
pub enum OutputFmt {
|
||||||
Shell,
|
Shell,
|
||||||
|
@ -57,6 +56,77 @@ pub fn guess_syntax() -> OutputFmt {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_colors_format_strings(fmt: &OutputFmt) -> (String, String) {
|
||||||
|
let prefix = match fmt {
|
||||||
|
OutputFmt::Shell => "LS_COLORS='".to_string(),
|
||||||
|
OutputFmt::CShell => "setenv LS_COLORS '".to_string(),
|
||||||
|
OutputFmt::Display => String::new(),
|
||||||
|
OutputFmt::Unknown => unreachable!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let suffix = match fmt {
|
||||||
|
OutputFmt::Shell => "';\nexport LS_COLORS".to_string(),
|
||||||
|
OutputFmt::CShell => "'".to_string(),
|
||||||
|
OutputFmt::Display => String::new(),
|
||||||
|
OutputFmt::Unknown => unreachable!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
(prefix, suffix)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generate_type_output(fmt: &OutputFmt) -> String {
|
||||||
|
match fmt {
|
||||||
|
OutputFmt::Display => FILE_TYPES
|
||||||
|
.iter()
|
||||||
|
.map(|&(_, key, val)| format!("\x1b[{}m{}\t{}\x1b[0m", val, key, val))
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join("\n"),
|
||||||
|
_ => {
|
||||||
|
// Existing logic for other formats
|
||||||
|
FILE_TYPES
|
||||||
|
.iter()
|
||||||
|
.map(|&(_, v1, v2)| format!("{}={}", v1, v2))
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join(":")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_ls_colors(fmt: &OutputFmt, sep: &str) -> String {
|
||||||
|
match fmt {
|
||||||
|
OutputFmt::Display => {
|
||||||
|
let mut display_parts = vec![];
|
||||||
|
let type_output = generate_type_output(fmt);
|
||||||
|
display_parts.push(type_output);
|
||||||
|
for &(extension, code) in FILE_COLORS {
|
||||||
|
let prefix = if extension.starts_with('*') { "" } else { "*" };
|
||||||
|
let formatted_extension =
|
||||||
|
format!("\x1b[{}m{}{}\t{}\x1b[0m", code, prefix, extension, code);
|
||||||
|
display_parts.push(formatted_extension);
|
||||||
|
}
|
||||||
|
display_parts.join("\n")
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
// existing logic for other formats
|
||||||
|
let mut parts = vec![];
|
||||||
|
for &(extension, code) in FILE_COLORS {
|
||||||
|
let prefix = if extension.starts_with('*') { "" } else { "*" };
|
||||||
|
let formatted_extension = format!("{}{}", prefix, extension);
|
||||||
|
parts.push(format!("{}={}", formatted_extension, code));
|
||||||
|
}
|
||||||
|
let (prefix, suffix) = get_colors_format_strings(fmt);
|
||||||
|
let ls_colors = parts.join(sep);
|
||||||
|
format!(
|
||||||
|
"{}{}:{}:{}",
|
||||||
|
prefix,
|
||||||
|
generate_type_output(fmt),
|
||||||
|
ls_colors,
|
||||||
|
suffix
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[uucore::main]
|
#[uucore::main]
|
||||||
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||||
let args = args.collect_ignore();
|
let args = args.collect_ignore();
|
||||||
|
@ -97,7 +167,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||||
),
|
),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
println!("{INTERNAL_DB}");
|
|
||||||
|
println!("{}", generate_dircolors_config());
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -125,7 +196,20 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||||
|
|
||||||
let result;
|
let result;
|
||||||
if files.is_empty() {
|
if files.is_empty() {
|
||||||
result = parse(INTERNAL_DB.lines(), &out_format, "");
|
println!("{}", generate_ls_colors(&out_format, ":"));
|
||||||
|
return Ok(());
|
||||||
|
/*
|
||||||
|
// Check if data is being piped into the program
|
||||||
|
if std::io::stdin().is_terminal() {
|
||||||
|
// No data piped, use default behavior
|
||||||
|
println!("{}", generate_ls_colors(&out_format, ":"));
|
||||||
|
return Ok(());
|
||||||
|
} else {
|
||||||
|
// Data is piped, process the input from stdin
|
||||||
|
let fin = BufReader::new(std::io::stdin());
|
||||||
|
result = parse(fin.lines().map_while(Result::ok), &out_format, "-");
|
||||||
|
}
|
||||||
|
*/
|
||||||
} else if files.len() > 1 {
|
} else if files.len() > 1 {
|
||||||
return Err(UUsageError::new(
|
return Err(UUsageError::new(
|
||||||
1,
|
1,
|
||||||
|
@ -133,6 +217,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||||
));
|
));
|
||||||
} else if files[0].eq("-") {
|
} else if files[0].eq("-") {
|
||||||
let fin = BufReader::new(std::io::stdin());
|
let fin = BufReader::new(std::io::stdin());
|
||||||
|
// For example, for echo "owt 40;33"|dircolors -b -
|
||||||
result = parse(fin.lines().map_while(Result::ok), &out_format, files[0]);
|
result = parse(fin.lines().map_while(Result::ok), &out_format, files[0]);
|
||||||
} else {
|
} else {
|
||||||
let path = Path::new(files[0]);
|
let path = Path::new(files[0]);
|
||||||
|
@ -276,69 +361,25 @@ enum ParseState {
|
||||||
Pass,
|
Pass,
|
||||||
}
|
}
|
||||||
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use uucore::{format_usage, parse_glob};
|
use uucore::{format_usage, parse_glob};
|
||||||
|
|
||||||
#[allow(clippy::cognitive_complexity)]
|
#[allow(clippy::cognitive_complexity)]
|
||||||
fn parse<T>(lines: T, fmt: &OutputFmt, fp: &str) -> Result<String, String>
|
fn parse<T>(user_input: T, fmt: &OutputFmt, fp: &str) -> Result<String, String>
|
||||||
where
|
where
|
||||||
T: IntoIterator,
|
T: IntoIterator,
|
||||||
T::Item: Borrow<str>,
|
T::Item: Borrow<str>,
|
||||||
{
|
{
|
||||||
// 1790 > $(dircolors | wc -m)
|
|
||||||
let mut result = String::with_capacity(1790);
|
let mut result = String::with_capacity(1790);
|
||||||
match fmt {
|
let (prefix, suffix) = get_colors_format_strings(fmt);
|
||||||
OutputFmt::Shell => result.push_str("LS_COLORS='"),
|
|
||||||
OutputFmt::CShell => result.push_str("setenv LS_COLORS '"),
|
|
||||||
OutputFmt::Display => (),
|
|
||||||
OutputFmt::Unknown => unreachable!(),
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut table: HashMap<&str, &str> = HashMap::with_capacity(48);
|
result.push_str(&prefix);
|
||||||
table.insert("normal", "no");
|
|
||||||
table.insert("norm", "no");
|
|
||||||
table.insert("file", "fi");
|
|
||||||
table.insert("reset", "rs");
|
|
||||||
table.insert("dir", "di");
|
|
||||||
table.insert("lnk", "ln");
|
|
||||||
table.insert("link", "ln");
|
|
||||||
table.insert("symlink", "ln");
|
|
||||||
table.insert("orphan", "or");
|
|
||||||
table.insert("missing", "mi");
|
|
||||||
table.insert("fifo", "pi");
|
|
||||||
table.insert("pipe", "pi");
|
|
||||||
table.insert("sock", "so");
|
|
||||||
table.insert("blk", "bd");
|
|
||||||
table.insert("block", "bd");
|
|
||||||
table.insert("chr", "cd");
|
|
||||||
table.insert("char", "cd");
|
|
||||||
table.insert("door", "do");
|
|
||||||
table.insert("exec", "ex");
|
|
||||||
table.insert("left", "lc");
|
|
||||||
table.insert("leftcode", "lc");
|
|
||||||
table.insert("right", "rc");
|
|
||||||
table.insert("rightcode", "rc");
|
|
||||||
table.insert("end", "ec");
|
|
||||||
table.insert("endcode", "ec");
|
|
||||||
table.insert("suid", "su");
|
|
||||||
table.insert("setuid", "su");
|
|
||||||
table.insert("sgid", "sg");
|
|
||||||
table.insert("setgid", "sg");
|
|
||||||
table.insert("sticky", "st");
|
|
||||||
table.insert("other_writable", "ow");
|
|
||||||
table.insert("owr", "ow");
|
|
||||||
table.insert("sticky_other_writable", "tw");
|
|
||||||
table.insert("owt", "tw");
|
|
||||||
table.insert("capability", "ca");
|
|
||||||
table.insert("multihardlink", "mh");
|
|
||||||
table.insert("clrtoeol", "cl");
|
|
||||||
|
|
||||||
let term = env::var("TERM").unwrap_or_else(|_| "none".to_owned());
|
let term = env::var("TERM").unwrap_or_else(|_| "none".to_owned());
|
||||||
let term = term.as_str();
|
let term = term.as_str();
|
||||||
|
|
||||||
let mut state = ParseState::Global;
|
let mut state = ParseState::Global;
|
||||||
|
|
||||||
for (num, line) in lines.into_iter().enumerate() {
|
for (num, line) in user_input.into_iter().enumerate() {
|
||||||
let num = num + 1;
|
let num = num + 1;
|
||||||
let line = line.borrow().purify();
|
let line = line.borrow().purify();
|
||||||
if line.is_empty() {
|
if line.is_empty() {
|
||||||
|
@ -350,13 +391,13 @@ where
|
||||||
let (key, val) = line.split_two();
|
let (key, val) = line.split_two();
|
||||||
if val.is_empty() {
|
if val.is_empty() {
|
||||||
return Err(format!(
|
return Err(format!(
|
||||||
|
// The double space is what GNU is doing
|
||||||
"{}:{}: invalid line; missing second token",
|
"{}:{}: invalid line; missing second token",
|
||||||
fp.maybe_quote(),
|
fp.maybe_quote(),
|
||||||
num
|
num
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
let lower = key.to_lowercase();
|
let lower = key.to_lowercase();
|
||||||
|
|
||||||
if lower == "term" || lower == "colorterm" {
|
if lower == "term" || lower == "colorterm" {
|
||||||
if term.fnmatch(val) {
|
if term.fnmatch(val) {
|
||||||
state = ParseState::Matched;
|
state = ParseState::Matched;
|
||||||
|
@ -370,6 +411,8 @@ where
|
||||||
state = ParseState::Continue;
|
state = ParseState::Continue;
|
||||||
}
|
}
|
||||||
if state != ParseState::Pass {
|
if state != ParseState::Pass {
|
||||||
|
let search_key = lower.as_str();
|
||||||
|
|
||||||
if key.starts_with('.') {
|
if key.starts_with('.') {
|
||||||
if *fmt == OutputFmt::Display {
|
if *fmt == OutputFmt::Display {
|
||||||
result.push_str(format!("\x1b[{val}m*{key}\t{val}\x1b[0m\n").as_str());
|
result.push_str(format!("\x1b[{val}m*{key}\t{val}\x1b[0m\n").as_str());
|
||||||
|
@ -384,7 +427,10 @@ where
|
||||||
}
|
}
|
||||||
} else if lower == "options" || lower == "color" || lower == "eightbit" {
|
} else if lower == "options" || lower == "color" || lower == "eightbit" {
|
||||||
// Slackware only. Ignore
|
// Slackware only. Ignore
|
||||||
} else if let Some(s) = table.get(lower.as_str()) {
|
} else if let Some((_, s)) = FILE_ATTRIBUTE_CODES
|
||||||
|
.iter()
|
||||||
|
.find(|&&(key, _)| key == search_key)
|
||||||
|
{
|
||||||
if *fmt == OutputFmt::Display {
|
if *fmt == OutputFmt::Display {
|
||||||
result.push_str(format!("\x1b[{val}m{s}\t{val}\x1b[0m\n").as_str());
|
result.push_str(format!("\x1b[{val}m{s}\t{val}\x1b[0m\n").as_str());
|
||||||
} else {
|
} else {
|
||||||
|
@ -402,15 +448,11 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
match fmt {
|
if fmt == &OutputFmt::Display {
|
||||||
OutputFmt::Shell => result.push_str("';\nexport LS_COLORS"),
|
// remove latest "\n"
|
||||||
OutputFmt::CShell => result.push('\''),
|
result.pop();
|
||||||
OutputFmt::Display => {
|
|
||||||
// remove latest "\n"
|
|
||||||
result.pop();
|
|
||||||
}
|
|
||||||
OutputFmt::Unknown => unreachable!(),
|
|
||||||
}
|
}
|
||||||
|
result.push_str(&suffix);
|
||||||
|
|
||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
@ -436,6 +478,58 @@ fn escape(s: &str) -> String {
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn generate_dircolors_config() -> String {
|
||||||
|
let mut config = String::new();
|
||||||
|
|
||||||
|
config.push_str(
|
||||||
|
"\
|
||||||
|
# Configuration file for dircolors, a utility to help you set the\n\
|
||||||
|
# LS_COLORS environment variable used by GNU ls with the --color option.\n\
|
||||||
|
# The keywords COLOR, OPTIONS, and EIGHTBIT (honored by the\n\
|
||||||
|
# slackware version of dircolors) are recognized but ignored.\n\
|
||||||
|
# Global config options can be specified before TERM or COLORTERM entries\n\
|
||||||
|
# Below are TERM or COLORTERM entries, which can be glob patterns, which\n\
|
||||||
|
# restrict following config to systems with matching environment variables.\n\
|
||||||
|
",
|
||||||
|
);
|
||||||
|
config.push_str("COLORTERM ?*\n");
|
||||||
|
for term in TERMS {
|
||||||
|
config.push_str(&format!("TERM {}\n", term));
|
||||||
|
}
|
||||||
|
|
||||||
|
config.push_str(
|
||||||
|
"\
|
||||||
|
# Below are the color init strings for the basic file types.\n\
|
||||||
|
# One can use codes for 256 or more colors supported by modern terminals.\n\
|
||||||
|
# The default color codes use the capabilities of an 8 color terminal\n\
|
||||||
|
# with some additional attributes as per the following codes:\n\
|
||||||
|
# Attribute codes:\n\
|
||||||
|
# 00=none 01=bold 04=underscore 05=blink 07=reverse 08=concealed\n\
|
||||||
|
# Text color codes:\n\
|
||||||
|
# 30=black 31=red 32=green 33=yellow 34=blue 35=magenta 36=cyan 37=white\n\
|
||||||
|
# Background color codes:\n\
|
||||||
|
# 40=black 41=red 42=green 43=yellow 44=blue 45=magenta 46=cyan 47=white\n\
|
||||||
|
#NORMAL 00 # no color code at all\n\
|
||||||
|
#FILE 00 # regular file: use no color at all\n\
|
||||||
|
",
|
||||||
|
);
|
||||||
|
|
||||||
|
for (name, _, code) in FILE_TYPES {
|
||||||
|
config.push_str(&format!("{} {}\n", name, code));
|
||||||
|
}
|
||||||
|
|
||||||
|
config.push_str("# List any file extensions like '.gz' or '.tar' that you would like ls\n");
|
||||||
|
config.push_str("# to color below. Put the extension, a space, and the color init string.\n");
|
||||||
|
|
||||||
|
for (ext, color) in FILE_COLORS {
|
||||||
|
config.push_str(&format!("{} {}\n", ext, color));
|
||||||
|
}
|
||||||
|
config.push_str("# Subsequent TERM or COLORTERM entries, can be used to add / override\n");
|
||||||
|
config.push_str("# config specific to those matching environment variables.");
|
||||||
|
|
||||||
|
config
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::escape;
|
use super::escape;
|
||||||
|
|
|
@ -3,35 +3,30 @@
|
||||||
// For the full copyright and license information, please view the LICENSE
|
// For the full copyright and license information, please view the LICENSE
|
||||||
// file that was distributed with this source code.
|
// file that was distributed with this source code.
|
||||||
|
|
||||||
use chrono::prelude::DateTime;
|
use chrono::{DateTime, Local};
|
||||||
use chrono::Local;
|
use clap::{crate_version, Arg, ArgAction, ArgMatches, Command};
|
||||||
use clap::ArgAction;
|
|
||||||
use clap::{crate_version, Arg, ArgMatches, Command};
|
|
||||||
use glob::Pattern;
|
use glob::Pattern;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::fs;
|
use std::error::Error;
|
||||||
use std::fs::File;
|
use std::fmt::Display;
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
use std::fs::Metadata;
|
use std::fs::Metadata;
|
||||||
use std::io::BufRead;
|
use std::fs::{self, File};
|
||||||
use std::io::BufReader;
|
use std::io::{BufRead, BufReader};
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
use std::os::unix::fs::MetadataExt;
|
use std::os::unix::fs::MetadataExt;
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
use std::os::windows::fs::MetadataExt;
|
use std::os::windows::fs::MetadataExt;
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
use std::os::windows::io::AsRawHandle;
|
use std::os::windows::io::AsRawHandle;
|
||||||
use std::path::Path;
|
use std::path::{Path, PathBuf};
|
||||||
use std::path::PathBuf;
|
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::sync::mpsc;
|
use std::sync::mpsc;
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use std::time::{Duration, UNIX_EPOCH};
|
use std::time::{Duration, UNIX_EPOCH};
|
||||||
use std::{error::Error, fmt::Display};
|
|
||||||
use uucore::display::{print_verbatim, Quotable};
|
use uucore::display::{print_verbatim, Quotable};
|
||||||
use uucore::error::FromIo;
|
use uucore::error::{FromIo, UError, UResult, USimpleError};
|
||||||
use uucore::error::{UError, UResult, USimpleError};
|
|
||||||
use uucore::line_ending::LineEnding;
|
use uucore::line_ending::LineEnding;
|
||||||
use uucore::parse_glob;
|
use uucore::parse_glob;
|
||||||
use uucore::parse_size::{parse_size_u64, ParseSizeError};
|
use uucore::parse_size::{parse_size_u64, ParseSizeError};
|
||||||
|
@ -81,17 +76,27 @@ const USAGE: &str = help_usage!("du.md");
|
||||||
// TODO: Support Z & Y (currently limited by size of u64)
|
// TODO: Support Z & Y (currently limited by size of u64)
|
||||||
const UNITS: [(char, u32); 6] = [('E', 6), ('P', 5), ('T', 4), ('G', 3), ('M', 2), ('K', 1)];
|
const UNITS: [(char, u32); 6] = [('E', 6), ('P', 5), ('T', 4), ('G', 3), ('M', 2), ('K', 1)];
|
||||||
|
|
||||||
#[derive(Clone)]
|
struct TraversalOptions {
|
||||||
struct Options {
|
|
||||||
all: bool,
|
all: bool,
|
||||||
max_depth: Option<usize>,
|
|
||||||
total: bool,
|
|
||||||
separate_dirs: bool,
|
separate_dirs: bool,
|
||||||
one_file_system: bool,
|
one_file_system: bool,
|
||||||
dereference: Deref,
|
dereference: Deref,
|
||||||
count_links: bool,
|
count_links: bool,
|
||||||
inodes: bool,
|
|
||||||
verbose: bool,
|
verbose: bool,
|
||||||
|
excludes: Vec<Pattern>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct StatPrinter {
|
||||||
|
total: bool,
|
||||||
|
inodes: bool,
|
||||||
|
max_depth: Option<usize>,
|
||||||
|
threshold: Option<Threshold>,
|
||||||
|
apparent_size: bool,
|
||||||
|
size_format: SizeFormat,
|
||||||
|
time: Option<Time>,
|
||||||
|
time_format: String,
|
||||||
|
line_ending: LineEnding,
|
||||||
|
summarize: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Clone)]
|
#[derive(PartialEq, Clone)]
|
||||||
|
@ -101,6 +106,19 @@ enum Deref {
|
||||||
None,
|
None,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
enum Time {
|
||||||
|
Accessed,
|
||||||
|
Modified,
|
||||||
|
Created,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
enum SizeFormat {
|
||||||
|
Human(u64),
|
||||||
|
BlockSize(u64),
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Hash, Clone, Copy)]
|
#[derive(PartialEq, Eq, Hash, Clone, Copy)]
|
||||||
struct FileInfo {
|
struct FileInfo {
|
||||||
file_id: u128,
|
file_id: u128,
|
||||||
|
@ -120,7 +138,7 @@ struct Stat {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Stat {
|
impl Stat {
|
||||||
fn new(path: &Path, options: &Options) -> std::io::Result<Self> {
|
fn new(path: &Path, options: &TraversalOptions) -> std::io::Result<Self> {
|
||||||
// Determine whether to dereference (follow) the symbolic link
|
// Determine whether to dereference (follow) the symbolic link
|
||||||
let should_dereference = match &options.dereference {
|
let should_dereference = match &options.dereference {
|
||||||
Deref::All => true,
|
Deref::All => true,
|
||||||
|
@ -278,26 +296,13 @@ fn read_block_size(s: Option<&str>) -> UResult<u64> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn choose_size(matches: &ArgMatches, stat: &Stat) -> u64 {
|
|
||||||
if matches.get_flag(options::INODES) {
|
|
||||||
stat.inodes
|
|
||||||
} else if matches.get_flag(options::APPARENT_SIZE) || matches.get_flag(options::BYTES) {
|
|
||||||
stat.size
|
|
||||||
} else {
|
|
||||||
// The st_blocks field indicates the number of blocks allocated to the file, 512-byte units.
|
|
||||||
// See: http://linux.die.net/man/2/stat
|
|
||||||
stat.blocks * 512
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// this takes `my_stat` to avoid having to stat files multiple times.
|
// this takes `my_stat` to avoid having to stat files multiple times.
|
||||||
#[allow(clippy::cognitive_complexity)]
|
#[allow(clippy::cognitive_complexity)]
|
||||||
fn du(
|
fn du(
|
||||||
mut my_stat: Stat,
|
mut my_stat: Stat,
|
||||||
options: &Options,
|
options: &TraversalOptions,
|
||||||
depth: usize,
|
depth: usize,
|
||||||
seen_inodes: &mut HashSet<FileInfo>,
|
seen_inodes: &mut HashSet<FileInfo>,
|
||||||
exclude: &[Pattern],
|
|
||||||
print_tx: &mpsc::Sender<UResult<StatPrintInfo>>,
|
print_tx: &mpsc::Sender<UResult<StatPrintInfo>>,
|
||||||
) -> Result<Stat, Box<mpsc::SendError<UResult<StatPrintInfo>>>> {
|
) -> Result<Stat, Box<mpsc::SendError<UResult<StatPrintInfo>>>> {
|
||||||
if my_stat.is_dir {
|
if my_stat.is_dir {
|
||||||
|
@ -317,7 +322,7 @@ fn du(
|
||||||
match Stat::new(&entry.path(), options) {
|
match Stat::new(&entry.path(), options) {
|
||||||
Ok(this_stat) => {
|
Ok(this_stat) => {
|
||||||
// We have an exclude list
|
// We have an exclude list
|
||||||
for pattern in exclude {
|
for pattern in &options.excludes {
|
||||||
// Look at all patterns with both short and long paths
|
// Look at all patterns with both short and long paths
|
||||||
// if we have 'du foo' but search to exclude 'foo/bar'
|
// if we have 'du foo' but search to exclude 'foo/bar'
|
||||||
// we need the full path
|
// we need the full path
|
||||||
|
@ -353,14 +358,8 @@ fn du(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let this_stat = du(
|
let this_stat =
|
||||||
this_stat,
|
du(this_stat, options, depth + 1, seen_inodes, print_tx)?;
|
||||||
options,
|
|
||||||
depth + 1,
|
|
||||||
seen_inodes,
|
|
||||||
exclude,
|
|
||||||
print_tx,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
if !options.separate_dirs {
|
if !options.separate_dirs {
|
||||||
my_stat.size += this_stat.size;
|
my_stat.size += this_stat.size;
|
||||||
|
@ -396,58 +395,12 @@ fn du(
|
||||||
Ok(my_stat)
|
Ok(my_stat)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn convert_size_human(size: u64, multiplier: u64, _block_size: u64) -> String {
|
|
||||||
for &(unit, power) in &UNITS {
|
|
||||||
let limit = multiplier.pow(power);
|
|
||||||
if size >= limit {
|
|
||||||
return format!("{:.1}{}", (size as f64) / (limit as f64), unit);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if size == 0 {
|
|
||||||
return "0".to_string();
|
|
||||||
}
|
|
||||||
format!("{size}B")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn convert_size_b(size: u64, _multiplier: u64, _block_size: u64) -> String {
|
|
||||||
format!("{}", ((size as f64) / (1_f64)).ceil())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn convert_size_k(size: u64, multiplier: u64, _block_size: u64) -> String {
|
|
||||||
format!("{}", ((size as f64) / (multiplier as f64)).ceil())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn convert_size_m(size: u64, multiplier: u64, _block_size: u64) -> String {
|
|
||||||
format!(
|
|
||||||
"{}",
|
|
||||||
((size as f64) / ((multiplier * multiplier) as f64)).ceil()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn convert_size_other(size: u64, _multiplier: u64, block_size: u64) -> String {
|
|
||||||
format!("{}", ((size as f64) / (block_size as f64)).ceil())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_convert_size_fn(matches: &ArgMatches) -> Box<dyn Fn(u64, u64, u64) -> String + Send> {
|
|
||||||
if matches.get_flag(options::HUMAN_READABLE) || matches.get_flag(options::SI) {
|
|
||||||
Box::new(convert_size_human)
|
|
||||||
} else if matches.get_flag(options::BYTES) {
|
|
||||||
Box::new(convert_size_b)
|
|
||||||
} else if matches.get_flag(options::BLOCK_SIZE_1K) {
|
|
||||||
Box::new(convert_size_k)
|
|
||||||
} else if matches.get_flag(options::BLOCK_SIZE_1M) {
|
|
||||||
Box::new(convert_size_m)
|
|
||||||
} else {
|
|
||||||
Box::new(convert_size_other)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
enum DuError {
|
enum DuError {
|
||||||
InvalidMaxDepthArg(String),
|
InvalidMaxDepthArg(String),
|
||||||
SummarizeDepthConflict(String),
|
SummarizeDepthConflict(String),
|
||||||
InvalidTimeStyleArg(String),
|
InvalidTimeStyleArg(String),
|
||||||
InvalidTimeArg(String),
|
InvalidTimeArg,
|
||||||
InvalidGlob(String),
|
InvalidGlob(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -473,11 +426,9 @@ Try '{} --help' for more information.",
|
||||||
s.quote(),
|
s.quote(),
|
||||||
uucore::execution_phrase()
|
uucore::execution_phrase()
|
||||||
),
|
),
|
||||||
Self::InvalidTimeArg(s) => write!(
|
Self::InvalidTimeArg => write!(
|
||||||
f,
|
f,
|
||||||
"Invalid argument {} for --time.
|
"'birth' and 'creation' arguments for --time are not supported on this platform.",
|
||||||
'birth' and 'creation' arguments are not supported on this platform.",
|
|
||||||
s.quote()
|
|
||||||
),
|
),
|
||||||
Self::InvalidGlob(s) => write!(f, "Invalid exclude syntax: {s}"),
|
Self::InvalidGlob(s) => write!(f, "Invalid exclude syntax: {s}"),
|
||||||
}
|
}
|
||||||
|
@ -492,7 +443,7 @@ impl UError for DuError {
|
||||||
Self::InvalidMaxDepthArg(_)
|
Self::InvalidMaxDepthArg(_)
|
||||||
| Self::SummarizeDepthConflict(_)
|
| Self::SummarizeDepthConflict(_)
|
||||||
| Self::InvalidTimeStyleArg(_)
|
| Self::InvalidTimeStyleArg(_)
|
||||||
| Self::InvalidTimeArg(_)
|
| Self::InvalidTimeArg
|
||||||
| Self::InvalidGlob(_) => 1,
|
| Self::InvalidGlob(_) => 1,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -539,66 +490,17 @@ struct StatPrintInfo {
|
||||||
depth: usize,
|
depth: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct StatPrinter {
|
|
||||||
matches: ArgMatches,
|
|
||||||
threshold: Option<Threshold>,
|
|
||||||
summarize: bool,
|
|
||||||
time_format_str: String,
|
|
||||||
line_ending: LineEnding,
|
|
||||||
options: Options,
|
|
||||||
convert_size: Box<dyn Fn(u64) -> String + Send>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl StatPrinter {
|
impl StatPrinter {
|
||||||
fn new(matches: ArgMatches, options: Options, summarize: bool) -> UResult<Self> {
|
fn choose_size(&self, stat: &Stat) -> u64 {
|
||||||
let block_size = read_block_size(
|
if self.inodes {
|
||||||
matches
|
stat.inodes
|
||||||
.get_one::<String>(options::BLOCK_SIZE)
|
} else if self.apparent_size {
|
||||||
.map(|s| s.as_str()),
|
stat.size
|
||||||
)?;
|
|
||||||
|
|
||||||
let multiplier: u64 = if matches.get_flag(options::SI) {
|
|
||||||
1000
|
|
||||||
} else {
|
} else {
|
||||||
1024
|
// The st_blocks field indicates the number of blocks allocated to the file, 512-byte units.
|
||||||
};
|
// See: http://linux.die.net/man/2/stat
|
||||||
|
stat.blocks * 512
|
||||||
let convert_size_fn = get_convert_size_fn(&matches);
|
}
|
||||||
|
|
||||||
let convert_size: Box<dyn Fn(u64) -> String + Send> = if options.inodes {
|
|
||||||
Box::new(|size: u64| size.to_string())
|
|
||||||
} else {
|
|
||||||
Box::new(move |size: u64| convert_size_fn(size, multiplier, block_size))
|
|
||||||
};
|
|
||||||
|
|
||||||
let threshold = match matches.get_one::<String>(options::THRESHOLD) {
|
|
||||||
Some(s) => match Threshold::from_str(s) {
|
|
||||||
Ok(t) => Some(t),
|
|
||||||
Err(e) => {
|
|
||||||
return Err(USimpleError::new(
|
|
||||||
1,
|
|
||||||
format_error_message(&e, s, options::THRESHOLD),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
None => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
let time_format_str =
|
|
||||||
parse_time_style(matches.get_one::<String>("time-style").map(|s| s.as_str()))?
|
|
||||||
.to_string();
|
|
||||||
|
|
||||||
let line_ending = LineEnding::from_zero_flag(matches.get_flag(options::NULL));
|
|
||||||
|
|
||||||
Ok(Self {
|
|
||||||
matches,
|
|
||||||
threshold,
|
|
||||||
summarize,
|
|
||||||
time_format_str,
|
|
||||||
line_ending,
|
|
||||||
options,
|
|
||||||
convert_size,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn print_stats(&self, rx: &mpsc::Receiver<UResult<StatPrintInfo>>) -> UResult<()> {
|
fn print_stats(&self, rx: &mpsc::Receiver<UResult<StatPrintInfo>>) -> UResult<()> {
|
||||||
|
@ -609,7 +511,7 @@ impl StatPrinter {
|
||||||
match received {
|
match received {
|
||||||
Ok(message) => match message {
|
Ok(message) => match message {
|
||||||
Ok(stat_info) => {
|
Ok(stat_info) => {
|
||||||
let size = choose_size(&self.matches, &stat_info.stat);
|
let size = self.choose_size(&stat_info.stat);
|
||||||
|
|
||||||
if stat_info.depth == 0 {
|
if stat_info.depth == 0 {
|
||||||
grand_total += size;
|
grand_total += size;
|
||||||
|
@ -619,7 +521,6 @@ impl StatPrinter {
|
||||||
.threshold
|
.threshold
|
||||||
.map_or(false, |threshold| threshold.should_exclude(size))
|
.map_or(false, |threshold| threshold.should_exclude(size))
|
||||||
&& self
|
&& self
|
||||||
.options
|
|
||||||
.max_depth
|
.max_depth
|
||||||
.map_or(true, |max_depth| stat_info.depth <= max_depth)
|
.map_or(true, |max_depth| stat_info.depth <= max_depth)
|
||||||
&& (!self.summarize || stat_info.depth == 0)
|
&& (!self.summarize || stat_info.depth == 0)
|
||||||
|
@ -633,29 +534,43 @@ impl StatPrinter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.options.total {
|
if self.total {
|
||||||
print!("{}\ttotal", (self.convert_size)(grand_total));
|
print!("{}\ttotal", self.convert_size(grand_total));
|
||||||
print!("{}", self.line_ending);
|
print!("{}", self.line_ending);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn convert_size(&self, size: u64) -> String {
|
||||||
|
if self.inodes {
|
||||||
|
return size.to_string();
|
||||||
|
}
|
||||||
|
match self.size_format {
|
||||||
|
SizeFormat::Human(multiplier) => {
|
||||||
|
if size == 0 {
|
||||||
|
return "0".to_string();
|
||||||
|
}
|
||||||
|
for &(unit, power) in &UNITS {
|
||||||
|
let limit = multiplier.pow(power);
|
||||||
|
if size >= limit {
|
||||||
|
return format!("{:.1}{}", (size as f64) / (limit as f64), unit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
format!("{size}B")
|
||||||
|
}
|
||||||
|
SizeFormat::BlockSize(block_size) => div_ceil(size, block_size).to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn print_stat(&self, stat: &Stat, size: u64) -> UResult<()> {
|
fn print_stat(&self, stat: &Stat, size: u64) -> UResult<()> {
|
||||||
if self.matches.contains_id(options::TIME) {
|
if let Some(time) = self.time {
|
||||||
let tm = {
|
let secs = get_time_secs(time, stat)?;
|
||||||
let secs = self
|
let tm = DateTime::<Local>::from(UNIX_EPOCH + Duration::from_secs(secs));
|
||||||
.matches
|
let time_str = tm.format(&self.time_format).to_string();
|
||||||
.get_one::<String>(options::TIME)
|
print!("{}\t{}\t", self.convert_size(size), time_str);
|
||||||
.map(|s| get_time_secs(s, stat))
|
|
||||||
.transpose()?
|
|
||||||
.unwrap_or(stat.modified);
|
|
||||||
DateTime::<Local>::from(UNIX_EPOCH + Duration::from_secs(secs))
|
|
||||||
};
|
|
||||||
let time_str = tm.format(&self.time_format_str).to_string();
|
|
||||||
print!("{}\t{}\t", (self.convert_size)(size), time_str);
|
|
||||||
} else {
|
} else {
|
||||||
print!("{}\t", (self.convert_size)(size));
|
print!("{}\t", self.convert_size(size));
|
||||||
}
|
}
|
||||||
|
|
||||||
print_verbatim(&stat.path).unwrap();
|
print_verbatim(&stat.path).unwrap();
|
||||||
|
@ -665,6 +580,13 @@ impl StatPrinter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This can be replaced with u64::div_ceil once it is stabilized.
|
||||||
|
// This implementation approach is optimized for when `b` is a constant,
|
||||||
|
// particularly a power of two.
|
||||||
|
pub fn div_ceil(a: u64, b: u64) -> u64 {
|
||||||
|
(a + b - 1) / b
|
||||||
|
}
|
||||||
|
|
||||||
#[uucore::main]
|
#[uucore::main]
|
||||||
#[allow(clippy::cognitive_complexity)]
|
#[allow(clippy::cognitive_complexity)]
|
||||||
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||||
|
@ -690,10 +612,35 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||||
None => vec![PathBuf::from(".")],
|
None => vec![PathBuf::from(".")],
|
||||||
};
|
};
|
||||||
|
|
||||||
let options = Options {
|
let time = matches.contains_id(options::TIME).then(|| {
|
||||||
|
match matches.get_one::<String>(options::TIME).map(AsRef::as_ref) {
|
||||||
|
None | Some("ctime" | "status") => Time::Modified,
|
||||||
|
Some("access" | "atime" | "use") => Time::Accessed,
|
||||||
|
Some("birth" | "creation") => Time::Created,
|
||||||
|
_ => unreachable!("should be caught by clap"),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let size_format = if matches.get_flag(options::HUMAN_READABLE) {
|
||||||
|
SizeFormat::Human(1024)
|
||||||
|
} else if matches.get_flag(options::SI) {
|
||||||
|
SizeFormat::Human(1000)
|
||||||
|
} else if matches.get_flag(options::BYTES) {
|
||||||
|
SizeFormat::BlockSize(1)
|
||||||
|
} else if matches.get_flag(options::BLOCK_SIZE_1K) {
|
||||||
|
SizeFormat::BlockSize(1024)
|
||||||
|
} else if matches.get_flag(options::BLOCK_SIZE_1M) {
|
||||||
|
SizeFormat::BlockSize(1024 * 1024)
|
||||||
|
} else {
|
||||||
|
SizeFormat::BlockSize(read_block_size(
|
||||||
|
matches
|
||||||
|
.get_one::<String>(options::BLOCK_SIZE)
|
||||||
|
.map(AsRef::as_ref),
|
||||||
|
)?)
|
||||||
|
};
|
||||||
|
|
||||||
|
let traversal_options = TraversalOptions {
|
||||||
all: matches.get_flag(options::ALL),
|
all: matches.get_flag(options::ALL),
|
||||||
max_depth,
|
|
||||||
total: matches.get_flag(options::TOTAL),
|
|
||||||
separate_dirs: matches.get_flag(options::SEPARATE_DIRS),
|
separate_dirs: matches.get_flag(options::SEPARATE_DIRS),
|
||||||
one_file_system: matches.get_flag(options::ONE_FILE_SYSTEM),
|
one_file_system: matches.get_flag(options::ONE_FILE_SYSTEM),
|
||||||
dereference: if matches.get_flag(options::DEREFERENCE) {
|
dereference: if matches.get_flag(options::DEREFERENCE) {
|
||||||
|
@ -705,31 +652,49 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||||
Deref::None
|
Deref::None
|
||||||
},
|
},
|
||||||
count_links: matches.get_flag(options::COUNT_LINKS),
|
count_links: matches.get_flag(options::COUNT_LINKS),
|
||||||
inodes: matches.get_flag(options::INODES),
|
|
||||||
verbose: matches.get_flag(options::VERBOSE),
|
verbose: matches.get_flag(options::VERBOSE),
|
||||||
|
excludes: build_exclude_patterns(&matches)?,
|
||||||
};
|
};
|
||||||
|
|
||||||
if options.inodes
|
let stat_printer = StatPrinter {
|
||||||
|
max_depth,
|
||||||
|
size_format,
|
||||||
|
summarize,
|
||||||
|
total: matches.get_flag(options::TOTAL),
|
||||||
|
inodes: matches.get_flag(options::INODES),
|
||||||
|
threshold: matches
|
||||||
|
.get_one::<String>(options::THRESHOLD)
|
||||||
|
.map(|s| {
|
||||||
|
Threshold::from_str(s).map_err(|e| {
|
||||||
|
USimpleError::new(1, format_error_message(&e, s, options::THRESHOLD))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.transpose()?,
|
||||||
|
apparent_size: matches.get_flag(options::APPARENT_SIZE) || matches.get_flag(options::BYTES),
|
||||||
|
time,
|
||||||
|
time_format: parse_time_style(matches.get_one::<String>("time-style").map(|s| s.as_str()))?
|
||||||
|
.to_string(),
|
||||||
|
line_ending: LineEnding::from_zero_flag(matches.get_flag(options::NULL)),
|
||||||
|
};
|
||||||
|
|
||||||
|
if stat_printer.inodes
|
||||||
&& (matches.get_flag(options::APPARENT_SIZE) || matches.get_flag(options::BYTES))
|
&& (matches.get_flag(options::APPARENT_SIZE) || matches.get_flag(options::BYTES))
|
||||||
{
|
{
|
||||||
show_warning!("options --apparent-size and -b are ineffective with --inodes");
|
show_warning!("options --apparent-size and -b are ineffective with --inodes");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use separate thread to print output, so we can print finished results while computation is still running
|
// Use separate thread to print output, so we can print finished results while computation is still running
|
||||||
let stat_printer = StatPrinter::new(matches.clone(), options.clone(), summarize)?;
|
|
||||||
let (print_tx, rx) = mpsc::channel::<UResult<StatPrintInfo>>();
|
let (print_tx, rx) = mpsc::channel::<UResult<StatPrintInfo>>();
|
||||||
let printing_thread = thread::spawn(move || stat_printer.print_stats(&rx));
|
let printing_thread = thread::spawn(move || stat_printer.print_stats(&rx));
|
||||||
|
|
||||||
let excludes = build_exclude_patterns(&matches)?;
|
|
||||||
|
|
||||||
'loop_file: for path in files {
|
'loop_file: for path in files {
|
||||||
// Skip if we don't want to ignore anything
|
// Skip if we don't want to ignore anything
|
||||||
if !&excludes.is_empty() {
|
if !&traversal_options.excludes.is_empty() {
|
||||||
let path_string = path.to_string_lossy();
|
let path_string = path.to_string_lossy();
|
||||||
for pattern in &excludes {
|
for pattern in &traversal_options.excludes {
|
||||||
if pattern.matches(&path_string) {
|
if pattern.matches(&path_string) {
|
||||||
// if the directory is ignored, leave early
|
// if the directory is ignored, leave early
|
||||||
if options.verbose {
|
if traversal_options.verbose {
|
||||||
println!("{} ignored", path_string.quote());
|
println!("{} ignored", path_string.quote());
|
||||||
}
|
}
|
||||||
continue 'loop_file;
|
continue 'loop_file;
|
||||||
|
@ -738,13 +703,13 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check existence of path provided in argument
|
// Check existence of path provided in argument
|
||||||
if let Ok(stat) = Stat::new(&path, &options) {
|
if let Ok(stat) = Stat::new(&path, &traversal_options) {
|
||||||
// Kick off the computation of disk usage from the initial path
|
// Kick off the computation of disk usage from the initial path
|
||||||
let mut seen_inodes: HashSet<FileInfo> = HashSet::new();
|
let mut seen_inodes: HashSet<FileInfo> = HashSet::new();
|
||||||
if let Some(inode) = stat.inode {
|
if let Some(inode) = stat.inode {
|
||||||
seen_inodes.insert(inode);
|
seen_inodes.insert(inode);
|
||||||
}
|
}
|
||||||
let stat = du(stat, &options, 0, &mut seen_inodes, &excludes, &print_tx)
|
let stat = du(stat, &traversal_options, 0, &mut seen_inodes, &print_tx)
|
||||||
.map_err(|e| USimpleError::new(1, e.to_string()))?;
|
.map_err(|e| USimpleError::new(1, e.to_string()))?;
|
||||||
|
|
||||||
print_tx
|
print_tx
|
||||||
|
@ -772,17 +737,12 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_time_secs(s: &str, stat: &Stat) -> Result<u64, DuError> {
|
fn get_time_secs(time: Time, stat: &Stat) -> Result<u64, DuError> {
|
||||||
let secs = match s {
|
match time {
|
||||||
"ctime" | "status" => stat.modified,
|
Time::Modified => Ok(stat.modified),
|
||||||
"access" | "atime" | "use" => stat.accessed,
|
Time::Accessed => Ok(stat.accessed),
|
||||||
"birth" | "creation" => stat
|
Time::Created => stat.created.ok_or(DuError::InvalidTimeArg),
|
||||||
.created
|
}
|
||||||
.ok_or_else(|| DuError::InvalidTimeArg(s.into()))?,
|
|
||||||
// below should never happen as clap already restricts the values.
|
|
||||||
_ => unreachable!("Invalid field for --time"),
|
|
||||||
};
|
|
||||||
Ok(secs)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_time_style(s: Option<&str>) -> UResult<&str> {
|
fn parse_time_style(s: Option<&str>) -> UResult<&str> {
|
||||||
|
|
|
@ -16,7 +16,7 @@ path = "src/hostname.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
clap = { workspace = true }
|
clap = { workspace = true }
|
||||||
hostname = { version = "0.3", features = ["set"] }
|
hostname = { workspace = true, features = ["set"] }
|
||||||
uucore = { workspace = true, features = ["wide"] }
|
uucore = { workspace = true, features = ["wide"] }
|
||||||
|
|
||||||
[target.'cfg(target_os = "windows")'.dependencies]
|
[target.'cfg(target_os = "windows")'.dependencies]
|
||||||
|
|
|
@ -31,6 +31,7 @@ uucore = { workspace = true, features = [
|
||||||
] }
|
] }
|
||||||
once_cell = { workspace = true }
|
once_cell = { workspace = true }
|
||||||
selinux = { workspace = true, optional = true }
|
selinux = { workspace = true, optional = true }
|
||||||
|
hostname = { workspace = true }
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "ls"
|
name = "ls"
|
||||||
|
|
|
@ -3,14 +3,15 @@
|
||||||
// For the full copyright and license information, please view the LICENSE
|
// For the full copyright and license information, please view the LICENSE
|
||||||
// file that was distributed with this source code.
|
// file that was distributed with this source code.
|
||||||
|
|
||||||
// spell-checker:ignore (ToDO) cpio svgz webm somegroup nlink rmvb xspf tabsize dired subdired dtype
|
// spell-checker:ignore (ToDO) somegroup nlink tabsize dired subdired dtype
|
||||||
|
|
||||||
use clap::{
|
use clap::{
|
||||||
builder::{NonEmptyStringValueParser, ValueParser},
|
builder::{NonEmptyStringValueParser, ValueParser},
|
||||||
crate_version, Arg, ArgAction, Command,
|
crate_version, Arg, ArgAction, Command,
|
||||||
};
|
};
|
||||||
use glob::{MatchOptions, Pattern};
|
use glob::{MatchOptions, Pattern};
|
||||||
use lscolors::LsColors;
|
use lscolors::{LsColors, Style};
|
||||||
|
|
||||||
use number_prefix::NumberPrefix;
|
use number_prefix::NumberPrefix;
|
||||||
use std::{cell::OnceCell, num::IntErrorKind};
|
use std::{cell::OnceCell, num::IntErrorKind};
|
||||||
use std::{collections::HashSet, io::IsTerminal};
|
use std::{collections::HashSet, io::IsTerminal};
|
||||||
|
@ -20,7 +21,7 @@ use std::os::windows::fs::MetadataExt;
|
||||||
use std::{
|
use std::{
|
||||||
cmp::Reverse,
|
cmp::Reverse,
|
||||||
error::Error,
|
error::Error,
|
||||||
ffi::{OsStr, OsString},
|
ffi::OsString,
|
||||||
fmt::{Display, Write as FmtWrite},
|
fmt::{Display, Write as FmtWrite},
|
||||||
fs::{self, DirEntry, FileType, Metadata, ReadDir},
|
fs::{self, DirEntry, FileType, Metadata, ReadDir},
|
||||||
io::{stdout, BufWriter, ErrorKind, Stdout, Write},
|
io::{stdout, BufWriter, ErrorKind, Stdout, Write},
|
||||||
|
@ -155,6 +156,7 @@ pub mod options {
|
||||||
pub static GROUP_DIRECTORIES_FIRST: &str = "group-directories-first";
|
pub static GROUP_DIRECTORIES_FIRST: &str = "group-directories-first";
|
||||||
pub static ZERO: &str = "zero";
|
pub static ZERO: &str = "zero";
|
||||||
pub static DIRED: &str = "dired";
|
pub static DIRED: &str = "dired";
|
||||||
|
pub static HYPERLINK: &str = "hyperlink";
|
||||||
}
|
}
|
||||||
|
|
||||||
const DEFAULT_TERM_WIDTH: u16 = 80;
|
const DEFAULT_TERM_WIDTH: u16 = 80;
|
||||||
|
@ -418,6 +420,7 @@ pub struct Config {
|
||||||
group_directories_first: bool,
|
group_directories_first: bool,
|
||||||
line_ending: LineEnding,
|
line_ending: LineEnding,
|
||||||
dired: bool,
|
dired: bool,
|
||||||
|
hyperlink: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fields that can be removed or added to the long format
|
// Fields that can be removed or added to the long format
|
||||||
|
@ -566,6 +569,25 @@ fn extract_color(options: &clap::ArgMatches) -> bool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Extracts the hyperlink option to use based on the options provided.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// A boolean representing whether to hyperlink files.
|
||||||
|
fn extract_hyperlink(options: &clap::ArgMatches) -> bool {
|
||||||
|
let hyperlink = options
|
||||||
|
.get_one::<String>(options::HYPERLINK)
|
||||||
|
.unwrap()
|
||||||
|
.as_str();
|
||||||
|
|
||||||
|
match hyperlink {
|
||||||
|
"always" | "yes" | "force" => true,
|
||||||
|
"auto" | "tty" | "if-tty" => std::io::stdout().is_terminal(),
|
||||||
|
"never" | "no" | "none" => false,
|
||||||
|
_ => unreachable!("should be handled by clap"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Extracts the quoting style to use based on the options provided.
|
/// Extracts the quoting style to use based on the options provided.
|
||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
|
@ -620,7 +642,9 @@ fn extract_quoting_style(options: &clap::ArgMatches, show_control: bool) -> Quot
|
||||||
QuotingStyle::C {
|
QuotingStyle::C {
|
||||||
quotes: quoting_style::Quotes::Double,
|
quotes: quoting_style::Quotes::Double,
|
||||||
}
|
}
|
||||||
} else if options.get_flag(options::DIRED) {
|
} else if options.get_flag(options::DIRED) || !std::io::stdout().is_terminal() {
|
||||||
|
// By default, `ls` uses Literal quoting when
|
||||||
|
// writing to a non-terminal file descriptor
|
||||||
QuotingStyle::Literal { show_control }
|
QuotingStyle::Literal { show_control }
|
||||||
} else {
|
} else {
|
||||||
// TODO: use environment variable if available
|
// TODO: use environment variable if available
|
||||||
|
@ -736,19 +760,18 @@ impl Config {
|
||||||
}
|
}
|
||||||
|
|
||||||
let sort = extract_sort(options);
|
let sort = extract_sort(options);
|
||||||
|
|
||||||
let time = extract_time(options);
|
let time = extract_time(options);
|
||||||
|
|
||||||
let mut needs_color = extract_color(options);
|
let mut needs_color = extract_color(options);
|
||||||
|
let hyperlink = extract_hyperlink(options);
|
||||||
|
|
||||||
let cmd_line_bs = options.get_one::<String>(options::size::BLOCK_SIZE);
|
let opt_block_size = options.get_one::<String>(options::size::BLOCK_SIZE);
|
||||||
let opt_si = cmd_line_bs.is_some()
|
let opt_si = opt_block_size.is_some()
|
||||||
&& options
|
&& options
|
||||||
.get_one::<String>(options::size::BLOCK_SIZE)
|
.get_one::<String>(options::size::BLOCK_SIZE)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.eq("si")
|
.eq("si")
|
||||||
|| options.get_flag(options::size::SI);
|
|| options.get_flag(options::size::SI);
|
||||||
let opt_hr = (cmd_line_bs.is_some()
|
let opt_hr = (opt_block_size.is_some()
|
||||||
&& options
|
&& options
|
||||||
.get_one::<String>(options::size::BLOCK_SIZE)
|
.get_one::<String>(options::size::BLOCK_SIZE)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
@ -756,9 +779,9 @@ impl Config {
|
||||||
|| options.get_flag(options::size::HUMAN_READABLE);
|
|| options.get_flag(options::size::HUMAN_READABLE);
|
||||||
let opt_kb = options.get_flag(options::size::KIBIBYTES);
|
let opt_kb = options.get_flag(options::size::KIBIBYTES);
|
||||||
|
|
||||||
let bs_env_var = std::env::var_os("BLOCK_SIZE");
|
let env_var_block_size = std::env::var_os("BLOCK_SIZE");
|
||||||
let ls_bs_env_var = std::env::var_os("LS_BLOCK_SIZE");
|
let env_var_ls_block_size = std::env::var_os("LS_BLOCK_SIZE");
|
||||||
let pc_env_var = std::env::var_os("POSIXLY_CORRECT");
|
let env_var_posixly_correct = std::env::var_os("POSIXLY_CORRECT");
|
||||||
|
|
||||||
let size_format = if opt_si {
|
let size_format = if opt_si {
|
||||||
SizeFormat::Decimal
|
SizeFormat::Decimal
|
||||||
|
@ -768,13 +791,13 @@ impl Config {
|
||||||
SizeFormat::Bytes
|
SizeFormat::Bytes
|
||||||
};
|
};
|
||||||
|
|
||||||
let raw_bs = if let Some(cmd_line_bs) = cmd_line_bs {
|
let raw_block_size = if let Some(opt_block_size) = opt_block_size {
|
||||||
OsString::from(cmd_line_bs)
|
OsString::from(opt_block_size)
|
||||||
} else if !opt_kb {
|
} else if !opt_kb {
|
||||||
if let Some(ls_bs_env_var) = ls_bs_env_var {
|
if let Some(env_var_ls_block_size) = env_var_ls_block_size {
|
||||||
ls_bs_env_var
|
env_var_ls_block_size
|
||||||
} else if let Some(bs_env_var) = bs_env_var {
|
} else if let Some(env_var_block_size) = env_var_block_size {
|
||||||
bs_env_var
|
env_var_block_size
|
||||||
} else {
|
} else {
|
||||||
OsString::from("")
|
OsString::from("")
|
||||||
}
|
}
|
||||||
|
@ -782,20 +805,18 @@ impl Config {
|
||||||
OsString::from("")
|
OsString::from("")
|
||||||
};
|
};
|
||||||
|
|
||||||
let block_size: Option<u64> = if !opt_si && !opt_hr && !raw_bs.is_empty() {
|
let block_size: Option<u64> = if !opt_si && !opt_hr && !raw_block_size.is_empty() {
|
||||||
match parse_size_u64(&raw_bs.to_string_lossy()) {
|
match parse_size_u64(&raw_block_size.to_string_lossy()) {
|
||||||
Ok(size) => Some(size),
|
Ok(size) => Some(size),
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
show!(LsError::BlockSizeParseError(cmd_line_bs.unwrap().clone()));
|
show!(LsError::BlockSizeParseError(
|
||||||
|
opt_block_size.unwrap().clone()
|
||||||
|
));
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if let Some(pc) = pc_env_var {
|
} else if env_var_posixly_correct.is_some() {
|
||||||
if pc.as_os_str() == OsStr::new("true") || pc == OsStr::new("1") {
|
Some(POSIXLY_CORRECT_BLOCK_SIZE)
|
||||||
Some(POSIXLY_CORRECT_BLOCK_SIZE)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
@ -1022,6 +1043,7 @@ impl Config {
|
||||||
group_directories_first: options.get_flag(options::GROUP_DIRECTORIES_FIRST),
|
group_directories_first: options.get_flag(options::GROUP_DIRECTORIES_FIRST),
|
||||||
line_ending: LineEnding::from_zero_flag(options.get_flag(options::ZERO)),
|
line_ending: LineEnding::from_zero_flag(options.get_flag(options::ZERO)),
|
||||||
dired,
|
dired,
|
||||||
|
hyperlink,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1156,6 +1178,19 @@ pub fn uu_app() -> Command {
|
||||||
.help("generate output designed for Emacs' dired (Directory Editor) mode")
|
.help("generate output designed for Emacs' dired (Directory Editor) mode")
|
||||||
.action(ArgAction::SetTrue),
|
.action(ArgAction::SetTrue),
|
||||||
)
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::new(options::HYPERLINK)
|
||||||
|
.long(options::HYPERLINK)
|
||||||
|
.help("hyperlink file names WHEN")
|
||||||
|
.value_parser([
|
||||||
|
"always", "yes", "force", "auto", "tty", "if-tty", "never", "no", "none",
|
||||||
|
])
|
||||||
|
.require_equals(true)
|
||||||
|
.num_args(0..=1)
|
||||||
|
.default_missing_value("always")
|
||||||
|
.default_value("never")
|
||||||
|
.value_name("WHEN"),
|
||||||
|
)
|
||||||
// The next four arguments do not override with the other format
|
// The next four arguments do not override with the other format
|
||||||
// options, see the comment in Config::from for the reason.
|
// options, see the comment in Config::from for the reason.
|
||||||
// Ideally, they would use Arg::override_with, with their own name
|
// Ideally, they would use Arg::override_with, with their own name
|
||||||
|
@ -1868,6 +1903,7 @@ pub fn list(locs: Vec<&Path>, config: &Config) -> UResult<()> {
|
||||||
let mut dirs = Vec::<PathData>::new();
|
let mut dirs = Vec::<PathData>::new();
|
||||||
let mut out = BufWriter::new(stdout());
|
let mut out = BufWriter::new(stdout());
|
||||||
let mut dired = DiredOutput::default();
|
let mut dired = DiredOutput::default();
|
||||||
|
let mut style_manager = StyleManager::new();
|
||||||
let initial_locs_len = locs.len();
|
let initial_locs_len = locs.len();
|
||||||
|
|
||||||
for loc in locs {
|
for loc in locs {
|
||||||
|
@ -1901,7 +1937,7 @@ pub fn list(locs: Vec<&Path>, config: &Config) -> UResult<()> {
|
||||||
sort_entries(&mut files, config, &mut out);
|
sort_entries(&mut files, config, &mut out);
|
||||||
sort_entries(&mut dirs, config, &mut out);
|
sort_entries(&mut dirs, config, &mut out);
|
||||||
|
|
||||||
display_items(&files, config, &mut out, &mut dired)?;
|
display_items(&files, config, &mut out, &mut dired, &mut style_manager)?;
|
||||||
|
|
||||||
for (pos, path_data) in dirs.iter().enumerate() {
|
for (pos, path_data) in dirs.iter().enumerate() {
|
||||||
// Do read_dir call here to match GNU semantics by printing
|
// Do read_dir call here to match GNU semantics by printing
|
||||||
|
@ -1953,6 +1989,7 @@ pub fn list(locs: Vec<&Path>, config: &Config) -> UResult<()> {
|
||||||
&mut out,
|
&mut out,
|
||||||
&mut listed_ancestors,
|
&mut listed_ancestors,
|
||||||
&mut dired,
|
&mut dired,
|
||||||
|
&mut style_manager,
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
if config.dired {
|
if config.dired {
|
||||||
|
@ -2069,6 +2106,7 @@ fn enter_directory(
|
||||||
out: &mut BufWriter<Stdout>,
|
out: &mut BufWriter<Stdout>,
|
||||||
listed_ancestors: &mut HashSet<FileInformation>,
|
listed_ancestors: &mut HashSet<FileInformation>,
|
||||||
dired: &mut DiredOutput,
|
dired: &mut DiredOutput,
|
||||||
|
style_manager: &mut StyleManager,
|
||||||
) -> UResult<()> {
|
) -> UResult<()> {
|
||||||
// Create vec of entries with initial dot files
|
// Create vec of entries with initial dot files
|
||||||
let mut entries: Vec<PathData> = if config.files == Files::All {
|
let mut entries: Vec<PathData> = if config.files == Files::All {
|
||||||
|
@ -2121,7 +2159,7 @@ fn enter_directory(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
display_items(&entries, config, out, dired)?;
|
display_items(&entries, config, out, dired, style_manager)?;
|
||||||
|
|
||||||
if config.recursive {
|
if config.recursive {
|
||||||
for e in entries
|
for e in entries
|
||||||
|
@ -2162,7 +2200,15 @@ fn enter_directory(
|
||||||
|
|
||||||
show_dir_name(&e.p_buf, out);
|
show_dir_name(&e.p_buf, out);
|
||||||
writeln!(out)?;
|
writeln!(out)?;
|
||||||
enter_directory(e, rd, config, out, listed_ancestors, dired)?;
|
enter_directory(
|
||||||
|
e,
|
||||||
|
rd,
|
||||||
|
config,
|
||||||
|
out,
|
||||||
|
listed_ancestors,
|
||||||
|
dired,
|
||||||
|
style_manager,
|
||||||
|
)?;
|
||||||
listed_ancestors
|
listed_ancestors
|
||||||
.remove(&FileInformation::from_path(&e.p_buf, e.must_dereference)?);
|
.remove(&FileInformation::from_path(&e.p_buf, e.must_dereference)?);
|
||||||
} else {
|
} else {
|
||||||
|
@ -2284,6 +2330,7 @@ fn display_items(
|
||||||
config: &Config,
|
config: &Config,
|
||||||
out: &mut BufWriter<Stdout>,
|
out: &mut BufWriter<Stdout>,
|
||||||
dired: &mut DiredOutput,
|
dired: &mut DiredOutput,
|
||||||
|
style_manager: &mut StyleManager,
|
||||||
) -> UResult<()> {
|
) -> UResult<()> {
|
||||||
// `-Z`, `--context`:
|
// `-Z`, `--context`:
|
||||||
// Display the SELinux security context or '?' if none is found. When used with the `-l`
|
// Display the SELinux security context or '?' if none is found. When used with the `-l`
|
||||||
|
@ -2306,7 +2353,7 @@ fn display_items(
|
||||||
display_additional_leading_info(item, &padding_collection, config, out)?;
|
display_additional_leading_info(item, &padding_collection, config, out)?;
|
||||||
write!(out, "{more_info}")?;
|
write!(out, "{more_info}")?;
|
||||||
}
|
}
|
||||||
display_item_long(item, &padding_collection, config, out, dired)?;
|
display_item_long(item, &padding_collection, config, out, dired, style_manager)?;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let mut longest_context_len = 1;
|
let mut longest_context_len = 1;
|
||||||
|
@ -2326,7 +2373,7 @@ fn display_items(
|
||||||
|
|
||||||
for i in items {
|
for i in items {
|
||||||
let more_info = display_additional_leading_info(i, &padding, config, out)?;
|
let more_info = display_additional_leading_info(i, &padding, config, out)?;
|
||||||
let cell = display_file_name(i, config, prefix_context, more_info, out);
|
let cell = display_file_name(i, config, prefix_context, more_info, out, style_manager);
|
||||||
names_vec.push(cell);
|
names_vec.push(cell);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2481,6 +2528,7 @@ fn display_item_long(
|
||||||
config: &Config,
|
config: &Config,
|
||||||
out: &mut BufWriter<Stdout>,
|
out: &mut BufWriter<Stdout>,
|
||||||
dired: &mut DiredOutput,
|
dired: &mut DiredOutput,
|
||||||
|
style_manager: &mut StyleManager,
|
||||||
) -> UResult<()> {
|
) -> UResult<()> {
|
||||||
let mut output_display: String = String::new();
|
let mut output_display: String = String::new();
|
||||||
if config.dired {
|
if config.dired {
|
||||||
|
@ -2573,7 +2621,8 @@ fn display_item_long(
|
||||||
|
|
||||||
write!(output_display, " {} ", display_date(md, config)).unwrap();
|
write!(output_display, " {} ", display_date(md, config)).unwrap();
|
||||||
|
|
||||||
let displayed_file = display_file_name(item, config, None, String::new(), out).contents;
|
let displayed_file =
|
||||||
|
display_file_name(item, config, None, String::new(), out, style_manager).contents;
|
||||||
if config.dired {
|
if config.dired {
|
||||||
let (start, end) = dired::calculate_dired(
|
let (start, end) = dired::calculate_dired(
|
||||||
&dired.dired_positions,
|
&dired.dired_positions,
|
||||||
|
@ -2655,7 +2704,8 @@ fn display_item_long(
|
||||||
write!(output_display, " {}", pad_right("?", padding.uname)).unwrap();
|
write!(output_display, " {}", pad_right("?", padding.uname)).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
let displayed_file = display_file_name(item, config, None, String::new(), out).contents;
|
let displayed_file =
|
||||||
|
display_file_name(item, config, None, String::new(), out, style_manager).contents;
|
||||||
let date_len = 12;
|
let date_len = 12;
|
||||||
|
|
||||||
write!(
|
write!(
|
||||||
|
@ -2946,7 +2996,6 @@ fn classify_file(path: &PathData, out: &mut BufWriter<Stdout>) -> Option<char> {
|
||||||
///
|
///
|
||||||
/// Note that non-unicode sequences in symlink targets are dealt with using
|
/// Note that non-unicode sequences in symlink targets are dealt with using
|
||||||
/// [`std::path::Path::to_string_lossy`].
|
/// [`std::path::Path::to_string_lossy`].
|
||||||
#[allow(unused_variables)]
|
|
||||||
#[allow(clippy::cognitive_complexity)]
|
#[allow(clippy::cognitive_complexity)]
|
||||||
fn display_file_name(
|
fn display_file_name(
|
||||||
path: &PathData,
|
path: &PathData,
|
||||||
|
@ -2954,6 +3003,7 @@ fn display_file_name(
|
||||||
prefix_context: Option<usize>,
|
prefix_context: Option<usize>,
|
||||||
more_info: String,
|
more_info: String,
|
||||||
out: &mut BufWriter<Stdout>,
|
out: &mut BufWriter<Stdout>,
|
||||||
|
style_manager: &mut StyleManager,
|
||||||
) -> Cell {
|
) -> Cell {
|
||||||
// This is our return value. We start by `&path.display_name` and modify it along the way.
|
// This is our return value. We start by `&path.display_name` and modify it along the way.
|
||||||
let mut name = escape_name(&path.display_name, &config.quoting_style);
|
let mut name = escape_name(&path.display_name, &config.quoting_style);
|
||||||
|
@ -2962,16 +3012,29 @@ fn display_file_name(
|
||||||
// infer it because the color codes mess up term_grid's width calculation.
|
// infer it because the color codes mess up term_grid's width calculation.
|
||||||
let mut width = name.width();
|
let mut width = name.width();
|
||||||
|
|
||||||
|
if config.hyperlink {
|
||||||
|
let hostname = hostname::get().unwrap_or(OsString::from(""));
|
||||||
|
let hostname = hostname.to_string_lossy();
|
||||||
|
|
||||||
|
let absolute_path = fs::canonicalize(&path.p_buf).unwrap_or_default();
|
||||||
|
let absolute_path = absolute_path.to_string_lossy();
|
||||||
|
|
||||||
|
// TODO encode path
|
||||||
|
// \x1b = ESC, \x07 = BEL
|
||||||
|
name = format!("\x1b]8;;file://{hostname}{absolute_path}\x07{name}\x1b]8;;\x07");
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(ls_colors) = &config.color {
|
if let Some(ls_colors) = &config.color {
|
||||||
let md = path.md(out);
|
let md = path.md(out);
|
||||||
name = if md.is_some() {
|
name = if md.is_some() {
|
||||||
color_name(name, &path.p_buf, md, ls_colors)
|
color_name(name, &path.p_buf, md, ls_colors, style_manager)
|
||||||
} else {
|
} else {
|
||||||
color_name(
|
color_name(
|
||||||
name,
|
name,
|
||||||
&path.p_buf,
|
&path.p_buf,
|
||||||
path.p_buf.symlink_metadata().ok().as_ref(),
|
path.p_buf.symlink_metadata().ok().as_ref(),
|
||||||
ls_colors,
|
ls_colors,
|
||||||
|
style_manager,
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -3060,6 +3123,7 @@ fn display_file_name(
|
||||||
&target_data.p_buf,
|
&target_data.p_buf,
|
||||||
Some(&target_metadata),
|
Some(&target_metadata),
|
||||||
ls_colors,
|
ls_colors,
|
||||||
|
style_manager,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -3094,11 +3158,50 @@ fn display_file_name(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn color_name(name: String, path: &Path, md: Option<&Metadata>, ls_colors: &LsColors) -> String {
|
/// We need this struct to be able to store the previous style.
|
||||||
match ls_colors.style_for_path_with_metadata(path, md) {
|
/// This because we need to check the previous value in case we don't need
|
||||||
Some(style) => {
|
/// the reset
|
||||||
return style.to_nu_ansi_term_style().paint(name).to_string();
|
struct StyleManager {
|
||||||
|
current_style: Option<Style>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StyleManager {
|
||||||
|
fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
current_style: None,
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn apply_style(&mut self, new_style: &Style, name: &str) -> String {
|
||||||
|
if let Some(current) = &self.current_style {
|
||||||
|
if *current == *new_style {
|
||||||
|
// Current style is the same as new style, apply without reset.
|
||||||
|
let mut style = new_style.to_nu_ansi_term_style();
|
||||||
|
style.prefix_with_reset = false;
|
||||||
|
return style.paint(name).to_string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We are getting a new style, we need to reset it
|
||||||
|
self.current_style = Some(new_style.clone());
|
||||||
|
new_style
|
||||||
|
.to_nu_ansi_term_style()
|
||||||
|
.reset_before_style()
|
||||||
|
.paint(name)
|
||||||
|
.to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Colors the provided name based on the style determined for the given path.
|
||||||
|
fn color_name(
|
||||||
|
name: String,
|
||||||
|
path: &Path,
|
||||||
|
md: Option<&Metadata>,
|
||||||
|
ls_colors: &LsColors,
|
||||||
|
style_manager: &mut StyleManager,
|
||||||
|
) -> String {
|
||||||
|
match ls_colors.style_for_path_with_metadata(path, md) {
|
||||||
|
Some(style) => style_manager.apply_style(style, &name),
|
||||||
None => name,
|
None => name,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -341,7 +341,7 @@ fn handle_two_paths(source: &Path, target: &Path, opts: &Options) -> UResult<()>
|
||||||
|
|
||||||
let target_is_dir = target.is_dir();
|
let target_is_dir = target.is_dir();
|
||||||
|
|
||||||
if path_ends_with_terminator(target) && !target_is_dir {
|
if path_ends_with_terminator(target) && !target_is_dir && !opts.no_target_dir {
|
||||||
return Err(MvError::FailedToAccessNotADirectory(target.quote().to_string()).into());
|
return Err(MvError::FailedToAccessNotADirectory(target.quote().to_string()).into());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
//
|
//
|
||||||
// For the full copyright and license information, please view the LICENSE
|
// For the full copyright and license information, please view the LICENSE
|
||||||
// file that was distributed with this source code.
|
// file that was distributed with this source code.
|
||||||
|
|
||||||
|
// cSpell:ignore sysconf
|
||||||
use crate::word_count::WordCount;
|
use crate::word_count::WordCount;
|
||||||
|
|
||||||
use super::WordCountable;
|
use super::WordCountable;
|
||||||
|
@ -11,11 +13,19 @@ use std::fs::OpenOptions;
|
||||||
use std::io::{self, ErrorKind, Read};
|
use std::io::{self, ErrorKind, Read};
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
use libc::S_IFREG;
|
use libc::{sysconf, S_IFREG, _SC_PAGESIZE};
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
use nix::sys::stat;
|
use nix::sys::stat;
|
||||||
|
#[cfg(unix)]
|
||||||
|
use std::io::{Seek, SeekFrom};
|
||||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||||
use std::os::unix::io::AsRawFd;
|
use std::os::unix::io::AsRawFd;
|
||||||
|
#[cfg(windows)]
|
||||||
|
use std::os::windows::fs::MetadataExt;
|
||||||
|
#[cfg(windows)]
|
||||||
|
const FILE_ATTRIBUTE_ARCHIVE: u32 = 32;
|
||||||
|
#[cfg(windows)]
|
||||||
|
const FILE_ATTRIBUTE_NORMAL: u32 = 128;
|
||||||
|
|
||||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||||
use libc::S_IFIFO;
|
use libc::S_IFIFO;
|
||||||
|
@ -72,6 +82,8 @@ fn count_bytes_using_splice(fd: &impl AsRawFd) -> Result<usize, usize> {
|
||||||
/// 1. On Unix, we can simply `stat` the file if it is regular.
|
/// 1. On Unix, we can simply `stat` the file if it is regular.
|
||||||
/// 2. On Linux -- if the above did not work -- we can use splice to count
|
/// 2. On Linux -- if the above did not work -- we can use splice to count
|
||||||
/// the number of bytes if the file is a FIFO.
|
/// the number of bytes if the file is a FIFO.
|
||||||
|
/// 3. On Windows we can use `std::os::windows::fs::MetadataExt` to get file size
|
||||||
|
/// for regular files
|
||||||
/// 3. Otherwise, we just read normally, but without the overhead of counting
|
/// 3. Otherwise, we just read normally, but without the overhead of counting
|
||||||
/// other things such as lines and words.
|
/// other things such as lines and words.
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -87,11 +99,60 @@ pub(crate) fn count_bytes_fast<T: WordCountable>(handle: &mut T) -> (usize, Opti
|
||||||
// If stat.st_size = 0 then
|
// If stat.st_size = 0 then
|
||||||
// - either the size is 0
|
// - either the size is 0
|
||||||
// - or the size is unknown.
|
// - or the size is unknown.
|
||||||
// The second case happens for files in pseudo-filesystems. For
|
// The second case happens for files in pseudo-filesystems.
|
||||||
// example with /proc/version and /sys/kernel/profiling. So,
|
// For example with /proc/version.
|
||||||
// if it is 0 we don't report that and instead do a full read.
|
// So, if it is 0 we don't report that and instead do a full read.
|
||||||
if (stat.st_mode as libc::mode_t & S_IFREG) != 0 && stat.st_size > 0 {
|
//
|
||||||
return (stat.st_size as usize, None);
|
// Another thing to consider for files in pseudo-filesystems like /proc, /sys
|
||||||
|
// and similar is that they could report `st_size` greater than actual content.
|
||||||
|
// For example /sys/kernel/profiling could report `st_size` equal to
|
||||||
|
// system page size (typically 4096 on 64bit system), while it's file content
|
||||||
|
// would count up only to a couple of bytes.
|
||||||
|
// This condition usually occurs for files in pseudo-filesystems like /proc, /sys
|
||||||
|
// that report `st_size` in the multiples of system page size.
|
||||||
|
// In such cases - attempt `seek()` almost to the end of the file
|
||||||
|
// and then fall back on read to count the rest.
|
||||||
|
//
|
||||||
|
// And finally a special case of input redirection in *nix shell:
|
||||||
|
// `( wc -c ; wc -c ) < file` should return
|
||||||
|
// ```
|
||||||
|
// size_of_file
|
||||||
|
// 0
|
||||||
|
// ```
|
||||||
|
// Similarly
|
||||||
|
// `( head -c1 ; wc -c ) < file` should return
|
||||||
|
// ```
|
||||||
|
// first_byte_of_file
|
||||||
|
// size_of_file - 1
|
||||||
|
// ```
|
||||||
|
// Since the input stream from file is treated as continuous across both commands inside ().
|
||||||
|
// In cases like this, due to `<` redirect, the `stat.st_mode` would report input as a regular file
|
||||||
|
// and `stat.st_size` would report the size of file on disk
|
||||||
|
// and NOT the remaining number of bytes in the input stream.
|
||||||
|
// However, the raw file descriptor in this situation would be equal to `0`
|
||||||
|
// for STDIN in both invocations.
|
||||||
|
// Therefore we cannot rely of `st_size` here and should fall back on full read.
|
||||||
|
if fd > 0 && (stat.st_mode as libc::mode_t & S_IFREG) != 0 && stat.st_size > 0 {
|
||||||
|
let sys_page_size = unsafe { sysconf(_SC_PAGESIZE) as usize };
|
||||||
|
if stat.st_size as usize % sys_page_size > 0 {
|
||||||
|
// regular file or file from /proc, /sys and similar pseudo-filesystems
|
||||||
|
// with size that is NOT a multiple of system page size
|
||||||
|
return (stat.st_size as usize, None);
|
||||||
|
} else if let Some(file) = handle.inner_file() {
|
||||||
|
// On some platforms `stat.st_blksize` and `stat.st_size`
|
||||||
|
// are of different types: i64 vs i32
|
||||||
|
// i.e. MacOS on Apple Silicon (aarch64-apple-darwin),
|
||||||
|
// Debian Linux on ARM (aarch64-unknown-linux-gnu),
|
||||||
|
// 32bit i686 targets, etc.
|
||||||
|
// While on the others they are of the same type.
|
||||||
|
#[allow(clippy::unnecessary_cast)]
|
||||||
|
let offset =
|
||||||
|
stat.st_size as i64 - stat.st_size as i64 % (stat.st_blksize as i64 + 1);
|
||||||
|
|
||||||
|
if let Ok(n) = file.seek(SeekFrom::Start(offset as u64)) {
|
||||||
|
byte_count = n as usize;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||||
{
|
{
|
||||||
|
@ -107,6 +168,21 @@ pub(crate) fn count_bytes_fast<T: WordCountable>(handle: &mut T) -> (usize, Opti
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
{
|
||||||
|
if let Some(file) = handle.inner_file() {
|
||||||
|
if let Ok(metadata) = file.metadata() {
|
||||||
|
let attributes = metadata.file_attributes();
|
||||||
|
|
||||||
|
if (attributes & FILE_ATTRIBUTE_ARCHIVE) != 0
|
||||||
|
|| (attributes & FILE_ATTRIBUTE_NORMAL) != 0
|
||||||
|
{
|
||||||
|
return (metadata.file_size() as usize, None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Fall back on `read`, but without the overhead of counting words and lines.
|
// Fall back on `read`, but without the overhead of counting words and lines.
|
||||||
let mut buf = [0_u8; BUF_SIZE];
|
let mut buf = [0_u8; BUF_SIZE];
|
||||||
loop {
|
loop {
|
||||||
|
|
|
@ -17,12 +17,14 @@ use std::os::unix::io::AsRawFd;
|
||||||
pub trait WordCountable: AsRawFd + Read {
|
pub trait WordCountable: AsRawFd + Read {
|
||||||
type Buffered: BufRead;
|
type Buffered: BufRead;
|
||||||
fn buffered(self) -> Self::Buffered;
|
fn buffered(self) -> Self::Buffered;
|
||||||
|
fn inner_file(&mut self) -> Option<&mut File>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(unix))]
|
#[cfg(not(unix))]
|
||||||
pub trait WordCountable: Read {
|
pub trait WordCountable: Read {
|
||||||
type Buffered: BufRead;
|
type Buffered: BufRead;
|
||||||
fn buffered(self) -> Self::Buffered;
|
fn buffered(self) -> Self::Buffered;
|
||||||
|
fn inner_file(&mut self) -> Option<&mut File>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WordCountable for StdinLock<'_> {
|
impl WordCountable for StdinLock<'_> {
|
||||||
|
@ -31,6 +33,9 @@ impl WordCountable for StdinLock<'_> {
|
||||||
fn buffered(self) -> Self::Buffered {
|
fn buffered(self) -> Self::Buffered {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
fn inner_file(&mut self) -> Option<&mut File> {
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WordCountable for File {
|
impl WordCountable for File {
|
||||||
|
@ -39,4 +44,8 @@ impl WordCountable for File {
|
||||||
fn buffered(self) -> Self::Buffered {
|
fn buffered(self) -> Self::Buffered {
|
||||||
BufReader::new(self)
|
BufReader::new(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn inner_file(&mut self) -> Option<&mut File> {
|
||||||
|
Some(self)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -72,6 +72,7 @@ windows-sys = { workspace = true, optional = true, default-features = false, fea
|
||||||
default = []
|
default = []
|
||||||
# * non-default features
|
# * non-default features
|
||||||
backup-control = []
|
backup-control = []
|
||||||
|
colors = []
|
||||||
encoding = ["data-encoding", "data-encoding-macro", "z85", "thiserror"]
|
encoding = ["data-encoding", "data-encoding-macro", "z85", "thiserror"]
|
||||||
entries = ["libc"]
|
entries = ["libc"]
|
||||||
fs = ["dunce", "libc", "winapi-util", "windows-sys"]
|
fs = ["dunce", "libc", "winapi-util", "windows-sys"]
|
||||||
|
|
|
@ -6,6 +6,8 @@
|
||||||
|
|
||||||
#[cfg(feature = "backup-control")]
|
#[cfg(feature = "backup-control")]
|
||||||
pub mod backup_control;
|
pub mod backup_control;
|
||||||
|
#[cfg(feature = "colors")]
|
||||||
|
pub mod colors;
|
||||||
#[cfg(feature = "encoding")]
|
#[cfg(feature = "encoding")]
|
||||||
pub mod encoding;
|
pub mod encoding;
|
||||||
#[cfg(feature = "format")]
|
#[cfg(feature = "format")]
|
||||||
|
|
264
src/uucore/src/lib/features/colors.rs
Normal file
264
src/uucore/src/lib/features/colors.rs
Normal file
|
@ -0,0 +1,264 @@
|
||||||
|
// This file is part of the uutils coreutils package.
|
||||||
|
//
|
||||||
|
// For the full copyright and license information, please view the LICENSE
|
||||||
|
// file that was distributed with this source code.
|
||||||
|
// cSpell:disable
|
||||||
|
|
||||||
|
/// The keywords COLOR, OPTIONS, and EIGHTBIT (honored by the
|
||||||
|
/// slackware version of dircolors) are recognized but ignored.
|
||||||
|
/// Global config options can be specified before TERM or COLORTERM entries
|
||||||
|
/// below are TERM or COLORTERM entries, which can be glob patterns, which
|
||||||
|
/// restrict following config to systems with matching environment variables.
|
||||||
|
pub static TERMS: &[&str] = &[
|
||||||
|
"Eterm",
|
||||||
|
"ansi",
|
||||||
|
"*color*",
|
||||||
|
"con[0-9]*x[0-9]*",
|
||||||
|
"cons25",
|
||||||
|
"console",
|
||||||
|
"cygwin",
|
||||||
|
"*direct*",
|
||||||
|
"dtterm",
|
||||||
|
"gnome",
|
||||||
|
"hurd",
|
||||||
|
"jfbterm",
|
||||||
|
"konsole",
|
||||||
|
"kterm",
|
||||||
|
"linux",
|
||||||
|
"linux-c",
|
||||||
|
"mlterm",
|
||||||
|
"putty",
|
||||||
|
"rxvt*",
|
||||||
|
"screen*",
|
||||||
|
"st",
|
||||||
|
"terminator",
|
||||||
|
"tmux*",
|
||||||
|
"vt100",
|
||||||
|
"xterm*",
|
||||||
|
];
|
||||||
|
|
||||||
|
/// Below are the color init strings for the basic file types.
|
||||||
|
/// One can use codes for 256 or more colors supported by modern terminals.
|
||||||
|
/// The default color codes use the capabilities of an 8 color terminal
|
||||||
|
/// with some additional attributes as per the following codes:
|
||||||
|
/// Attribute codes:
|
||||||
|
/// 00=none 01=bold 04=underscore 05=blink 07=reverse 08=concealed
|
||||||
|
/// Text color codes:
|
||||||
|
/// 30=black 31=red 32=green 33=yellow 34=blue 35=magenta 36=cyan 37=white
|
||||||
|
/// Background color codes:
|
||||||
|
/// 40=black 41=red 42=green 43=yellow 44=blue 45=magenta 46=cyan 47=white
|
||||||
|
/// #NORMAL 00 /// no color code at all
|
||||||
|
/// #FILE 00 /// regular file: use no color at all
|
||||||
|
pub static FILE_TYPES: &[(&str, &str, &str)] = &[
|
||||||
|
("RESET", "rs", "0"), // reset to "normal" color
|
||||||
|
("DIR", "di", "01;34"), // directory
|
||||||
|
("LINK", "ln", "01;36"), // symbolic link
|
||||||
|
("MULTIHARDLINK", "mh", "00"), // regular file with more than one link
|
||||||
|
("FIFO", "pi", "40;33"), // pipe
|
||||||
|
("SOCK", "so", "01;35"), // socket
|
||||||
|
("DOOR", "do", "01;35"), // door
|
||||||
|
("BLK", "bd", "40;33;01"), // block device driver
|
||||||
|
("CHR", "cd", "40;33;01"), // character device driver
|
||||||
|
("ORPHAN", "or", "40;31;01"), // symlink to nonexistent file, or non-stat'able file
|
||||||
|
("MISSING", "mi", "00"), // ... and the files they point to
|
||||||
|
("SETUID", "su", "37;41"), // file that is setuid (u+s)
|
||||||
|
("SETGID", "sg", "30;43"), // file that is setgid (g+s)
|
||||||
|
("CAPABILITY", "ca", "00"), // file with capability
|
||||||
|
("STICKY_OTHER_WRITABLE", "tw", "30;42"), // dir that is sticky and other-writable (+t,o+w)
|
||||||
|
("OTHER_WRITABLE", "ow", "34;42"), // dir that is other-writable (o+w) and not sticky
|
||||||
|
("STICKY", "st", "37;44"), // dir with the sticky bit set (+t) and not other-writable
|
||||||
|
("EXEC", "ex", "01;32"), // files with execute permission
|
||||||
|
];
|
||||||
|
|
||||||
|
/// Colors for file types
|
||||||
|
///
|
||||||
|
/// List any file extensions like '.gz' or '.tar' that you would like ls
|
||||||
|
/// to color below. Put the extension, a space, and the color init string.
|
||||||
|
/// (and any comments you want to add after a '#')
|
||||||
|
pub static FILE_COLORS: &[(&str, &str)] = &[
|
||||||
|
/*
|
||||||
|
// Executables (Windows)
|
||||||
|
(".cmd", "01;32"),
|
||||||
|
(".exe", "01;32"),
|
||||||
|
(".com", "01;32"),
|
||||||
|
(".btm", "01;32"),
|
||||||
|
(".bat", "01;32"),
|
||||||
|
(".sh", "01;32"),
|
||||||
|
(".csh", "01;32"),*/
|
||||||
|
// Archives or compressed
|
||||||
|
(".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"),
|
||||||
|
(".dz", "01;31"),
|
||||||
|
(".gz", "01;31"),
|
||||||
|
(".lrz", "01;31"),
|
||||||
|
(".lz", "01;31"),
|
||||||
|
(".lzo", "01;31"),
|
||||||
|
(".xz", "01;31"),
|
||||||
|
(".zst", "01;31"),
|
||||||
|
(".tzst", "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"),
|
||||||
|
(".wim", "01;31"),
|
||||||
|
(".swm", "01;31"),
|
||||||
|
(".dwm", "01;31"),
|
||||||
|
(".esd", "01;31"),
|
||||||
|
// Image formats
|
||||||
|
(".avif", "01;35"),
|
||||||
|
(".jpg", "01;35"),
|
||||||
|
(".jpeg", "01;35"),
|
||||||
|
(".mjpg", "01;35"),
|
||||||
|
(".mjpeg", "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"),
|
||||||
|
(".webp", "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"),
|
||||||
|
// https://wiki.xiph.org/MIME_Types_and_File_Extensions
|
||||||
|
(".ogv", "01;35"),
|
||||||
|
(".ogx", "01;35"),
|
||||||
|
// Audio formats
|
||||||
|
(".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"),
|
||||||
|
// https://wiki.xiph.org/MIME_Types_and_File_Extensions
|
||||||
|
(".oga", "00;36"),
|
||||||
|
(".opus", "00;36"),
|
||||||
|
(".spx", "00;36"),
|
||||||
|
(".xspf", "00;36"),
|
||||||
|
// Backup files
|
||||||
|
("*~", "00;90"),
|
||||||
|
("*#", "00;90"),
|
||||||
|
(".bak", "00;90"),
|
||||||
|
(".old", "00;90"),
|
||||||
|
(".orig", "00;90"),
|
||||||
|
(".part", "00;90"),
|
||||||
|
(".rej", "00;90"),
|
||||||
|
(".swp", "00;90"),
|
||||||
|
(".tmp", "00;90"),
|
||||||
|
(".dpkg-dist", "00;90"),
|
||||||
|
(".dpkg-old", "00;90"),
|
||||||
|
(".ucf-dist", "00;90"),
|
||||||
|
(".ucf-new", "00;90"),
|
||||||
|
(".ucf-old", "00;90"),
|
||||||
|
(".rpmnew", "00;90"),
|
||||||
|
(".rpmorig", "00;90"),
|
||||||
|
(".rpmsave", "00;90"),
|
||||||
|
];
|
||||||
|
|
||||||
|
pub static FILE_ATTRIBUTE_CODES: &[(&str, &str)] = &[
|
||||||
|
("normal", "no"),
|
||||||
|
("norm", "no"),
|
||||||
|
("file", "fi"),
|
||||||
|
("reset", "rs"),
|
||||||
|
("dir", "di"),
|
||||||
|
("lnk", "ln"),
|
||||||
|
("link", "ln"),
|
||||||
|
("symlink", "ln"),
|
||||||
|
("orphan", "or"),
|
||||||
|
("missing", "mi"),
|
||||||
|
("fifo", "pi"),
|
||||||
|
("pipe", "pi"),
|
||||||
|
("sock", "so"),
|
||||||
|
("blk", "bd"),
|
||||||
|
("block", "bd"),
|
||||||
|
("chr", "cd"),
|
||||||
|
("char", "cd"),
|
||||||
|
("door", "do"),
|
||||||
|
("exec", "ex"),
|
||||||
|
("left", "lc"),
|
||||||
|
("leftcode", "lc"),
|
||||||
|
("right", "rc"),
|
||||||
|
("rightcode", "rc"),
|
||||||
|
("end", "ec"),
|
||||||
|
("endcode", "ec"),
|
||||||
|
("suid", "su"),
|
||||||
|
("setuid", "su"),
|
||||||
|
("sgid", "sg"),
|
||||||
|
("setgid", "sg"),
|
||||||
|
("sticky", "st"),
|
||||||
|
("other_writable", "ow"),
|
||||||
|
("owr", "ow"),
|
||||||
|
("sticky_other_writable", "tw"),
|
||||||
|
("owt", "tw"),
|
||||||
|
("capability", "ca"),
|
||||||
|
("multihardlink", "mh"),
|
||||||
|
("clrtoeol", "cl"),
|
||||||
|
];
|
|
@ -115,6 +115,7 @@ impl FileInformation {
|
||||||
not(target_os = "android"),
|
not(target_os = "android"),
|
||||||
not(target_os = "freebsd"),
|
not(target_os = "freebsd"),
|
||||||
not(target_os = "netbsd"),
|
not(target_os = "netbsd"),
|
||||||
|
not(target_os = "openbsd"),
|
||||||
not(target_os = "illumos"),
|
not(target_os = "illumos"),
|
||||||
not(target_os = "solaris"),
|
not(target_os = "solaris"),
|
||||||
not(target_arch = "aarch64"),
|
not(target_arch = "aarch64"),
|
||||||
|
@ -130,6 +131,7 @@ impl FileInformation {
|
||||||
target_os = "android",
|
target_os = "android",
|
||||||
target_os = "freebsd",
|
target_os = "freebsd",
|
||||||
target_os = "netbsd",
|
target_os = "netbsd",
|
||||||
|
target_os = "openbsd",
|
||||||
target_os = "illumos",
|
target_os = "illumos",
|
||||||
target_os = "solaris",
|
target_os = "solaris",
|
||||||
target_arch = "aarch64",
|
target_arch = "aarch64",
|
||||||
|
@ -146,13 +148,14 @@ impl FileInformation {
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
pub fn inode(&self) -> u64 {
|
pub fn inode(&self) -> u64 {
|
||||||
#[cfg(all(
|
#[cfg(all(
|
||||||
not(any(target_os = "freebsd", target_os = "netbsd")),
|
not(any(target_os = "freebsd", target_os = "netbsd", target_os = "openbsd")),
|
||||||
target_pointer_width = "64"
|
target_pointer_width = "64"
|
||||||
))]
|
))]
|
||||||
return self.0.st_ino;
|
return self.0.st_ino;
|
||||||
#[cfg(any(
|
#[cfg(any(
|
||||||
target_os = "freebsd",
|
target_os = "freebsd",
|
||||||
target_os = "netbsd",
|
target_os = "netbsd",
|
||||||
|
target_os = "openbsd",
|
||||||
not(target_pointer_width = "64")
|
not(target_pointer_width = "64")
|
||||||
))]
|
))]
|
||||||
return self.0.st_ino.into();
|
return self.0.st_ino.into();
|
||||||
|
|
|
@ -497,7 +497,10 @@ impl FsUsage {
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
pub fn new(statvfs: StatFs) -> Self {
|
pub fn new(statvfs: StatFs) -> Self {
|
||||||
{
|
{
|
||||||
#[cfg(all(not(target_os = "freebsd"), target_pointer_width = "64"))]
|
#[cfg(all(
|
||||||
|
not(any(target_os = "freebsd", target_os = "openbsd")),
|
||||||
|
target_pointer_width = "64"
|
||||||
|
))]
|
||||||
return Self {
|
return Self {
|
||||||
blocksize: statvfs.f_bsize as u64, // or `statvfs.f_frsize` ?
|
blocksize: statvfs.f_bsize as u64, // or `statvfs.f_frsize` ?
|
||||||
blocks: statvfs.f_blocks,
|
blocks: statvfs.f_blocks,
|
||||||
|
@ -507,7 +510,10 @@ impl FsUsage {
|
||||||
files: statvfs.f_files,
|
files: statvfs.f_files,
|
||||||
ffree: statvfs.f_ffree,
|
ffree: statvfs.f_ffree,
|
||||||
};
|
};
|
||||||
#[cfg(all(not(target_os = "freebsd"), not(target_pointer_width = "64")))]
|
#[cfg(all(
|
||||||
|
not(any(target_os = "freebsd", target_os = "openbsd")),
|
||||||
|
not(target_pointer_width = "64")
|
||||||
|
))]
|
||||||
return Self {
|
return Self {
|
||||||
blocksize: statvfs.f_bsize as u64, // or `statvfs.f_frsize` ?
|
blocksize: statvfs.f_bsize as u64, // or `statvfs.f_frsize` ?
|
||||||
blocks: statvfs.f_blocks.into(),
|
blocks: statvfs.f_blocks.into(),
|
||||||
|
@ -530,6 +536,19 @@ impl FsUsage {
|
||||||
files: statvfs.f_files,
|
files: statvfs.f_files,
|
||||||
ffree: statvfs.f_ffree.try_into().unwrap(),
|
ffree: statvfs.f_ffree.try_into().unwrap(),
|
||||||
};
|
};
|
||||||
|
#[cfg(target_os = "openbsd")]
|
||||||
|
return Self {
|
||||||
|
blocksize: statvfs.f_bsize.into(),
|
||||||
|
blocks: statvfs.f_blocks,
|
||||||
|
bfree: statvfs.f_bfree,
|
||||||
|
bavail: statvfs.f_bavail.try_into().unwrap(),
|
||||||
|
bavail_top_bit_set: ((std::convert::TryInto::<u64>::try_into(statvfs.f_bavail)
|
||||||
|
.unwrap())
|
||||||
|
& (1u64.rotate_right(1)))
|
||||||
|
!= 0,
|
||||||
|
files: statvfs.f_files,
|
||||||
|
ffree: statvfs.f_ffree,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[cfg(not(unix))]
|
#[cfg(not(unix))]
|
||||||
|
@ -617,6 +636,7 @@ impl FsMeta for StatFs {
|
||||||
not(target_vendor = "apple"),
|
not(target_vendor = "apple"),
|
||||||
not(target_os = "android"),
|
not(target_os = "android"),
|
||||||
not(target_os = "freebsd"),
|
not(target_os = "freebsd"),
|
||||||
|
not(target_os = "openbsd"),
|
||||||
not(target_os = "illumos"),
|
not(target_os = "illumos"),
|
||||||
not(target_os = "solaris"),
|
not(target_os = "solaris"),
|
||||||
not(target_arch = "s390x"),
|
not(target_arch = "s390x"),
|
||||||
|
@ -630,6 +650,7 @@ impl FsMeta for StatFs {
|
||||||
target_arch = "s390x",
|
target_arch = "s390x",
|
||||||
target_vendor = "apple",
|
target_vendor = "apple",
|
||||||
target_os = "android",
|
target_os = "android",
|
||||||
|
target_os = "openbsd",
|
||||||
not(target_pointer_width = "64")
|
not(target_pointer_width = "64")
|
||||||
)
|
)
|
||||||
))]
|
))]
|
||||||
|
@ -655,11 +676,19 @@ impl FsMeta for StatFs {
|
||||||
return self.f_bfree.into();
|
return self.f_bfree.into();
|
||||||
}
|
}
|
||||||
fn avail_blocks(&self) -> u64 {
|
fn avail_blocks(&self) -> u64 {
|
||||||
#[cfg(all(not(target_os = "freebsd"), target_pointer_width = "64"))]
|
#[cfg(all(
|
||||||
|
not(target_os = "freebsd"),
|
||||||
|
not(target_os = "openbsd"),
|
||||||
|
target_pointer_width = "64"
|
||||||
|
))]
|
||||||
return self.f_bavail;
|
return self.f_bavail;
|
||||||
#[cfg(all(not(target_os = "freebsd"), not(target_pointer_width = "64")))]
|
#[cfg(all(
|
||||||
|
not(target_os = "freebsd"),
|
||||||
|
not(target_os = "openbsd"),
|
||||||
|
not(target_pointer_width = "64")
|
||||||
|
))]
|
||||||
return self.f_bavail.into();
|
return self.f_bavail.into();
|
||||||
#[cfg(target_os = "freebsd")]
|
#[cfg(any(target_os = "freebsd", target_os = "openbsd"))]
|
||||||
return self.f_bavail.try_into().unwrap();
|
return self.f_bavail.try_into().unwrap();
|
||||||
}
|
}
|
||||||
fn total_file_nodes(&self) -> u64 {
|
fn total_file_nodes(&self) -> u64 {
|
||||||
|
|
|
@ -35,6 +35,8 @@ pub use crate::parser::shortcut_value_parser;
|
||||||
// * feature-gated modules
|
// * feature-gated modules
|
||||||
#[cfg(feature = "backup-control")]
|
#[cfg(feature = "backup-control")]
|
||||||
pub use crate::features::backup_control;
|
pub use crate::features::backup_control;
|
||||||
|
#[cfg(feature = "colors")]
|
||||||
|
pub use crate::features::colors;
|
||||||
#[cfg(feature = "encoding")]
|
#[cfg(feature = "encoding")]
|
||||||
pub use crate::features::encoding;
|
pub use crate::features::encoding;
|
||||||
#[cfg(feature = "format")]
|
#[cfg(feature = "format")]
|
||||||
|
|
|
@ -13,7 +13,7 @@ use std::os::unix::fs;
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
use std::os::unix::fs::MetadataExt;
|
use std::os::unix::fs::MetadataExt;
|
||||||
#[cfg(all(unix, not(target_os = "freebsd")))]
|
#[cfg(unix)]
|
||||||
use std::os::unix::fs::PermissionsExt;
|
use std::os::unix::fs::PermissionsExt;
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
use std::os::windows::fs::symlink_file;
|
use std::os::windows::fs::symlink_file;
|
||||||
|
@ -2381,13 +2381,18 @@ fn test_copy_symlink_force() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(all(unix, not(target_os = "freebsd")))]
|
#[cfg(unix)]
|
||||||
fn test_no_preserve_mode() {
|
fn test_no_preserve_mode() {
|
||||||
use std::os::unix::prelude::MetadataExt;
|
use std::os::unix::prelude::MetadataExt;
|
||||||
|
|
||||||
use uucore::mode::get_umask;
|
use uucore::mode::get_umask;
|
||||||
|
|
||||||
const PERMS_ALL: u32 = 0o7777;
|
const PERMS_ALL: u32 = if cfg!(target_os = "freebsd") {
|
||||||
|
// Only the superuser can set the sticky bit on a file.
|
||||||
|
0o6777
|
||||||
|
} else {
|
||||||
|
0o7777
|
||||||
|
};
|
||||||
|
|
||||||
let (at, mut ucmd) = at_and_ucmd!();
|
let (at, mut ucmd) = at_and_ucmd!();
|
||||||
at.touch("file");
|
at.touch("file");
|
||||||
|
@ -2407,11 +2412,16 @@ fn test_no_preserve_mode() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(all(unix, not(target_os = "freebsd")))]
|
#[cfg(unix)]
|
||||||
fn test_preserve_mode() {
|
fn test_preserve_mode() {
|
||||||
use std::os::unix::prelude::MetadataExt;
|
use std::os::unix::prelude::MetadataExt;
|
||||||
|
|
||||||
const PERMS_ALL: u32 = 0o7777;
|
const PERMS_ALL: u32 = if cfg!(target_os = "freebsd") {
|
||||||
|
// Only the superuser can set the sticky bit on a file.
|
||||||
|
0o6777
|
||||||
|
} else {
|
||||||
|
0o7777
|
||||||
|
};
|
||||||
|
|
||||||
let (at, mut ucmd) = at_and_ucmd!();
|
let (at, mut ucmd) = at_and_ucmd!();
|
||||||
at.touch("file");
|
at.touch("file");
|
||||||
|
|
|
@ -159,6 +159,18 @@ fn test_quoting() {
|
||||||
.no_stderr();
|
.no_stderr();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
#[test]
|
||||||
|
fn test_print_ls_colors() {
|
||||||
|
new_ucmd!()
|
||||||
|
.pipe_in("OWT 40;33\n")
|
||||||
|
.args(&["--print-ls-colors"])
|
||||||
|
.succeeds()
|
||||||
|
.stdout_is("\x1B[40;33mtw\t40;33\x1B[0m\n")
|
||||||
|
.no_stderr();
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_extra_operand() {
|
fn test_extra_operand() {
|
||||||
new_ucmd!()
|
new_ucmd!()
|
||||||
|
|
|
@ -365,12 +365,19 @@ fn test_du_no_dereference() {
|
||||||
.stdout_does_not_contain(symlink);
|
.stdout_does_not_contain(symlink);
|
||||||
|
|
||||||
// ensure dereference "wins"
|
// ensure dereference "wins"
|
||||||
ts.ucmd()
|
let result = ts.ucmd().arg(arg).arg("--dereference").succeeds();
|
||||||
.arg(arg)
|
|
||||||
.arg("--dereference")
|
#[cfg(target_os = "linux")]
|
||||||
.succeeds()
|
{
|
||||||
.stdout_contains(symlink)
|
let result_reference = unwrap_or_return!(expected_result(&ts, &[arg, "--dereference"]));
|
||||||
.stdout_does_not_contain(dir);
|
|
||||||
|
if result_reference.succeeded() {
|
||||||
|
assert_eq!(result.stdout_str(), result_reference.stdout_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "linux"))]
|
||||||
|
result.stdout_contains(symlink).stdout_does_not_contain(dir);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -441,6 +448,7 @@ fn test_du_inodes() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "android"))]
|
||||||
#[test]
|
#[test]
|
||||||
fn test_du_inodes_with_count_links() {
|
fn test_du_inodes_with_count_links() {
|
||||||
let ts = TestScenario::new(util_name!());
|
let ts = TestScenario::new(util_name!());
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
//
|
//
|
||||||
// For the full copyright and license information, please view the LICENSE
|
// For the full copyright and license information, please view the LICENSE
|
||||||
// file that was distributed with this source code.
|
// file that was distributed with this source code.
|
||||||
// spell-checker:ignore (words) READMECAREFULLY birthtime doesntexist oneline somebackup lrwx somefile somegroup somehiddenbackup somehiddenfile tabsize aaaaaaaa bbbb cccc dddddddd ncccc neee naaaaa nbcdef nfffff dired subdired tmpfs
|
// spell-checker:ignore (words) READMECAREFULLY birthtime doesntexist oneline somebackup lrwx somefile somegroup somehiddenbackup somehiddenfile tabsize aaaaaaaa bbbb cccc dddddddd ncccc neee naaaaa nbcdef nfffff dired subdired tmpfs mdir
|
||||||
|
|
||||||
#[cfg(any(unix, feature = "feat_selinux"))]
|
#[cfg(any(unix, feature = "feat_selinux"))]
|
||||||
use crate::common::util::expected_result;
|
use crate::common::util::expected_result;
|
||||||
|
@ -864,11 +864,11 @@ fn test_ls_zero() {
|
||||||
.succeeds()
|
.succeeds()
|
||||||
.stdout_only("\"0-test-zero\"\x00\"2-test-zero\"\x00\"3-test-zero\"\x00");
|
.stdout_only("\"0-test-zero\"\x00\"2-test-zero\"\x00\"3-test-zero\"\x00");
|
||||||
|
|
||||||
scene
|
let result = scene.ucmd().args(&["--zero", "--color=always"]).succeeds();
|
||||||
.ucmd()
|
assert_eq!(
|
||||||
.args(&["--zero", "--color=always"])
|
result.stdout_str(),
|
||||||
.succeeds()
|
"\u{1b}[0m\u{1b}[01;34m0-test-zero\x1b[0m\x002-test-zero\x003-test-zero\x00"
|
||||||
.stdout_only("\x1b[1;34m0-test-zero\x1b[0m\x002-test-zero\x003-test-zero\x00");
|
);
|
||||||
|
|
||||||
scene
|
scene
|
||||||
.ucmd()
|
.ucmd()
|
||||||
|
@ -921,12 +921,9 @@ fn test_ls_zero() {
|
||||||
"\"0-test-zero\"\x00\"1\\ntest-zero\"\x00\"2-test-zero\"\x00\"3-test-zero\"\x00",
|
"\"0-test-zero\"\x00\"1\\ntest-zero\"\x00\"2-test-zero\"\x00\"3-test-zero\"\x00",
|
||||||
);
|
);
|
||||||
|
|
||||||
scene
|
let result = scene.ucmd().args(&["--zero", "--color=always"]).succeeds();
|
||||||
.ucmd()
|
assert_eq!(result.stdout_str(),
|
||||||
.args(&["--zero", "--color=always"])
|
"\u{1b}[0m\u{1b}[01;34m0-test-zero\x1b[0m\x001\ntest-zero\x002-test-zero\x003-test-zero\x00",
|
||||||
.succeeds()
|
|
||||||
.stdout_only(
|
|
||||||
"\x1b[1;34m0-test-zero\x1b[0m\x001\ntest-zero\x002-test-zero\x003-test-zero\x00",
|
|
||||||
);
|
);
|
||||||
|
|
||||||
scene
|
scene
|
||||||
|
@ -1202,12 +1199,21 @@ fn test_ls_long_symlink_color() {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn capture_colored_string(input: &str) -> (Color, Name) {
|
fn capture_colored_string(input: &str) -> (Color, Name) {
|
||||||
let colored_name = Regex::new(r"\x1b\[([0-9;]+)m(.+)\x1b\[0m").unwrap();
|
// Input can be:
|
||||||
|
// \u{1b}[0m\u{1b}[01;36mln-dir3\u{1b}[0m
|
||||||
|
// \u{1b}[0m\u{1b}[01;34m./dir1/dir2/dir3\u{1b}[0m
|
||||||
|
// \u{1b}[0m\u{1b}[01;36mln-file-invalid\u{1b}[0m
|
||||||
|
// \u{1b}[01;36mdir1/invalid-target\u{1b}[0m
|
||||||
|
let colored_name = Regex::new(r"(?:\x1b\[0m\x1b)?\[([0-9;]+)m(.+)\x1b\[0m").unwrap();
|
||||||
match colored_name.captures(input) {
|
match colored_name.captures(input) {
|
||||||
Some(captures) => (
|
Some(captures) => {
|
||||||
captures.get(1).unwrap().as_str().to_string(),
|
dbg!(captures.get(1).unwrap().as_str().to_string());
|
||||||
captures.get(2).unwrap().as_str().to_string(),
|
dbg!(captures.get(2).unwrap().as_str().to_string());
|
||||||
),
|
return (
|
||||||
|
captures.get(1).unwrap().as_str().to_string(),
|
||||||
|
captures.get(2).unwrap().as_str().to_string(),
|
||||||
|
);
|
||||||
|
}
|
||||||
None => (String::new(), input.to_string()),
|
None => (String::new(), input.to_string()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1995,9 +2001,9 @@ fn test_ls_color() {
|
||||||
at.touch(nested_file);
|
at.touch(nested_file);
|
||||||
at.touch("test-color");
|
at.touch("test-color");
|
||||||
|
|
||||||
let a_with_colors = "\x1b[1;34ma\x1b[0m";
|
let a_with_colors = "\x1b[0m\x1b[01;34ma\x1b[0m";
|
||||||
let z_with_colors = "\x1b[1;34mz\x1b[0m";
|
let z_with_colors = "\x1b[01;34mz\x1b[0m\n";
|
||||||
let nested_dir_with_colors = "\x1b[1;34mnested_dir\x1b[0m"; // spell-checker:disable-line
|
let nested_dir_with_colors = "\x1b[0m\x1b[01;34mnested_dir\x1b[0m\x0anested_file"; // spell-checker:disable-line
|
||||||
|
|
||||||
// Color is disabled by default
|
// Color is disabled by default
|
||||||
let result = scene.ucmd().succeeds();
|
let result = scene.ucmd().succeeds();
|
||||||
|
@ -2006,12 +2012,9 @@ fn test_ls_color() {
|
||||||
|
|
||||||
// Color should be enabled
|
// Color should be enabled
|
||||||
for param in ["--color", "--col", "--color=always", "--col=always"] {
|
for param in ["--color", "--col", "--color=always", "--col=always"] {
|
||||||
scene
|
let result = scene.ucmd().arg(param).succeeds();
|
||||||
.ucmd()
|
assert!(result.stdout_str().contains(a_with_colors));
|
||||||
.arg(param)
|
assert!(result.stdout_str().contains(z_with_colors));
|
||||||
.succeeds()
|
|
||||||
.stdout_contains(a_with_colors)
|
|
||||||
.stdout_contains(z_with_colors);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Color should be disabled
|
// Color should be disabled
|
||||||
|
@ -2020,12 +2023,8 @@ fn test_ls_color() {
|
||||||
assert!(!result.stdout_str().contains(z_with_colors));
|
assert!(!result.stdout_str().contains(z_with_colors));
|
||||||
|
|
||||||
// Nested dir should be shown and colored
|
// Nested dir should be shown and colored
|
||||||
scene
|
let result = scene.ucmd().arg("--color").arg("a").succeeds();
|
||||||
.ucmd()
|
assert!(result.stdout_str().contains(nested_dir_with_colors));
|
||||||
.arg("--color")
|
|
||||||
.arg("a")
|
|
||||||
.succeeds()
|
|
||||||
.stdout_contains(nested_dir_with_colors);
|
|
||||||
|
|
||||||
// No output
|
// No output
|
||||||
scene
|
scene
|
||||||
|
@ -2037,13 +2036,18 @@ fn test_ls_color() {
|
||||||
|
|
||||||
// The colors must not mess up the grid layout
|
// The colors must not mess up the grid layout
|
||||||
at.touch("b");
|
at.touch("b");
|
||||||
scene
|
let result = scene
|
||||||
.ucmd()
|
.ucmd()
|
||||||
.arg("--color")
|
.arg("--color")
|
||||||
.arg("-w=15")
|
.arg("-w=15")
|
||||||
.arg("-C")
|
.arg("-C")
|
||||||
.succeeds()
|
.succeeds();
|
||||||
.stdout_only(format!("{a_with_colors} test-color\nb {z_with_colors}\n"));
|
let expected = format!("{} test-color\x0ab {}", a_with_colors, z_with_colors);
|
||||||
|
assert_eq!(
|
||||||
|
result.stdout_str().escape_default().to_string(),
|
||||||
|
expected.escape_default().to_string()
|
||||||
|
);
|
||||||
|
assert_eq!(result.stdout_str(), expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
|
@ -2468,13 +2472,16 @@ fn test_ls_quoting_style() {
|
||||||
{
|
{
|
||||||
at.touch("one\ntwo");
|
at.touch("one\ntwo");
|
||||||
at.touch("one\\two");
|
at.touch("one\\two");
|
||||||
// Default is shell-escape
|
// Default is literal, when stdout is not a TTY.
|
||||||
|
// Otherwise, it is shell-escape
|
||||||
scene
|
scene
|
||||||
.ucmd()
|
.ucmd()
|
||||||
.arg("--hide-control-chars")
|
.arg("--hide-control-chars")
|
||||||
.arg("one\ntwo")
|
.arg("one\ntwo")
|
||||||
.succeeds()
|
.succeeds()
|
||||||
.stdout_only("'one'$'\\n''two'\n");
|
.stdout_only("one?two\n");
|
||||||
|
// TODO: TTY-expected output, find a way to check this as well
|
||||||
|
// .stdout_only("'one'$'\\n''two'\n");
|
||||||
|
|
||||||
for (arg, correct) in [
|
for (arg, correct) in [
|
||||||
("--quoting-style=literal", "one?two"),
|
("--quoting-style=literal", "one?two"),
|
||||||
|
@ -2561,7 +2568,9 @@ fn test_ls_quoting_style() {
|
||||||
.ucmd()
|
.ucmd()
|
||||||
.arg("one two")
|
.arg("one two")
|
||||||
.succeeds()
|
.succeeds()
|
||||||
.stdout_only("'one two'\n");
|
.stdout_only("one two\n");
|
||||||
|
// TODO: TTY-expected output
|
||||||
|
// .stdout_only("'one two'\n");
|
||||||
|
|
||||||
for (arg, correct) in [
|
for (arg, correct) in [
|
||||||
("--quoting-style=literal", "one two"),
|
("--quoting-style=literal", "one two"),
|
||||||
|
@ -2624,7 +2633,9 @@ fn test_ls_quoting_and_color() {
|
||||||
.arg("--color")
|
.arg("--color")
|
||||||
.arg("one two")
|
.arg("one two")
|
||||||
.succeeds()
|
.succeeds()
|
||||||
.stdout_only("'one two'\n");
|
.stdout_only("one two\n");
|
||||||
|
// TODO: TTY-expected output
|
||||||
|
// .stdout_only("'one two'\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -3156,11 +3167,8 @@ fn test_ls_path() {
|
||||||
.stdout_is(expected_stdout);
|
.stdout_is(expected_stdout);
|
||||||
|
|
||||||
let abs_path = format!("{}/{}", at.as_string(), path);
|
let abs_path = format!("{}/{}", at.as_string(), path);
|
||||||
let expected_stdout = if cfg!(windows) {
|
let expected_stdout = format!("{abs_path}\n");
|
||||||
format!("\'{abs_path}\'\n")
|
|
||||||
} else {
|
|
||||||
format!("{abs_path}\n")
|
|
||||||
};
|
|
||||||
scene.ucmd().arg(&abs_path).run().stdout_is(expected_stdout);
|
scene.ucmd().arg(&abs_path).run().stdout_is(expected_stdout);
|
||||||
|
|
||||||
let expected_stdout = format!("{path}\n{file1}\n");
|
let expected_stdout = format!("{path}\n{file1}\n");
|
||||||
|
@ -3828,3 +3836,80 @@ fn test_ls_cf_output_should_be_delimited_by_tab() {
|
||||||
.succeeds()
|
.succeeds()
|
||||||
.stdout_is("a2345/\tb/\n");
|
.stdout_is("a2345/\tb/\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(all(unix, feature = "dd"))]
|
||||||
|
#[test]
|
||||||
|
fn test_posixly_correct() {
|
||||||
|
let scene = TestScenario::new(util_name!());
|
||||||
|
|
||||||
|
scene
|
||||||
|
.ccmd("dd")
|
||||||
|
.arg("if=/dev/zero")
|
||||||
|
.arg("of=file")
|
||||||
|
.arg("bs=1024")
|
||||||
|
.arg("count=1")
|
||||||
|
.succeeds();
|
||||||
|
|
||||||
|
scene
|
||||||
|
.ucmd()
|
||||||
|
.arg("-s")
|
||||||
|
.succeeds()
|
||||||
|
.stdout_contains_line("total 4");
|
||||||
|
|
||||||
|
scene
|
||||||
|
.ucmd()
|
||||||
|
.arg("-s")
|
||||||
|
.env("POSIXLY_CORRECT", "some_value")
|
||||||
|
.succeeds()
|
||||||
|
.stdout_contains_line("total 8");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_ls_hyperlink() {
|
||||||
|
let scene = TestScenario::new(util_name!());
|
||||||
|
let at = &scene.fixtures;
|
||||||
|
let file = "a.txt";
|
||||||
|
|
||||||
|
at.touch(file);
|
||||||
|
|
||||||
|
let path = at.root_dir_resolved();
|
||||||
|
let separator = std::path::MAIN_SEPARATOR_STR;
|
||||||
|
|
||||||
|
let result = scene.ucmd().arg("--hyperlink").succeeds();
|
||||||
|
assert!(result.stdout_str().contains("\x1b]8;;file://"));
|
||||||
|
assert!(result
|
||||||
|
.stdout_str()
|
||||||
|
.contains(&format!("{path}{separator}{file}\x07{file}\x1b]8;;\x07")));
|
||||||
|
|
||||||
|
let result = scene.ucmd().arg("--hyperlink=always").succeeds();
|
||||||
|
assert!(result.stdout_str().contains("\x1b]8;;file://"));
|
||||||
|
assert!(result
|
||||||
|
.stdout_str()
|
||||||
|
.contains(&format!("{path}{separator}{file}\x07{file}\x1b]8;;\x07")));
|
||||||
|
|
||||||
|
scene
|
||||||
|
.ucmd()
|
||||||
|
.arg("--hyperlink=never")
|
||||||
|
.succeeds()
|
||||||
|
.stdout_is(format!("{file}\n"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_ls_color_do_not_reset() {
|
||||||
|
let scene: TestScenario = TestScenario::new(util_name!());
|
||||||
|
let at = &scene.fixtures;
|
||||||
|
at.mkdir("example");
|
||||||
|
at.mkdir("example/a");
|
||||||
|
at.mkdir("example/b");
|
||||||
|
|
||||||
|
let result = scene
|
||||||
|
.ucmd()
|
||||||
|
.arg("--color=always")
|
||||||
|
.arg("example/")
|
||||||
|
.succeeds();
|
||||||
|
// the second color code should not have a reset
|
||||||
|
assert_eq!(
|
||||||
|
result.stdout_str().escape_default().to_string(),
|
||||||
|
"\\u{1b}[0m\\u{1b}[01;34ma\\u{1b}[0m\\n\\u{1b}[01;34mb\\u{1b}[0m\\n"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -1158,6 +1158,32 @@ fn test_mv_overwrite_dir() {
|
||||||
assert!(at.dir_exists(dir_b));
|
assert!(at.dir_exists(dir_b));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_mv_no_target_dir_with_dest_not_existing() {
|
||||||
|
let (at, mut ucmd) = at_and_ucmd!();
|
||||||
|
let dir_a = "a";
|
||||||
|
let dir_b = "b";
|
||||||
|
|
||||||
|
at.mkdir(dir_a);
|
||||||
|
ucmd.arg("-T").arg(dir_a).arg(dir_b).succeeds().no_output();
|
||||||
|
|
||||||
|
assert!(!at.dir_exists(dir_a));
|
||||||
|
assert!(at.dir_exists(dir_b));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_mv_no_target_dir_with_dest_not_existing_and_ending_with_slash() {
|
||||||
|
let (at, mut ucmd) = at_and_ucmd!();
|
||||||
|
let dir_a = "a";
|
||||||
|
let dir_b = "b/";
|
||||||
|
|
||||||
|
at.mkdir(dir_a);
|
||||||
|
ucmd.arg("-T").arg(dir_a).arg(dir_b).succeeds().no_output();
|
||||||
|
|
||||||
|
assert!(!at.dir_exists(dir_a));
|
||||||
|
assert!(at.dir_exists(dir_b));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_mv_overwrite_nonempty_dir() {
|
fn test_mv_overwrite_nonempty_dir() {
|
||||||
let (at, mut ucmd) = at_and_ucmd!();
|
let (at, mut ucmd) = at_and_ucmd!();
|
||||||
|
|
|
@ -553,7 +553,7 @@ fn test_nonexistent_file_is_not_symlink() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
// FixME: freebsd fails with 'chmod: sticky_file: Inappropriate file type or format'
|
// Only the superuser is allowed to set the sticky bit on files on FreeBSD.
|
||||||
// Windows has no concept of sticky bit
|
// Windows has no concept of sticky bit
|
||||||
#[cfg(not(any(windows, target_os = "freebsd")))]
|
#[cfg(not(any(windows, target_os = "freebsd")))]
|
||||||
fn test_file_is_sticky() {
|
fn test_file_is_sticky() {
|
||||||
|
|
|
@ -243,6 +243,14 @@ fn test_single_only_lines() {
|
||||||
.stdout_is("18 moby_dick.txt\n");
|
.stdout_is("18 moby_dick.txt\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_single_only_bytes() {
|
||||||
|
new_ucmd!()
|
||||||
|
.args(&["-c", "lorem_ipsum.txt"])
|
||||||
|
.run()
|
||||||
|
.stdout_is("772 lorem_ipsum.txt\n");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_single_all_counts() {
|
fn test_single_all_counts() {
|
||||||
new_ucmd!()
|
new_ucmd!()
|
||||||
|
@ -419,6 +427,14 @@ fn test_files_from_pseudo_filesystem() {
|
||||||
use pretty_assertions::assert_ne;
|
use pretty_assertions::assert_ne;
|
||||||
let result = new_ucmd!().arg("-c").arg("/proc/cpuinfo").succeeds();
|
let result = new_ucmd!().arg("-c").arg("/proc/cpuinfo").succeeds();
|
||||||
assert_ne!(result.stdout_str(), "0 /proc/cpuinfo\n");
|
assert_ne!(result.stdout_str(), "0 /proc/cpuinfo\n");
|
||||||
|
|
||||||
|
let (at, mut ucmd) = at_and_ucmd!();
|
||||||
|
let result = ucmd.arg("-c").arg("/sys/kernel/profiling").succeeds();
|
||||||
|
let actual = at.read("/sys/kernel/profiling").len();
|
||||||
|
assert_eq!(
|
||||||
|
result.stdout_str(),
|
||||||
|
format!("{} /sys/kernel/profiling\n", actual)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
56
tests/fixtures/dircolors/internal.expected
vendored
56
tests/fixtures/dircolors/internal.expected
vendored
|
@ -1,8 +1,5 @@
|
||||||
# Configuration file for dircolors, a utility to help you set the
|
# Configuration file for dircolors, a utility to help you set the
|
||||||
# LS_COLORS environment variable used by GNU ls with the --color option.
|
# LS_COLORS environment variable used by GNU ls with the --color option.
|
||||||
# Copyright (C) 1996-2022 Free Software Foundation, Inc.
|
|
||||||
# Copying and distribution of this file, with or without modification,
|
|
||||||
# are permitted provided the copyright notice and this notice are preserved.
|
|
||||||
# The keywords COLOR, OPTIONS, and EIGHTBIT (honored by the
|
# The keywords COLOR, OPTIONS, and EIGHTBIT (honored by the
|
||||||
# slackware version of dircolors) are recognized but ignored.
|
# slackware version of dircolors) are recognized but ignored.
|
||||||
# Global config options can be specified before TERM or COLORTERM entries
|
# Global config options can be specified before TERM or COLORTERM entries
|
||||||
|
@ -46,40 +43,26 @@ TERM xterm*
|
||||||
# 40=black 41=red 42=green 43=yellow 44=blue 45=magenta 46=cyan 47=white
|
# 40=black 41=red 42=green 43=yellow 44=blue 45=magenta 46=cyan 47=white
|
||||||
#NORMAL 00 # no color code at all
|
#NORMAL 00 # no color code at all
|
||||||
#FILE 00 # regular file: use no color at all
|
#FILE 00 # regular file: use no color at all
|
||||||
RESET 0 # reset to "normal" color
|
RESET 0
|
||||||
DIR 01;34 # directory
|
DIR 01;34
|
||||||
LINK 01;36 # symbolic link. (If you set this to 'target' instead of a
|
LINK 01;36
|
||||||
# numerical value, the color is as for the file pointed to.)
|
MULTIHARDLINK 00
|
||||||
MULTIHARDLINK 00 # regular file with more than one link
|
FIFO 40;33
|
||||||
FIFO 40;33 # pipe
|
SOCK 01;35
|
||||||
SOCK 01;35 # socket
|
DOOR 01;35
|
||||||
DOOR 01;35 # door
|
BLK 40;33;01
|
||||||
BLK 40;33;01 # block device driver
|
CHR 40;33;01
|
||||||
CHR 40;33;01 # character device driver
|
ORPHAN 40;31;01
|
||||||
ORPHAN 40;31;01 # symlink to nonexistent file, or non-stat'able file ...
|
MISSING 00
|
||||||
MISSING 00 # ... and the files they point to
|
SETUID 37;41
|
||||||
SETUID 37;41 # file that is setuid (u+s)
|
SETGID 30;43
|
||||||
SETGID 30;43 # file that is setgid (g+s)
|
CAPABILITY 00
|
||||||
CAPABILITY 00 # file with capability (very expensive to lookup)
|
STICKY_OTHER_WRITABLE 30;42
|
||||||
STICKY_OTHER_WRITABLE 30;42 # dir that is sticky and other-writable (+t,o+w)
|
OTHER_WRITABLE 34;42
|
||||||
OTHER_WRITABLE 34;42 # dir that is other-writable (o+w) and not sticky
|
STICKY 37;44
|
||||||
STICKY 37;44 # dir with the sticky bit set (+t) and not other-writable
|
|
||||||
# This is for files with execute permission:
|
|
||||||
EXEC 01;32
|
EXEC 01;32
|
||||||
# List any file extensions like '.gz' or '.tar' that you would like ls
|
# List any file extensions like '.gz' or '.tar' that you would like ls
|
||||||
# to color below. Put the extension, a space, and the color init string.
|
# to color below. Put the extension, a space, and the color init string.
|
||||||
# (and any comments you want to add after a '#')
|
|
||||||
# If you use DOS-style suffixes, you may want to uncomment the following:
|
|
||||||
#.cmd 01;32 # executables (bright green)
|
|
||||||
#.exe 01;32
|
|
||||||
#.com 01;32
|
|
||||||
#.btm 01;32
|
|
||||||
#.bat 01;32
|
|
||||||
# Or if you want to color scripts even if they do not have the
|
|
||||||
# executable bit actually set.
|
|
||||||
#.sh 01;32
|
|
||||||
#.csh 01;32
|
|
||||||
# archives or compressed (bright red)
|
|
||||||
.tar 01;31
|
.tar 01;31
|
||||||
.tgz 01;31
|
.tgz 01;31
|
||||||
.arc 01;31
|
.arc 01;31
|
||||||
|
@ -126,7 +109,6 @@ EXEC 01;32
|
||||||
.swm 01;31
|
.swm 01;31
|
||||||
.dwm 01;31
|
.dwm 01;31
|
||||||
.esd 01;31
|
.esd 01;31
|
||||||
# image formats
|
|
||||||
.avif 01;35
|
.avif 01;35
|
||||||
.jpg 01;35
|
.jpg 01;35
|
||||||
.jpeg 01;35
|
.jpeg 01;35
|
||||||
|
@ -176,10 +158,8 @@ EXEC 01;32
|
||||||
.yuv 01;35
|
.yuv 01;35
|
||||||
.cgm 01;35
|
.cgm 01;35
|
||||||
.emf 01;35
|
.emf 01;35
|
||||||
# https://wiki.xiph.org/MIME_Types_and_File_Extensions
|
|
||||||
.ogv 01;35
|
.ogv 01;35
|
||||||
.ogx 01;35
|
.ogx 01;35
|
||||||
# audio formats
|
|
||||||
.aac 00;36
|
.aac 00;36
|
||||||
.au 00;36
|
.au 00;36
|
||||||
.flac 00;36
|
.flac 00;36
|
||||||
|
@ -192,12 +172,10 @@ EXEC 01;32
|
||||||
.ogg 00;36
|
.ogg 00;36
|
||||||
.ra 00;36
|
.ra 00;36
|
||||||
.wav 00;36
|
.wav 00;36
|
||||||
# https://wiki.xiph.org/MIME_Types_and_File_Extensions
|
|
||||||
.oga 00;36
|
.oga 00;36
|
||||||
.opus 00;36
|
.opus 00;36
|
||||||
.spx 00;36
|
.spx 00;36
|
||||||
.xspf 00;36
|
.xspf 00;36
|
||||||
# backup files
|
|
||||||
*~ 00;90
|
*~ 00;90
|
||||||
*# 00;90
|
*# 00;90
|
||||||
.bak 00;90
|
.bak 00;90
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue