mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-28 19:47:45 +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
|
||||
uses: mozilla-actions/sccache-action@v0.0.3
|
||||
- name: Prepare, build and test
|
||||
uses: vmactions/freebsd-vm@v1.0.2
|
||||
uses: vmactions/freebsd-vm@v1.0.5
|
||||
with:
|
||||
usesh: true
|
||||
sync: rsync
|
||||
|
@ -131,7 +131,7 @@ jobs:
|
|||
- name: Run sccache-cache
|
||||
uses: mozilla-actions/sccache-action@v0.0.3
|
||||
- name: Prepare, build and test
|
||||
uses: vmactions/freebsd-vm@v1.0.2
|
||||
uses: vmactions/freebsd-vm@v1.0.5
|
||||
with:
|
||||
usesh: true
|
||||
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_expr, should_pass: true }
|
||||
- { 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_size, 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]]
|
||||
name = "once_cell"
|
||||
version = "1.18.0"
|
||||
version = "1.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
|
||||
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
|
||||
|
||||
[[package]]
|
||||
name = "onig"
|
||||
|
@ -2618,6 +2618,7 @@ dependencies = [
|
|||
"chrono",
|
||||
"clap",
|
||||
"glob",
|
||||
"hostname",
|
||||
"lscolors",
|
||||
"number_prefix",
|
||||
"once_cell",
|
||||
|
@ -3518,9 +3519,9 @@ checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
|
|||
|
||||
[[package]]
|
||||
name = "xattr"
|
||||
version = "1.0.1"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f4686009f71ff3e5c4dbcf1a282d0a44db3f021ba69350cd42086b3e5f1c6985"
|
||||
checksum = "fbc6ab6ec1907d1a901cdbcd2bd4cb9e7d64ce5c9739cbb97d3c391acd8c7fae"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
|
|
@ -284,11 +284,12 @@ fundu = "2.0.0"
|
|||
gcd = "2.3"
|
||||
glob = "0.3.1"
|
||||
half = "2.3"
|
||||
hostname = "0.3"
|
||||
indicatif = "0.17"
|
||||
itertools = "0.12.0"
|
||||
libc = "0.2.150"
|
||||
lscolors = { version = "0.16.0", default-features = false, features = [
|
||||
"nu-ansi-term",
|
||||
"gnu_legacy",
|
||||
] }
|
||||
memchr = "2"
|
||||
memmap2 = "0.9"
|
||||
|
@ -298,7 +299,7 @@ notify = { version = "=6.0.1", features = ["macos_kqueue"] }
|
|||
num-bigint = "0.4.4"
|
||||
num-traits = "0.2.17"
|
||||
number_prefix = "0.4"
|
||||
once_cell = "1.18.0"
|
||||
once_cell = "1.19.0"
|
||||
onig = { version = "~6.4", default-features = false }
|
||||
parse_datetime = "0.5.0"
|
||||
phf = "0.11.2"
|
||||
|
@ -329,7 +330,7 @@ utf-8 = "0.7.6"
|
|||
walkdir = "2.4"
|
||||
winapi-util = "0.1.6"
|
||||
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"] }
|
||||
|
||||
hex = "0.4.3"
|
||||
|
|
|
@ -17,7 +17,8 @@ uu_date = { path = "../src/uu/date/" }
|
|||
uu_test = { path = "../src/uu/test/" }
|
||||
uu_expr = { path = "../src/uu/expr/" }
|
||||
uu_printf = { path = "../src/uu/printf/" }
|
||||
|
||||
uu_echo = { path = "../src/uu/echo/" }
|
||||
uu_seq = { path = "../src/uu/seq/" }
|
||||
|
||||
# Prevent this from interfering with workspaces
|
||||
[workspace]
|
||||
|
@ -35,6 +36,18 @@ path = "fuzz_targets/fuzz_printf.rs"
|
|||
test = 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]]
|
||||
name = "fuzz_expr"
|
||||
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]
|
||||
clap = { workspace = true }
|
||||
uucore = { workspace = true }
|
||||
uucore = { workspace = true, features = ["colors"] }
|
||||
|
||||
[[bin]]
|
||||
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::env;
|
||||
use std::fs::File;
|
||||
//use std::io::IsTerminal;
|
||||
use std::io::{BufRead, BufReader};
|
||||
use std::path::Path;
|
||||
|
||||
use clap::{crate_version, Arg, ArgAction, Command};
|
||||
use uucore::colors::{FILE_ATTRIBUTE_CODES, FILE_COLORS, FILE_TYPES, TERMS};
|
||||
use uucore::display::Quotable;
|
||||
use uucore::error::{UResult, USimpleError, UUsageError};
|
||||
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 AFTER_HELP: &str = help_section!("after help", "dircolors.md");
|
||||
|
||||
mod colors;
|
||||
use self::colors::INTERNAL_DB;
|
||||
|
||||
#[derive(PartialEq, Eq, Debug)]
|
||||
pub enum OutputFmt {
|
||||
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]
|
||||
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||
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(());
|
||||
}
|
||||
|
||||
|
@ -125,7 +196,20 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
|||
|
||||
let result;
|
||||
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 {
|
||||
return Err(UUsageError::new(
|
||||
1,
|
||||
|
@ -133,6 +217,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
|||
));
|
||||
} else if files[0].eq("-") {
|
||||
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]);
|
||||
} else {
|
||||
let path = Path::new(files[0]);
|
||||
|
@ -276,69 +361,25 @@ enum ParseState {
|
|||
Pass,
|
||||
}
|
||||
|
||||
use std::collections::HashMap;
|
||||
use uucore::{format_usage, parse_glob};
|
||||
|
||||
#[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
|
||||
T: IntoIterator,
|
||||
T::Item: Borrow<str>,
|
||||
{
|
||||
// 1790 > $(dircolors | wc -m)
|
||||
let mut result = String::with_capacity(1790);
|
||||
match fmt {
|
||||
OutputFmt::Shell => result.push_str("LS_COLORS='"),
|
||||
OutputFmt::CShell => result.push_str("setenv LS_COLORS '"),
|
||||
OutputFmt::Display => (),
|
||||
OutputFmt::Unknown => unreachable!(),
|
||||
}
|
||||
let (prefix, suffix) = get_colors_format_strings(fmt);
|
||||
|
||||
let mut table: HashMap<&str, &str> = HashMap::with_capacity(48);
|
||||
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");
|
||||
result.push_str(&prefix);
|
||||
|
||||
let term = env::var("TERM").unwrap_or_else(|_| "none".to_owned());
|
||||
let term = term.as_str();
|
||||
|
||||
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 line = line.borrow().purify();
|
||||
if line.is_empty() {
|
||||
|
@ -350,13 +391,13 @@ where
|
|||
let (key, val) = line.split_two();
|
||||
if val.is_empty() {
|
||||
return Err(format!(
|
||||
// The double space is what GNU is doing
|
||||
"{}:{}: invalid line; missing second token",
|
||||
fp.maybe_quote(),
|
||||
num
|
||||
));
|
||||
}
|
||||
let lower = key.to_lowercase();
|
||||
|
||||
if lower == "term" || lower == "colorterm" {
|
||||
if term.fnmatch(val) {
|
||||
state = ParseState::Matched;
|
||||
|
@ -370,6 +411,8 @@ where
|
|||
state = ParseState::Continue;
|
||||
}
|
||||
if state != ParseState::Pass {
|
||||
let search_key = lower.as_str();
|
||||
|
||||
if key.starts_with('.') {
|
||||
if *fmt == OutputFmt::Display {
|
||||
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" {
|
||||
// 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 {
|
||||
result.push_str(format!("\x1b[{val}m{s}\t{val}\x1b[0m\n").as_str());
|
||||
} else {
|
||||
|
@ -402,15 +448,11 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
match fmt {
|
||||
OutputFmt::Shell => result.push_str("';\nexport LS_COLORS"),
|
||||
OutputFmt::CShell => result.push('\''),
|
||||
OutputFmt::Display => {
|
||||
// remove latest "\n"
|
||||
result.pop();
|
||||
}
|
||||
OutputFmt::Unknown => unreachable!(),
|
||||
if fmt == &OutputFmt::Display {
|
||||
// remove latest "\n"
|
||||
result.pop();
|
||||
}
|
||||
result.push_str(&suffix);
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
@ -436,6 +478,58 @@ fn escape(s: &str) -> String {
|
|||
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)]
|
||||
mod tests {
|
||||
use super::escape;
|
||||
|
|
|
@ -3,35 +3,30 @@
|
|||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
|
||||
use chrono::prelude::DateTime;
|
||||
use chrono::Local;
|
||||
use clap::ArgAction;
|
||||
use clap::{crate_version, Arg, ArgMatches, Command};
|
||||
use chrono::{DateTime, Local};
|
||||
use clap::{crate_version, Arg, ArgAction, ArgMatches, Command};
|
||||
use glob::Pattern;
|
||||
use std::collections::HashSet;
|
||||
use std::env;
|
||||
use std::fs;
|
||||
use std::fs::File;
|
||||
use std::error::Error;
|
||||
use std::fmt::Display;
|
||||
#[cfg(not(windows))]
|
||||
use std::fs::Metadata;
|
||||
use std::io::BufRead;
|
||||
use std::io::BufReader;
|
||||
use std::fs::{self, File};
|
||||
use std::io::{BufRead, BufReader};
|
||||
#[cfg(not(windows))]
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
#[cfg(windows)]
|
||||
use std::os::windows::fs::MetadataExt;
|
||||
#[cfg(windows)]
|
||||
use std::os::windows::io::AsRawHandle;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::str::FromStr;
|
||||
use std::sync::mpsc;
|
||||
use std::thread;
|
||||
use std::time::{Duration, UNIX_EPOCH};
|
||||
use std::{error::Error, fmt::Display};
|
||||
use uucore::display::{print_verbatim, Quotable};
|
||||
use uucore::error::FromIo;
|
||||
use uucore::error::{UError, UResult, USimpleError};
|
||||
use uucore::error::{FromIo, UError, UResult, USimpleError};
|
||||
use uucore::line_ending::LineEnding;
|
||||
use uucore::parse_glob;
|
||||
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)
|
||||
const UNITS: [(char, u32); 6] = [('E', 6), ('P', 5), ('T', 4), ('G', 3), ('M', 2), ('K', 1)];
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Options {
|
||||
struct TraversalOptions {
|
||||
all: bool,
|
||||
max_depth: Option<usize>,
|
||||
total: bool,
|
||||
separate_dirs: bool,
|
||||
one_file_system: bool,
|
||||
dereference: Deref,
|
||||
count_links: bool,
|
||||
inodes: 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)]
|
||||
|
@ -101,6 +106,19 @@ enum Deref {
|
|||
None,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
enum Time {
|
||||
Accessed,
|
||||
Modified,
|
||||
Created,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
enum SizeFormat {
|
||||
Human(u64),
|
||||
BlockSize(u64),
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Hash, Clone, Copy)]
|
||||
struct FileInfo {
|
||||
file_id: u128,
|
||||
|
@ -120,7 +138,7 @@ struct 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
|
||||
let should_dereference = match &options.dereference {
|
||||
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.
|
||||
#[allow(clippy::cognitive_complexity)]
|
||||
fn du(
|
||||
mut my_stat: Stat,
|
||||
options: &Options,
|
||||
options: &TraversalOptions,
|
||||
depth: usize,
|
||||
seen_inodes: &mut HashSet<FileInfo>,
|
||||
exclude: &[Pattern],
|
||||
print_tx: &mpsc::Sender<UResult<StatPrintInfo>>,
|
||||
) -> Result<Stat, Box<mpsc::SendError<UResult<StatPrintInfo>>>> {
|
||||
if my_stat.is_dir {
|
||||
|
@ -317,7 +322,7 @@ fn du(
|
|||
match Stat::new(&entry.path(), options) {
|
||||
Ok(this_stat) => {
|
||||
// We have an exclude list
|
||||
for pattern in exclude {
|
||||
for pattern in &options.excludes {
|
||||
// Look at all patterns with both short and long paths
|
||||
// if we have 'du foo' but search to exclude 'foo/bar'
|
||||
// we need the full path
|
||||
|
@ -353,14 +358,8 @@ fn du(
|
|||
}
|
||||
}
|
||||
|
||||
let this_stat = du(
|
||||
this_stat,
|
||||
options,
|
||||
depth + 1,
|
||||
seen_inodes,
|
||||
exclude,
|
||||
print_tx,
|
||||
)?;
|
||||
let this_stat =
|
||||
du(this_stat, options, depth + 1, seen_inodes, print_tx)?;
|
||||
|
||||
if !options.separate_dirs {
|
||||
my_stat.size += this_stat.size;
|
||||
|
@ -396,58 +395,12 @@ fn du(
|
|||
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)]
|
||||
enum DuError {
|
||||
InvalidMaxDepthArg(String),
|
||||
SummarizeDepthConflict(String),
|
||||
InvalidTimeStyleArg(String),
|
||||
InvalidTimeArg(String),
|
||||
InvalidTimeArg,
|
||||
InvalidGlob(String),
|
||||
}
|
||||
|
||||
|
@ -473,11 +426,9 @@ Try '{} --help' for more information.",
|
|||
s.quote(),
|
||||
uucore::execution_phrase()
|
||||
),
|
||||
Self::InvalidTimeArg(s) => write!(
|
||||
Self::InvalidTimeArg => write!(
|
||||
f,
|
||||
"Invalid argument {} for --time.
|
||||
'birth' and 'creation' arguments are not supported on this platform.",
|
||||
s.quote()
|
||||
"'birth' and 'creation' arguments for --time are not supported on this platform.",
|
||||
),
|
||||
Self::InvalidGlob(s) => write!(f, "Invalid exclude syntax: {s}"),
|
||||
}
|
||||
|
@ -492,7 +443,7 @@ impl UError for DuError {
|
|||
Self::InvalidMaxDepthArg(_)
|
||||
| Self::SummarizeDepthConflict(_)
|
||||
| Self::InvalidTimeStyleArg(_)
|
||||
| Self::InvalidTimeArg(_)
|
||||
| Self::InvalidTimeArg
|
||||
| Self::InvalidGlob(_) => 1,
|
||||
}
|
||||
}
|
||||
|
@ -539,66 +490,17 @@ struct StatPrintInfo {
|
|||
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 {
|
||||
fn new(matches: ArgMatches, options: Options, summarize: bool) -> UResult<Self> {
|
||||
let block_size = read_block_size(
|
||||
matches
|
||||
.get_one::<String>(options::BLOCK_SIZE)
|
||||
.map(|s| s.as_str()),
|
||||
)?;
|
||||
|
||||
let multiplier: u64 = if matches.get_flag(options::SI) {
|
||||
1000
|
||||
fn choose_size(&self, stat: &Stat) -> u64 {
|
||||
if self.inodes {
|
||||
stat.inodes
|
||||
} else if self.apparent_size {
|
||||
stat.size
|
||||
} else {
|
||||
1024
|
||||
};
|
||||
|
||||
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,
|
||||
})
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
||||
fn print_stats(&self, rx: &mpsc::Receiver<UResult<StatPrintInfo>>) -> UResult<()> {
|
||||
|
@ -609,7 +511,7 @@ impl StatPrinter {
|
|||
match received {
|
||||
Ok(message) => match message {
|
||||
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 {
|
||||
grand_total += size;
|
||||
|
@ -619,7 +521,6 @@ impl StatPrinter {
|
|||
.threshold
|
||||
.map_or(false, |threshold| threshold.should_exclude(size))
|
||||
&& self
|
||||
.options
|
||||
.max_depth
|
||||
.map_or(true, |max_depth| stat_info.depth <= max_depth)
|
||||
&& (!self.summarize || stat_info.depth == 0)
|
||||
|
@ -633,29 +534,43 @@ impl StatPrinter {
|
|||
}
|
||||
}
|
||||
|
||||
if self.options.total {
|
||||
print!("{}\ttotal", (self.convert_size)(grand_total));
|
||||
if self.total {
|
||||
print!("{}\ttotal", self.convert_size(grand_total));
|
||||
print!("{}", self.line_ending);
|
||||
}
|
||||
|
||||
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<()> {
|
||||
if self.matches.contains_id(options::TIME) {
|
||||
let tm = {
|
||||
let secs = self
|
||||
.matches
|
||||
.get_one::<String>(options::TIME)
|
||||
.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);
|
||||
if let Some(time) = self.time {
|
||||
let secs = get_time_secs(time, stat)?;
|
||||
let tm = DateTime::<Local>::from(UNIX_EPOCH + Duration::from_secs(secs));
|
||||
let time_str = tm.format(&self.time_format).to_string();
|
||||
print!("{}\t{}\t", self.convert_size(size), time_str);
|
||||
} else {
|
||||
print!("{}\t", (self.convert_size)(size));
|
||||
print!("{}\t", self.convert_size(size));
|
||||
}
|
||||
|
||||
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]
|
||||
#[allow(clippy::cognitive_complexity)]
|
||||
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||
|
@ -690,10 +612,35 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
|||
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),
|
||||
max_depth,
|
||||
total: matches.get_flag(options::TOTAL),
|
||||
separate_dirs: matches.get_flag(options::SEPARATE_DIRS),
|
||||
one_file_system: matches.get_flag(options::ONE_FILE_SYSTEM),
|
||||
dereference: if matches.get_flag(options::DEREFERENCE) {
|
||||
|
@ -705,31 +652,49 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
|||
Deref::None
|
||||
},
|
||||
count_links: matches.get_flag(options::COUNT_LINKS),
|
||||
inodes: matches.get_flag(options::INODES),
|
||||
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))
|
||||
{
|
||||
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
|
||||
let stat_printer = StatPrinter::new(matches.clone(), options.clone(), summarize)?;
|
||||
let (print_tx, rx) = mpsc::channel::<UResult<StatPrintInfo>>();
|
||||
let printing_thread = thread::spawn(move || stat_printer.print_stats(&rx));
|
||||
|
||||
let excludes = build_exclude_patterns(&matches)?;
|
||||
|
||||
'loop_file: for path in files {
|
||||
// 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();
|
||||
for pattern in &excludes {
|
||||
for pattern in &traversal_options.excludes {
|
||||
if pattern.matches(&path_string) {
|
||||
// if the directory is ignored, leave early
|
||||
if options.verbose {
|
||||
if traversal_options.verbose {
|
||||
println!("{} ignored", path_string.quote());
|
||||
}
|
||||
continue 'loop_file;
|
||||
|
@ -738,13 +703,13 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
|||
}
|
||||
|
||||
// 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
|
||||
let mut seen_inodes: HashSet<FileInfo> = HashSet::new();
|
||||
if let Some(inode) = stat.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()))?;
|
||||
|
||||
print_tx
|
||||
|
@ -772,17 +737,12 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn get_time_secs(s: &str, stat: &Stat) -> Result<u64, DuError> {
|
||||
let secs = match s {
|
||||
"ctime" | "status" => stat.modified,
|
||||
"access" | "atime" | "use" => stat.accessed,
|
||||
"birth" | "creation" => stat
|
||||
.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 get_time_secs(time: Time, stat: &Stat) -> Result<u64, DuError> {
|
||||
match time {
|
||||
Time::Modified => Ok(stat.modified),
|
||||
Time::Accessed => Ok(stat.accessed),
|
||||
Time::Created => stat.created.ok_or(DuError::InvalidTimeArg),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_time_style(s: Option<&str>) -> UResult<&str> {
|
||||
|
|
|
@ -16,7 +16,7 @@ path = "src/hostname.rs"
|
|||
|
||||
[dependencies]
|
||||
clap = { workspace = true }
|
||||
hostname = { version = "0.3", features = ["set"] }
|
||||
hostname = { workspace = true, features = ["set"] }
|
||||
uucore = { workspace = true, features = ["wide"] }
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
|
|
|
@ -31,6 +31,7 @@ uucore = { workspace = true, features = [
|
|||
] }
|
||||
once_cell = { workspace = true }
|
||||
selinux = { workspace = true, optional = true }
|
||||
hostname = { workspace = true }
|
||||
|
||||
[[bin]]
|
||||
name = "ls"
|
||||
|
|
|
@ -3,14 +3,15 @@
|
|||
// For the full copyright and license information, please view the LICENSE
|
||||
// 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::{
|
||||
builder::{NonEmptyStringValueParser, ValueParser},
|
||||
crate_version, Arg, ArgAction, Command,
|
||||
};
|
||||
use glob::{MatchOptions, Pattern};
|
||||
use lscolors::LsColors;
|
||||
use lscolors::{LsColors, Style};
|
||||
|
||||
use number_prefix::NumberPrefix;
|
||||
use std::{cell::OnceCell, num::IntErrorKind};
|
||||
use std::{collections::HashSet, io::IsTerminal};
|
||||
|
@ -20,7 +21,7 @@ use std::os::windows::fs::MetadataExt;
|
|||
use std::{
|
||||
cmp::Reverse,
|
||||
error::Error,
|
||||
ffi::{OsStr, OsString},
|
||||
ffi::OsString,
|
||||
fmt::{Display, Write as FmtWrite},
|
||||
fs::{self, DirEntry, FileType, Metadata, ReadDir},
|
||||
io::{stdout, BufWriter, ErrorKind, Stdout, Write},
|
||||
|
@ -155,6 +156,7 @@ pub mod options {
|
|||
pub static GROUP_DIRECTORIES_FIRST: &str = "group-directories-first";
|
||||
pub static ZERO: &str = "zero";
|
||||
pub static DIRED: &str = "dired";
|
||||
pub static HYPERLINK: &str = "hyperlink";
|
||||
}
|
||||
|
||||
const DEFAULT_TERM_WIDTH: u16 = 80;
|
||||
|
@ -418,6 +420,7 @@ pub struct Config {
|
|||
group_directories_first: bool,
|
||||
line_ending: LineEnding,
|
||||
dired: bool,
|
||||
hyperlink: bool,
|
||||
}
|
||||
|
||||
// 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.
|
||||
///
|
||||
/// # Arguments
|
||||
|
@ -620,7 +642,9 @@ fn extract_quoting_style(options: &clap::ArgMatches, show_control: bool) -> Quot
|
|||
QuotingStyle::C {
|
||||
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 }
|
||||
} else {
|
||||
// TODO: use environment variable if available
|
||||
|
@ -736,19 +760,18 @@ impl Config {
|
|||
}
|
||||
|
||||
let sort = extract_sort(options);
|
||||
|
||||
let time = extract_time(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_si = cmd_line_bs.is_some()
|
||||
let opt_block_size = options.get_one::<String>(options::size::BLOCK_SIZE);
|
||||
let opt_si = opt_block_size.is_some()
|
||||
&& options
|
||||
.get_one::<String>(options::size::BLOCK_SIZE)
|
||||
.unwrap()
|
||||
.eq("si")
|
||||
|| options.get_flag(options::size::SI);
|
||||
let opt_hr = (cmd_line_bs.is_some()
|
||||
let opt_hr = (opt_block_size.is_some()
|
||||
&& options
|
||||
.get_one::<String>(options::size::BLOCK_SIZE)
|
||||
.unwrap()
|
||||
|
@ -756,9 +779,9 @@ impl Config {
|
|||
|| options.get_flag(options::size::HUMAN_READABLE);
|
||||
let opt_kb = options.get_flag(options::size::KIBIBYTES);
|
||||
|
||||
let bs_env_var = std::env::var_os("BLOCK_SIZE");
|
||||
let ls_bs_env_var = std::env::var_os("LS_BLOCK_SIZE");
|
||||
let pc_env_var = std::env::var_os("POSIXLY_CORRECT");
|
||||
let env_var_block_size = std::env::var_os("BLOCK_SIZE");
|
||||
let env_var_ls_block_size = std::env::var_os("LS_BLOCK_SIZE");
|
||||
let env_var_posixly_correct = std::env::var_os("POSIXLY_CORRECT");
|
||||
|
||||
let size_format = if opt_si {
|
||||
SizeFormat::Decimal
|
||||
|
@ -768,13 +791,13 @@ impl Config {
|
|||
SizeFormat::Bytes
|
||||
};
|
||||
|
||||
let raw_bs = if let Some(cmd_line_bs) = cmd_line_bs {
|
||||
OsString::from(cmd_line_bs)
|
||||
let raw_block_size = if let Some(opt_block_size) = opt_block_size {
|
||||
OsString::from(opt_block_size)
|
||||
} else if !opt_kb {
|
||||
if let Some(ls_bs_env_var) = ls_bs_env_var {
|
||||
ls_bs_env_var
|
||||
} else if let Some(bs_env_var) = bs_env_var {
|
||||
bs_env_var
|
||||
if let Some(env_var_ls_block_size) = env_var_ls_block_size {
|
||||
env_var_ls_block_size
|
||||
} else if let Some(env_var_block_size) = env_var_block_size {
|
||||
env_var_block_size
|
||||
} else {
|
||||
OsString::from("")
|
||||
}
|
||||
|
@ -782,20 +805,18 @@ impl Config {
|
|||
OsString::from("")
|
||||
};
|
||||
|
||||
let block_size: Option<u64> = if !opt_si && !opt_hr && !raw_bs.is_empty() {
|
||||
match parse_size_u64(&raw_bs.to_string_lossy()) {
|
||||
let block_size: Option<u64> = if !opt_si && !opt_hr && !raw_block_size.is_empty() {
|
||||
match parse_size_u64(&raw_block_size.to_string_lossy()) {
|
||||
Ok(size) => Some(size),
|
||||
Err(_) => {
|
||||
show!(LsError::BlockSizeParseError(cmd_line_bs.unwrap().clone()));
|
||||
show!(LsError::BlockSizeParseError(
|
||||
opt_block_size.unwrap().clone()
|
||||
));
|
||||
None
|
||||
}
|
||||
}
|
||||
} else if let Some(pc) = pc_env_var {
|
||||
if pc.as_os_str() == OsStr::new("true") || pc == OsStr::new("1") {
|
||||
Some(POSIXLY_CORRECT_BLOCK_SIZE)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else if env_var_posixly_correct.is_some() {
|
||||
Some(POSIXLY_CORRECT_BLOCK_SIZE)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
@ -1022,6 +1043,7 @@ impl Config {
|
|||
group_directories_first: options.get_flag(options::GROUP_DIRECTORIES_FIRST),
|
||||
line_ending: LineEnding::from_zero_flag(options.get_flag(options::ZERO)),
|
||||
dired,
|
||||
hyperlink,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -1156,6 +1178,19 @@ pub fn uu_app() -> Command {
|
|||
.help("generate output designed for Emacs' dired (Directory Editor) mode")
|
||||
.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
|
||||
// options, see the comment in Config::from for the reason.
|
||||
// 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 out = BufWriter::new(stdout());
|
||||
let mut dired = DiredOutput::default();
|
||||
let mut style_manager = StyleManager::new();
|
||||
let initial_locs_len = locs.len();
|
||||
|
||||
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 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() {
|
||||
// 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 listed_ancestors,
|
||||
&mut dired,
|
||||
&mut style_manager,
|
||||
)?;
|
||||
}
|
||||
if config.dired {
|
||||
|
@ -2069,6 +2106,7 @@ fn enter_directory(
|
|||
out: &mut BufWriter<Stdout>,
|
||||
listed_ancestors: &mut HashSet<FileInformation>,
|
||||
dired: &mut DiredOutput,
|
||||
style_manager: &mut StyleManager,
|
||||
) -> UResult<()> {
|
||||
// Create vec of entries with initial dot files
|
||||
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 {
|
||||
for e in entries
|
||||
|
@ -2162,7 +2200,15 @@ fn enter_directory(
|
|||
|
||||
show_dir_name(&e.p_buf, 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
|
||||
.remove(&FileInformation::from_path(&e.p_buf, e.must_dereference)?);
|
||||
} else {
|
||||
|
@ -2284,6 +2330,7 @@ fn display_items(
|
|||
config: &Config,
|
||||
out: &mut BufWriter<Stdout>,
|
||||
dired: &mut DiredOutput,
|
||||
style_manager: &mut StyleManager,
|
||||
) -> UResult<()> {
|
||||
// `-Z`, `--context`:
|
||||
// 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)?;
|
||||
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 {
|
||||
let mut longest_context_len = 1;
|
||||
|
@ -2326,7 +2373,7 @@ fn display_items(
|
|||
|
||||
for i in items {
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -2481,6 +2528,7 @@ fn display_item_long(
|
|||
config: &Config,
|
||||
out: &mut BufWriter<Stdout>,
|
||||
dired: &mut DiredOutput,
|
||||
style_manager: &mut StyleManager,
|
||||
) -> UResult<()> {
|
||||
let mut output_display: String = String::new();
|
||||
if config.dired {
|
||||
|
@ -2573,7 +2621,8 @@ fn display_item_long(
|
|||
|
||||
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 {
|
||||
let (start, end) = dired::calculate_dired(
|
||||
&dired.dired_positions,
|
||||
|
@ -2655,7 +2704,8 @@ fn display_item_long(
|
|||
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;
|
||||
|
||||
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
|
||||
/// [`std::path::Path::to_string_lossy`].
|
||||
#[allow(unused_variables)]
|
||||
#[allow(clippy::cognitive_complexity)]
|
||||
fn display_file_name(
|
||||
path: &PathData,
|
||||
|
@ -2954,6 +3003,7 @@ fn display_file_name(
|
|||
prefix_context: Option<usize>,
|
||||
more_info: String,
|
||||
out: &mut BufWriter<Stdout>,
|
||||
style_manager: &mut StyleManager,
|
||||
) -> Cell {
|
||||
// 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);
|
||||
|
@ -2962,16 +3012,29 @@ fn display_file_name(
|
|||
// infer it because the color codes mess up term_grid's width calculation.
|
||||
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 {
|
||||
let md = path.md(out);
|
||||
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 {
|
||||
color_name(
|
||||
name,
|
||||
&path.p_buf,
|
||||
path.p_buf.symlink_metadata().ok().as_ref(),
|
||||
ls_colors,
|
||||
style_manager,
|
||||
)
|
||||
};
|
||||
}
|
||||
|
@ -3060,6 +3123,7 @@ fn display_file_name(
|
|||
&target_data.p_buf,
|
||||
Some(&target_metadata),
|
||||
ls_colors,
|
||||
style_manager,
|
||||
));
|
||||
}
|
||||
} else {
|
||||
|
@ -3094,11 +3158,50 @@ fn display_file_name(
|
|||
}
|
||||
}
|
||||
|
||||
fn color_name(name: String, path: &Path, md: Option<&Metadata>, ls_colors: &LsColors) -> String {
|
||||
match ls_colors.style_for_path_with_metadata(path, md) {
|
||||
Some(style) => {
|
||||
return style.to_nu_ansi_term_style().paint(name).to_string();
|
||||
/// We need this struct to be able to store the previous style.
|
||||
/// This because we need to check the previous value in case we don't need
|
||||
/// the reset
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -341,7 +341,7 @@ fn handle_two_paths(source: &Path, target: &Path, opts: &Options) -> UResult<()>
|
|||
|
||||
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());
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
//
|
||||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
|
||||
// cSpell:ignore sysconf
|
||||
use crate::word_count::WordCount;
|
||||
|
||||
use super::WordCountable;
|
||||
|
@ -11,11 +13,19 @@ use std::fs::OpenOptions;
|
|||
use std::io::{self, ErrorKind, Read};
|
||||
|
||||
#[cfg(unix)]
|
||||
use libc::S_IFREG;
|
||||
use libc::{sysconf, S_IFREG, _SC_PAGESIZE};
|
||||
#[cfg(unix)]
|
||||
use nix::sys::stat;
|
||||
#[cfg(unix)]
|
||||
use std::io::{Seek, SeekFrom};
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
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"))]
|
||||
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.
|
||||
/// 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.
|
||||
/// 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
|
||||
/// other things such as lines and words.
|
||||
#[inline]
|
||||
|
@ -87,11 +99,60 @@ pub(crate) fn count_bytes_fast<T: WordCountable>(handle: &mut T) -> (usize, Opti
|
|||
// If stat.st_size = 0 then
|
||||
// - either the size is 0
|
||||
// - or the size is unknown.
|
||||
// The second case happens for files in pseudo-filesystems. For
|
||||
// example with /proc/version and /sys/kernel/profiling. 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);
|
||||
// The second case happens for files in pseudo-filesystems.
|
||||
// For example with /proc/version.
|
||||
// So, if it is 0 we don't report that and instead do a full read.
|
||||
//
|
||||
// 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"))]
|
||||
{
|
||||
|
@ -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.
|
||||
let mut buf = [0_u8; BUF_SIZE];
|
||||
loop {
|
||||
|
|
|
@ -17,12 +17,14 @@ use std::os::unix::io::AsRawFd;
|
|||
pub trait WordCountable: AsRawFd + Read {
|
||||
type Buffered: BufRead;
|
||||
fn buffered(self) -> Self::Buffered;
|
||||
fn inner_file(&mut self) -> Option<&mut File>;
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
pub trait WordCountable: Read {
|
||||
type Buffered: BufRead;
|
||||
fn buffered(self) -> Self::Buffered;
|
||||
fn inner_file(&mut self) -> Option<&mut File>;
|
||||
}
|
||||
|
||||
impl WordCountable for StdinLock<'_> {
|
||||
|
@ -31,6 +33,9 @@ impl WordCountable for StdinLock<'_> {
|
|||
fn buffered(self) -> Self::Buffered {
|
||||
self
|
||||
}
|
||||
fn inner_file(&mut self) -> Option<&mut File> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl WordCountable for File {
|
||||
|
@ -39,4 +44,8 @@ impl WordCountable for File {
|
|||
fn buffered(self) -> Self::Buffered {
|
||||
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 = []
|
||||
# * non-default features
|
||||
backup-control = []
|
||||
colors = []
|
||||
encoding = ["data-encoding", "data-encoding-macro", "z85", "thiserror"]
|
||||
entries = ["libc"]
|
||||
fs = ["dunce", "libc", "winapi-util", "windows-sys"]
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
|
||||
#[cfg(feature = "backup-control")]
|
||||
pub mod backup_control;
|
||||
#[cfg(feature = "colors")]
|
||||
pub mod colors;
|
||||
#[cfg(feature = "encoding")]
|
||||
pub mod encoding;
|
||||
#[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 = "freebsd"),
|
||||
not(target_os = "netbsd"),
|
||||
not(target_os = "openbsd"),
|
||||
not(target_os = "illumos"),
|
||||
not(target_os = "solaris"),
|
||||
not(target_arch = "aarch64"),
|
||||
|
@ -130,6 +131,7 @@ impl FileInformation {
|
|||
target_os = "android",
|
||||
target_os = "freebsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd",
|
||||
target_os = "illumos",
|
||||
target_os = "solaris",
|
||||
target_arch = "aarch64",
|
||||
|
@ -146,13 +148,14 @@ impl FileInformation {
|
|||
#[cfg(unix)]
|
||||
pub fn inode(&self) -> u64 {
|
||||
#[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"
|
||||
))]
|
||||
return self.0.st_ino;
|
||||
#[cfg(any(
|
||||
target_os = "freebsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd",
|
||||
not(target_pointer_width = "64")
|
||||
))]
|
||||
return self.0.st_ino.into();
|
||||
|
|
|
@ -497,7 +497,10 @@ impl FsUsage {
|
|||
#[cfg(unix)]
|
||||
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 {
|
||||
blocksize: statvfs.f_bsize as u64, // or `statvfs.f_frsize` ?
|
||||
blocks: statvfs.f_blocks,
|
||||
|
@ -507,7 +510,10 @@ impl FsUsage {
|
|||
files: statvfs.f_files,
|
||||
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 {
|
||||
blocksize: statvfs.f_bsize as u64, // or `statvfs.f_frsize` ?
|
||||
blocks: statvfs.f_blocks.into(),
|
||||
|
@ -530,6 +536,19 @@ impl FsUsage {
|
|||
files: statvfs.f_files,
|
||||
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))]
|
||||
|
@ -617,6 +636,7 @@ impl FsMeta for StatFs {
|
|||
not(target_vendor = "apple"),
|
||||
not(target_os = "android"),
|
||||
not(target_os = "freebsd"),
|
||||
not(target_os = "openbsd"),
|
||||
not(target_os = "illumos"),
|
||||
not(target_os = "solaris"),
|
||||
not(target_arch = "s390x"),
|
||||
|
@ -630,6 +650,7 @@ impl FsMeta for StatFs {
|
|||
target_arch = "s390x",
|
||||
target_vendor = "apple",
|
||||
target_os = "android",
|
||||
target_os = "openbsd",
|
||||
not(target_pointer_width = "64")
|
||||
)
|
||||
))]
|
||||
|
@ -655,11 +676,19 @@ impl FsMeta for StatFs {
|
|||
return self.f_bfree.into();
|
||||
}
|
||||
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;
|
||||
#[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();
|
||||
#[cfg(target_os = "freebsd")]
|
||||
#[cfg(any(target_os = "freebsd", target_os = "openbsd"))]
|
||||
return self.f_bavail.try_into().unwrap();
|
||||
}
|
||||
fn total_file_nodes(&self) -> u64 {
|
||||
|
|
|
@ -35,6 +35,8 @@ pub use crate::parser::shortcut_value_parser;
|
|||
// * feature-gated modules
|
||||
#[cfg(feature = "backup-control")]
|
||||
pub use crate::features::backup_control;
|
||||
#[cfg(feature = "colors")]
|
||||
pub use crate::features::colors;
|
||||
#[cfg(feature = "encoding")]
|
||||
pub use crate::features::encoding;
|
||||
#[cfg(feature = "format")]
|
||||
|
|
|
@ -13,7 +13,7 @@ use std::os::unix::fs;
|
|||
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
#[cfg(all(unix, not(target_os = "freebsd")))]
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
#[cfg(windows)]
|
||||
use std::os::windows::fs::symlink_file;
|
||||
|
@ -2381,13 +2381,18 @@ fn test_copy_symlink_force() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(all(unix, not(target_os = "freebsd")))]
|
||||
#[cfg(unix)]
|
||||
fn test_no_preserve_mode() {
|
||||
use std::os::unix::prelude::MetadataExt;
|
||||
|
||||
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!();
|
||||
at.touch("file");
|
||||
|
@ -2407,11 +2412,16 @@ fn test_no_preserve_mode() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(all(unix, not(target_os = "freebsd")))]
|
||||
#[cfg(unix)]
|
||||
fn test_preserve_mode() {
|
||||
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!();
|
||||
at.touch("file");
|
||||
|
|
|
@ -159,6 +159,18 @@ fn test_quoting() {
|
|||
.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]
|
||||
fn test_extra_operand() {
|
||||
new_ucmd!()
|
||||
|
|
|
@ -365,12 +365,19 @@ fn test_du_no_dereference() {
|
|||
.stdout_does_not_contain(symlink);
|
||||
|
||||
// ensure dereference "wins"
|
||||
ts.ucmd()
|
||||
.arg(arg)
|
||||
.arg("--dereference")
|
||||
.succeeds()
|
||||
.stdout_contains(symlink)
|
||||
.stdout_does_not_contain(dir);
|
||||
let result = ts.ucmd().arg(arg).arg("--dereference").succeeds();
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
let result_reference = unwrap_or_return!(expected_result(&ts, &[arg, "--dereference"]));
|
||||
|
||||
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]
|
||||
fn test_du_inodes_with_count_links() {
|
||||
let ts = TestScenario::new(util_name!());
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
//
|
||||
// For the full copyright and license information, please view the LICENSE
|
||||
// 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"))]
|
||||
use crate::common::util::expected_result;
|
||||
|
@ -864,11 +864,11 @@ fn test_ls_zero() {
|
|||
.succeeds()
|
||||
.stdout_only("\"0-test-zero\"\x00\"2-test-zero\"\x00\"3-test-zero\"\x00");
|
||||
|
||||
scene
|
||||
.ucmd()
|
||||
.args(&["--zero", "--color=always"])
|
||||
.succeeds()
|
||||
.stdout_only("\x1b[1;34m0-test-zero\x1b[0m\x002-test-zero\x003-test-zero\x00");
|
||||
let result = scene.ucmd().args(&["--zero", "--color=always"]).succeeds();
|
||||
assert_eq!(
|
||||
result.stdout_str(),
|
||||
"\u{1b}[0m\u{1b}[01;34m0-test-zero\x1b[0m\x002-test-zero\x003-test-zero\x00"
|
||||
);
|
||||
|
||||
scene
|
||||
.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",
|
||||
);
|
||||
|
||||
scene
|
||||
.ucmd()
|
||||
.args(&["--zero", "--color=always"])
|
||||
.succeeds()
|
||||
.stdout_only(
|
||||
"\x1b[1;34m0-test-zero\x1b[0m\x001\ntest-zero\x002-test-zero\x003-test-zero\x00",
|
||||
let result = scene.ucmd().args(&["--zero", "--color=always"]).succeeds();
|
||||
assert_eq!(result.stdout_str(),
|
||||
"\u{1b}[0m\u{1b}[01;34m0-test-zero\x1b[0m\x001\ntest-zero\x002-test-zero\x003-test-zero\x00",
|
||||
);
|
||||
|
||||
scene
|
||||
|
@ -1202,12 +1199,21 @@ fn test_ls_long_symlink_color() {
|
|||
}
|
||||
|
||||
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) {
|
||||
Some(captures) => (
|
||||
captures.get(1).unwrap().as_str().to_string(),
|
||||
captures.get(2).unwrap().as_str().to_string(),
|
||||
),
|
||||
Some(captures) => {
|
||||
dbg!(captures.get(1).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()),
|
||||
}
|
||||
}
|
||||
|
@ -1995,9 +2001,9 @@ fn test_ls_color() {
|
|||
at.touch(nested_file);
|
||||
at.touch("test-color");
|
||||
|
||||
let a_with_colors = "\x1b[1;34ma\x1b[0m";
|
||||
let z_with_colors = "\x1b[1;34mz\x1b[0m";
|
||||
let nested_dir_with_colors = "\x1b[1;34mnested_dir\x1b[0m"; // spell-checker:disable-line
|
||||
let a_with_colors = "\x1b[0m\x1b[01;34ma\x1b[0m";
|
||||
let z_with_colors = "\x1b[01;34mz\x1b[0m\n";
|
||||
let nested_dir_with_colors = "\x1b[0m\x1b[01;34mnested_dir\x1b[0m\x0anested_file"; // spell-checker:disable-line
|
||||
|
||||
// Color is disabled by default
|
||||
let result = scene.ucmd().succeeds();
|
||||
|
@ -2006,12 +2012,9 @@ fn test_ls_color() {
|
|||
|
||||
// Color should be enabled
|
||||
for param in ["--color", "--col", "--color=always", "--col=always"] {
|
||||
scene
|
||||
.ucmd()
|
||||
.arg(param)
|
||||
.succeeds()
|
||||
.stdout_contains(a_with_colors)
|
||||
.stdout_contains(z_with_colors);
|
||||
let result = scene.ucmd().arg(param).succeeds();
|
||||
assert!(result.stdout_str().contains(a_with_colors));
|
||||
assert!(result.stdout_str().contains(z_with_colors));
|
||||
}
|
||||
|
||||
// Color should be disabled
|
||||
|
@ -2020,12 +2023,8 @@ fn test_ls_color() {
|
|||
assert!(!result.stdout_str().contains(z_with_colors));
|
||||
|
||||
// Nested dir should be shown and colored
|
||||
scene
|
||||
.ucmd()
|
||||
.arg("--color")
|
||||
.arg("a")
|
||||
.succeeds()
|
||||
.stdout_contains(nested_dir_with_colors);
|
||||
let result = scene.ucmd().arg("--color").arg("a").succeeds();
|
||||
assert!(result.stdout_str().contains(nested_dir_with_colors));
|
||||
|
||||
// No output
|
||||
scene
|
||||
|
@ -2037,13 +2036,18 @@ fn test_ls_color() {
|
|||
|
||||
// The colors must not mess up the grid layout
|
||||
at.touch("b");
|
||||
scene
|
||||
let result = scene
|
||||
.ucmd()
|
||||
.arg("--color")
|
||||
.arg("-w=15")
|
||||
.arg("-C")
|
||||
.succeeds()
|
||||
.stdout_only(format!("{a_with_colors} test-color\nb {z_with_colors}\n"));
|
||||
.succeeds();
|
||||
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)]
|
||||
|
@ -2468,13 +2472,16 @@ fn test_ls_quoting_style() {
|
|||
{
|
||||
at.touch("one\ntwo");
|
||||
at.touch("one\\two");
|
||||
// Default is shell-escape
|
||||
// Default is literal, when stdout is not a TTY.
|
||||
// Otherwise, it is shell-escape
|
||||
scene
|
||||
.ucmd()
|
||||
.arg("--hide-control-chars")
|
||||
.arg("one\ntwo")
|
||||
.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 [
|
||||
("--quoting-style=literal", "one?two"),
|
||||
|
@ -2561,7 +2568,9 @@ fn test_ls_quoting_style() {
|
|||
.ucmd()
|
||||
.arg("one two")
|
||||
.succeeds()
|
||||
.stdout_only("'one two'\n");
|
||||
.stdout_only("one two\n");
|
||||
// TODO: TTY-expected output
|
||||
// .stdout_only("'one two'\n");
|
||||
|
||||
for (arg, correct) in [
|
||||
("--quoting-style=literal", "one two"),
|
||||
|
@ -2624,7 +2633,9 @@ fn test_ls_quoting_and_color() {
|
|||
.arg("--color")
|
||||
.arg("one two")
|
||||
.succeeds()
|
||||
.stdout_only("'one two'\n");
|
||||
.stdout_only("one two\n");
|
||||
// TODO: TTY-expected output
|
||||
// .stdout_only("'one two'\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -3156,11 +3167,8 @@ fn test_ls_path() {
|
|||
.stdout_is(expected_stdout);
|
||||
|
||||
let abs_path = format!("{}/{}", at.as_string(), path);
|
||||
let expected_stdout = if cfg!(windows) {
|
||||
format!("\'{abs_path}\'\n")
|
||||
} else {
|
||||
format!("{abs_path}\n")
|
||||
};
|
||||
let expected_stdout = format!("{abs_path}\n");
|
||||
|
||||
scene.ucmd().arg(&abs_path).run().stdout_is(expected_stdout);
|
||||
|
||||
let expected_stdout = format!("{path}\n{file1}\n");
|
||||
|
@ -3828,3 +3836,80 @@ fn test_ls_cf_output_should_be_delimited_by_tab() {
|
|||
.succeeds()
|
||||
.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));
|
||||
}
|
||||
|
||||
#[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]
|
||||
fn test_mv_overwrite_nonempty_dir() {
|
||||
let (at, mut ucmd) = at_and_ucmd!();
|
||||
|
|
|
@ -553,7 +553,7 @@ fn test_nonexistent_file_is_not_symlink() {
|
|||
}
|
||||
|
||||
#[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
|
||||
#[cfg(not(any(windows, target_os = "freebsd")))]
|
||||
fn test_file_is_sticky() {
|
||||
|
|
|
@ -243,6 +243,14 @@ fn test_single_only_lines() {
|
|||
.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]
|
||||
fn test_single_all_counts() {
|
||||
new_ucmd!()
|
||||
|
@ -419,6 +427,14 @@ fn test_files_from_pseudo_filesystem() {
|
|||
use pretty_assertions::assert_ne;
|
||||
let result = new_ucmd!().arg("-c").arg("/proc/cpuinfo").succeeds();
|
||||
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]
|
||||
|
|
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
|
||||
# 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
|
||||
|
@ -46,40 +43,26 @@ TERM xterm*
|
|||
# 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:
|
||||
RESET 0
|
||||
DIR 01;34
|
||||
LINK 01;36
|
||||
MULTIHARDLINK 00
|
||||
FIFO 40;33
|
||||
SOCK 01;35
|
||||
DOOR 01;35
|
||||
BLK 40;33;01
|
||||
CHR 40;33;01
|
||||
ORPHAN 40;31;01
|
||||
MISSING 00
|
||||
SETUID 37;41
|
||||
SETGID 30;43
|
||||
CAPABILITY 00
|
||||
STICKY_OTHER_WRITABLE 30;42
|
||||
OTHER_WRITABLE 34;42
|
||||
STICKY 37;44
|
||||
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
|
||||
|
@ -126,7 +109,6 @@ EXEC 01;32
|
|||
.swm 01;31
|
||||
.dwm 01;31
|
||||
.esd 01;31
|
||||
# image formats
|
||||
.avif 01;35
|
||||
.jpg 01;35
|
||||
.jpeg 01;35
|
||||
|
@ -176,10 +158,8 @@ EXEC 01;32
|
|||
.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
|
||||
|
@ -192,12 +172,10 @@ EXEC 01;32
|
|||
.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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue