1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-08-01 05:27:45 +00:00

Merge pull request #4150 from tertsdiepraam/stat-refactor

`stat` refactor
This commit is contained in:
Sylvestre Ledru 2022-11-19 09:57:23 +01:00 committed by GitHub
commit 6d7850549b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 305 additions and 498 deletions

View file

@ -19,36 +19,36 @@ use clap::{crate_version, Arg, ArgAction, ArgMatches, Command};
use std::borrow::Cow; use std::borrow::Cow;
use std::convert::AsRef; use std::convert::AsRef;
use std::ffi::{OsStr, OsString}; use std::ffi::{OsStr, OsString};
use std::fs;
use std::os::unix::fs::{FileTypeExt, MetadataExt}; use std::os::unix::fs::{FileTypeExt, MetadataExt};
use std::os::unix::prelude::OsStrExt; use std::os::unix::prelude::OsStrExt;
use std::path::Path; use std::path::Path;
use std::{cmp, fs, iter};
static ABOUT: &str = "Display file or file system status."; const ABOUT: &str = "Display file or file system status.";
const USAGE: &str = "{} [OPTION]... FILE..."; const USAGE: &str = "{} [OPTION]... FILE...";
pub mod options { mod options {
pub static DEREFERENCE: &str = "dereference"; pub const DEREFERENCE: &str = "dereference";
pub static FILE_SYSTEM: &str = "file-system"; pub const FILE_SYSTEM: &str = "file-system";
pub static FORMAT: &str = "format"; pub const FORMAT: &str = "format";
pub static PRINTF: &str = "printf"; pub const PRINTF: &str = "printf";
pub static TERSE: &str = "terse"; pub const TERSE: &str = "terse";
pub const FILES: &str = "files";
} }
static ARG_FILES: &str = "files"; #[derive(Default, Debug, PartialEq, Eq, Clone, Copy)]
struct Flags {
pub const F_ALTER: u8 = 1; alter: bool,
pub const F_ZERO: u8 = 1 << 1; zero: bool,
pub const F_LEFT: u8 = 1 << 2; left: bool,
pub const F_SPACE: u8 = 1 << 3; space: bool,
pub const F_SIGN: u8 = 1 << 4; sign: bool,
// unused at present group: bool,
pub const F_GROUP: u8 = 1 << 5; }
/// checks if the string is within the specified bound, /// checks if the string is within the specified bound,
/// if it gets out of bound, error out by printing sub-string from index `beg` to`end`, /// if it gets out of bound, error out by printing sub-string from index `beg` to`end`,
/// where `beg` & `end` is the beginning and end index of sub-string, respectively /// where `beg` & `end` is the beginning and end index of sub-string, respectively
///
fn check_bound(slice: &str, bound: usize, beg: usize, end: usize) -> UResult<()> { fn check_bound(slice: &str, bound: usize, beg: usize, end: usize) -> UResult<()> {
if end >= bound { if end >= bound {
return Err(USimpleError::new( return Err(USimpleError::new(
@ -59,23 +59,6 @@ fn check_bound(slice: &str, bound: usize, beg: usize, end: usize) -> UResult<()>
Ok(()) Ok(())
} }
/// pads the string with zeroes if supplied min is greater
/// then the length of the string, else returns the original string
///
fn extend_digits(string: &str, min: usize) -> Cow<'_, str> {
if min > string.len() {
let mut pad = String::with_capacity(min);
iter::repeat('0')
.take(min - string.len())
.map(|_| pad.push('0'))
.all(|_| true);
pad.push_str(string);
pad.into()
} else {
string.into()
}
}
enum Padding { enum Padding {
Zero, Zero,
Space, Space,
@ -89,8 +72,7 @@ enum Padding {
/// ``` /// ```
/// currently only supports '0' & ' ' as the padding character /// currently only supports '0' & ' ' as the padding character
/// because the format specification of print! does not support general /// because the format specification of print! does not support general
/// fill characters /// fill characters.
///
fn pad_and_print(result: &str, left: bool, width: usize, padding: Padding) { fn pad_and_print(result: &str, left: bool, width: usize, padding: Padding) {
match (left, padding) { match (left, padding) {
(false, Padding::Zero) => print!("{result:0>width$}"), (false, Padding::Zero) => print!("{result:0>width$}"),
@ -100,54 +82,28 @@ fn pad_and_print(result: &str, left: bool, width: usize, padding: Padding) {
}; };
} }
/// prints the adjusted string after padding #[derive(Debug)]
/// `left` flag specifies the type of alignment of the string
/// `width` is the supplied padding width of the string needed
/// `prefix` & `need_prefix` are Optional, which adjusts the `field_width` accordingly, where
/// `field_width` is the max of supplied `width` and size of string
/// `padding`, specifies type of padding, which is '0' or ' ' in this case.
fn print_adjusted(
s: &str,
left: bool,
need_prefix: Option<bool>,
prefix: Option<&str>,
width: usize,
padding: Padding,
) {
let mut field_width = cmp::max(width, s.len());
if let Some(p) = prefix {
if let Some(prefix_flag) = need_prefix {
if prefix_flag {
field_width -= p.len();
}
}
pad_and_print(s, left, field_width, padding);
} else {
pad_and_print(s, left, field_width, padding);
}
}
#[derive(Debug, PartialEq, Eq)]
pub enum OutputType { pub enum OutputType {
Str, Str(String),
Integer, Integer(i64),
Unsigned, Unsigned(u64),
UnsignedHex, UnsignedHex(u64),
UnsignedOct, UnsignedOct(u32),
Unknown, Unknown,
} }
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
pub enum Token { enum Token {
Char(char), Char(char),
Directive { Directive {
flag: u8, flag: Flags,
width: usize, width: usize,
precision: i32, precision: Option<usize>,
format: char, format: char,
}, },
} }
pub trait ScanUtil { trait ScanUtil {
fn scan_num<F>(&self) -> Option<(F, usize)> fn scan_num<F>(&self) -> Option<(F, usize)>
where where
F: std::str::FromStr; F: std::str::FromStr;
@ -162,7 +118,7 @@ impl ScanUtil for str {
let mut chars = self.chars(); let mut chars = self.chars();
let mut i = 0; let mut i = 0;
match chars.next() { match chars.next() {
Some('-') | Some('+') | Some('0'..='9') => i += 1, Some('-' | '+' | '0'..='9') => i += 1,
_ => return None, _ => return None,
} }
for c in chars { for c in chars {
@ -180,13 +136,13 @@ impl ScanUtil for str {
fn scan_char(&self, radix: u32) -> Option<(char, usize)> { fn scan_char(&self, radix: u32) -> Option<(char, usize)> {
let count = match radix { let count = match radix {
8 => 3_usize, 8 => 3,
16 => 2, 16 => 2,
_ => return None, _ => return None,
}; };
let chars = self.chars().enumerate(); let chars = self.chars().enumerate();
let mut res = 0_u32; let mut res = 0;
let mut offset = 0_usize; let mut offset = 0;
for (i, c) in chars { for (i, c) in chars {
if i >= count { if i >= count {
break; break;
@ -212,7 +168,7 @@ impl ScanUtil for str {
} }
} }
pub fn group_num(s: &str) -> Cow<str> { fn group_num(s: &str) -> Cow<str> {
let is_negative = s.starts_with('-'); let is_negative = s.starts_with('-');
assert!(is_negative || s.chars().take(1).all(|c| c.is_ascii_digit())); assert!(is_negative || s.chars().take(1).all(|c| c.is_ascii_digit()));
assert!(s.chars().skip(1).all(|c| c.is_ascii_digit())); assert!(s.chars().skip(1).all(|c| c.is_ascii_digit()));
@ -236,7 +192,7 @@ pub fn group_num(s: &str) -> Cow<str> {
res.into() res.into()
} }
pub struct Stater { struct Stater {
follow: bool, follow: bool,
show_fs: bool, show_fs: bool,
from_user: bool, from_user: bool,
@ -247,7 +203,7 @@ pub struct Stater {
} }
#[allow(clippy::cognitive_complexity)] #[allow(clippy::cognitive_complexity)]
fn print_it(arg: &str, output_type: &OutputType, flag: u8, width: usize, precision: i32) { fn print_it(output: &OutputType, flags: Flags, width: usize, precision: Option<usize>) {
// If the precision is given as just '.', the precision is taken to be zero. // If the precision is given as just '.', the precision is taken to be zero.
// A negative precision is taken as if the precision were omitted. // A negative precision is taken as if the precision were omitted.
// This gives the minimum number of digits to appear for d, i, o, u, x, and X conversions, // This gives the minimum number of digits to appear for d, i, o, u, x, and X conversions,
@ -278,111 +234,76 @@ fn print_it(arg: &str, output_type: &OutputType, flag: u8, width: usize, precisi
// By default, a sign is used only for negative numbers. // By default, a sign is used only for negative numbers.
// A + overrides a space if both are used. // A + overrides a space if both are used.
if output_type == &OutputType::Unknown { let padding_char = if flags.zero && !flags.left && precision.is_none() {
return print!("?");
}
let left_align = has!(flag, F_LEFT);
let padding_char: Padding = if has!(flag, F_ZERO) && !left_align && precision == -1 {
Padding::Zero Padding::Zero
} else { } else {
Padding::Space Padding::Space
}; };
let has_sign = has!(flag, F_SIGN) || has!(flag, F_SPACE); match output {
OutputType::Str(s) => {
let should_alter = has!(flag, F_ALTER); let s = match precision {
let prefix = match output_type { Some(p) if p < s.len() => &s[..p],
OutputType::UnsignedOct => "0", _ => s,
OutputType::UnsignedHex => "0x", };
OutputType::Integer => { pad_and_print(s, flags.left, width, Padding::Space);
if has!(flag, F_SIGN) { }
OutputType::Integer(num) => {
let num = num.to_string();
let arg = if flags.group {
group_num(&num)
} else {
Cow::Borrowed(num.as_str())
};
let prefix = if flags.sign {
"+" "+"
} else { } else if flags.space {
" " " "
}
}
_ => "",
};
match output_type {
OutputType::Str => {
let limit = cmp::min(precision, arg.len() as i32);
let s: &str = if limit >= 0 {
&arg[..limit as usize]
} else { } else {
arg ""
}; };
print_adjusted(s, left_align, None, None, width, Padding::Space); let extended = format!(
"{prefix}{arg:0>precision$}",
precision = precision.unwrap_or(0)
);
pad_and_print(&extended, flags.left, width, padding_char);
} }
OutputType::Integer => { OutputType::Unsigned(num) => {
let arg = if has!(flag, F_GROUP) { let num = num.to_string();
group_num(arg) let s = if flags.group {
group_num(&num)
} else { } else {
Cow::Borrowed(arg) Cow::Borrowed(num.as_str())
}; };
let min_digits = cmp::max(precision, arg.len() as i32) as usize; let s = format!("{s:0>precision$}", precision = precision.unwrap_or(0));
let extended: Cow<str> = extend_digits(arg.as_ref(), min_digits); pad_and_print(&s, flags.left, width, padding_char);
print_adjusted(
extended.as_ref(),
left_align,
Some(has_sign),
Some(prefix),
width,
padding_char,
);
} }
OutputType::Unsigned => { OutputType::UnsignedOct(num) => {
let arg = if has!(flag, F_GROUP) { let prefix = if flags.alter { "0" } else { "" };
group_num(arg) let s = format!(
} else { "{prefix}{num:0>precision$o}",
Cow::Borrowed(arg) precision = precision.unwrap_or(0)
};
let min_digits = cmp::max(precision, arg.len() as i32) as usize;
let extended: Cow<str> = extend_digits(arg.as_ref(), min_digits);
print_adjusted(
extended.as_ref(),
left_align,
None,
None,
width,
padding_char,
); );
pad_and_print(&s, flags.left, width, padding_char);
} }
OutputType::UnsignedOct => { OutputType::UnsignedHex(num) => {
let min_digits = cmp::max(precision, arg.len() as i32) as usize; let prefix = if flags.alter { "0x" } else { "" };
let extended: Cow<str> = extend_digits(arg, min_digits); let s = format!(
print_adjusted( "{prefix}{num:0>precision$x}",
extended.as_ref(), precision = precision.unwrap_or(0)
left_align,
Some(should_alter),
Some(prefix),
width,
padding_char,
); );
pad_and_print(&s, flags.left, width, padding_char);
} }
OutputType::UnsignedHex => { OutputType::Unknown => print!("?"),
let min_digits = cmp::max(precision, arg.len() as i32) as usize;
let extended: Cow<str> = extend_digits(arg, min_digits);
print_adjusted(
extended.as_ref(),
left_align,
Some(should_alter),
Some(prefix),
width,
padding_char,
);
}
_ => unreachable!(),
} }
} }
impl Stater { impl Stater {
pub fn generate_tokens(format_str: &str, use_printf: bool) -> UResult<Vec<Token>> { fn generate_tokens(format_str: &str, use_printf: bool) -> UResult<Vec<Token>> {
let mut tokens = Vec::new(); let mut tokens = Vec::new();
let bound = format_str.len(); let bound = format_str.len();
let chars = format_str.chars().collect::<Vec<char>>(); let chars = format_str.chars().collect::<Vec<char>>();
let mut i = 0_usize; let mut i = 0;
while i < bound { while i < bound {
match chars[i] { match chars[i] {
'%' => { '%' => {
@ -399,16 +320,16 @@ impl Stater {
continue; continue;
} }
let mut flag: u8 = 0; let mut flag = Flags::default();
while i < bound { while i < bound {
match chars[i] { match chars[i] {
'#' => flag |= F_ALTER, '#' => flag.alter = true,
'0' => flag |= F_ZERO, '0' => flag.zero = true,
'-' => flag |= F_LEFT, '-' => flag.left = true,
' ' => flag |= F_SPACE, ' ' => flag.space = true,
'+' => flag |= F_SIGN, '+' => flag.sign = true,
'\'' => flag |= F_GROUP, '\'' => flag.group = true,
'I' => unimplemented!(), 'I' => unimplemented!(),
_ => break, _ => break,
} }
@ -416,8 +337,8 @@ impl Stater {
} }
check_bound(format_str, bound, old, i)?; check_bound(format_str, bound, old, i)?;
let mut width = 0_usize; let mut width = 0;
let mut precision = -1_i32; let mut precision = None;
let mut j = i; let mut j = i;
if let Some((field_width, offset)) = format_str[j..].scan_num::<usize>() { if let Some((field_width, offset)) = format_str[j..].scan_num::<usize>() {
@ -433,11 +354,11 @@ impl Stater {
match format_str[j..].scan_num::<i32>() { match format_str[j..].scan_num::<i32>() {
Some((value, offset)) => { Some((value, offset)) => {
if value >= 0 { if value >= 0 {
precision = value; precision = Some(value as usize);
} }
j += offset; j += offset;
} }
None => precision = 0, None => precision = Some(0),
} }
check_bound(format_str, bound, old, j)?; check_bound(format_str, bound, old, j)?;
} }
@ -505,7 +426,7 @@ impl Stater {
fn new(matches: &ArgMatches) -> UResult<Self> { fn new(matches: &ArgMatches) -> UResult<Self> {
let files = matches let files = matches
.get_many::<OsString>(ARG_FILES) .get_many::<OsString>(options::FILES)
.map(|v| v.map(OsString::from).collect()) .map(|v| v.map(OsString::from).collect())
.unwrap_or_default(); .unwrap_or_default();
let format_str = if matches.contains_id(options::PRINTF) { let format_str = if matches.contains_id(options::PRINTF) {
@ -558,15 +479,11 @@ impl Stater {
} }
fn find_mount_point<P: AsRef<Path>>(&self, p: P) -> Option<String> { fn find_mount_point<P: AsRef<Path>>(&self, p: P) -> Option<String> {
let path = match p.as_ref().canonicalize() { let path = p.as_ref().canonicalize().ok()?;
Ok(s) => s,
Err(_) => return None, for root in self.mount_list.as_ref()? {
}; if path.starts_with(root) {
if let Some(ref mount_list) = self.mount_list { return Some(root.clone());
for root in mount_list.iter() {
if path.starts_with(root) {
return Some(root.clone());
}
} }
} }
None None
@ -589,7 +506,7 @@ impl Stater {
fn do_stat(&self, file: &OsStr, stdin_is_fifo: bool) -> i32 { fn do_stat(&self, file: &OsStr, stdin_is_fifo: bool) -> i32 {
let display_name = file.to_string_lossy(); let display_name = file.to_string_lossy();
let file: OsString = if cfg!(unix) && display_name.eq("-") { let file = if cfg!(unix) && display_name == "-" {
if let Ok(p) = Path::new("/dev/stdin").canonicalize() { if let Ok(p) = Path::new("/dev/stdin").canonicalize() {
p.into_os_string() p.into_os_string()
} else { } else {
@ -600,7 +517,7 @@ impl Stater {
}; };
if !self.show_fs { if !self.show_fs {
let result = if self.follow || stdin_is_fifo && display_name.eq("-") { let result = if self.follow || stdin_is_fifo && display_name == "-" {
fs::metadata(&file) fs::metadata(&file)
} else { } else {
fs::symlink_metadata(&file) fs::symlink_metadata(&file)
@ -625,92 +542,49 @@ impl Stater {
precision, precision,
format, format,
} => { } => {
let arg: String; let output = match format {
let output_type: OutputType;
match format {
// access rights in octal // access rights in octal
'a' => { 'a' => OutputType::UnsignedOct(0o7777 & meta.mode()),
arg = format!("{:o}", 0o7777 & meta.mode());
output_type = OutputType::UnsignedOct;
}
// access rights in human readable form // access rights in human readable form
'A' => { 'A' => OutputType::Str(display_permissions(&meta, true)),
arg = display_permissions(&meta, true);
output_type = OutputType::Str;
}
// number of blocks allocated (see %B) // number of blocks allocated (see %B)
'b' => { 'b' => OutputType::Unsigned(meta.blocks()),
arg = format!("{}", meta.blocks());
output_type = OutputType::Unsigned;
}
// the size in bytes of each block reported by %b // the size in bytes of each block reported by %b
// FIXME: blocksize differs on various platform // FIXME: blocksize differs on various platform
// See coreutils/gnulib/lib/stat-size.h ST_NBLOCKSIZE // spell-checker:disable-line // See coreutils/gnulib/lib/stat-size.h ST_NBLOCKSIZE // spell-checker:disable-line
'B' => { 'B' => OutputType::Unsigned(512),
// the size in bytes of each block reported by %b
arg = format!("{}", 512);
output_type = OutputType::Unsigned;
}
// device number in decimal // device number in decimal
'd' => { 'd' => OutputType::Unsigned(meta.dev()),
arg = format!("{}", meta.dev());
output_type = OutputType::Unsigned;
}
// device number in hex // device number in hex
'D' => { 'D' => OutputType::UnsignedHex(meta.dev()),
arg = format!("{:x}", meta.dev());
output_type = OutputType::UnsignedHex;
}
// raw mode in hex // raw mode in hex
'f' => { 'f' => OutputType::UnsignedHex(meta.mode() as u64),
arg = format!("{:x}", meta.mode());
output_type = OutputType::UnsignedHex;
}
// file type // file type
'F' => { 'F' => OutputType::Str(
arg = pretty_filetype(meta.mode() as mode_t, meta.len()) pretty_filetype(meta.mode() as mode_t, meta.len())
.to_owned(); .to_owned(),
output_type = OutputType::Str; ),
}
// group ID of owner // group ID of owner
'g' => { 'g' => OutputType::Unsigned(meta.gid() as u64),
arg = format!("{}", meta.gid());
output_type = OutputType::Unsigned;
}
// group name of owner // group name of owner
'G' => { 'G' => {
arg = entries::gid2grp(meta.gid()) let group_name = entries::gid2grp(meta.gid())
.unwrap_or_else(|_| "UNKNOWN".to_owned()); .unwrap_or_else(|_| "UNKNOWN".to_owned());
output_type = OutputType::Str; OutputType::Str(group_name)
} }
// number of hard links // number of hard links
'h' => { 'h' => OutputType::Unsigned(meta.nlink()),
arg = format!("{}", meta.nlink());
output_type = OutputType::Unsigned;
}
// inode number // inode number
'i' => { 'i' => OutputType::Unsigned(meta.ino()),
arg = format!("{}", meta.ino());
output_type = OutputType::Unsigned;
}
// mount point // mount point
'm' => { 'm' => OutputType::Str(self.find_mount_point(&file).unwrap()),
arg = self.find_mount_point(&file).unwrap();
output_type = OutputType::Str;
}
// file name // file name
'n' => { 'n' => OutputType::Str(display_name.to_string()),
arg = display_name.to_string();
output_type = OutputType::Str;
}
// quoted file name with dereference if symbolic link // quoted file name with dereference if symbolic link
'N' => { 'N' => {
if file_type.is_symlink() { let file_name = if file_type.is_symlink() {
let dst = match fs::read_link(&file) { let dst = match fs::read_link(&file) {
Ok(path) => path, Ok(path) => path,
Err(e) => { Err(e) => {
@ -718,99 +592,62 @@ impl Stater {
return 1; return 1;
} }
}; };
arg = format!( format!("{} -> {}", display_name.quote(), dst.quote())
"{} -> {}",
display_name.quote(),
dst.quote()
);
} else { } else {
arg = display_name.to_string(); display_name.to_string()
} };
output_type = OutputType::Str; OutputType::Str(file_name)
} }
// optimal I/O transfer size hint // optimal I/O transfer size hint
'o' => { 'o' => OutputType::Unsigned(meta.blksize()),
arg = format!("{}", meta.blksize());
output_type = OutputType::Unsigned;
}
// total size, in bytes // total size, in bytes
's' => { 's' => OutputType::Integer(meta.len() as i64),
arg = format!("{}", meta.len());
output_type = OutputType::Integer;
}
// major device type in hex, for character/block device special // major device type in hex, for character/block device special
// files // files
't' => { 't' => OutputType::UnsignedHex(meta.rdev() >> 8),
arg = format!("{:x}", meta.rdev() >> 8);
output_type = OutputType::UnsignedHex;
}
// minor device type in hex, for character/block device special // minor device type in hex, for character/block device special
// files // files
'T' => { 'T' => OutputType::UnsignedHex(meta.rdev() & 0xff),
arg = format!("{:x}", meta.rdev() & 0xff);
output_type = OutputType::UnsignedHex;
}
// user ID of owner // user ID of owner
'u' => { 'u' => OutputType::Unsigned(meta.uid() as u64),
arg = format!("{}", meta.uid());
output_type = OutputType::Unsigned;
}
// user name of owner // user name of owner
'U' => { 'U' => {
arg = entries::uid2usr(meta.uid()) let user_name = entries::uid2usr(meta.uid())
.unwrap_or_else(|_| "UNKNOWN".to_owned()); .unwrap_or_else(|_| "UNKNOWN".to_owned());
output_type = OutputType::Str; OutputType::Str(user_name)
} }
// time of file birth, human-readable; - if unknown // time of file birth, human-readable; - if unknown
'w' => { 'w' => OutputType::Str(meta.pretty_birth()),
arg = meta.pretty_birth();
output_type = OutputType::Str;
}
// time of file birth, seconds since Epoch; 0 if unknown // time of file birth, seconds since Epoch; 0 if unknown
'W' => { 'W' => OutputType::Unsigned(meta.birth()),
arg = meta.birth();
output_type = OutputType::Integer;
}
// time of last access, human-readable // time of last access, human-readable
'x' => { 'x' => OutputType::Str(pretty_time(
arg = pretty_time(meta.atime(), meta.atime_nsec()); meta.atime(),
output_type = OutputType::Str; meta.atime_nsec(),
} )),
// time of last access, seconds since Epoch // time of last access, seconds since Epoch
'X' => { 'X' => OutputType::Integer(meta.atime()),
arg = format!("{}", meta.atime());
output_type = OutputType::Integer;
}
// time of last data modification, human-readable // time of last data modification, human-readable
'y' => { 'y' => OutputType::Str(pretty_time(
arg = pretty_time(meta.mtime(), meta.mtime_nsec()); meta.mtime(),
output_type = OutputType::Str; meta.mtime_nsec(),
} )),
// time of last data modification, seconds since Epoch // time of last data modification, seconds since Epoch
'Y' => { 'Y' => OutputType::Integer(meta.mtime()),
arg = format!("{}", meta.mtime());
output_type = OutputType::Str;
}
// time of last status change, human-readable // time of last status change, human-readable
'z' => { 'z' => OutputType::Str(pretty_time(
arg = pretty_time(meta.ctime(), meta.ctime_nsec()); meta.ctime(),
output_type = OutputType::Str; meta.ctime_nsec(),
} )),
// time of last status change, seconds since Epoch // time of last status change, seconds since Epoch
'Z' => { 'Z' => OutputType::Integer(meta.ctime()),
arg = format!("{}", meta.ctime());
output_type = OutputType::Integer;
}
_ => { _ => OutputType::Unknown,
arg = "?".to_owned(); };
output_type = OutputType::Unknown; print_it(&output, flag, width, precision);
}
}
print_it(&arg, &output_type, flag, width, precision);
} }
} }
} }
@ -838,76 +675,35 @@ impl Stater {
precision, precision,
format, format,
} => { } => {
let arg: String; let output = match format {
let output_type: OutputType;
match format {
// free blocks available to non-superuser // free blocks available to non-superuser
'a' => { 'a' => OutputType::Unsigned(meta.avail_blocks()),
arg = format!("{}", meta.avail_blocks());
output_type = OutputType::Integer;
}
// total data blocks in file system // total data blocks in file system
'b' => { 'b' => OutputType::Unsigned(meta.total_blocks()),
arg = format!("{}", meta.total_blocks());
output_type = OutputType::Integer;
}
// total file nodes in file system // total file nodes in file system
'c' => { 'c' => OutputType::Unsigned(meta.total_file_nodes()),
arg = format!("{}", meta.total_file_nodes());
output_type = OutputType::Unsigned;
}
// free file nodes in file system // free file nodes in file system
'd' => { 'd' => OutputType::Unsigned(meta.free_file_nodes()),
arg = format!("{}", meta.free_file_nodes());
output_type = OutputType::Integer;
}
// free blocks in file system // free blocks in file system
'f' => { 'f' => OutputType::Unsigned(meta.free_blocks()),
arg = format!("{}", meta.free_blocks());
output_type = OutputType::Integer;
}
// file system ID in hex // file system ID in hex
'i' => { 'i' => OutputType::UnsignedHex(meta.fsid()),
arg = format!("{:x}", meta.fsid());
output_type = OutputType::UnsignedHex;
}
// maximum length of filenames // maximum length of filenames
'l' => { 'l' => OutputType::Unsigned(meta.namelen()),
arg = format!("{}", meta.namelen());
output_type = OutputType::Unsigned;
}
// file name // file name
'n' => { 'n' => OutputType::Str(display_name.to_string()),
arg = display_name.to_string();
output_type = OutputType::Str;
}
// block size (for faster transfers) // block size (for faster transfers)
's' => { 's' => OutputType::Unsigned(meta.io_size()),
arg = format!("{}", meta.io_size());
output_type = OutputType::Unsigned;
}
// fundamental block size (for block counts) // fundamental block size (for block counts)
'S' => { 'S' => OutputType::Integer(meta.block_size()),
arg = format!("{}", meta.block_size());
output_type = OutputType::Unsigned;
}
// file system type in hex // file system type in hex
't' => { 't' => OutputType::UnsignedHex(meta.fs_type() as u64),
arg = format!("{:x}", meta.fs_type());
output_type = OutputType::UnsignedHex;
}
// file system type in human readable form // file system type in human readable form
'T' => { 'T' => OutputType::Str(pretty_fstype(meta.fs_type()).into()),
arg = pretty_fstype(meta.fs_type()).into_owned(); _ => OutputType::Unknown,
output_type = OutputType::Str; };
}
_ => {
arg = "?".to_owned();
output_type = OutputType::Unknown;
}
}
print_it(&arg, &output_type, flag, width, precision); print_it(&output, flag, width, precision);
} }
} }
} }
@ -928,37 +724,35 @@ impl Stater {
fn default_format(show_fs: bool, terse: bool, show_dev_type: bool) -> String { fn default_format(show_fs: bool, terse: bool, show_dev_type: bool) -> String {
// SELinux related format is *ignored* // SELinux related format is *ignored*
let mut format_str = String::with_capacity(36);
if show_fs { if show_fs {
if terse { if terse {
format_str.push_str("%n %i %l %t %s %S %b %f %a %c %d\n"); "%n %i %l %t %s %S %b %f %a %c %d\n".into()
} else { } else {
format_str.push_str( " File: \"%n\"\n ID: %-8i Namelen: %-7l Type: %T\nBlock \
" File: \"%n\"\n ID: %-8i Namelen: %-7l Type: %T\nBlock \ size: %-10s Fundamental block size: %S\nBlocks: Total: %-10b \
size: %-10s Fundamental block size: %S\nBlocks: Total: %-10b \ Free: %-10f Available: %a\nInodes: Total: %-10c Free: %d\n"
Free: %-10f Available: %a\nInodes: Total: %-10c Free: %d\n", .into()
);
} }
} else if terse { } else if terse {
format_str.push_str("%n %s %b %f %u %g %D %i %h %t %T %X %Y %Z %W %o\n"); "%n %s %b %f %u %g %D %i %h %t %T %X %Y %Z %W %o\n".into()
} else { } else {
format_str.push_str(" File: %N\n Size: %-10s\tBlocks: %-10b IO Block: %-6o %F\n"); [
if show_dev_type { " File: %N\n Size: %-10s\tBlocks: %-10b IO Block: %-6o %F\n",
format_str if show_dev_type {
.push_str("Device: %Dh/%dd\tInode: %-10i Links: %-5h Device type: %t,%T\n"); "Device: %Dh/%dd\tInode: %-10i Links: %-5h Device type: %t,%T\n"
} else { } else {
format_str.push_str("Device: %Dh/%dd\tInode: %-10i Links: %h\n"); "Device: %Dh/%dd\tInode: %-10i Links: %h\n"
} },
format_str.push_str("Access: (%04a/%10.10A) Uid: (%5u/%8U) Gid: (%5g/%8G)\n"); "Access: (%04a/%10.10A) Uid: (%5u/%8U) Gid: (%5g/%8G)\n",
format_str.push_str("Access: %x\nModify: %y\nChange: %z\n Birth: %w\n"); "Access: %x\nModify: %y\nChange: %z\n Birth: %w\n",
]
.join("")
} }
format_str
} }
} }
fn get_long_usage() -> String { fn get_long_usage() -> &'static str {
String::from( "
"
The valid format sequences for files (without --file-system): The valid format sequences for files (without --file-system):
%a access rights in octal (note '#' and '0' printf flags) %a access rights in octal (note '#' and '0' printf flags)
@ -1010,8 +804,7 @@ Valid format sequences for file systems:
NOTE: your shell may have its own version of stat, which usually supersedes NOTE: your shell may have its own version of stat, which usually supersedes
the version described here. Please refer to your shell's documentation the version described here. Please refer to your shell's documentation
for details about the options it supports. for details about the options it supports.
", "
)
} }
#[uucore::main] #[uucore::main]
@ -1077,9 +870,121 @@ pub fn uu_app() -> Command {
), ),
) )
.arg( .arg(
Arg::new(ARG_FILES) Arg::new(options::FILES)
.action(ArgAction::Append) .action(ArgAction::Append)
.value_parser(ValueParser::os_string()) .value_parser(ValueParser::os_string())
.value_hint(clap::ValueHint::FilePath), .value_hint(clap::ValueHint::FilePath),
) )
} }
#[cfg(test)]
mod tests {
use super::{group_num, Flags, ScanUtil, Stater, Token};
#[test]
fn test_scanners() {
assert_eq!(Some((-5, 2)), "-5zxc".scan_num::<i32>());
assert_eq!(Some((51, 2)), "51zxc".scan_num::<u32>());
assert_eq!(Some((192, 4)), "+192zxc".scan_num::<i32>());
assert_eq!(None, "z192zxc".scan_num::<i32>());
assert_eq!(Some(('a', 3)), "141zxc".scan_char(8));
assert_eq!(Some(('\n', 2)), "12qzxc".scan_char(8)); // spell-checker:disable-line
assert_eq!(Some(('\r', 1)), "dqzxc".scan_char(16)); // spell-checker:disable-line
assert_eq!(None, "z2qzxc".scan_char(8)); // spell-checker:disable-line
}
#[test]
fn test_group_num() {
assert_eq!("12,379,821,234", group_num("12379821234"));
assert_eq!("21,234", group_num("21234"));
assert_eq!("821,234", group_num("821234"));
assert_eq!("1,821,234", group_num("1821234"));
assert_eq!("1,234", group_num("1234"));
assert_eq!("234", group_num("234"));
assert_eq!("24", group_num("24"));
assert_eq!("4", group_num("4"));
assert_eq!("", group_num(""));
assert_eq!("-5", group_num("-5"));
assert_eq!("-1,234", group_num("-1234"));
}
#[test]
#[should_panic]
fn test_group_num_panic_if_invalid_numeric_characters() {
group_num("³³³³³");
}
#[test]
fn normal_format() {
let s = "%'010.2ac%-#5.w\n";
let expected = vec![
Token::Directive {
flag: Flags {
group: true,
zero: true,
..Default::default()
},
width: 10,
precision: Some(2),
format: 'a',
},
Token::Char('c'),
Token::Directive {
flag: Flags {
left: true,
alter: true,
..Default::default()
},
width: 5,
precision: Some(0),
format: 'w',
},
Token::Char('\n'),
];
assert_eq!(&expected, &Stater::generate_tokens(s, false).unwrap());
}
#[test]
fn printf_format() {
let s = r#"%-# 15a\t\r\"\\\a\b\e\f\v%+020.-23w\x12\167\132\112\n"#;
let expected = vec![
Token::Directive {
flag: Flags {
left: true,
alter: true,
space: true,
..Default::default()
},
width: 15,
precision: None,
format: 'a',
},
Token::Char('\t'),
Token::Char('\r'),
Token::Char('"'),
Token::Char('\\'),
Token::Char('\x07'),
Token::Char('\x08'),
Token::Char('\x1B'),
Token::Char('\x0C'),
Token::Char('\x0B'),
Token::Directive {
flag: Flags {
sign: true,
zero: true,
..Default::default()
},
width: 20,
precision: None,
format: 'w',
},
Token::Char('\x12'),
Token::Char('w'),
Token::Char('Z'),
Token::Char('J'),
Token::Char('\n'),
];
assert_eq!(&expected, &Stater::generate_tokens(s, true).unwrap());
}
}

View file

@ -123,7 +123,7 @@ pub use libc::statvfs as statfs_fn;
pub trait BirthTime { pub trait BirthTime {
fn pretty_birth(&self) -> String; fn pretty_birth(&self) -> String;
fn birth(&self) -> String; fn birth(&self) -> u64;
} }
use std::fs::Metadata; use std::fs::Metadata;
@ -136,12 +136,12 @@ impl BirthTime for Metadata {
.unwrap_or_else(|| "-".to_owned()) .unwrap_or_else(|| "-".to_owned())
} }
fn birth(&self) -> String { fn birth(&self) -> u64 {
self.created() self.created()
.ok() .ok()
.and_then(|t| t.duration_since(UNIX_EPOCH).ok()) .and_then(|t| t.duration_since(UNIX_EPOCH).ok())
.map(|e| format!("{}", e.as_secs())) .map(|e| e.as_secs())
.unwrap_or_else(|| "0".to_owned()) .unwrap_or_default()
} }
} }

View file

@ -7,109 +7,11 @@ extern crate regex;
use crate::common::util::*; use crate::common::util::*;
extern crate stat;
pub use self::stat::*;
#[test] #[test]
fn test_invalid_arg() { fn test_invalid_arg() {
new_ucmd!().arg("--definitely-invalid").fails().code_is(1); new_ucmd!().arg("--definitely-invalid").fails().code_is(1);
} }
#[test]
fn test_scanners() {
assert_eq!(Some((-5, 2)), "-5zxc".scan_num::<i32>());
assert_eq!(Some((51, 2)), "51zxc".scan_num::<u32>());
assert_eq!(Some((192, 4)), "+192zxc".scan_num::<i32>());
assert_eq!(None, "z192zxc".scan_num::<i32>());
assert_eq!(Some(('a', 3)), "141zxc".scan_char(8));
assert_eq!(Some(('\n', 2)), "12qzxc".scan_char(8)); // spell-checker:disable-line
assert_eq!(Some(('\r', 1)), "dqzxc".scan_char(16)); // spell-checker:disable-line
assert_eq!(None, "z2qzxc".scan_char(8)); // spell-checker:disable-line
}
#[test]
fn test_group_num() {
assert_eq!("12,379,821,234", group_num("12379821234"));
assert_eq!("21,234", group_num("21234"));
assert_eq!("821,234", group_num("821234"));
assert_eq!("1,821,234", group_num("1821234"));
assert_eq!("1,234", group_num("1234"));
assert_eq!("234", group_num("234"));
assert_eq!("24", group_num("24"));
assert_eq!("4", group_num("4"));
assert_eq!("", group_num(""));
assert_eq!("-5", group_num("-5"));
assert_eq!("-1,234", group_num("-1234"));
}
#[test]
#[should_panic]
fn test_group_num_panic_if_invalid_numeric_characters() {
group_num("³³³³³");
}
#[cfg(test)]
mod test_generate_tokens {
use super::*;
#[test]
fn normal_format() {
let s = "%'010.2ac%-#5.w\n";
let expected = vec![
Token::Directive {
flag: F_GROUP | F_ZERO,
width: 10,
precision: 2,
format: 'a',
},
Token::Char('c'),
Token::Directive {
flag: F_LEFT | F_ALTER,
width: 5,
precision: 0,
format: 'w',
},
Token::Char('\n'),
];
assert_eq!(&expected, &Stater::generate_tokens(s, false).unwrap());
}
#[test]
fn printf_format() {
let s = "%-# 15a\\t\\r\\\"\\\\\\a\\b\\e\\f\\v%+020.-23w\\x12\\167\\132\\112\\n";
let expected = vec![
Token::Directive {
flag: F_LEFT | F_ALTER | F_SPACE,
width: 15,
precision: -1,
format: 'a',
},
Token::Char('\t'),
Token::Char('\r'),
Token::Char('"'),
Token::Char('\\'),
Token::Char('\x07'),
Token::Char('\x08'),
Token::Char('\x1B'),
Token::Char('\x0C'),
Token::Char('\x0B'),
Token::Directive {
flag: F_SIGN | F_ZERO,
width: 20,
precision: -1,
format: 'w',
},
Token::Char('\x12'),
Token::Char('w'),
Token::Char('Z'),
Token::Char('J'),
Token::Char('\n'),
];
assert_eq!(&expected, &Stater::generate_tokens(s, true).unwrap());
}
}
#[test] #[test]
fn test_invalid_option() { fn test_invalid_option() {
new_ucmd!().arg("-w").arg("-q").arg("/").fails(); new_ucmd!().arg("-w").arg("-q").arg("/").fails();