Merge branch 'main' into 2884-time-0.3

This commit is contained in:
Sylvestre Ledru
2022-05-07 20:57:14 +02:00
committed by GitHub
25 changed files with 673 additions and 341 deletions
+5
View File
@@ -5,3 +5,8 @@ updates:
schedule:
interval: "daily"
open-pull-requests-limit: 5
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: daily
open-pull-requests-limit: 5
+4
View File
@@ -8,6 +8,10 @@ on: [push, pull_request]
jobs:
gnu:
permissions:
actions: read # for dawidd6/action-download-artifact to query and download artifacts
contents: read # for actions/checkout to fetch code
pull-requests: read # for dawidd6/action-download-artifact to query commit hash
name: Run GNU tests
runs-on: ubuntu-latest
steps:
Generated
+112 -112
View File
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -5,7 +5,7 @@ utilities in [Rust](https://www.rust-lang.org). It is available for
Linux, Windows, Mac and other platforms.
The API reference for `uucore`, the library of functions shared between
various utils, is hosted at at
various utils, is hosted at
[docs.rs](https://docs.rs/uucore/latest/uucore/).
uutils is licensed under the [MIT License](https://github.com/uutils/coreutils/blob/main/LICENSE).
@@ -17,4 +17,4 @@ uutils is licensed under the [MIT License](https://github.com/uutils/coreutils/b
* [Discord](https://discord.gg/wQVJbvJ)
> Note: This manual is automatically generated from the source code and is
> a work in progress.
> a work in progress.
+1 -1
View File
@@ -90,7 +90,7 @@ pub fn parse_base_cmd_args(args: impl uucore::Args, about: &str, usage: &str) ->
let arg_list = args
.collect_str(InvalidEncodingHandling::ConvertLossy)
.accept_any();
Config::from(&command.get_matches_from(arg_list))
Config::from(&command.try_get_matches_from(arg_list)?)
}
pub fn base_app<'a>(about: &'a str, usage: &'a str) -> Command<'a> {
+1 -1
View File
@@ -188,7 +188,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
.collect_str(InvalidEncodingHandling::Ignore)
.accept_any();
let matches = uu_app().get_matches_from(args);
let matches = uu_app().try_get_matches_from(args)?;
let number_mode = if matches.is_present(options::NUMBER_NONBLANK) {
NumberingMode::NonEmpty
+1 -1
View File
@@ -34,7 +34,7 @@ ioctl-sys = "0.8"
winapi = { version="0.3", features=["fileapi"] }
[target.'cfg(unix)'.dependencies]
xattr="0.2.1"
xattr="0.2.3"
exacl= { version = "0.8.0", optional=true }
[[bin]]
+9 -6
View File
@@ -57,7 +57,7 @@ use std::path::{Path, PathBuf, StripPrefixError};
use std::str::FromStr;
use std::string::ToString;
use uucore::backup_control::{self, BackupMode};
use uucore::error::{set_exit_code, ExitCode, UError, UResult};
use uucore::error::{set_exit_code, ExitCode, UClapError, UError, UResult};
use uucore::fs::{canonicalize, MissingHandling, ResolveMode};
use walkdir::WalkDir;
@@ -485,7 +485,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
app.print_help()?;
}
clap::ErrorKind::DisplayVersion => println!("{}", app.render_version()),
_ => return Err(Box::new(e)),
_ => return Err(Box::new(e.with_exit_code(1))),
};
} else if let Ok(matches) = matches {
let options = Options::from_matches(&matches)?;
@@ -992,7 +992,9 @@ fn copy_directory(
}
// if no-dereference is enabled and this is a symlink, copy it as a file
if !options.dereference && fs::symlink_metadata(root).unwrap().file_type().is_symlink() {
if !options.dereference && fs::symlink_metadata(root).unwrap().file_type().is_symlink()
// replace by is_symlink in rust>=1.58
{
return copy_file(root, target, options, symlinked_files);
}
@@ -1036,6 +1038,7 @@ fn copy_directory(
{
let p = or_continue!(path);
let is_symlink = fs::symlink_metadata(p.path())?.file_type().is_symlink();
// replace by is_symlink in rust >=1.58
let path = current_dir.join(&p.path());
let local_to_root_parent = match root_parent {
@@ -1288,7 +1291,7 @@ fn copy_file(
// Fail if dest is a dangling symlink or a symlink this program created previously
if fs::symlink_metadata(dest)
.map(|m| m.file_type().is_symlink())
.map(|m| m.file_type().is_symlink()) // replace by is_symlink in rust>=1.58
.unwrap_or(false)
{
if FileInformation::from_path(dest, false)
@@ -1301,7 +1304,7 @@ fn copy_file(
dest.display()
)));
}
if !dest.exists() {
if options.dereference && !dest.exists() {
return Err(Error::Error(format!(
"not writing through dangling symlink '{}'",
dest.display()
@@ -1535,7 +1538,7 @@ fn copy_link(
} else {
// we always need to remove the file to be able to create a symlink,
// even if it is writeable.
if dest.exists() {
if dest.is_file() {
fs::remove_file(dest)?;
}
dest.into()
+122 -65
View File
@@ -3,7 +3,7 @@
// * For the full copyright and license information, please view the LICENSE
// * file that was distributed with this source code.
//! Types for representing and displaying block sizes.
use crate::{OPT_BLOCKSIZE, OPT_HUMAN_READABLE_BINARY, OPT_HUMAN_READABLE_DECIMAL};
use crate::OPT_BLOCKSIZE;
use clap::ArgMatches;
use std::fmt;
@@ -26,6 +26,22 @@ const IEC_BASES: [u128; 10] = [
/// Suffixes for the first nine multi-byte unit suffixes.
const SUFFIXES: [char; 9] = ['B', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'];
const SI_BASES: [u128; 10] = [
1,
1_000,
1_000_000,
1_000_000_000,
1_000_000_000_000,
1_000_000_000_000_000,
1_000_000_000_000_000_000,
1_000_000_000_000_000_000_000,
1_000_000_000_000_000_000_000_000,
1_000_000_000_000_000_000_000_000_000,
];
// we use "kB" instead of "KB" because of GNU df
const SI_SUFFIXES: [&str; 9] = ["B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
/// Convert a multiple of 1024 into a string like "12K" or "34M".
///
/// # Examples
@@ -57,28 +73,90 @@ fn to_magnitude_and_suffix_1024(n: u128) -> Result<String, ()> {
Err(())
}
/// Convert a number into a string like "12kB" or "34MB".
///
/// Powers of 1000 become "1kB", "1MB", "1GB", etc.
///
/// The returned string has a maximum length of 5 chars, for example: "1.1kB", "999kB", "1MB".
fn to_magnitude_and_suffix_not_powers_of_1024(n: u128) -> Result<String, ()> {
let mut i = 0;
while SI_BASES[i + 1] - SI_BASES[i] < n && i < SI_SUFFIXES.len() {
i += 1;
}
let quot = n / SI_BASES[i];
let rem = n % SI_BASES[i];
let suffix = SI_SUFFIXES[i];
if rem == 0 {
Ok(format!("{}{}", quot, suffix))
} else {
let tenths_place = rem / (SI_BASES[i] / 10);
if rem % (SI_BASES[i] / 10) == 0 {
Ok(format!("{}.{}{}", quot, tenths_place, suffix))
} else if tenths_place + 1 == 10 {
Ok(format!("{}{}", quot + 1, suffix))
} else {
Ok(format!("{}.{}{}", quot, tenths_place + 1, suffix))
}
}
}
/// Convert a number into a magnitude and a multi-byte unit suffix.
///
/// # Errors
///
/// If the number is too large to represent.
fn to_magnitude_and_suffix(n: u128) -> Result<String, ()> {
if n % 1024 == 0 {
if n % 1024 == 0 && n % 1000 != 0 {
to_magnitude_and_suffix_1024(n)
} else {
// TODO Implement this, probably using code from `numfmt`.
Ok("1kB".into())
to_magnitude_and_suffix_not_powers_of_1024(n)
}
}
/// A mode to use in condensing the display of a large number of bytes.
pub(crate) enum SizeFormat {
HumanReadable(HumanReadable),
StaticBlockSize,
}
impl Default for SizeFormat {
fn default() -> Self {
Self::StaticBlockSize
}
}
/// A mode to use in condensing the human readable display of a large number
/// of bytes.
///
/// The [`HumanReadable::Decimal`] and[`HumanReadable::Binary`] variants
/// represent dynamic block sizes: as the number of bytes increases, the
/// divisor increases as well (for example, from 1 to 1,000 to 1,000,000
/// and so on in the case of [`HumanReadable::Decimal`]).
#[derive(Clone, Copy)]
pub(crate) enum HumanReadable {
/// Use the largest divisor corresponding to a unit, like B, K, M, G, etc.
///
/// This variant represents powers of 1,000. Contrast with
/// [`HumanReadable::Binary`], which represents powers of
/// 1,024.
Decimal,
/// Use the largest divisor corresponding to a unit, like B, K, M, G, etc.
///
/// This variant represents powers of 1,024. Contrast with
/// [`HumanReadable::Decimal`], which represents powers
/// of 1,000.
Binary,
}
/// A block size to use in condensing the display of a large number of bytes.
///
/// The [`BlockSize::Bytes`] variant represents a static block
/// size. The [`BlockSize::HumanReadableDecimal`] and
/// [`BlockSize::HumanReadableBinary`] variants represent dynamic
/// block sizes: as the number of bytes increases, the divisor
/// increases as well (for example, from 1 to 1,000 to 1,000,000 and
/// so on in the case of [`BlockSize::HumanReadableDecimal`]).
/// size.
///
/// The default variant is `Bytes(1024)`.
pub(crate) enum BlockSize {
@@ -86,20 +164,6 @@ pub(crate) enum BlockSize {
///
/// The number must be positive.
Bytes(u64),
/// Use the largest divisor corresponding to a unit, like B, K, M, G, etc.
///
/// This variant represents powers of 1,000. Contrast with
/// [`BlockSize::HumanReadableBinary`], which represents powers of
/// 1,024.
HumanReadableDecimal,
/// Use the largest divisor corresponding to a unit, like B, K, M, G, etc.
///
/// This variant represents powers of 1,024. Contrast with
/// [`BlockSize::HumanReadableDecimal`], which represents powers
/// of 1,000.
HumanReadableBinary,
}
impl Default for BlockSize {
@@ -109,11 +173,7 @@ impl Default for BlockSize {
}
pub(crate) fn block_size_from_matches(matches: &ArgMatches) -> Result<BlockSize, ParseSizeError> {
if matches.is_present(OPT_HUMAN_READABLE_BINARY) {
Ok(BlockSize::HumanReadableBinary)
} else if matches.is_present(OPT_HUMAN_READABLE_DECIMAL) {
Ok(BlockSize::HumanReadableDecimal)
} else if matches.is_present(OPT_BLOCKSIZE) {
if matches.is_present(OPT_BLOCKSIZE) {
let s = matches.value_of(OPT_BLOCKSIZE).unwrap();
Ok(BlockSize::Bytes(parse_size(s)?))
} else {
@@ -124,10 +184,8 @@ pub(crate) fn block_size_from_matches(matches: &ArgMatches) -> Result<BlockSize,
impl fmt::Display for BlockSize {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::HumanReadableBinary => write!(f, "Size"),
Self::HumanReadableDecimal => write!(f, "Size"),
Self::Bytes(n) => match to_magnitude_and_suffix(*n as u128) {
Ok(s) => write!(f, "{}-blocks", s),
Ok(s) => write!(f, "{}", s),
Err(_) => Err(fmt::Error),
},
}
@@ -153,46 +211,45 @@ mod tests {
);
}
// TODO We have not yet implemented this behavior, but when we do,
// uncomment this test.
#[test]
fn test_to_magnitude_and_suffix_not_powers_of_1024() {
assert_eq!(to_magnitude_and_suffix(1).unwrap(), "1B");
assert_eq!(to_magnitude_and_suffix(999).unwrap(), "999B");
// #[test]
// fn test_to_magnitude_and_suffix_not_powers_of_1024() {
// assert_eq!(to_magnitude_and_suffix(1).unwrap(), "1B");
// assert_eq!(to_magnitude_and_suffix(999).unwrap(), "999B");
assert_eq!(to_magnitude_and_suffix(1000).unwrap(), "1kB");
assert_eq!(to_magnitude_and_suffix(1001).unwrap(), "1.1kB");
assert_eq!(to_magnitude_and_suffix(1023).unwrap(), "1.1kB");
assert_eq!(to_magnitude_and_suffix(1025).unwrap(), "1.1kB");
assert_eq!(to_magnitude_and_suffix(999_000).unwrap(), "999kB");
// assert_eq!(to_magnitude_and_suffix(1000).unwrap(), "1kB");
// assert_eq!(to_magnitude_and_suffix(1001).unwrap(), "1.1kB");
// assert_eq!(to_magnitude_and_suffix(1023).unwrap(), "1.1kB");
// assert_eq!(to_magnitude_and_suffix(1025).unwrap(), "1.1kB");
// assert_eq!(to_magnitude_and_suffix(999_000).unwrap(), "999kB");
assert_eq!(to_magnitude_and_suffix(999_001).unwrap(), "1MB");
assert_eq!(to_magnitude_and_suffix(999_999).unwrap(), "1MB");
assert_eq!(to_magnitude_and_suffix(1_000_000).unwrap(), "1MB");
assert_eq!(to_magnitude_and_suffix(1_000_001).unwrap(), "1.1MB");
assert_eq!(to_magnitude_and_suffix(1_100_000).unwrap(), "1.1MB");
assert_eq!(to_magnitude_and_suffix(1_100_001).unwrap(), "1.2MB");
assert_eq!(to_magnitude_and_suffix(1_900_000).unwrap(), "1.9MB");
assert_eq!(to_magnitude_and_suffix(1_900_001).unwrap(), "2MB");
assert_eq!(to_magnitude_and_suffix(9_900_000).unwrap(), "9.9MB");
assert_eq!(to_magnitude_and_suffix(9_900_001).unwrap(), "10MB");
assert_eq!(to_magnitude_and_suffix(999_000_000).unwrap(), "999MB");
// assert_eq!(to_magnitude_and_suffix(999_001).unwrap(), "1MB");
// assert_eq!(to_magnitude_and_suffix(999_999).unwrap(), "1MB");
// assert_eq!(to_magnitude_and_suffix(1_000_000).unwrap(), "1MB");
// assert_eq!(to_magnitude_and_suffix(1_000_001).unwrap(), "1.1MB");
// assert_eq!(to_magnitude_and_suffix(1_100_000).unwrap(), "1.1MB");
// assert_eq!(to_magnitude_and_suffix(1_100_001).unwrap(), "1.2MB");
// assert_eq!(to_magnitude_and_suffix(1_900_000).unwrap(), "1.9MB");
// assert_eq!(to_magnitude_and_suffix(1_900_001).unwrap(), "2MB");
// assert_eq!(to_magnitude_and_suffix(9_900_000).unwrap(), "9.9MB");
// assert_eq!(to_magnitude_and_suffix(9_900_001).unwrap(), "10MB");
// assert_eq!(to_magnitude_and_suffix(999_000_000).unwrap(), "999MB");
assert_eq!(to_magnitude_and_suffix(999_000_001).unwrap(), "1GB");
assert_eq!(to_magnitude_and_suffix(1_000_000_000).unwrap(), "1GB");
assert_eq!(to_magnitude_and_suffix(1_000_000_001).unwrap(), "1.1GB");
}
// assert_eq!(to_magnitude_and_suffix(999_000_001).unwrap(), "1GB");
// assert_eq!(to_magnitude_and_suffix(1_000_000_000).unwrap(), "1GB");
// // etc.
// }
#[test]
fn test_to_magnitude_and_suffix_multiples_of_1000_and_1024() {
assert_eq!(to_magnitude_and_suffix(128_000).unwrap(), "128kB");
assert_eq!(to_magnitude_and_suffix(1000 * 1024).unwrap(), "1.1MB");
assert_eq!(to_magnitude_and_suffix(1_000_000_000_000).unwrap(), "1TB");
}
#[test]
fn test_block_size_display() {
assert_eq!(format!("{}", BlockSize::HumanReadableBinary), "Size");
assert_eq!(format!("{}", BlockSize::HumanReadableDecimal), "Size");
assert_eq!(format!("{}", BlockSize::Bytes(1024)), "1K-blocks");
assert_eq!(format!("{}", BlockSize::Bytes(2 * 1024)), "2K-blocks");
assert_eq!(
format!("{}", BlockSize::Bytes(3 * 1024 * 1024)),
"3M-blocks"
);
assert_eq!(format!("{}", BlockSize::Bytes(1024)), "1K");
assert_eq!(format!("{}", BlockSize::Bytes(2 * 1024)), "2K");
assert_eq!(format!("{}", BlockSize::Bytes(3 * 1024 * 1024)), "3M");
}
}
+1
View File
@@ -197,6 +197,7 @@ impl Column {
match column {
// 14 = length of "Filesystem" plus 4 spaces
Self::Source => 14,
Self::Used => 5,
// the shortest headers have a length of 4 chars so we use that as the minimum width
_ => 4,
}
+32 -7
View File
@@ -11,9 +11,11 @@ mod columns;
mod filesystem;
mod table;
use blocks::{HumanReadable, SizeFormat};
use uucore::display::Quotable;
use uucore::error::{UError, UResult, USimpleError};
use uucore::fsext::{read_fs_list, MountInfo};
use uucore::parse_size::ParseSizeError;
use uucore::{format_usage, show};
use clap::{crate_version, Arg, ArgMatches, Command};
@@ -61,6 +63,7 @@ static OUTPUT_FIELD_LIST: [&str; 12] = [
struct Options {
show_local_fs: bool,
show_all_fs: bool,
size_format: SizeFormat,
block_size: BlockSize,
/// Optional list of filesystem types to include in the output table.
@@ -88,6 +91,7 @@ impl Default for Options {
show_local_fs: Default::default(),
show_all_fs: Default::default(),
block_size: Default::default(),
size_format: Default::default(),
include: Default::default(),
exclude: Default::default(),
show_total: Default::default(),
@@ -105,7 +109,8 @@ impl Default for Options {
#[derive(Debug)]
enum OptionsError {
InvalidBlockSize,
BlockSizeTooLarge(String),
InvalidBlockSize(String),
/// An error getting the columns to display in the output table.
ColumnError(ColumnError),
@@ -116,11 +121,14 @@ enum OptionsError {
impl fmt::Display for OptionsError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
// TODO This should include the raw string provided as the argument.
//
// TODO This needs to vary based on whether `--block-size`
// or `-B` were provided.
Self::InvalidBlockSize => write!(f, "invalid --block-size argument"),
Self::BlockSizeTooLarge(s) => {
write!(f, "--block-size argument {} too large", s.quote())
}
// TODO This needs to vary based on whether `--block-size`
// or `-B` were provided.
Self::InvalidBlockSize(s) => write!(f, "invalid --block-size argument {}", s),
Self::ColumnError(ColumnError::MultipleColumns(s)) => write!(
f,
"option --output: field {} used more than once",
@@ -155,8 +163,21 @@ impl Options {
Ok(Self {
show_local_fs: matches.is_present(OPT_LOCAL),
show_all_fs: matches.is_present(OPT_ALL),
block_size: block_size_from_matches(matches)
.map_err(|_| OptionsError::InvalidBlockSize)?,
block_size: block_size_from_matches(matches).map_err(|e| match e {
ParseSizeError::SizeTooBig(_) => OptionsError::BlockSizeTooLarge(
matches.value_of(OPT_BLOCKSIZE).unwrap().to_string(),
),
ParseSizeError::ParseFailure(s) => OptionsError::InvalidBlockSize(s),
})?,
size_format: {
if matches.is_present(OPT_HUMAN_READABLE_BINARY) {
SizeFormat::HumanReadable(HumanReadable::Binary)
} else if matches.is_present(OPT_HUMAN_READABLE_DECIMAL) {
SizeFormat::HumanReadable(HumanReadable::Decimal)
} else {
SizeFormat::StaticBlockSize
}
},
include,
exclude,
show_total: matches.is_present(OPT_TOTAL),
@@ -424,6 +445,7 @@ pub fn uu_app<'a>() -> Command<'a> {
.short('B')
.long("block-size")
.takes_value(true)
.value_name("SIZE")
.overrides_with_all(&[OPT_KILO, OPT_BLOCKSIZE])
.help(
"scale sizes by SIZE before printing them; e.g.\
@@ -480,6 +502,7 @@ pub fn uu_app<'a>() -> Command<'a> {
Arg::new(OPT_OUTPUT)
.long("output")
.takes_value(true)
.value_name("FIELD_LIST")
.min_values(0)
.require_equals(true)
.use_value_delimiter(true)
@@ -489,7 +512,7 @@ pub fn uu_app<'a>() -> Command<'a> {
.default_values(&["source", "size", "used", "avail", "pcent", "target"])
.conflicts_with_all(&[OPT_INODES, OPT_PORTABILITY, OPT_PRINT_TYPE])
.help(
"use the output format defined by FIELD_LIST,\
"use the output format defined by FIELD_LIST, \
or print all fields if FIELD_LIST is omitted.",
),
)
@@ -512,6 +535,7 @@ pub fn uu_app<'a>() -> Command<'a> {
.long("type")
.allow_invalid_utf8(true)
.takes_value(true)
.value_name("TYPE")
.multiple_occurrences(true)
.help("limit listing to file systems of type TYPE"),
)
@@ -528,6 +552,7 @@ pub fn uu_app<'a>() -> Command<'a> {
.long("exclude-type")
.allow_invalid_utf8(true)
.takes_value(true)
.value_name("TYPE")
.use_value_delimiter(true)
.multiple_occurrences(true)
.help("limit listing to file systems not of type TYPE"),
+59 -101
View File
@@ -10,6 +10,7 @@
use number_prefix::NumberPrefix;
use unicode_width::UnicodeWidthStr;
use crate::blocks::{HumanReadable, SizeFormat};
use crate::columns::{Alignment, Column};
use crate::filesystem::Filesystem;
use crate::{BlockSize, Options};
@@ -213,15 +214,10 @@ impl<'a> RowFormatter<'a> {
}
/// Get a human readable string giving the scaled version of the input number.
///
/// The scaling factor is defined in the `options` field.
///
/// This function is supposed to be used by `scaled_bytes()` and `scaled_inodes()` only.
fn scaled_human_readable(&self, size: u64) -> String {
let number_prefix = match self.options.block_size {
BlockSize::HumanReadableDecimal => NumberPrefix::decimal(size as f64),
BlockSize::HumanReadableBinary => NumberPrefix::binary(size as f64),
_ => unreachable!(),
fn scaled_human_readable(&self, size: u64, human_readable: HumanReadable) -> String {
let number_prefix = match human_readable {
HumanReadable::Decimal => NumberPrefix::decimal(size as f64),
HumanReadable::Binary => NumberPrefix::binary(size as f64),
};
match number_prefix {
NumberPrefix::Standalone(bytes) => bytes.to_string(),
@@ -233,10 +229,12 @@ impl<'a> RowFormatter<'a> {
///
/// The scaling factor is defined in the `options` field.
fn scaled_bytes(&self, size: u64) -> String {
if let BlockSize::Bytes(d) = self.options.block_size {
(size / d).to_string()
} else {
self.scaled_human_readable(size)
match self.options.size_format {
SizeFormat::HumanReadable(h) => self.scaled_human_readable(size, h),
SizeFormat::StaticBlockSize => {
let BlockSize::Bytes(d) = self.options.block_size;
(size / d).to_string()
}
}
}
@@ -244,10 +242,9 @@ impl<'a> RowFormatter<'a> {
///
/// The scaling factor is defined in the `options` field.
fn scaled_inodes(&self, size: u64) -> String {
if let BlockSize::Bytes(_) = self.options.block_size {
size.to_string()
} else {
self.scaled_human_readable(size)
match self.options.size_format {
SizeFormat::HumanReadable(h) => self.scaled_human_readable(size, h),
SizeFormat::StaticBlockSize => size.to_string(),
}
}
@@ -305,7 +302,12 @@ impl Header {
for column in &options.columns {
let header = match column {
Column::Source => String::from("Filesystem"),
Column::Size => options.block_size.to_string(),
Column::Size => match options.size_format {
SizeFormat::HumanReadable(_) => String::from("Size"),
SizeFormat::StaticBlockSize => {
format!("{}-blocks", options.block_size)
}
},
Column::Used => String::from("Used"),
Column::Avail => String::from("Available"),
Column::Pcent => String::from("Use%"),
@@ -424,6 +426,7 @@ impl fmt::Display for Table {
#[cfg(test)]
mod tests {
use crate::blocks::{HumanReadable, SizeFormat};
use crate::columns::Column;
use crate::table::{Header, Row, RowFormatter};
use crate::{BlockSize, Options};
@@ -446,6 +449,30 @@ mod tests {
Column::Target,
];
impl Default for Row {
fn default() -> Self {
Self {
file: Some("/path/to/file".to_string()),
fs_device: "my_device".to_string(),
fs_type: "my_type".to_string(),
fs_mount: "my_mount".to_string(),
bytes: 100,
bytes_used: 25,
bytes_avail: 75,
bytes_usage: Some(0.25),
#[cfg(target_os = "macos")]
bytes_capacity: Some(0.5),
inodes: 10,
inodes_used: 2,
inodes_free: 8,
inodes_usage: Some(0.2),
}
}
}
#[test]
fn test_default_header() {
let options = Default::default();
@@ -523,7 +550,7 @@ mod tests {
#[test]
fn test_header_with_human_readable_binary() {
let options = Options {
block_size: BlockSize::HumanReadableBinary,
size_format: SizeFormat::HumanReadable(HumanReadable::Binary),
..Default::default()
};
assert_eq!(
@@ -542,7 +569,7 @@ mod tests {
#[test]
fn test_header_with_human_readable_si() {
let options = Options {
block_size: BlockSize::HumanReadableDecimal,
size_format: SizeFormat::HumanReadable(HumanReadable::Decimal),
..Default::default()
};
assert_eq!(
@@ -565,9 +592,7 @@ mod tests {
..Default::default()
};
let row = Row {
file: Some("/path/to/file".to_string()),
fs_device: "my_device".to_string(),
fs_type: "my_type".to_string(),
fs_mount: "my_mount".to_string(),
bytes: 100,
@@ -575,13 +600,7 @@ mod tests {
bytes_avail: 75,
bytes_usage: Some(0.25),
#[cfg(target_os = "macos")]
bytes_capacity: Some(0.5),
inodes: 10,
inodes_used: 2,
inodes_free: 8,
inodes_usage: Some(0.2),
..Default::default()
};
let fmt = RowFormatter::new(&row, &options);
assert_eq!(
@@ -598,7 +617,6 @@ mod tests {
..Default::default()
};
let row = Row {
file: Some("/path/to/file".to_string()),
fs_device: "my_device".to_string(),
fs_type: "my_type".to_string(),
fs_mount: "my_mount".to_string(),
@@ -608,13 +626,7 @@ mod tests {
bytes_avail: 75,
bytes_usage: Some(0.25),
#[cfg(target_os = "macos")]
bytes_capacity: Some(0.5),
inodes: 10,
inodes_used: 2,
inodes_free: 8,
inodes_usage: Some(0.2),
..Default::default()
};
let fmt = RowFormatter::new(&row, &options);
assert_eq!(
@@ -631,23 +643,15 @@ mod tests {
..Default::default()
};
let row = Row {
file: Some("/path/to/file".to_string()),
fs_device: "my_device".to_string(),
fs_type: "my_type".to_string(),
fs_mount: "my_mount".to_string(),
bytes: 100,
bytes_used: 25,
bytes_avail: 75,
bytes_usage: Some(0.25),
#[cfg(target_os = "macos")]
bytes_capacity: Some(0.5),
inodes: 10,
inodes_used: 2,
inodes_free: 8,
inodes_usage: Some(0.2),
..Default::default()
};
let fmt = RowFormatter::new(&row, &options);
assert_eq!(
@@ -664,23 +668,9 @@ mod tests {
..Default::default()
};
let row = Row {
file: Some("/path/to/file".to_string()),
fs_device: "my_device".to_string(),
fs_type: "my_type".to_string(),
fs_mount: "my_mount".to_string(),
bytes: 100,
bytes_used: 25,
bytes_avail: 75,
bytes_usage: Some(0.25),
#[cfg(target_os = "macos")]
bytes_capacity: Some(0.5),
inodes: 10,
inodes_used: 2,
inodes_free: 8,
inodes_usage: Some(0.2),
..Default::default()
};
let fmt = RowFormatter::new(&row, &options);
assert_eq!(fmt.get_values(), vec!("1", "10"));
@@ -689,12 +679,11 @@ mod tests {
#[test]
fn test_row_formatter_with_human_readable_si() {
let options = Options {
block_size: BlockSize::HumanReadableDecimal,
size_format: SizeFormat::HumanReadable(HumanReadable::Decimal),
columns: COLUMNS_WITH_FS_TYPE.to_vec(),
..Default::default()
};
let row = Row {
file: Some("/path/to/file".to_string()),
fs_device: "my_device".to_string(),
fs_type: "my_type".to_string(),
fs_mount: "my_mount".to_string(),
@@ -704,13 +693,7 @@ mod tests {
bytes_avail: 3000,
bytes_usage: Some(0.25),
#[cfg(target_os = "macos")]
bytes_capacity: Some(0.5),
inodes: 10,
inodes_used: 2,
inodes_free: 8,
inodes_usage: Some(0.2),
..Default::default()
};
let fmt = RowFormatter::new(&row, &options);
assert_eq!(
@@ -730,12 +713,11 @@ mod tests {
#[test]
fn test_row_formatter_with_human_readable_binary() {
let options = Options {
block_size: BlockSize::HumanReadableBinary,
size_format: SizeFormat::HumanReadable(HumanReadable::Binary),
columns: COLUMNS_WITH_FS_TYPE.to_vec(),
..Default::default()
};
let row = Row {
file: Some("/path/to/file".to_string()),
fs_device: "my_device".to_string(),
fs_type: "my_type".to_string(),
fs_mount: "my_mount".to_string(),
@@ -745,13 +727,7 @@ mod tests {
bytes_avail: 3072,
bytes_usage: Some(0.25),
#[cfg(target_os = "macos")]
bytes_capacity: Some(0.5),
inodes: 10,
inodes_used: 2,
inodes_free: 8,
inodes_usage: Some(0.2),
..Default::default()
};
let fmt = RowFormatter::new(&row, &options);
assert_eq!(
@@ -771,32 +747,14 @@ mod tests {
#[test]
fn test_row_formatter_with_round_up_usage() {
let options = Options {
block_size: BlockSize::Bytes(1),
columns: vec![Column::Pcent],
..Default::default()
};
let row = Row {
file: Some("/path/to/file".to_string()),
fs_device: "my_device".to_string(),
fs_type: "my_type".to_string(),
fs_mount: "my_mount".to_string(),
bytes: 100,
bytes_used: 25,
bytes_avail: 75,
bytes_usage: Some(0.251),
#[cfg(target_os = "macos")]
bytes_capacity: Some(0.5),
inodes: 10,
inodes_used: 2,
inodes_free: 8,
inodes_usage: Some(0.2),
..Default::default()
};
let fmt = RowFormatter::new(&row, &options);
assert_eq!(
fmt.get_values(),
vec!("my_device", "100", "25", "75", "26%", "my_mount")
);
assert_eq!(fmt.get_values(), vec!("26%"));
}
}
+8 -22
View File
@@ -20,7 +20,7 @@ use std::fs::File;
use std::fs::Metadata;
use std::io::BufRead;
use std::io::BufReader;
use std::io::{ErrorKind, Result};
use std::io::Result;
use std::iter;
#[cfg(not(windows))]
use std::os::unix::fs::MetadataExt;
@@ -34,7 +34,8 @@ use std::str::FromStr;
use std::time::{Duration, UNIX_EPOCH};
use std::{error::Error, fmt::Display};
use uucore::display::{print_verbatim, Quotable};
use uucore::error::{set_exit_code, UError, UResult};
use uucore::error::FromIo;
use uucore::error::{UError, UResult};
use uucore::format_usage;
use uucore::parse_size::{parse_size, ParseSizeError};
use uucore::InvalidEncodingHandling;
@@ -102,7 +103,6 @@ const UNITS: [(char, u32); 6] = [('E', 6), ('P', 5), ('T', 4), ('G', 3), ('M', 2
struct Options {
all: bool,
util_name: String,
max_depth: Option<usize>,
total: bool,
separate_dirs: bool,
@@ -309,13 +309,9 @@ fn du(
let read = match fs::read_dir(&my_stat.path) {
Ok(read) => read,
Err(e) => {
eprintln!(
"{}: cannot read directory {}: {}",
options.util_name,
my_stat.path.quote(),
e
show!(
e.map_err_context(|| format!("cannot read directory {}", my_stat.path.quote()))
);
set_exit_code(1);
return Box::new(iter::once(my_stat));
}
};
@@ -368,18 +364,9 @@ fn du(
}
}
}
Err(error) => match error.kind() {
ErrorKind::PermissionDenied => {
let description = format!("cannot access {}", entry.path().quote());
let error_message = "Permission denied";
show_error_custom_description!(description, "{}", error_message);
set_exit_code(1);
}
_ => {
set_exit_code(1);
show_error!("cannot access {}: {}", entry.path().quote(), error);
}
},
Err(e) => show!(
e.map_err_context(|| format!("cannot access {}", entry.path().quote()))
),
}
}
Err(error) => show_error!("{}", error),
@@ -567,7 +554,6 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let options = Options {
all: matches.is_present(options::ALL),
util_name: uucore::util_name().to_owned(),
max_depth,
total: matches.is_present(options::TOTAL),
separate_dirs: matches.is_present(options::SEPARATE_DIRS),
+1 -1
View File
@@ -17,7 +17,7 @@ path = "src/expr.rs"
[dependencies]
clap = { version = "3.1", features = ["wrap_help", "cargo"] }
num-bigint = "0.4.0"
num-traits = "0.2.14"
num-traits = "0.2.15"
onig = { version = "~6.3", default-features = false }
uucore = { version=">=0.0.11", package="uucore", path="../../uucore" }
+2 -2
View File
@@ -12,12 +12,12 @@ categories = ["command-line-utilities"]
edition = "2021"
[build-dependencies]
num-traits = "0.2.13" # used in src/numerics.rs, which is included by build.rs
num-traits = "0.2.15" # used in src/numerics.rs, which is included by build.rs
[dependencies]
clap = { version = "3.1", features = ["wrap_help", "cargo"] }
coz = { version = "0.1.3", optional = true }
num-traits = "0.2.13" # Needs at least version 0.2.13 for "OverflowingAdd"
num-traits = "0.2.15" # Needs at least version 0.2.15 for "OverflowingAdd"
rand = { version = "0.8", features = ["small_rng"] }
smallvec = "1.7" # TODO(nicoo): Use `union` feature, requires Rust 1.49 or later.
uucore = { version = ">=0.0.8", package = "uucore", path = "../../uucore" }
+64 -5
View File
@@ -205,20 +205,41 @@ pub fn uu_app<'a>() -> Command<'a> {
)
}
/// Parse a template string into prefix, suffix, and random components.
///
/// `temp` is the template string, with three or more consecutive `X`s
/// representing a placeholder for randomly generated characters (for
/// example, `"abc_XXX.txt"`). If `temp` ends in an `X`, then a suffix
/// can be specified by `suffix` instead.
///
/// # Errors
///
/// * If there are fewer than three consecutive `X`s in `temp`.
/// * If `suffix` is a [`Some`] object but `temp` does not end in `X`.
/// * If the suffix (specified either way) contains a path separator.
///
/// # Examples
///
/// ```rust,ignore
/// assert_eq!(parse_template("XXX", None).unwrap(), ("", 3, ""));
/// assert_eq!(parse_template("abcXXX", None).unwrap(), ("abc", 3, ""));
/// assert_eq!(parse_template("XXXdef", None).unwrap(), ("", 3, "def"));
/// assert_eq!(parse_template("abcXXXdef", None).unwrap(), ("abc", 3, "def"));
/// ```
fn parse_template<'a>(
temp: &'a str,
suffix: Option<&'a str>,
) -> UResult<(&'a str, usize, &'a str)> {
) -> Result<(&'a str, usize, &'a str), MkTempError> {
let right = match temp.rfind('X') {
Some(r) => r + 1,
None => return Err(MkTempError::TooFewXs(temp.into()).into()),
None => return Err(MkTempError::TooFewXs(temp.into())),
};
let left = temp[..right].rfind(|c| c != 'X').map_or(0, |i| i + 1);
let prefix = &temp[..left];
let rand = right - left;
if rand < 3 {
return Err(MkTempError::TooFewXs(temp.into()).into());
return Err(MkTempError::TooFewXs(temp.into()));
}
let mut suf = &temp[right..];
@@ -227,12 +248,12 @@ fn parse_template<'a>(
if suf.is_empty() {
suf = s;
} else {
return Err(MkTempError::MustEndInX(temp.into()).into());
return Err(MkTempError::MustEndInX(temp.into()));
}
};
if suf.chars().any(is_separator) {
return Err(MkTempError::ContainsDirSeparator(suf.into()).into());
return Err(MkTempError::ContainsDirSeparator(suf.into()));
}
Ok((prefix, rand, suf))
@@ -304,3 +325,41 @@ fn exec(dir: &Path, prefix: &str, rand: usize, suffix: &str, make_dir: bool) ->
println_verbatim(path).map_err_context(|| "failed to print directory name".to_owned())
}
#[cfg(test)]
mod tests {
use crate::parse_template;
#[test]
fn test_parse_template_no_suffix() {
assert_eq!(parse_template("XXX", None).unwrap(), ("", 3, ""));
assert_eq!(parse_template("abcXXX", None).unwrap(), ("abc", 3, ""));
assert_eq!(parse_template("XXXdef", None).unwrap(), ("", 3, "def"));
assert_eq!(
parse_template("abcXXXdef", None).unwrap(),
("abc", 3, "def")
);
}
#[test]
fn test_parse_template_suffix() {
assert_eq!(parse_template("XXX", Some("def")).unwrap(), ("", 3, "def"));
assert_eq!(
parse_template("abcXXX", Some("def")).unwrap(),
("abc", 3, "def")
);
}
#[test]
fn test_parse_template_errors() {
// TODO This should be an error as well, but we are not
// catching it just yet. A future commit will correct this.
//
// assert!(parse_template("a/bXXX", None).is_err());
//
assert!(parse_template("XXXa/b", None).is_err());
assert!(parse_template("XX", None).is_err());
assert!(parse_template("XXXabc", Some("def")).is_err());
assert!(parse_template("XXX", Some("a/b")).is_err());
}
}
+2 -2
View File
@@ -17,7 +17,7 @@ use std::ptr;
use clap::{crate_version, Arg, Command};
use uucore::{
error::{set_exit_code, UResult, USimpleError, UUsageError},
error::{set_exit_code, UClapError, UResult, USimpleError, UUsageError},
format_usage,
};
@@ -35,7 +35,7 @@ const USAGE: &str = "{} [OPTIONS] [COMMAND [ARGS]]";
#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let matches = uu_app().get_matches_from(args);
let matches = uu_app().try_get_matches_from(args).with_exit_code(125)?;
let mut niceness = unsafe {
nix::errno::Errno::clear();
+1 -1
View File
@@ -19,7 +19,7 @@ path = "src/seq.rs"
bigdecimal = "0.3"
clap = { version = "3.1", features = ["wrap_help", "cargo"] }
num-bigint = "0.4.0"
num-traits = "0.2.14"
num-traits = "0.2.15"
uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=["memo"] }
[[bin]]
+68 -5
View File
@@ -617,12 +617,75 @@ impl From<i32> for Box<dyn UError> {
}
}
/// Implementations for clap::Error
impl UError for clap::Error {
/// A wrapper for `clap::Error` that implements [`UError`]
///
/// Contains a custom error code. When `Display::fmt` is called on this struct
/// the [`clap::Error`] will be printed _directly to `stdout` or `stderr`_.
/// This is because `clap` only supports colored output when it prints directly.
///
/// [`ClapErrorWrapper`] is generally created by calling the
/// [`UClapError::with_exit_code`] method on [`clap::Error`] or using the [`From`]
/// implementation from [`clap::Error`] to `Box<dyn UError>`, which constructs
/// a [`ClapErrorWrapper`] with an exit code of `1`.
///
/// ```rust
/// use uucore::error::{ClapErrorWrapper, UError, UClapError};
/// let command = clap::Command::new("test");
/// let result: Result<_, ClapErrorWrapper> = command.try_get_matches().with_exit_code(125);
///
/// let command = clap::Command::new("test");
/// let result: Result<_, Box<dyn UError>> = command.try_get_matches().map_err(Into::into);
/// ```
#[derive(Debug)]
pub struct ClapErrorWrapper {
code: i32,
error: clap::Error,
}
/// Extension trait for `clap::Error` to adjust the exit code.
pub trait UClapError<T> {
fn with_exit_code(self, code: i32) -> T;
}
impl From<clap::Error> for Box<dyn UError> {
fn from(e: clap::Error) -> Self {
Box::new(ClapErrorWrapper { code: 1, error: e })
}
}
impl UClapError<ClapErrorWrapper> for clap::Error {
fn with_exit_code(self, code: i32) -> ClapErrorWrapper {
ClapErrorWrapper { code, error: self }
}
}
impl UClapError<Result<clap::ArgMatches, ClapErrorWrapper>>
for Result<clap::ArgMatches, clap::Error>
{
fn with_exit_code(self, code: i32) -> Result<clap::ArgMatches, ClapErrorWrapper> {
self.map_err(|e| e.with_exit_code(code))
}
}
impl UError for ClapErrorWrapper {
fn code(&self) -> i32 {
match self.kind() {
clap::ErrorKind::DisplayHelp | clap::ErrorKind::DisplayVersion => 0,
_ => 1,
// If the error is a DisplayHelp or DisplayVersion variant,
// we don't want to apply the custom error code, but leave
// it 0.
if let clap::ErrorKind::DisplayHelp | clap::ErrorKind::DisplayVersion = self.error.kind() {
0
} else {
self.code
}
}
}
impl Error for ClapErrorWrapper {}
// This is abuse of the Display trait
impl Display for ClapErrorWrapper {
fn fmt(&self, _f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
self.error.print().unwrap();
Ok(())
}
}
+12
View File
@@ -1506,6 +1506,18 @@ fn test_copy_through_dangling_symlink() {
.stderr_only("cp: not writing through dangling symlink 'target'");
}
#[test]
fn test_copy_through_dangling_symlink_no_dereference() {
let (at, mut ucmd) = at_and_ucmd!();
at.symlink_file("no-such-file", "dangle");
ucmd.arg("-P")
.arg("dangle")
.arg("d2")
.succeeds()
.no_stderr()
.no_stdout();
}
#[test]
#[cfg(unix)]
fn test_cp_archive_on_nonexistent_file() {

Some files were not shown because too many files have changed in this diff Show More