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:
commit
6d7850549b
3 changed files with 305 additions and 498 deletions
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue